详解Linux tcpdump 抓包的原理

tcpdump是一个用于截取网络分组,并输出分组内容的工具。凭借强大的功能和灵活的截取策略,使其成为类UNIX系统下用于网络分析和问题排查的首选工具,本篇文章为大家详细讲解一下Linux tcpdump 抓包的原理。

1.1.1.1 如何实现

先来看看包传递过来的流程,如下图。包从网卡到内存,到内核态,最后给用户程序使用。我们知道tcpdump程序运行在用户态,那如何实现从内核态的抓包呢?

img

这个就是通过libpcap库来实现的,tcpdump调用libpcap的api函数,由libpcap进入到内核态到链路层来抓包,如下图。图中的BPF是过滤器,可以根据用户设置用于数据包过滤减少应用程序的数据包的包数和字节数从而提高性能。BufferQ是缓存供应用程序读取的数据包。我们可以说tcpdump底层原理其实就是libpcap的实现原理。

而libpcap在linux系统链路层中抓包是通过PF_PACKET套接字来实现的(不同的系统其实现机制是由差异的),该方法在创建的时候,可以指定第二参数为SOCK_DGRAM或者SOCK_RAW,影响是否扣除链路层的首部。

​ libpcap在内核收发包的接口处将skb_clone()拿走的包.

关于内核中如何注册网络协议和钩子函数的过程,此处先不展开,后续专门讲解。我们接下去是看下libpcap的一些实现及其api.

1.1.1.2 libpcap

当在系统中输入tcpdump –version的时候,输出的其实还有libpcap,足见其在tcpdump中的地位。

​ 其实最早的编译系统和过滤引擎是在tcpdump项目中的,后来为了编译其他抓包的应用,将其独立出来。现在libpcap提供独立于平台的库和API,来满足执行网络嗅探。

tcpdump.c正式使用libpcap里的函数完成两个最关键的动作:获取捕获报文的接口,和捕获报文并将报文交给callback。

libpcap支持“伯克利包过滤(BPF)”语法。BPF能够通过比较第2、3、4层协议中各个数据字段值的方法对流量进行过滤。Libpcap的使用逻辑如下图:

如果愿意,大家也可以基于libpcap开发一个类似tcpdump的抓包工具。需要注意的是如果使用分组捕获设备,只能在单个接口上接收到达的分组。

1.1.1.3 核心函数

我们先来看下libpcap中的一些核心函数,根据函数的功能,可以分为如下几类:

l  为读包打开句柄

l  为抓包选择链路层

l  抓包函数

l  过滤器

l  选定抓包方向(进还是出)

l  抓统计信息

l  将包写入文件打开句柄

l  写包

l  注入包

l  报告错误

l  获取库版本信息

官方的介绍查看http://www.tcpdump.org/manpages/pcap.3pcap.html

常用的一些函数如下:

pcap_lookupdev,如果分组捕获设备未曾指定(-i命令行选项),该函数选择一个设备。

pcap_open_offine打开一个保存的文件。

pcap_setfilter设置过滤器

pcap_open_live打开选择的设备。

pcap_next接收一个包

pcap_dump将包写入到pcap_dump_t结构体

pcap_loopupnet返回分组捕获设备的网络地址和子网掩码,然后在调用pcap_compile时必须指定这个子网掩码。

pcap_compile把cmd字符数组中构造的过滤器字符串编译成一个过滤器程序,存放在fcode中。

pcap_setfilter把编译出来的过滤器程序装载到分组捕获设备,同时引发用该过滤器选取的分组的捕获。

pcap_datalink返回分组捕获设备的数据链路类型。

等等,那么如何去使用libpcap库呢,一起来看下。

1.1.1.4 使用准备

先在系统中安装pcap-dev包(apt-get install pcap-dev),然后创建一个test.c文件如下:

#include

#include

int

main (int argc, char *argv[])

{undefined

char *dev, errbuf[PCAP_ERRBUF_SIZE];

dev = pcap_lookupdev (errbuf);

if (dev == NULL)

{undefined

fprintf (stderr, “Couldn’t find default device: %s\n”, errbuf);

return (2);

}

printf (“Device: %s\n”, dev);

return (0);

}

然后编译如下:

gcc test.c -lpcap -lpthread

​ 就可以执行了,在系统中寻找一个可以抓包的接口。

​ 有了接口设备,可以继续创建嗅探会话了,使用函数

pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf)

