linux之网络子系统-tcpdump 原理

news2024/11/28 16:31:05

一、tcpdump 的用途

tcpdump是Linux系统抓包工具,tcpdump基于libpcap库,根据使用者的定义对网络上的数据包进行截获,tcpdump可以将网络中传送的数据包中的"头"完全截获下来提供分析,支持针对网络层、协议、主机、网络或端口的过滤,并提供and、or、not等逻辑语句来帮助去掉无用的信息。通过tcpdump可以分析很多网络行为,比如丢包重传、详细报文、tcp分组等,总之通过tcpdunp可以为各种网络问题进行排查,可以在服务器上将捕获的数据包信息以pcap文件保存下来,通过wireshark打开,更直观地分析。

tcpdump是基于libpcap库的,数据包的过滤是基于BPF(tcpdump依附标准的bpf机器来运行,tcpdump过滤规则会被转化为一段bpf指令并加载到内核中的bpf虚拟机器上执行),使用bpf虚拟机的tcpdump完美解决了包过滤问题。总之tcpdump使用libpcap这种链路层旁路处理的形式进行包捕获,使用bpf机制实现对包的完美过滤。

二、libpcap简单介绍

libpcap(Packet Capture Library),即数据包捕获函数库,是Unix/Linux平台下的网络数据包捕获函数库,独立于系统的用户层包捕获的API接口,为底层网络监测提供了一个可移植的框架。

利用libpcap函数库开发应用程序的基本步骤:

  1. 捕获各种数据包,例如:网络流量统计。
  2. 过滤网络数据包,例如:过滤掉本地上的一些数据,类似防火墙。
  3. 分析网络数据包,例如:分析网络协议,数据的采集。
  4. 存储网络数据包,例如:保存捕获的数据以为将来进行分析。

libpcap库在linux上的安装过程

sudo apt-get insatll flex
sudo apt-get install bison
wget -c http://www.tcpdump.org/release/libpcap-1.7.4.tar.gz 
cd libpcap-1.7.4
./congigure
sudo make
sudo make install

测试:

//demo:查找当前系统的可用网络设备
#include <stdio.h>
#include <pcap.h>

int main(int argc, char *argv[])
{
    char *dev,errbuf[1024];
   
    dev=pcap_lookupdev(errbuf);//函数用来查找网络设备

    if(dev==NULL){
        printf("%s\n",errbuf);
        return 0;
    }

    printf("Device: %s\n", dev);
    return 0;
}

编译:

#注意编译时加上:-lpcap
gcc test.c -o test -lpcap

报错提醒:

error while loading shared libraries: libpcap.so.1: cannot open shared object file: No such file or directory

解决:

执行 locate libpcap.so.1 , 查看libpcap.so.1在系统中的路径 , 显示为 : /usr/local/lib/libpcap.so.1.2.1
以管理员权限打开编辑 /etc/ld.so.conf 文件, 末尾新一行追加 /usr/local/lib , /usr/local/lib 为 libpcap.so.1.7.4 所在目录, 保存退出
以管理员权限执行 ldconfig(如果不支持改命令用whereis ldconfig查看并设置环境变量)命令,
成功

几个重要的API

  • pcap_lookupdev():函数用于查找网络设备,返回可被 pcap_open_live() 函数调用的网络设备名指针。
/*errbuf:存放出错信息字符串,宏定义PCAP_ERRBUF_SIZE为错误缓冲区大小
返回值为设备名(第一个合适的网络接口的字符串指针)
*/
char *pcap_lookupdev(char *errbuf)
  • pcap_lookupnet():函数获得指定网络设备的网络号和掩码。
/*获取指定网卡的ip地址,子网掩码
device:网络设备名
netp:存放ip地址的指针
maskp:存放子网掩码的指针
errbuf:存放出错信息*/
int pcap_lookupnet(char *device,bpf_u_int32 *netp,bpf_u_int32 *maskp,char *errbuf  );
  • pcap_open_live(): 函数用于打开网络设备,并且返回用于捕获网络数据包的数据包捕获描述字。对于此网络设备的操作都要基于此网络设备描述字。
