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机制实现对包的完美过滤。
更多介绍和使用可以从官网:
http://www.tcpdump.org/index.html#documentation查阅到。
tips
下面将先介绍libpcap(第二章),对libpcap有一个了解,然后再从Linux内核角度分析tcpdunp的抓包原理(第三章)。
libpcap(Packet Capture Library),即数据包捕获函数库,是Unix/Linux平台下的网络数据包捕获函数库,独立于系统的用户层包捕获的API接口,为底层网络监测提供了一个可移植的框架。
利用libpcap函数库开发应用程序的基本步骤:
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
/*errbuf:存放出错信息字符串,宏定义PCAP_ERRBUF_SIZE为错误缓冲区大小
返回值为设备名(第一个合适的网络接口的字符串指针)
*/
char *pcap_lookupdev(char *errbuf)
/*获取指定网卡的ip地址,子网掩码
device:网络设备名
netp:存放ip地址的指针
maskp:存放子网掩码的指针
errbuf:存放出错信息*/
int pcap_lookupnet(char *device,bpf_u_int32 *netp,bpf_u_int32 *maskp,char *errbuf );
/*
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 );
/*
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)
/*
p:pcap_open_live() 返回的 pcap_t 类型的指针
fp:pcap_compile() 的第二个参数
*/
int pcap_setfilter( pcap_t * p, struct bpf_program * fp );
使用strace追踪
strace tcpdump tcp port 80
可以看到tcpdump抓包创建的的套接字类型AF_PACKET
在libpcap库源码中也可以看到有调用socket系统调用:
tatic 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;
}
...
}
AF_PACKET和socket应用结合一般都是用于抓包分析,packet套接字提供的是L2的抓包能力。
socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))
系统调用:
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
......
retval = sock_create(family, type, protocol, &sock);
if (retval < 0)
return retval;
return sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
}
socket创建函数:
int sock_create(int family, int type, int protocol, struct socket **res)
{
return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);
}
调用:__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)创建完成,相应的准备工作完成。
网络收包时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到用户空间
......
}
到这,网络接收数据包时的抓包过程就结束了
网络发包时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)
{
......
if (!list_empty(&ptype_all) || !list_empty(&dev->ptype_all))
dev_queue_xmit_nit(skb, dev);//通过调用dev_queue_xmit这个网络设备接口层函数发送给driver,
......
}
dev_queue_xmit_nit():将数据包发送给driver
void dev_queue_xmit_nit(struct sk_buff *skb, struct net_device *dev)
{
......
list_for_each_entry_rcu(ptype, ptype_list, list) {
/* 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;
}
......
}
在遍历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进行抓包的内核流程梳理
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总结
本文主要从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放到当前的接收队列中
......
}
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/vdFiiwL25-hie8c95kGvXw
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。