一文讲解如何学习 Linux 内核网络协议栈

这篇具有很好参考价值的文章主要介绍了一文讲解如何学习 Linux 内核网络协议栈。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

协议栈的细节

下面将介绍一些内核网络协议栈中常常涉及到的概念。

sk_buff

内核显然需要一个数据结构来表示报文,这个结构就是 sk_buff ( socket buffer 的简称),它等同于在<TCP/IP详解 卷2>中描述的 BSD 内核中的 mbuf。

sk_buff 结构自身并不存储报文内容,它通过多个指针指向真正的报文内存空间:

一文讲解如何学习 Linux 内核网络协议栈

sk_buff 是一个贯穿整个协议栈层次的结构,在各层间传递时,内核只需要调整 sk_buff 中的指针位置就行。

net_device

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

如下图所示,每个网卡都有两端,一端是协议栈(IP、TCP、UDP),另一端则有所区别,对物理网卡来说,这一端是网卡生产厂商提供的设备驱动程序,而对虚拟网卡来说差别就大了,正是由于虚拟网卡的存在,内核才能支持各种隧道封装、容器通信等功能。

一文讲解如何学习 Linux 内核网络协议栈

  资料直通车:Linux内核源码技术学习路线+视频教程内核源码

学习直通车:Linux内核源码内存调优文件系统进程管理设备驱动/网络协议栈

socket & sock

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

如下图所示,这两个结构实际上是一一对应的。

一文讲解如何学习 Linux 内核网络协议栈

注意到,这两个结构上都有一个叫 ops 的指针, 但它们的类型不同。socket 的 ops 是一个指向 struct proto_ops 的指针,sock 的 ops 是一个指向 struct proto 的指针, 它们在结构被创建时确定。

回忆网络编程中 socket() 函数的原型:

#include <sys/socket.h>

sockfd = socket(int socket_family, int socket_type, int protocol);

实际上, socket->ops 和 sock->ops 由前两个参数 socket_family 和 socket_type 共同确定。

如果 socket_family 是最常用的 PF_INET 协议簇, 则 socket->ops 和 sock->ops 的取值就记录在 INET 协议开关表中:

static struct inet_protosw inetsw_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,
    },
}
.......

L3->L4

我们知道网络协议栈是分层的,但实际上,具体到实现,内核协议栈的分层只是逻辑上的,本质还是函数调用。发送流程(上层调用下层)通常是直接调用(因为没有不确定性,比如TCP知道下面一定IP),但接收过程不一样了,比如报文在 IP 层时,它上面可能是 TCP,也可能是 UDP,或者是 ICMP 等等,所以接收过程使用的是注册-回调机制。

还是以 INET 协议簇为例,注册接口是:

int inet_add_protocol(const struct net_protocol *prot, unsigned char protocol);

在内核网络子系统初始化时,L4 层协议(如下面的 TCP 和 UDP)会被注册:

static struct net_protocol tcp_protocol = {
    ......
    .handler = tcp_v4_rcv,
    ......
};

static struct net_protocol udp_protocol = {
    .....
    .handler = udp_rcv,
    .....
};
.......

而在IP层,查询过路由后,如果该报文是需要上送本机的,则会根据报文的 L4 协议,送给不同的 L4 处理:

static int ip_local_deliver_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{
    ......
    ipprot = rcu_dereference(inet_protos[protocol]);
    ......
    ret = ipprot->handler(skb);     
    ......
}
.......

L2->L3

L2->L3 如出一辙。只不过注册接口变成了:

void dev_add_pack(struct packet_type *pt)

谁会注册呢?显然至少 IP 会:

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

而在报文接收过程中,设备驱动程序会将报文的 L3 类型设置到 skb->protocol,然后在内核 netif_receive_skb 收包时,会根据这个 protocol 调用不同的回调函数:

__netif_receive_skb(struct sk_buff *skb)
{
    ......
    type = skb->protocol;
    ......
    ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
}
.......

Netfilter

Netfilter 是报文在内核协议栈必然会通过的路径,我们从下面这张图就可以看到,Netfilter 在内核的 5 个地方设置了 HOOK 点,用户可以通过配置 iptables 规则,在 HOOK 点对报文进行过滤、修改等操作。

一文讲解如何学习 Linux 内核网络协议栈

在内核代码中,我们时常可见 NF_HOOK 这样的调用。我的建议是,如果你暂时不考虑 Netfilter,那么就直接跳过, 跟踪 okfn 就行。

static inline int NF_HOOK(uint8_t pf, unsigned int hook, struct net *net, struct sock *sk, 
    struct sk_buff *skb, struct net_device *in, struct net_device *out,
    int (*okfn)(struct net *, struct sock *, struct sk_buff *))
{
    int ret = nf_hook(pf, hook, net, sk, skb, in, out, okfn);
    if (ret == 1)
        ret = okfn(net, sk, skb);
    return ret;
}
.......

dst_entry

内核需要确定收到的报文是应该本地上送(local deliver)还是转发(forward),对本机发送(local out)的报文需要确定是从哪个网卡发送出去,这都是内核通过查询 fib (forward information base, 转发信息表) 确定。fib 可以理解为一个数据库,数据来源是用户配置或者内核自动生成的路由。

fib 查询的输入是报文 sk_buff,输出是 dst_entry. dst_entry 会被设置到 skb 上:

static inline void skb_dst_set(struct sk_buff *skb, struct dst_entry *dst)
{
    skb->_skb_refdst = (unsigned long)dst;
}