/*
device:网络接口名字
snaplen:数据包大小,最大为65535字节
promise:“1” 代表混杂模式,其它非混杂模式。什么为混杂模式
to_ms:指定需要等待的毫秒数,超过这个数值后,获取数据包的函数就会立即返回(这个函数不会阻塞,后面的抓包函数才会阻塞)。0 表示一直等待直到有数据包到来。

ebuf:存储错误信息。
返回值:pcap_t类型指针,后面的所有操作都要用这个指针

    */
pcap_t *pcap_open_live(const char *device,int snaplen,int promisc,int to_ms,char *ebuf );
  • pcap_compile(): 函数用于将用户制定的过滤策略编译到过滤程序中。
/*
p是嗅探器回话句柄
fp是一个bpf_program结构的指针,在pcap_compile()函数中被赋值。
str:指定的过滤条件
optimize参数控制结果代码的优化。
netmask参数指定本地网络的网络掩码
*/
int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask)
  • pcap_setfilter():函数用于设置过滤器。
/*
p:pcap_open_live() 返回的 pcap_t 类型的指针
fp:pcap_compile() 的第二个参数
*/
int pcap_setfilter( pcap_t * p,  struct bpf_program * fp );
  • pcap_loop():函数 pcap_dispatch() 函数用于捕获数据包,捕获后还可以进行处理,此外 pcap_next() 和 pcap_next_ex() 两个函数也可以用来捕获数据包。
  • pcap_close():函数用于关闭网络设备,释放资源。

三、tcpdump实现抓包原理剖析(重点)

在libpcap库源码中也可以看到有调用socket系统调用:

static int
pcap_can_set_rfmon_linux(pcap_t *handle)
{
     ...
 
 sock_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
 if (sock_fd == -1) {
  (void)snprintf(handle->errbuf, PCAP_ERRBUF_SIZE,
      "socket: %s", pcap_strerror(errno));
  return PCAP_ERROR;
 }
 ...
}

可以看到tcpdump抓包创建的的套接字类型AF_PACKET

AF_PACKET和socket应用结合一般都是用于抓包分析,packet套接字提供的是L2的抓包能力。

socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))

PF_PACKET 是协议族,与PF_INET 一样,可以理解为PACKET协议族;

SOCK_RAW 是数据包的类型;

htons(ETH_P_ALL) 是一个网络字节序转换函数,意为 “host to network short”。它将主机字节序(通常是小端序)转换为网络字节序(大端序),ETH_P_ALL代表以太网协议的类型,值为 0x0003。

下面看一下 内核源码中,af_packet 协议族的定义:

路径:kernel/net/packet/af_packet.c

static struct proto packet_proto = {
        .name     = "PACKET",
        .owner    = THIS_MODULE,
        .obj_size = sizeof(struct packet_sock),
};


static const struct net_proto_family packet_family_ops = {
        .family =       PF_PACKET,
        .create =       packet_create,
        .owner  =       THIS_MODULE,
};


static int __init packet_init(void)
{
        int rc;

        rc = proto_register(&packet_proto, 0);
        if (rc)
                goto out;
        rc = sock_register(&packet_family_ops);
        if (rc)
                goto out_proto;
        rc = register_pernet_subsys(&packet_net_ops);
        if (rc)
                goto out_sock;
        rc = register_netdevice_notifier(&packet_netdev_notifier);
        if (rc)
                goto out_pernet;

        return 0;

out_pernet:
        unregister_pernet_subsys(&packet_net_ops);
out_sock:
        sock_unregister(PF_PACKET);
out_proto:
        proto_unregister(&packet_proto);
out:
        return rc;
}

module_init(packet_init);
module_exit(packet_exit);
MODULE_LICENSE("GPL");
MODULE_ALIAS_NETPROTO(PF_PACKET);

 调用函数栈:socket->sock_create->_sock_create,

sock_create 函数主要就是创建了socket . 同时根据之前PF_PACKET 模块注册到全局变量net_families 。 找到af_packet.c 中初始化的 static const struct net_proto_family packet_family_ops。而sock_create 函数中 err = pf->create(net, sock, protocol, kern); 最终就会调用 packet_family_ops 里的packet_create。

