【博客635】tcpdump原理与网卡混杂模式

148 篇文章 17 订阅
130 篇文章 11 订阅

tcpdump原理与网卡混杂模式

案例分析

场景:

k8s机器上由于桥上未打开hairpin mode,使得流量通过service回到本身的时候不通了,但是使用tcpdump抓包的时候,流量就通了,不抓又不通了

原因:

使用tcpdump的时候,使网络设备进入了promiscuous mode,自然原来由于没有打开hairpin mod而不通的流量,由于网络设备打开了promiscuous mode,流量就通了

1、网卡混杂模式(promiscuous mode)

网卡有以下几种工作模式,通常网卡会配置广播和多播模式:

  • 广播模式(Broad Cast Model):它的物理地址地址是 0Xffffff 的帧为广播帧,工作在广播模式的网卡接收广播帧。它将会接收所有目的地址为广播地址的数据包,一般所有的网卡都会设置为这个模式
  • 多播传送(MultiCast Model):多播传送地址作为目的物理地址的帧可以被组内的其它主机同时接收,而组外主机却接收不到。但是,如果将网卡设置为多播传送模式,它可以接收所有的多播传送帧,而不论它是不是组内成员。当数据包的目的地址为多播地址,而且网卡地址是属于那个多播地址所代表的多播组时,网卡将接纳此数据包,即使一个网卡并不是一个多播组的成员,程序也可以将网卡设置为多播模式而接收那些多播的数据包。
  • 直接模式(Direct Model):工作在直接模式下的网卡只接收目地址是自己 Mac 地址的帧。只有当数据包的目的地址为网卡自己的地址时,网卡才接收它。
  • 混杂模式(Promiscuous Model):工作在混杂模式下的网卡接收所有的流过网卡的帧,抓包程序就是在这种模式下运行的。网卡的缺省工作模式包含广播模式和直接模式,即它只接收广播帧和发给自己的帧。如果采用混杂模式,网卡将接受同一网络内所有所发送的数据包,这样就可以到达对于网络信息监视捕获的目的。它将接收所有经过的数据包,这个特性是编写网络监听程序的关键。

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

一般计算机网卡都工作在非混杂模式下,此时网卡只接受来自网络端口的目的地址指向自己的数据。当网卡工作在混杂模式下时,网卡将来自接口的所有数据都捕获并交给相应的驱动程序。网卡的混杂模式一般在网络管理员分析网络数据作为网络故障诊断手段时用到,同时这个模式也被网络黑客利用来作为网络数据窃听的入口。在Linux操作系统中设置网卡混杂模式时需要管理员权限。在Windows操作系统和Linux操作系统中都有使用混杂模式的抓包工具,比如著名的开源软件Wireshark。

工作原理:

混杂模式就是接收所有经过网卡的数据包,而不论其目的地址是否是他,包括不是发给本机的包。默认情况下网卡只把发给本机的包(包括广播包)传递给上层程序,其它的包一律丢弃。简单的讲,混杂模式就是指网卡能接受所有通过它的数据流,不管是什么格式,什么地址的。事实上,计算机收到数据包后,由网络层进行判断,确定是递交上层(传输层),还是丢弃,还是递交下层(数据链路层、MAC子层)转发。

promiscuous mode是双刃剑:

相对于通常模式(又称“非混杂模式”)而言,这被网络管理员使用来诊断网络问题,但是也被无认证的想偷听网络通信(其可能包括密码和其它敏感的信息)的人利用。

查看网卡是否开启混杂模式:

1、ifconfig:

实际上,网卡是否处于PROMISC模式,ifconfig并不是最直接的判断依据。原因:有PROMISC一定是混杂模式,没有时也可能是混杂模式。