而 dst_entry 中最重要的是一个 input 指针和 output 指针:

struct dst_entry 
{
    ......
    int (*input)(struct sk_buff *);
    int (*output)(struct net *net, struct sock *sk, struct sk_buff *skb);
    ......
}

对于需要本机上送的报文:

rth->dst.input = ip_local_deliver;

对需要转发的报文:

rth->dst.input = ip_forward;

对本机发送的报文:

rth->dst.output = ip_output;

一文讲解如何学习 Linux 内核网络协议栈

 文章来源地址https://www.toymoban.com/news/detail-403953.html

到了这里,关于一文讲解如何学习 Linux 内核网络协议栈的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 浅析linux内核网络协议栈--linux bridge

    本文是参考附录上的资料整理而成,以帮助读者更好的理解kernel中brdige 模块代码。 简单来说,桥接就是把一台机器上的若干个网络接口“连接”起来。其结果是,其中一个网口收到的报文会被复制给其他网口并发送出去。以使得网口之间的报文能够互相转发。 交换机就是这

    2023年04月08日
    浏览(40)
  • Linux内核--网络协议栈(二)UDP数据包发送

    一、引言 二、数据包发送 ------2.1、数据发送流程 三、协议层注册 ------3.1、socket系统调用 ------3.2、socket创建 ------3.3、协议族初始化 ------3.4、对应协议的socket创建 ------------3.4.1、sock ------3.5、协议注册 四、通过套接字发送网络数据 ------4.1、inet_sendmsg 本文首先从宏观上概述了

    2024年01月15日
    浏览(45)
  • Linux内核--网络协议栈(三)sk_buff介绍

    一、引言 二、sk_buff ------2.1、skb介绍 ------2.2、控制字段 ------2.3、其他字段 ------2.4、特定功能字段 ------2.5、管理字段 ------2.6、内存分配 ------2.7、内存释放 ------2.8、克隆和拷贝 ------2.9、队列管理函数 三、sk_buff ------3.1、网络数据流向 ------------3.1.1、TX 方向 ------------3.1.2、R

    2024年01月21日
    浏览(47)
  • Linux内核--网络协议栈(四)sk_buff介绍

    一、引言 二、sk_buff ------2.1、skb介绍 ------2.2、控制字段 ------2.3、其他字段 ------2.4、特定功能字段 ------2.5、管理字段 ------2.6、内存分配 ------2.7、内存释放 ------2.8、克隆和拷贝 ------2.9、队列管理函数 三、sk_buff ------3.1、网络数据流向 ------------3.1.1、TX 方向 ------------3.1.2、R

    2024年01月21日
    浏览(43)
  • Linux内核--网络协议栈(五)TCP IP栈的实现原理与具体过程

    一、引言 二、Linux内核的结构 三、Linux网络子系统 四、TCP/IP协议栈 ------4.1、网络架构 ------4.2、协议无关接口 ------4.3、套接口缓存 ------4.4、重要的数据结构 五、网络信息处理流程 ------5.1、硬中断处理 ------5.2、ksoftirqd内核线程处理软中断 ------5.3、网络协议栈处理 ------5.4、

    2024年01月21日
    浏览(74)
  • 深入理解Linux内核网络——内核是如何接收到网络包的

    系列文章: 深入理解Linux网络——内核是如何接收到网络包的 深入理解Linux网络——内核与用户进程协作之同步阻塞方案(BIO) 深入理解Linux网络——内核与用户进程协作之多路复用方案(epoll) 深入理解Linux网络——内核是如何发送网络包的 深入理解Linux网络——本机网络

    2024年02月13日
    浏览(43)
  • linux内核网络源码学习(二)

    skb_reserve 函数通常用于网络编程中的数据包处理,特别是在构建自定义协议栈或数据包处理模块时。它的作用是为数据包的头部预留额外的空间,以确保数据包的头部数据在内存中是对齐的。 边界对齐的概念是因为许多硬件平台和网络协议要求数据包头的字节对齐。如果数据

    2024年02月08日
    浏览(53)
  • 深入理解Linux网络——内核是如何发送网络包的

    系列文章: 深入理解Linux网络——内核是如何接收到网络包的 深入理解Linux网络——内核与用户进程协作之同步阻塞方案(BIO) 深入理解Linux网络——内核与用户进程协作之多路复用方案(epoll) 深入理解Linux网络——内核是如何发送网络包的 深入理解Linux网络——本机网络

    2024年02月15日
    浏览(89)
  • 深入理解Linux网络——内核是如何接收到网络包的

    系列文章: 深入理解Linux网络——内核是如何接收到网络包的 深入理解Linux网络——内核与用户进程协作之同步阻塞方案(BIO) 深入理解Linux网络——内核与用户进程协作之多路复用方案(epoll) 深入理解Linux网络——内核是如何发送网络包的 深入理解Linux网络——本机网络

    2024年02月15日
    浏览(38)
  • RK3568平台开发系列讲解(Linux系统篇)Linux 内核打印

    🚀返回总目录 在终端使用 dmseg 命令可以获取内核打印信息,该命令的具体使用方法如下所示: 首先在串口终端使用 “ dmseg ”命令,可以看见相应的内核打印信息已经加载了出来,如下图所示: 然后使用以下组合命令查找 nfs 相关的打印信息

    2024年02月02日
    浏览(62)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包