深入理解Linux网络技术内幕——协议处理函数

网络帧在进入网络层时,需要区分不同的网络协议进行处理,这就需要涉及协议处理函数。

首先我们从驱动接收到一个数据帧,分析数据帧在协议栈中自下而上的传输流程。

设备驱动程序在接收到一个数据帧时,会将其保存在一个sk_buff缓冲区数据结构,并对其进行初始化。

struct sk_buff {
......
	__be16          protocol:16;
......
}

在这个缓冲区结构体中,有一个protocol字段,用于标识网络层的协议。

我们知道网络帧在设备驱动程序中处理后,设备驱动程序会调用netif_receive_skb用以将数据帧向协议栈上层传输(网络层),netif_receive_skb这是就会查询sk_buff结构体中的protocol,识别网络帧的网络层协议,然后决定网络帧接下来的处理函数。(如protocol为ETH_P_IP,则调用ip_rcv函数。)

protocol的定义在include/linux/if_ether.h中,如下所示:

#define ETH_P_802_3 0x0001      /* Dummy type for 802.3 frames  */
#define ETH_P_AX25  0x0002      /* Dummy protocol id for AX.25  */
#define ETH_P_ALL   0x0003      /* Every packet (be careful!!!) */
                        /*ETH_P_ALL不是真正的协议,而是作为通配符,主要用于sniff嗅探器等*/                                      
#define ETH_P_802_2 0x0004      /* 802.2 frames         */
#define ETH_P_SNAP  0x0005      /* Internal only        */
#define ETH_P_DDCMP     0x0006          /* DEC DDCMP: Internal only     */
#define ETH_P_WAN_PPP   0x0007          /* Dummy type for WAN PPP frames*/
#define ETH_P_PPP_MP    0x0008          /* Dummy type for PPP MP frames */
......

协议处理函数的组织

在内核中,每种协议都有结构体packet_tpye描述:

struct packet_type {
    __be16  type;   /* This is really htons(ether_type). */ //二层协议类型,ETH_P_IP、ETH_P_ARP等等
    struct net_device   *dev;   /* NULL is wildcarded here       */
    //钩子函数了,如 ip_rcv()、arp_rcv()等等
    int         (*func) (struct sk_buff *,
                     struct net_device *,
                     struct packet_type *,
                     struct net_device *);
    struct sk_buff      *(*gso_segment)(struct sk_buff *skb,
                        int features);
    int         (*gso_send_check)(struct sk_buff *skb);
    struct sk_buff      **(*gro_receive)(struct sk_buff **head,
                           struct sk_buff *skb);
    int         (*gro_complete)(struct sk_buff *skb);
    void            *af_packet_priv;
    struct list_head    list;
};

其中成员func即为各个协议的钩子函数(协议处理函数).

为了加快方便速度,大部分协议通过哈希表来组织,十六个列表组织成一个数组ptype_base[16].

而ETH_P_ALL类的协议则被组织到全局变量ptype_all中.

协议处理函数的注册

各种协议通过dev_add_pack注册.

//注册协议:把packet_type结构挂在与type对应的list_head上面
void dev_add_pack(struct packet_type *pt)
{
    int hash;                 

    spin_lock_bh(&ptype_lock);
    if (pt->type == htons(ETH_P_ALL))  //type为ETH_P_ALL时,挂在ptype_all上面
        list_add_rcu(&pt->list, &ptype_all);
    else {  //否则,挂在ptype_base[type&15]上面
        hash = ntohs(pt->type) & PTYPE_HASH_MASK;
        list_add_rcu(&pt->list, &ptype_base[hash]);
    }
    spin_unlock_bh(&ptype_lock);
}
EXPORT_SYMBOL(dev_add_pack);

接下来我们以ipv4为例,可靠具体的协议栈函数是如何组织的.(我们需要进入ipv4模块的初始化分析)

static struct packet_type ip_packet_type __read_mostly = {
    .type = cpu_to_be16(ETH_P_IP),
    .func = ip_rcv,   //ipv4 的协议处理函数 ,在netif_receive_skb会使用
    .gso_send_check = inet_gso_send_check,
    .gso_segment = inet_gso_segment,
    .gro_receive = inet_gro_receive,
    .gro_complete = inet_gro_complete,
};     
static int __init inet_init(void)
{
	......
    dev_add_pack(&ip_packet_type);
	......
}

由此,网络帧,经过网络驱动程序处理,通过netif_receive_skb,最终转入具体的协议处理函数进行处理.

时间: 2024-08-30 01:27:58

深入理解Linux网络技术内幕——协议处理函数的相关文章

深入理解Linux网络技术内幕——IPv4 报文的传输发送

报文传输,指的是报文离开本机,发往其他系统的过程. 传输可以由L4层协议发起,也可以由报文转发发起. 在深入理解Linux网络技术内幕--IPv4 报文的接收(转发与本地传递)一文中,我们可以看到,报文转发最后会调用dst_output与邻居子系统进行交互,然后传给设备驱动程序. 这里,我们从L4层协议发起的传输,最后也会经历这一过程(调用dst_output).本文讨论的是L4层协议发起的传输,在IPv4协议处理(IP层)中的一些环节. 大蓝图 我们先看下传输环节的大蓝图,以便对传输这一过程有

