页面加载中,请稍候
来源:一口Linux发布时间:2022-10-25757浏览
询问 AI协议栈的细节
下面将介绍一些内核网络协议栈中常常涉及到的概念。
sk_buff
sk_buff 结构自身并不存储报文内容,它通过多个指针指向真正的报文内存空间:

net_device
内核使用 net_device 表示网卡。网卡可以分为物理网卡和虚拟网卡。物理网卡是指真正能把报文发出本机的网卡,包括真实物理机的网卡以及VM虚拟机的网卡,而像 tun/tap,vxlan、veth pair 这样的则属于虚拟网卡的范畴。

用户空间通过 socket()、bind()、listen()、accept() 等库函数进行网络编程。而这里提到的 socket 和 sock 是内核中的两个数据结构,其中 socket 向上面向用户,而 sock 向下面向协议栈。

注意到,这两个结构上都有一个叫 ops 的指针, 但它们的类型不同。socket 的 ops 是一个指向 struct proto_ops 的指针,sock 的 ops 是一个指向 struct proto 的指针, 它们在结构被创建时确定。
回忆网络编程中 socket() 函数的原型:
#include<sys/socket.h>
sockfd=socket(intsocket_family,intsocket_type,intprotocol);
实际上, socket->ops 和 sock->ops 由前两个参数 socket_family 和 socket_type 共同确定。
如果 socket_family 是最常用的 PF_INET 协议簇, 则 socket->ops 和 sock->ops 的取值就记录在 INET 协议开关表中:
staticstructinet_protoswinetsw_array[]=
{
{
.type=SOCK_STREAM,
.protocol=IPPROTO_TCP,
.prot=tcp_prot,//对应sock->ops
.ops=inet_stream_ops,//对应socket->ops
.flags=INET_PROTOSW_PERMANENT|INET_PROTOSW_ICSK,
},
{
.type=SOCK_DGRAM,
.protocol=IPPROTO_UDP,
.prot=udp_prot,//对应sock->ops
.ops=inet_dgram_ops,//对应socket->ops
.flags=INET_PROTOSW_PERMANENT,
},
}
.......
我们知道网络协议栈是分层的,但实际上,具体到实现,内核协议栈的分层只是逻辑上的,本质还是函数调用。发送流程(上层调用下层)通常是直接调用(因为没有不确定性,比如TCP知道下面一定IP),但接收过程不一样了,比如报文在 IP 层时,它上面可能是 TCP,也可能是 UDP,或者是 ICMP 等等,所以接收过程使用的是注册-回调机制。
还是以 INET 协议簇为例,注册接口是:
intinet_add_protocol(conststructnet_protocol*prot,unsignedcharprotocol);
在内核网络子系统初始化时,L4 层协议(如下面的 TCP 和 UDP)会被注册:
staticstructnet_protocoltcp_protocol={而在IP层,查询过路由后,如果该报文是需要上送本机的,则会根据报文的 L4 协议,送给不同的 L4 处理:
......
.handler=tcp_v4_rcv,
......
};
staticstructnet_protocoludp_protocol={
.....
.handler=udp_rcv,
.....
};
.......
staticintip_local_deliver_finish(structnet*net,structsock*sk,structsk_buff*skb)
{
......
ipprot=rcu_dereference(inet_protos[protocol]);
......
ret=ipprot->handler(skb);
......
}
.......
L2->L3 如出一辙。只不过注册接口变成了:
voiddev_add_pack(structpacket_type*pt)
谁会注册呢?显然至少 IP 会:
staticstructpacket_typeip_packet_type={
.type=cpu_to_be16(ETH_P_IP),
.func=ip_rcv,
}
.......
而在报文接收过程中,设备驱动程序会将报文的 L3 类型设置到 skb->protocol,然后在内核 netif_receive_skb 收包时,会根据这个 protocol 调用不同的回调函数:
__netif_receive_skb(structsk_buff*skb)
{
......
type=skb->protocol;
......
ret=pt_prev->func(skb,skb->dev,pt_prev,orig_dev);
}
.......

在内核代码中,我们时常可见 NF_HOOK 这样的调用。我的建议是,如果你暂时不考虑 Netfilter,那么就直接跳过, 跟踪 okfn 就行。
staticinlineintNF_HOOK(uint8_tpf,unsignedinthook,structnet*net,structsock*sk,
structsk_buff*skb,structnet_device*in,structnet_device*out,
int(*okfn)(structnet*,structsock*,structsk_buff*))
{
intret=nf_hook(pf,hook,net,sk,skb,in,out,okfn);
if(ret==1)
ret=okfn(net,sk,skb);
returnret;
}
.......
dst_entry
内核需要确定收到的报文是应该本地上送(local deliver)还是转发(forward),对本机发送(local out)的报文需要确定是从哪个网卡发送出去,这都是内核通过查询 fib (forward information base, 转发信息表) 确定。fib 可以理解为一个数据库,数据来源是用户配置或者内核自动生成的路由。fib 查询的输入是报文 sk_buff,输出是 dst_entry. dst_entry 会被设置到 skb 上:
staticinlinevoidskb_dst_set(structsk_buff*skb,structdst_entry*dst)
{
skb->_skb_refdst=(unsignedlong)dst;
}
而 dst_entry 中最重要的是一个 input 指针和 output 指针:
structdst_entry
{
......
int(*input)(structsk_buff*);
int(*output)(structnet*net,structsock*sk,structsk_buff*skb);
......
}
对于需要本机上送的报文:
rth->dst.input=ip_local_deliver;
对需要转发的报文:
rth->dst.input=ip_forward;
对本机发送的报文:
rth->dst.output=ip_output;
新闻来源:一口Linux,文中所述为作者独立观点,不代表icspec立场。更多精彩资讯请下载icspec App。如对本稿件有异议,请联系微信客服specltkj。
暂无评论哦,快来评论一下吧!

2026-06-24
2026-06-17