int __sock_create(struct net *net, int family, int type, int protocol,
    struct socket **res, int kern)
{
 int err;
 struct socket *sock;
 const struct net_proto_family *pf;
    ......
 
 sock = sock_alloc();//分配socket结构空间
 if (!sock) {
  net_warn_ratelimited("socket: no more sockets\n");
  return -ENFILE; /* Not exactly a match, but its the
       closest posix thing */
 }

 sock->type = type;//记录socket类型

#ifdef CONFIG_MODULES

 if (rcu_access_pointer(net_families[family]) == NULL)
  request_module("net-pf-%d", family);
#endif

 rcu_read_lock();
 pf = rcu_dereference(net_families[family]);//根据family协议簇找到注册的(PF_PACKET)协议族操作表
 err = -EAFNOSUPPORT;
 if (!pf)
  goto out_release;

 if (!try_module_get(pf->owner))
  goto out_release;
 rcu_read_unlock();

 err = pf->create(net, sock, protocol, kern);//执行该协议族(PF_PACKET)的创建函数
    ......
 
}

Linux内核中定义了net_proto_family结构体,用来指明不同的协议族对应的socket创建函数,family字段是协议族的类型,create是创建socket的函数,如下是PF_PACKET对应结构体。

static const struct net_proto_family packet_family_ops = {
 .family = PF_PACKET,
 .create = packet_create,
 .owner = THIS_MODULE,
};

找到AF_PACKET协议族对应的create函数:可以看到po->prot_hook.func = packet_rcv;po->prot_hook其实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 函数。

设置了回调函数:packet_rcv,并通过register_prot_hook(sk)完成了注册,其中注册过程将再下面分析:

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;
};

展开注册函数register_prot_hook(sk)

static void register_prot_hook(struct sock *sk)
{
 struct packet_sock *po = pkt_sk(sk);

 if (!po->running) {
  if (po->fanout)
   __fanout_link(sk, po);
  else
   dev_add_pack(&po->prot_hook);//将pacekt_type放到ptype_all链表上。

  sock_hold(sk);
  po->running = 1;
 }
}

//ptype_all链表:
struct list_head ptype_all __read_mostly;//全局变量

展开dev_add_pack

//将pacekt_type放到ptype_all链表上。
void dev_add_pack(struct packet_type *pt)
{
 struct list_head *head = ptype_head(pt);//获取ptype_all链表

 spin_lock(&ptype_lock);
 list_add_rcu(&pt->list, head);//将po->prot_hook挂载到ptype_all链表
 spin_unlock(&ptype_lock);
}


//获取ptype_all链表
static inline struct list_head *ptype_head(const struct packet_type *pt)
{
 if (pt->type == htons(ETH_P_ALL))//type为ETH_P_ALL时,则挂在ptype_all上面
  return pt->dev ? &pt->dev->ptype_all : &ptype_all;
 else
  return pt->dev ? &pt->dev->ptype_specific :
     &ptype_base[ntohs(pt->type) & PTYPE_HASH_MASK];否则,挂在ptype_base[type&15]上面
}

综上:tcpdump在刚开始工作时创建了PF_PACKET套接字,并在全局的ptype_all中挂载了该套接字的pt(packet_type *pt),其中pt的字段func设置了相应的回调函数packet_rcv(后面将分析该函数),到此tcpdump抓包的socket(AF_PACKET)创建完成,相应的准备工作完成。

3.1 网络收包时tcpdump进行抓包

函数调用关系:

netif_receive_skb-->netif_receive_skb-->netif_receive_skb_internal->__netif_receive_skb-->__netif_receive_skb_core

核心函数__netif_receive_skb_core:

static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc)
{
 ......
 //遍历ptype_all,cpdump在创建socket时已将其packet_type挂载到了遍历ptype_all
 list_for_each_entry_rcu(ptype, &ptype_all, list) {
  if (pt_prev)
   ret = deliver_skb(skb, pt_prev, orig_dev);//deliver函数回调用paket_type.func(),也就是packet_rcv 
  pt_prev = ptype;
 }

   ......
}

__netif_receive_skb_core函数在遍历ptype_all时,同时也执行了deliver_skb(skb, pt_prev, orig_dev);deliver函数调用了paket_type.func(),也就是packet_rcv ,如下源码所示:

static inline int deliver_skb(struct sk_buff *skb,
         struct packet_type *pt_prev,
         struct net_device *orig_dev)
{
 if (unlikely(skb_orphan_frags_rx(skb, GFP_ATOMIC)))
  return -ENOMEM;
 refcount_inc(&skb->users);
 return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);//调用tcpdump挂载的packet_rcv 函数
}

 下面将展开packet_rcv函数进行分析;函数接收到链路层网口的数据包后,会根据应用层设置的bpf过滤数据包,符合要求的最终会加到struct sock sk 的接收缓存中。使用BPF过滤过程将在后面进行分析。

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到用户空间
    ......
 
}

到这,网络接收数据包时的抓包过程就结束了

问题:抓包时在整个收包过程中,是属于哪个阶段?

抓包属于 网络设备层调用__netif_receive_skb_core 函数接口时,会触发tcpdump定义的收包函数;

 从上面的流程图可以看出,当napi_gro_receive 接收数据小包后,才调用到__netif_receive_skb_core。在__netif_receive_skb_core 函数中,会先遍历&ptype_all 所有的packet_type,再遍历 &ptype_base,这两个都是全局变量数组,收到数据包后,会遍历这两个数组,符合条件就进行接收数据包。

上面的梳理是对PACKET的定义到ptype_all,而ip_packet_type 是定义到 ptype_base中,下面介绍一下ip_packet_type 是如何定义的?

函数流程:

inet_init->

        dev_add_pack(&ip_packet_type);

                

 ip_packet_type

static struct packet_type ip_packet_type __read_mostly = {
        .type = cpu_to_be16(ETH_P_IP),
        .func = ip_rcv,
        .list_func = ip_list_rcv,
};
 

dev_add_pack

 

void dev_add_pack(struct packet_type *pt)
{
        struct list_head *head = ptype_head(pt);

        spin_lock(&ptype_lock);
        list_add_rcu(&pt->list, head);
        spin_unlock(&ptype_lock);
}
 

  ptype_head

static inline struct list_head *ptype_head(const struct packet_type *pt)
{
        if (pt->type == htons(ETH_P_ALL))
                return pt->dev ? &pt->dev->ptype_all : &ptype_all;
        else
                return pt->dev ? &pt->dev->ptype_specific :
                                 &ptype_base[ntohs(pt->type) & PTYPE_HASH_MASK];
}
 

 可以看到ip_packet_type 的类型是ETH_P_IP,所以定义在ptype_base 数组中。

__netif_receive_skb_core->deliver_skb->ip_recv;

问题答案:tcpdump 的接收包属于链路层L2的接收包。

3.2 网络发包时tcpdump进行抓包

 Linux协议栈中提供的报文发送函数有两个,一个是链路层提供给网络层的发包函数dev_queue_xmit(),另一个就是软中断发包函数之间调用的sch_direct_xmit(),这两个函数最终都会调用dev_hard_start_xmit()

struct sk_buff *dev_hard_start_xmit(struct sk_buff *first, struct net_device *dev,
        struct netdev_queue *txq, int *ret)
{
    ......
   while (skb) {
  struct sk_buff *next = skb->next;

  skb->next = NULL;
  rc = xmit_one(skb, dev, txq, next != NULL);//调用xmit_one来发送一个到多个数据包
  ......
}

 xmit_one():发送一个到多个数据包

static int xmit_one(struct sk_buff *skb, struct net_device *dev,
                    struct netdev_queue *txq, bool more)
{
        unsigned int len;
        int rc;

        if (dev_nit_active(dev)) //判断ptype_all 数组是否为空,或者dev 是否支持ptype_all
                dev_queue_xmit_nit(skb, dev); //是调用ptype_all数组定义的数据报类型族

        len = skb->len;
        PRANDOM_ADD_NOISE(skb, dev, txq, len + jiffies);
        trace_net_dev_start_xmit(skb, dev);
        rc = netdev_start_xmit(skb, dev, txq, more);//调用到网卡驱动的driver,
        trace_net_dev_xmit(skb, rc, dev, len);