深入理解Linux网络技术内幕——L4层协议与Raw IP的处理

我们简单了解下L4层协议和Raw IP是如何与IP层进行交互的. L4层协议 L4层协议可以通过静态编译和模块配置两种方式加入内核. 比较重要的协议如TCP.UDP.ICMP通常是静态编译至内核. 一些不常用的或者比较特殊的协议,则是通过内核配置加入内核.如IGMP,SCTP,IPIP等等. L4层协议的注册 L4层协议有net_protocol结构定义: /* This is used to register protocols. */ struct net_protocol { int (*

深入理解Linux网络技术内幕——IPv4 报文的接收(转发与本地传递)

我们知道,报文经过网卡驱动处理后,调用net_receive_skb传递给具体的协议处理函数,对于IPv4报文来说,其协议处理函数就是ip_rcv了,ip_rcv在进行一些健康检查等操作后,会调用ip_rcv_finish来处理报文.这也是IPv4协议对报文接收处理的开始. 我们先看下ip_rcv_finish源代码: ip_rcv_finish: //ip数据报文的主要处理程序(ip_rcv仅仅只是对ip数据报做一些健康性检查) //ip_rcv_finish 其实是进行路由表查询,,决定报文

《深入理解Linux网络技术内幕》阅读笔记 --- 路由

一.Linux内核中路由相关的主要数据结构 struct fib_result:对路由表查找后返回该结构,它的内容并不是简单的包含下一跳信息,而且包含其他特性,例如策略路由所需的更多参数. struct fib_rule:表示由策略路由在路由流量时选择路由表的规则 struct fib_node:一条路由表项.例如,该数据结构用于存储由route add或ip route add命令添加一条路由时生成的信息. struct fn_zone:一个zone表示子网掩码长度相同的一组路由 struct

深入理解Linux网络技术内幕——路由子系统的概念与高级路由

本文讨论IPv4的路由子系统.(IPv6对路由的处理不同). 基本概念 路由子系统工作在三层,用来转发入口流量. 路由子系统主要设计 路由器.路由.路由表等概念. 路由器: 配备多个网络接口卡(NIC),并且能利用自身网络信息进行入口流量转发的设备. 路由: 流量转发,决定目的地的过程 路由表:转发信息库,该库中储存路由需要本地接收还是转发的信息, 以及转发流量时所需要的信息.(即,信息库用来判断,要不要转发,如果要转发,向哪里转发). 我们了解,路由器有多个网卡,但是多个NIC的设备不一定就是

深入理解Linux网络技术内幕——网络设备初始化

概述 内核的初始化过程过程中,与网络相关的工作如下所示: 内核引导时执行start_kernel,start_kernel结束之前会调用rest_init,rest_init初始化内核线程init(在Linux3-12中为kernel_init). asmlinkage void __init start_kernel(void) { ... parse_early_param();//间接调用parse_args parse_args(...); //处理内核引导程序(boot loader)

深入理解Linux网络技术内幕——用户空间与内核空间交互

概述: 内核空间与用户空间经常需要进行交互.举个例子:当用户空间使用一些配置命令如ifconfig或route时,内核处理程序就要响应这些处理请求. 用户空间与内核有多种交互方式,最常用的有以下四种:通过/proc虚拟文件系统,通过/sys虚拟文件系统,通过ioctl系统调用,通过Netlink socket. 其中编写程序时最常使用ioctl,这四种方式中有两种是通过虚拟文件系统. procfs 与 sysctl procfs挂载/proc  sysctl挂载在/proc/sys(与后面介绍的

深入理解Linux网络技术内幕——中断与网络驱动程序

接收到帧时通知驱动程序 在网络环境中,设备(网卡)接收到一个数据帧时,需要通知驱动程序进行处理.有一下几种通知机制: 轮询: 内核不断检查设备是否有话要说.(比较耗资源,但在一些情况下却是最佳方法) 中断: 特定事件发生时,设备驱动程序代表内核指示设备产生硬件中断,内核中断其它活动满足设备的需要.多数网络驱动程序使用中断. 中断期间处理多帧: 中断被通知,且驱动程序执行.然后保持帧的接收(载入),直到输入队列达到指定的数目.或者一直做下去知道队列清空.或者经过指定时间. 定时器驱动的中断事件 驱

深入理解Linux网络技术内幕——IPv4 分段与重组

封包的分段和重组是IP协议最重要的工作之一. IPv4报头中有一个len字段(用于表示报文的总长度,单位:字节)占16bit,因此,封包的最大尺寸定义为64K,(2^16/1024=64). 但是,在实际网络传输中,没有几个网络接口能够传输64K这么大的封包,而是有一个MTU表示其最大传输单元.这样,当要传输的封包大于MTU时,就需要对封包进行分段. 这里需要说明,我们指的MTU,不仅仅是出口设备的MTU,它取决于很多因素,如路由表项所用的MTU.出口设备的MTU等. 我们先不用过多与关注MTU