[root@node1 21:06:51 ~]$ifconfig ens192
ens192: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.17.88  netmask 255.255.240.0  broadcast 192.168.31.255
        inet6 fe80::20c:29ff:fe99:4eb2  prefixlen 64  scopeid 0x20<link>
        ether 00:0c:29:99:4e:b2  txqueuelen 1000  (Ethernet)
        RX packets 163692  bytes 10031607 (9.5 MiB)
        RX errors 0  dropped 110  overruns 0  frame 0
        TX packets 1751  bytes 175974 (171.8 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

[root@node1 21:06:53 ~]$ifconfig ens192 promisc
[root@node1 21:06:57 ~]$ifconfig ens192
ens192: flags=4419<UP,BROADCAST,RUNNING,PROMISC,MULTICAST>  mtu 1500
        inet 192.168.17.88  netmask 255.255.240.0  broadcast 192.168.31.255
        inet6 fe80::20c:29ff:fe99:4eb2  prefixlen 64  scopeid 0x20<link>
        ether 00:0c:29:99:4e:b2  txqueuelen 1000  (Ethernet)
        RX packets 171193  bytes 10482754 (9.9 MiB)
        RX errors 0  dropped 110  overruns 0  frame 0
        TX packets 1776  bytes 179091 (174.8 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

2、查看/sys/class/net/xxxx/flags:

# 非混杂模式是0x1003,混杂模式是0x1103
[root@master ~]# cat /sys/class/net/enp0s3f0/flags
0x1003
[root@master ~]# ifconfig enp0s3f0 promisc
[root@master ~]# cat /sys/class/net/enp0s3f0/flags
0x1103

混杂模式与网络诊断:

我们对网卡进行抓包的时候,会使得网卡进入“混杂模式”,所谓混杂模式就是让网卡接收所有到达网卡的报文,因为默认情况下,不是给自己的报文网卡是不要的,要么丢弃要么转发,反正不读取内容,而进入混杂模式后,就可以看一眼报文内容了。

在这里插入图片描述

使用tcpdump命令可以使网卡自动进入混杂模式,这一点可以从dmesg系统日志中看到:

[311135.760098] device eth0 entered promiscuous mode
[311142.852087] device eth0 left promiscuous mode

tcpdump原理

原理剖析:

linux下的抓包是通过注册一种虚拟的底层网络协议来完成对网络报文(准确的说是网络设备)消息的处理权。当网卡接收到一个网络报文之后,它会遍历系统中所有已经注册的网络协议,例如以太网协议、x25协议处理模块来尝试进行报文的解析处理,这一点和一些文件系统的挂载相似,就是让系统中所有的已经注册的文件系统来进行尝试挂载,如果哪一个认为自己可以处理,那么就完成挂载。当抓包模块把自己伪装成一个网络协议的时候,系统在收到报文的时候就会给这个伪协议一次机会,让它来对网卡收到的报文进行一次处理,此时该模块就会趁机对报文进行窥探,也就是把这个报文完完整整的复制一份,假装是自己接收到的报文,汇报给抓包模块。

源码剖析:

  • 1、tcpdump在刚开始工作时创建了PF_PACKET套接字,并在全局的ptype_all中挂载了该套接字的pt(packet_type *pt),其中pt的字段func设置了相应的回调函数packet_rcv
static int packet_create(struct net *net, struct socket *sock, int protocol,
    int kern)
{
 struct sock *sk;
 struct packet_sock *po;
 __be16 proto = (__force __be16)protocol; /* weird, but documented */
 int err;
  ......
       po = pkt_sk(sk); 
 sk->sk_family = PF_PACKET;//设置sk协议族为PF_PACKET
 po->num = proto;  //数据包的类型ETH_P_ALL
 po->xmit = dev_queue_xmit;
 
 err = packet_alloc_pending(po);
 if (err)
  goto out2;
 
 packet_cached_dev_reset(po);
 
 sk->sk_destruct = packet_sock_destruct;
 sk_refcnt_debug_inc(sk);
 
 /*
  * Attach a protocol block
  */
 
 spin_lock_init(&po->bind_lock);
 mutex_init(&po->pg_vec_lock);
    .....
 po->rollover = NULL;
 po->prot_hook.func = packet_rcv;//设置回调函数
 
 if (sock->type == SOCK_PACKET)
  po->prot_hook.func = packet_rcv_spkt;
 
 po->prot_hook.af_packet_priv = sk;
 
 if (proto) {
  po->prot_hook.type = proto;
  register_prot_hook(sk);//将这个socket挂载到ptype_all连接串列上
 }
  ......
}
 
 
//packet_sock结构体
struct packet_sock {
 /* struct sock has to be the first member of packet_sock */
 struct sock  sk;
 ......
 struct net_device __rcu *cached_dev;
 int   (*xmit)(struct sk_buff *skb);
 struct packet_type prot_hook ____cacheline_aligned_in_smp;//packet_create函数中通过该字段进行下一步的设置:po->prot_hook
};
 
 
 
/*po->prot_hook其实packet_type,packet_type结构体:
数据包完成链路层的处理后,需要提交给协议栈上层继续处理,每个packet_type结构就是数据包的一个可能去向
packet_type 结构体第一个type 很重要,对应链路层中2个字节的以太网类型。而dev.c 链路层抓取的包上报给对应模块,就是根据抓取的链路层类型,然后给对应的模块处理,例如socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_ALL)); ETH_P_ALL表示所有的底层包都会给到PF_PACKET 模块的处理函数,这里处理函数就是packet_rcv 函数。
*/
struct packet_type {
 __be16   type; /* type指定了协议的标识符,标记了packet_type收取什么类型的数据包,处理程序func会使用该标识符 ,保存了三层协议类型,ETH_P_IP、ETH_P_ARP等等*/
 struct net_device *dev; /* NULL指针表示该处理程序对系统中所有网络设备都有效    */
    /* func:packet_create函数通过该字段设置的回调函数:po->prot_hook.func = packet_rcv;
    func是该结构的主要成员,它是一个指向网络层函数的指针,ip层处理时挂载的是ip_rcv
    */
 int   (*func) (struct sk_buff *,
      struct net_device *,
      struct packet_type *,
      struct net_device *);
 bool   (*id_match)(struct packet_type *ptype,
         struct sock *sk);
 void   *af_packet_priv;
 struct list_head list;
}; 
  • 2、网络包到了之后,tcpdump抓包点如何被触发

在这里插入图片描述

在这里插入图片描述

  • 3、tcpdump注册的回调函数packet_rcv分析
static int packet_rcv(struct sk_buff *skb, struct net_device *dev,
        struct packet_type *pt, struct net_device *orig_dev)
{
     ......
  if (sk->sk_type != SOCK_DGRAM)// 当 SOCK_DGRAM类型的时候,会截取掉链路层的数据包,从而返回给应用层的数据包是不包含链路层数据的
   skb_push(skb, skb->data - skb_mac_header(skb));
  else if (skb->pkt_type == PACKET_OUTGOING) {
   /* Special case: outgoing packets have ll header at head */
   skb_pull(skb, skb_network_offset(skb));
  }
 }
     ......
    //最后将底层网口符合应用层的数据复制到接收缓存队列中
         
 res = run_filter(skb, sk, snaplen);   //将用户指定的过滤条件使用BPF进行过滤
    ......
 spin_lock(&sk->sk_receive_queue.lock);
 po->stats.stats1.tp_packets++;
 sock_skb_set_dropcount(sk, skb);
 __skb_queue_tail(&sk->sk_receive_queue, skb);//将skb放到当前的接收队列中
 spin_unlock(&sk->sk_receive_queue.lock);
 sk->sk_data_ready(sk);
 return 0;
    ......
 
}

综上一旦关联上链路层抓到的包就会copy一份给上层接口(即PF_PACKET 注册的回调函数packet_rev). 而回调函数会根据应用层设置的bpf过滤数据包,最终放入接收缓存的数据包肯定是符合应用层想截取的数据。因此最后一步recvfrom 也就是从接收缓存的数据包copy给应用层,如下源码:

static int packet_recvmsg(struct socket *sock, struct msghdr *msg, size_t len,
     int flags)
{
    ......
        
 skb = skb_recv_datagram(sk, flags, flags & MSG_DONTWAIT, &err);//从接收缓存中接收数据
 
 ......
        
 err = skb_copy_datagram_msg(skb, 0, msg, copied);//将最终的数据copy到用户空间
    ......
 
}

tcpdump进行抓包的内核流程梳理:

  • 应用层通过libpcap库:调用系统调用创建socket,sock_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));tcpdump在socket创建过程中创建packet_type(struct packet_type),并挂载到全局的ptype_all链表上。(同时在packet_type设置回调函数packet_rcv

  • 网络收包/发包时,会在各自的处理函数(收包时:__netif_receive_skb_core,发包时:dev_queue_xmit_nit)中遍历ptype_all链表,并同时执行其回调函数,这里tcpdump的注册的回调函数就是packet_rcv

  • packet_rcv函数中会将用户设置的过滤条件,通过BPF进行过滤,并将过滤的数据包添加到接收队列中

  • 应用层调用recvfrom 。 PF_PACKET 协议簇模块调用packet_recvmsg 将接收队列中的数据copy应用层,到此将数据包捕获到。

总结:

tcpdump原理是注册一种虚拟协议,使得每个包在遍历当前的协议列表时有机会被处理,如果tcpdump发现包符合要求就会使用skb_clone一份,送到用户态程序去分析。tcpdump还会使网络设备进入混杂模式,是为了让包能过进入网络协议栈,从而有机会被捕捉,这样才能抓到包

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值