        return rc;
}

/**
 * dev_nit_active - return true if any network interface taps are in use
 *
 * @dev: network device to check for the presence of taps
 */
bool dev_nit_active(struct net_device *dev)
{
        return !list_empty(&ptype_all) || !list_empty(&dev->ptype_all);
}
EXPORT_SYMBOL_GPL(dev_nit_active);

dev_queue_xmit_nit():将数据包发送给定义在 ptype_all 数组中的协议族,此处就是PACKET 协议族

void dev_queue_xmit_nit(struct sk_buff *skb, struct net_device *dev)
{
        struct packet_type *ptype;
        struct sk_buff *skb2 = NULL;
        struct packet_type *pt_prev = NULL;
        struct list_head *ptype_list = &ptype_all; //这个数组是包含PACKET 的 

        rcu_read_lock();
again:
        list_for_each_entry_rcu(ptype, ptype_list, list) {
                if (ptype->ignore_outgoing)
                        continue;

                /* Never send packets back to the socket
                 * they originated from - MvS (miquels@drinkel.ow.org)
                 */
                if (skb_loop_sk(ptype, skb))
                        continue;

                if (pt_prev) {
                        deliver_skb(skb2, pt_prev, skb->dev);
                        pt_prev = ptype;
                        continue;
                }

                /* need to clone skb, done only once */
                skb2 = skb_clone(skb, GFP_ATOMIC);
                if (!skb2)
                        goto out_unlock;

                net_timestamp_set(skb2);

                /* skb->nh should be correctly
                 * set by sender, so that the second statement is
                 * just protection against buggy protocols.
                 */
                skb_reset_mac_header(skb2);

                if (skb_network_header(skb2) < skb2->data ||
                    skb_network_header(skb2) > skb_tail_pointer(skb2)) {
                        net_crit_ratelimited("protocol %04x is buggy, dev %s\n",
                                             ntohs(skb2->protocol),
                                             dev->name);
                        skb_reset_network_header(skb2);
                }

                skb2->transport_header = skb2->network_header;
                skb2->pkt_type = PACKET_OUTGOING;
                pt_prev = ptype;
        }

        if (ptype_list == &ptype_all) {
                ptype_list = &dev->ptype_all;
                goto again;
        }
out_unlock:
        if (pt_prev) {
                if (!skb_orphan_frags_rx(skb2, GFP_ATOMIC))
                        pt_prev->func(skb2, skb->dev, pt_prev, skb->dev);
                else
                        kfree_skb(skb2);
        }
        rcu_read_unlock();
}
EXPORT_SYMBOL_GPL(dev_queue_xmit_nit);

在遍历ptype_all时,同时也执行了deliver_skb(skb, pt_prev, orig_dev);deliver函数调用了paket_type.func(),也就是packet_rcv ,如下源码所示:

static inline int deliver_skb(struct sk_buff *skb,
         struct packet_type *pt_prev,
         struct net_device *orig_dev)
{
 if (unlikely(skb_orphan_frags_rx(skb, GFP_ATOMIC)))
  return -ENOMEM;
 refcount_inc(&skb->users);
 return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);//调用tcpdump挂载的packet_rcv 函数
}

下面的流程就和网络收包时tcpdump进行抓包一样了(packet_rcv函数中会将用户设置的过滤条件,通过BPF进行过滤,并将过滤的数据包添加到接收队列中,应用层在libpcap库中调用recvfrom 。 PF_PACKET 协议簇模块调用packet_recvmsg 将接收队列中的数据copy应用层)

这个tcpdump收包是发生在调用网卡驱动之前,在GSO之后.

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抓包时调用的libpcap库开始梳理,从socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))进入系统调用,再从内核角度对Tcpdump在收包和发包的流程分析了一遍,其实还有一个重点:BPF的过滤过程,如下源码所示:run_filter(skb, sk, snaplen),下次文章将对BPF的过滤过程进行一些分析。

static int packet_rcv(struct sk_buff *skb, struct net_device *dev,
        struct packet_type *pt, struct net_device *orig_dev)
{
  ......
       