其中snaplen是pcap抓包的字节数, promisc 是否启用混杂模式(不是混杂模式的话就只抓给本机的包。),to_ms是否超时,ebuf存放错误信息。

​ 创建了嗅探会话之后,就要一个过滤器。可以只提取我们想要的数据。过滤器在应用之前必须要先编译,调用函数如下:

int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask)

第一个参数就是pcap_open_live返回的值,fp 存储的过滤器的版本,optimize是表示是否需要优化,最后netmask是过滤器使用的所在子网掩码。

​ 有了过滤器之后就是要使用编译器,调用函数:

int pcap_setfilter(pcap_t *p, struct bpf_program *fp)

​ 到此整个代码流程参考如下代码段:

#include

pcap_t handle; / Session handle */

char dev[] = “rl0”; /* Device to sniff on */

char errbuf[PCAP_ERRBUF_SIZE]; /* Error string */

struct bpf_program fp; /* The compiled filter expression */

char filter_exp[] = “port 23”; /* The filter expression */

bpf_u_int32 mask; /* The netmask of our sniffing device */

bpf_u_int32 net; /* The IP of our sniffing device */

if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) {undefined

​ fprintf(stderr, “Can’t get netmask for device %s\n”, dev);

​ net = 0;

​ mask = 0;

}

handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf);

if (handle == NULL) {undefined

​ fprintf(stderr, “Couldn’t open device %s: %s\n”, dev, errbuf);

​ return(2);

}

if (pcap_compile(handle, &fp, filter_exp, 0, net) == -1) {undefined

​ fprintf(stderr, “Couldn’t parse filter %s: %s\n”, filter_exp, pcap_geterr(handle));

​ return(2);

}

if (pcap_setfilter(handle, &fp) == -1) {undefined

​ fprintf(stderr, “Couldn’t install filter %s: %s\n”, filter_exp, pcap_geterr(handle));

​ return(2);

}

1.1.1.5 开始抓包

已经准备好监听抓包,并设置了过滤器,下面就是启动抓包了。

抓包技术有两种,一种是一次抓一个包;另一种是等待有n个包的时候在一起抓。

​ 先看抓一次抓一个包,使用函数如下:

u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)

​ 第一个参数就是创建的会话句柄,第二个参数是存放包信息的。

​ 这个函数是比较少用的,现在大多数抓包工具都是使用第二种技术抓包的,其用到的函数就是:

int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)

​ 第一个参数是创建的会话句柄,第二个参数是数量(抓几个包),就是这个参数制定抓多少包,抓完就结束了,第三个函数是抓到足够数量后的回调函数,每次抓到都会调用回调函数,第四个参数经常设置为NULL,在一些应用中会有用。

​ 和pcap_loop函数类似的是pcap_dispatch,两者用法基本一致,主要差异是pcap_dispatch只会执行一次回调函数,而pcap_loop会一直调用回调函数处理包。

​ 其回调函数的定义如下:

void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet);

​ 是void型的,第一个参数args是pcap_loop函数的最后一个参数,第二个参数是pcap的头其包含了抓住的包的信息,第三个就是包本身了。

struct pcap_pkthdr {undefined

​ struct timeval ts; /* time stamp */

​ bpf_u_int32 caplen; /* length of portion present */

​ bpf_u_int32 len; /* length this packet (off wire) */

};

​ 关于包本身其实是一个字符串指针,怎么去寻找我的ip头,tcp头,以及头中的内容呢?这就需要是使用C语言中异常强大的指针了,定义一个宏如下:

/* Ethernet addresses are 6 bytes */

#define ETHER_ADDR_LEN 6

/* Ethernet header */

struct sniff_ethernet {undefined

​ u_char ether_dhost[ETHER_ADDR_LEN]; /* Destination host address */

​ u_char ether_shost[ETHER_ADDR_LEN]; /* Source host address */

​ u_short ether_type; /* IP? ARP? RARP? etc */

};

/* IP header */

struct sniff_ip {undefined

​ u_char ip_vhl; /* version > 2 */

​ u_char ip_tos; /* type of service */

​ u_short ip_len; /* total length */

​ u_short ip_id; /* identification */

​ u_short ip_off; /* fragment offset field */

#define IP_RF 0x8000 /* reserved fragment flag */

#define IP_DF 0x4000 /* dont fragment flag */

#define IP_MF 0x2000 /* more fragments flag */

#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */

​ u_char ip_ttl; /* time to live */

​ u_char ip_p; /* protocol */

​ u_short ip_sum; /* checksum */

​ struct in_addr ip_src,ip_dst; /* source and dest address */

};

