3 11
关于网络的混杂模式

什么是网络的混杂模式

混杂模式(promiscuous mode)是指一台机器的网卡能够接收所有经过它的数据流,而不论其目的地址是否是它。

维基百科:一般计算机网卡都工作在非混杂模式下,此时网卡只接受来自网络端口的目的地址指向自己的数据。当网卡工作在混杂模式下时,网卡将来自接口的所有数据都捕获并交给相应的驱动程序(即不验证MAC地址)。网卡的混杂模式一般在网络管理员分析网络数据作为网络故障诊断手段时用到,同时这个模式也被网络黑客利用来作为网络数据窃听的入口。

网卡具有如下的几种工作模式:

  • 广播模式(Broad Cast Model):它的物理地址(MAC)地址是 0Xffffff 的帧为广播帧,工作在广播模式的网卡接收广播帧。

  • 多播传送(MultiCast Model):多播传送地址作为目的物理地址的帧可以被组内的其它主机同时接收,而组外主机却接收不到。但是,如果将网卡设置为多播传送模式,它可以接收所有的多播传送帧,而不论它是不是组内成员。

  • 直接模式(Direct Model):工作在直接模式下的网卡只接收目地址是自己 Mac地址的帧。

  • 混杂模式(Promiscuous Model):工作在混杂模式下的网卡接收所有的流过网卡的帧,信包捕获程序就是在这种模式下运行的。

网卡的缺省工作模式包含广播模式和直接模式,即它只接收广播帧和发给自己的帧。如果采用混杂模式,一个站点的网卡将接受同一网络内所有站点所发送的数据包这样就可以到达对于网络信息监视捕获的目的。

linux 下通过命令ifconfig基本能查询当前网卡是使用哪种的工作模式

线上的一次真实案例

几个月前负责帮公司内部数据监控团队写了一个应用流量采集的底层进程模块,该模块基于Go使用的是libpcap库对网络设备的流量进行捕获并根据我们自身的业务进行封装整理。这个后台进程运行了几个月了,一直相当“安分”,没有引起告警或者占用过多资源等现场。不过”不幸”的事终于还是发生了。下面我还原一下事情的经过:

某天,收到运维的同学反馈某些机器上的组件如redis、mc有超时,运维的同事协助观察发现,组件的GC正常、网络层面无丢包、mc无异常,真正对比了Haproxy的配置均无变更。但涉及的影响面20w请求就有1000~2000次的timeout现象。

到后来,我们排查系统日志发现有蓝牙设备初始化的记录,时间点比较符合,网卡不断地在混杂模式中来回切换,可能与此相关:

pro

经过IDC的同事排查,机房并没有外接到蓝牙设备,系统本身也没有;后来我们在一些机器上移走蓝牙模块,单担心混杂模式来回切换的现象还是会出现。

后来我们了解到如果使用tcpdump的时候,会启用网卡的混杂模式,但服务器正常时间段内,并没有运维人员去使用tcpdump去进行操作。于是我们就把问题定位在可能是有一些后台应用定时地调用一些网卡采集的功能,于是我们通过日志分析,定位在之前的应用流量采集的后台工具中,于是我们针对有问题的机器,停止了该服务,观察了一段时间,结果问题得到解决,混杂模式没有再来回切换的情况,超时现象没有再发生。

修补方案

既然问题得到定位,就要思考问什么,和如何修复了。应用流量监控模块,我这边是直接采用了 github.com/google/gopacket/pcap 这个库。

后来发现问题所在了,我其中的handler调用的函数定义:


// OpenLive opens a device and returns a *Handle.
// It takes as arguments the name of the device ("eth0"), the maximum size to
// read for each packet (snaplen), whether to put the interface in promiscuous
// mode, and a timeout.
//
// See the package documentation for important details regarding 'timeout'.
func OpenLive(device string, snaplen int32, promisc bool, timeout time.Duration) (handle *Handle, _ error) {
    buf := (*C.char)(C.calloc(errorBufferSize, 1))
    defer C.free(unsafe.Pointer(buf))

    var pro C.int
    if promisc {
        pro = 1
    }
    p := &Handle{timeout: timeout, device: device}

    ifc, err := net.InterfaceByName(device)
    if err != nil {
        // The device wasn't found in the OS, but could be "any"
        // Set index to 0
        p.deviceIndex = 0
    } else {
        p.deviceIndex = ifc.Index
    }

    dev := C.CString(device)
    defer C.free(unsafe.Pointer(dev))

    p.cptr = C.pcap_open_live(dev, C.int(snaplen), pro, timeoutMillis(timeout), buf)
    if p.cptr == nil {
        return nil, errors.New(C.GoString(buf))
    }

    if err := p.openLive(); err != nil {
        C.pcap_close(p.cptr)
        return nil, err
    }

    return p, nil
}

其中第三个参数是指是否开启混杂模式,我之前调用的代码是这样的:

handle, err := pcap.OpenLive(faceName, int32(*snaplen), true, 500)

坑啊,之前没有仔细测试,直接赋值了一个true, 也就是说这个服务强制了使用了混杂模式对网卡进行监听,在流量高峰的时候,很容易导致某些网络服务timeout的现象。 啊~,心中万马奔腾,原来是自己的大意疏忽!!要引以为戒。