簡介
Monitor mode 與 promiscuous mode 比較
這是在網卡上的的兩個特殊的模式,簡而言之,都是將網卡的過濾器關閉。
- Monitor mode
這是我們常常提到的sniffer mode。它用於無線網絡中,無線網卡開啟監聽模式,監聽空氣中的所有數據包,其中它還可以切換channel。如果設置得當,可以同時監控所有信道的幀(切換式,或者同時多個網卡監聽)。在這個模式下面,STA是沒有連接到AP的。除了能夠得到數據包之外,還可以得到控制幀和管理幀。對於debug 802.11的問題(omnipeek or wireshark in linux)或者攻擊一個802.11的網絡(aircrack、reaver),常常會使網卡進入到這個模式。此文重點在於promiscuous mode,802.11的monitor mode不再贅述。
- Promiscuos mode
不處於promiscuous mode的網卡只會收取DA會自身的數據包或者BC/MC的數據包。在promiscuos mode下面,網卡會收到DA不是自身的包,在進入這個模式的時候,常常會開啟一個raw socket,來接收網卡傳遞上來的數據。
打開promiscuous mode
翻閱libpcap的源碼,可以整理出這兩種方式,可以打開promiscuous mode:
(activate_new) – new way
1.Try to open a packet socket using the new kernel PF_PACKET interface
2.Select promiscuous mode on
struct packet_mreq mr; memset(&mr, 0, sizeof(mr)); mr.mr_ifindex = handlep->ifindex; mr.mr_type = PACKET_MR_PROMISC; sock_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); setsockopt(sock_fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mr, sizeof(mr))
setsockopt 原型如下:
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
對應於內核的 /include/net/sock.h
(setsockopt)(struct sock sk, int level, int optname, char __user *optval, unsigned int optlen);
net/packet/af_packet.c
里面有對應的操作:
static int packet_setsockopt(struct socket *sock, int level, int optname, char __user *optval, unsigned int optlen) { struct sock *sk = sock->sk; struct packet_sock *po = pkt_sk(sk); int ret; if (level != SOL_PACKET) return -ENOPROTOOPT; switch (optname) { case PACKET_ADD_MEMBERSHIP: case PACKET_DROP_MEMBERSHIP: { struct packet_mreq_max mreq; int len = optlen; memset(&mreq, 0, sizeof(mreq)); if (len sizeof(mreq)) len = sizeof(mreq); if (copy_from_user(&mreq, optval, len)) return -EFAULT; if (len < (mreq.mr_alen + offsetof(struct packet_mreq, mr_address))) return -EINVAL; if (optname == PACKET_ADD_MEMBERSHIP) ret = packet_mc_add(sk, &mreq); else ret = packet_mc_drop(sk, &mreq); return ret; }
調用流程:
packet_mc_add -> packet_dev_mc -> dev_set_promiscuity(net/core/dev.c)
dev_set_promiscuity的定義如下:
int dev_set_promiscuity(struct net_device *dev, int inc) { unsigned int old_flags = dev->flags; int err; err = __dev_set_promiscuity(dev, inc, true); if (err <0 return err if dev->flags != old_flags) dev_set_rx_mode(dev); return err; }
__dev_set_promiscuity => dev->flags |= IFF_PROMISC; dev_change_rx_flags(dev, IFF_PROMISC); ops(net_device_ops)->ndo_change_rx_flags(dev, flags);
dev_set_rx_mode(dev); ==> ops(net_device_ops)->ndo_set_rx_mode(dev);
驅動基本上只實現了ndo_set_rx_mode
選擇一個Ethernet驅動作為例子:
const struct net_device_ops ei_netdev_ops = { .... .ndo_open = ei_open, .ndo_stop = ei_close, .ndo_start_xmit = ei_start_xmit, .ndo_set_rx_mode = ei_set_multicast_list, };
ei_set_multicast_list -> __ei_set_multicast_list -> do_set_multicast_list
static void do_set_multicast_list(struct net_device *dev) { .... if (dev->flags&IFF_PROMISC) { memset(ei_local->mcfilter, 0xFF, 8); ei_outb_p(E8390_RXCONFIG | 0x18, e8390_base + EN0_RXCR); } }
這邊可以看到驅動設置了EN0_RXCR,含義猜測是RX config register,將RX的接收狀態設置為accept all。所以說,設置設備進入promiscuous mode,實際上是設置硬件寄存器ndo_set_rx_mode
,需要設備的驅動支持。
(activate_old) – old way
struct ifreq ifr; ifr.ifr_flags |= IFF_PROMISC; ioctl(handle->fd, SIOCSIFFLAGS, &ifr)
這種開啟方法,多個開啟混雜模式的程序可能會干擾,容易出問題,不推薦使用。
在 net/core/dev_ioctl.c
=> dev_ifsioc
=> dev_change_flags
=> __dev_change_flags
=> dev_set_rx_mode
下面的路徑就和上一種方法一樣了,不再贅述。可以了解到,在底層的操作,兩個做法都是一樣的,都是關閉了網卡的過濾器。
如何判斷一個機器位於promiscuous mode
既然網卡驅動放行了所有的數據包並且將數據包上傳TCPIP協議棧,那么這里可以使用一個技巧。將進入promiscuous mode的主機設為A。從同一網段的B主機偽造一個目標MAC地址不是A,但是目標ip地址是A的包。當正常的主機收到這個包的時候,網卡驅動或者網卡的asic就會將它丟棄,當這個主機進入promiscuous mode之后,網卡驅動放行這個包,TCPIP協議棧檢查這個包的目標地址是A,所以會產生一個回應到B主機。當然了,如果主機A特意將TCPIP協議棧的收發包路徑關掉,那么就無法偵測到了。
另外,如果我偽造了一個不存在的mac地址,那么交換機就不知道這個mac地址是位於哪個端口,它也會幫忙轉發到所有端口。
比如我從B主機偽造一個錯誤MAC地址的ICMP包給A主機,判斷A主機是否回應,這樣就可以判斷出它是否處於promiscuous mode。
MAC | IP | Payload |
---|---|---|
局域網內沒有出現過的mac地址 | 192.168.0.5 | xxxxxxxx |
做一個測試
測試環境: ubuntu 12.4 + win10
測試網卡:Ethernet 網卡
測試軟件:wireshark,ICMP c語言測試程序
兩端的ip:192.168.0.4,192.168.0.5
測試方式:直連,家用路由器LAN口轉發
下面是測試的過程。首先我在目標機器上面(win10)開啟了wireshark,並且監聽本地連接。這時候我偽造了一個錯誤的MAC DA,但是其他參數,比如對方的ip地址是填寫正確的。這時候本機的wireshark可以看到對方的ICMP回應。緊接着我關閉win10上面的wireshark,ICMP回應消失。隨后我又開啟wireshark,又可以得到ICMP回應了。測試的時候,兩台機器使用網線直連或者通過家用路由器的LAN口轉發都是成功的,但是使用無線網絡的時候是失敗的,可能是無線網卡不支持promiscuous mode。注意到,因為是要偽造MAC地址,所以需要使用PF_PACKET
來操作RAW socket
下圖是在ubuntu上面的wireshark截取的
另外,如果兩個機器直連測試,需要在windows上面設置靜態ip地址(192.168.0.5/24),在linux機器上面關閉界面上的網絡連接,並且輸入測試網絡是否連通:
tanhangbo@ThinkPad:~$ sudo ifconfig eth0 up tanhangbo@ThinkPad:~$ sudo ifconfig eth0 192.168.0.4 tanhangbo@ThinkPad:~$ ping 192.168.0.5 PING 192.168.0.5 (192.168.0.5) 56(84) bytes of data. 64 bytes from 192.168.0.5: icmp_req=1 ttl=128 time=0.312 ms 64 bytes from 192.168.0.5: icmp_req=2 ttl=128 time=0.384 ms 64 bytes from 192.168.0.5: icmp_req=3 ttl=128 time=0.408 ms
實際作用
一般來說,進入這個模式,就是為了多收一些包,進行網絡診斷。一般開啟wireshark的時候就會幫你打開promiscuous mode。底層操作在libpcap里面。
- 參考資料:
- libpcap和linux kernel 源碼
- http://stackoverflow.com/questions/6666257/what-is-the-purpose-of-net-device-uc-promisc-field