#define IP_HL(ip) (((ip)->ip_vhl) & 0x0f)

#define IP_V(ip) (((ip)->ip_vhl) >> 4)

/* TCP header */

typedef u_int tcp_seq;

struct sniff_tcp {undefined

​ u_short th_sport; /* source port */

​ u_short th_dport; /* destination port */

​ tcp_seq th_seq; /* sequence number */

​ tcp_seq th_ack; /* acknowledgement number */

​ u_char th_offx2; /* data offset, rsvd */

#define TH_OFF(th) (((th)->th_offx2 & 0xf0) >> 4)

​ u_char th_flags;

#define TH_FIN 0x01

#define TH_SYN 0x02

#define TH_RST 0x04

#define TH_PUSH 0x08

#define TH_ACK 0x10

#define TH_URG 0x20

#define TH_ECE 0x40

#define TH_CWR 0x80

#define TH_FLAGS (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR)

​ u_short th_win; /* window */

​ u_short th_sum; /* checksum */

​ u_short th_urp; /* urgent pointer */

};

/* ethernet headers are always exactly 14 bytes */

#define SIZE_ETHERNET 14

const struct sniff_ethernet ethernet; / The ethernet header */

const struct sniff_ip ip; / The IP header */

const struct sniff_tcp tcp; / The TCP header */

const char payload; / Packet payload */

u_int size_ip;

u_int size_tcp;

​ 通过以上结构体定义,可以从回调函数的包指针地址出发,逐个找到链路帧头、IP帧头、TCP帧头、数据负载了。

​ 附上一个实例DEMO链接。

实例DEMO

                

文章来源网络,作者:运维,如若转载,请注明出处:https://shuyeidc.com/wp/208053.html<

(0)
运维的头像运维
上一篇2025-04-08 20:06
下一篇 2025-04-08 20:07

相关推荐

  • 个人主题怎么制作?

    制作个人主题是一个将个人风格、兴趣或专业领域转化为视觉化或结构化内容的过程,无论是用于个人博客、作品集、社交媒体账号还是品牌形象,核心都是围绕“个人特色”展开,以下从定位、内容规划、视觉设计、技术实现四个维度,详细拆解制作个人主题的完整流程,明确主题定位:找到个人特色的核心主题定位是所有工作的起点,需要先回答……

    2025-11-20
    0
  • 社群营销管理关键是什么?

    社群营销的核心在于通过建立有温度、有价值、有归属感的社群,实现用户留存、转化和品牌传播,其管理需贯穿“目标定位-内容运营-用户互动-数据驱动-风险控制”全流程,以下从五个维度展开详细说明:明确社群定位与目标社群管理的首要任务是精准定位,需明确社群的核心价值(如行业交流、产品使用指导、兴趣分享等)、目标用户画像……

    2025-11-20
    0
  • 香港公司网站备案需要什么材料?

    香港公司进行网站备案是一个涉及多部门协调、流程相对严谨的过程,尤其需兼顾中国内地与香港两地的监管要求,由于香港公司注册地与中国内地不同,其网站若主要服务内地用户或使用内地服务器,需根据服务器位置、网站内容性质等,选择对应的备案路径(如工信部ICP备案或公安备案),以下从备案主体资格、流程步骤、材料准备、注意事项……

    2025-11-20
    0
  • 如何企业上云推广

    企业上云已成为数字化转型的核心战略,但推广过程中需结合行业特性、企业痛点与市场需求,构建系统性、多维度的推广体系,以下从市场定位、策略设计、执行落地及效果优化四个维度,详细拆解企业上云推广的实践路径,精准定位:明确目标企业与核心价值企业上云并非“一刀切”的方案,需先锁定目标客户群体,提炼差异化价值主张,客户分层……

    2025-11-20
    0
  • PS设计搜索框的实用技巧有哪些?

    在PS中设计一个美观且功能性的搜索框需要结合创意构思、视觉设计和用户体验考量,以下从设计思路、制作步骤、细节优化及交互预览等方面详细说明,帮助打造符合需求的搜索框,设计前的规划明确使用场景:根据网站或APP的整体风格确定搜索框的调性,例如极简风适合细线条和纯色,科技感适合渐变和发光效果,电商类则可能需要突出搜索……

    2025-11-20
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注