 res = run_filter(skb, sk, snaplen);   //将用户指定的过滤条件使用BPF进行过滤
    ......
 __skb_queue_tail(&sk->sk_receive_queue, skb);//将skb放到当前的接收队列中
    ......

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2060252.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

刷题篇 - 03

题目一&#xff1a; 203. 移除链表元素 - 力扣&#xff08;LeetCode&#xff09; public ListNode removeElements(ListNode head, int val) {//1. 如果链表为null&#xff0c;直接返回headif (head null) {return head;}//2. 定义快慢指针ListNode pre head;ListNode del …

工业数据采集网关简介-天拓四方

随着工业4.0和物联网&#xff08;IoT&#xff09;技术的深入发展&#xff0c;工业数据采集网关作为连接现场设备与上层管理系统的关键节点&#xff0c;其在智能工厂中的作用愈发凸显。本文将深入探讨工业数据采集网关的功能、特点、应用场景及其实操性&#xff0c;以期为读者提…

【JPCS出版】第三届机械、航天技术与材料应用国际学术会议 (MATMA 2024)

第三届机械、航天技术与材料应用国际学术会议(MATMA 2024)定于2024年08月30-9月1日在中国呼和浩特隆重举行。 本会议由内蒙古工业大学主办&#xff0c;主要围绕“机械工程”、“航天技术”与“材料应用”等最新研究领域展开研讨&#xff0c;为来自国内外高等院校、科学研究所、…

【SpringCloud】(一文通)SpringCloud 环境和工程搭建

目 录 一. 开发环境安装二. 案例介绍2.1 需求2.2 服务拆分 三. 数据准备四. 工程搭建4.1 构建父子工程4.1.1 创建父工程4.1.2 创建子项目-订单服务4.1.3 创建子项目-商品服务 4.2 完善订单服务4.2.1 完善启动类, 配置文件4.2.2 业务代码4.2.3 测试 4.3 完善商品服务4.3.1 完善启…

Cesium实现单个无人机飞行

通过一组坐标&#xff0c;实现平滑的无人机飞行效果 测试步骤&#xff1a; 1、手动填写坐标并记录坐标(可通过点击球面查看坐标信息) 2、点击初始化&#xff0c;载入相应的坐标信息 3、点击漫游&#xff0c;镜头会平滑演进 低配置云服务器&#xff0c;首次加载速度较慢&…

设备状态图表-甘特图

1.背景&#xff1a;设备状态监控图表&#xff0c;监控不同状态的时间段&#xff0c;可以使用甘特图来展示效果 鼠标经过时的数据提示框 2、代码实现 <template><divref"ganttChartRefs":style"{ height: 6.2rem, width: 100% }"class"bg…

利用ai写作软件,一键智能改写文案很简单

在当今快节奏的时代&#xff0c;科技的发展日新月异&#xff0c;人工智能&#xff08;AI&#xff09;已经逐渐融入到我们生活的各个方面。其中&#xff0c;AI写作工具的出现为文案创作带来了极大的便利&#xff0c;让一键智能改写文案变得简单而高效。 AI写作工具利用先进的算法…

项目技巧1

目录 创建项目工程的技巧 示例&#xff1a;创建父工程 第一步&#xff1a;初始化maven项目 第二步&#xff1a;使用标签 抽取依赖的资源版本号&#xff0c;方便后续调整 第三步&#xff1a;配置父工程锁定的版本&#xff0c;使用 该标签的作用&#xff1a;锁定资源的版本号&…

初探Raft算法

在分布式系统有一个经典的CAP理论&#xff0c;C&#xff1a;一致性&#xff0c;即集群中所有节点都应该是一致的。A&#xff1a;可用性&#xff0c;集群一直处于可用状态。P&#xff1a;分区容错性&#xff1a;即复制所有数据到集群的所有节点&#xff0c;保证即使出现网络分区…

TQRFSOC开发板47DR,LMK04832更新配置

在利用RFSOC开发板进行项目开发时&#xff0c;面对多样化的时钟需求&#xff0c;巧妙地配置LMK04832时钟管理芯片以输出精确的时钟信号显得尤为重要。本期内容将讲解如何通过ZYNQ更新LMK04832的配置&#xff0c;以满足您的特定时钟需求。 每次配置成功后&#xff0c;配置文件都…

电路笔记(PCB):JLC PCB布局和走线基础教程笔记

对立创EDA 四层板PCB设计保姆级教程的笔记看完才发现是个虚假的教程&#xff0c;除了没教四层板咋画其它教了&#xff08;中间的两层全是GND的作用&#xff09; PCB布局 转换原理图 使用USB模块&#xff08;3.0集线器&#xff09;中的原理图。设计- - -》更新/转换原理图到P…

CSP内容安全策略

目录 CSP内容安全策略 一、引入 二、CSP内容安全策略 1、通过 HTTP 响应头信息的 Content-Security-Policy 的字段 2、通过网页的 meta 标签 3、在security的read.php页面&#xff0c;增加以下响应头 4、report-uri安全报告 5、其他安全配置 6、Web服务器全局配置 三…

机器学习:逻辑回归处理手写数字的识别

1、获取数据, 图像分割该数据有50行100列&#xff0c;每个数字占据20*20个像素点&#xff0c;可以进行切分,划分出训练集和测试集。 import numpy as np import pandas as pd import cv2 imgcv2.imread("digits.png")#读取文件 graycv2.cvtColor(img,cv2.COLOR_BGR2G…

8月20日

思维导图 面试题整理&#xff1a; 数据结构中顺序表和链表的区别? 顺序表&#xff1a; 1、存储方式&#xff1a;顺序表是一系列连续的内存空间&#xff0c;每个元素的地址可以通过索引计算出&#xff1b; 2、访问速度&#xff1a;由于不需要遍历查找&#xff0c;访问速度较…

一路坎坷-Mac下安装Python3、环境变量配置、安装jupyter notebook

遇到很多问题最终解决--Mac下安装Python3、环境变量配置、安装jupyter notebook 下载-安装python打开Mac终端-安装Jupyter给python3配置环境变量给jupyter配置环境变量配置遇到的问题 首先说一下我是先下载python3.8-然后安装&#xff1b;再在苹果电脑的终端下载Jupyter的&…

【JAVA CORE_API】Day18 网络编程、线程、在线聊天室v1.0

C/S&#xff1a;客户端/服务器端&#xff0c;所有网络应用都是基于客户端服务器端进行的&#xff0c;Java写的是服务端&#xff0c;客户端是一个软件&#xff0c;服务端也是一个软件&#xff0c;两个软件之间交互&#xff1b;&#xff08;只能连接对应的服务器&#xff09; B/…

使用 Arch Linux 几个月有感 | 为什么我选择 Arch Linux ,Arch 的优缺点有什么 | 一些Linux发行版推荐

&#xff08;终端是 Yakuake &#xff0c;KDE 自带&#xff09; 一点碎碎念&#xff0c;可以跳过不看 几年前从 CentOS 接触的 Linux &#xff0c;试图搭建一个KMS服务器 但是失败了 &#xff0c;后来装过 Ubuntu Debian deepin Kali Kubuntu Manjaro&#xff0c;踩一路坑最后…

线性数据结构的基本概念(数组,链表,栈,队列)

数组 数组由相同类型的元素组成&#xff0c;使用一块连续的内存来存储。 数组的特点是&#xff1a; 1.利用索引进行访问 2.容量固定 3.使用一块连续的内存来存储 各种操作的时间复杂度&#xff1a; 查找/修改&#xff1a;O&#xff08;1&#xff09;//访问特定位置的元素 插入…

day07_算法训练

案例零&#xff1a;整数反转&#xff08;大整数算法&#xff09; 给你一个 32 位的有符号整数 x &#xff0c;返回将 x 中的数字部分反转后的结果。 暂时不用考虑数据太大的问题&#xff0c;把本题的核心思路写出来即可 比如&#xff1a; ​ 输入&#xff1a;x 123 ​ 输出…

一分钟带你了解叉车!

一、叉车的概念 叉车是指对整件托盘货物或大件桶装进行装卸、堆垛和短距离运输作业的各种搬运车辆。叉车是实现整件货物和散装物料机械化装卸、堆垛和短途运输的高效率工作车辆。 叉车常用于仓储大型物件的运输、车站、码头、机场、仓库、工地、货厂和工矿企业&#xff0c;是…