简介
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