当前位置 : 主页 > 操作系统 > centos >

链路层和网络层的接口 (linux网络子系统学习 第五节 )

来源:互联网 收集:自由互联 发布时间:2022-06-20
网络驱动接收到报文后,会初始化 skb-protocol 字段。链路层的接收函数 netif_receive_skb 会根据该字段来确定把报文送给那个协议模块进一步处理。 以太网的设备调用 eth_type_trans ()来给

网络驱动接收到报文后,会初始化skb->protocol 字段。链路层的接收函数netif_receive_skb会根据该字段来确定把报文送给那个协议模块进一步处理。



以太网的设备调用 eth_type_trans()来给skb->protocol赋值。

__be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev) { struct ethhdr *eth; unsigned char *rawp;net /*把接收net_device 赋给skb*/ skb->dev = dev; /*给skb 的 mac 头指针赋值*/ skb_reset_mac_header(skb); /*把 skb->data 下移,跳过以太头*/ skb_pull(skb, ETH_HLEN); eth = eth_hdr(skb); if (unlikely(is_multicast_ether_addr(eth->h_dest))) { /*判断是否是广播和组播报文,是就给skb->pkt_type赋值*/ if (!compare_ether_addr_64bits(eth->h_dest, dev->broadcast)) skb->pkt_type = PACKET_BROADCAST; else skb->pkt_type = PACKET_MULTICAST; } /*如果报文的目的MAC不是到接收设备的MAC,设置skb->pkt_type*/ else if (1 /*dev->flags&IFF_PROMISC */ ) { if (unlikely(compare_ether_addr_64bits(eth->h_dest, dev->dev_addr))) skb->pkt_type = PACKET_OTHERHOST; } /*Marvell 交换芯片dsa 头*/ if (netdev_uses_dsa_tags(dev)) return htons(ETH_P_DSA); if (netdev_uses_trailer_tags(dev)) return htons(ETH_P_TRAILER); /*以太网头在此返回*/ if (ntohs(eth->h_proto) >= 1536) return eth->h_proto; /*以下处理非以太网的报文,不讨论*/ rawp = skb->data; if (*(unsigned short *)rawp == 0xFFFF) return htons(ETH_P_802_3); /* Real 802.2 LLC*/ return htons(ETH_P_802_2); }

网络设备驱动在调用netif_receive_skb()或netif_rx()前调用 eth_type_trans():


skb->protocol = eth_type_trans(skb,dev);


每个网络层协议都会初始化一个接收报文的函数。Linux内核中使用数据结构 struct packet_type 来描述单个网络层协议。


struct packet_type { /*协议类型,比如ip(0x0800),vlan(0x8100)*/ __be16 type; /* This is really htons(ether_type). */    /*指定接收的网络设备,如果为空,就从所有网络设备中接收, 如果指定,就只接收指定设备上的数据*/ struct net_device *dev; /* NULL is wildcarded here */ /*协议接收函数*/ int (*func) (struct sk_buff *, struct net_device *, struct packet_type *, struct net_device *);    /*下面是 gso 功能使用,每个协议要用gso功能就自己实现自己的函数*/ struct sk_buff *(*gso_segment)(struct sk_buff *skb, int features); int (*gso_send_check)(struct sk_buff *skb); /*下面是 gro 功能使用,每个协议要用gro功能就自己实现自己的函数*/  struct sk_buff **(*gro_receive)(struct sk_buff **head, struct sk_buff *skb); int (*gro_complete)(struct sk_buff *skb);    /*指向协议使用的私有数据指针,一般没有使用*/ void *af_packet_priv; /*把该结构体连接到相应的hash 链表上*/ struct list_head list; };


linux 内核中定义了一张hash 表 ptype_base,hash key 是struct packet_type 中的type字段。表中的每个元素都指向一个struct packet_type 的链表。


同时还定义了一个struct packet_type 的链表ptype_all。这个链表上的协议处理程序接收所以协议的报文,主要用于网络工具和网络嗅探器接收报文。比如tcpdump 抓包程序和原始套接字使用这种类型的packet_type结构。

/* 0800 IP * 8100 802.1Q VLAN * 0001 802.3 * 0002 AX.25 * 0004 802.2 * 8035 RARP * 0005 SNAP * 0805 X.25 * 0806 ARP * 8137 IPX * 0009 Localtalk * 86DD IPv6 */ #define PTYPE_HASH_SIZE (16) #define PTYPE_HASH_MASK (PTYPE_HASH_SIZE - 1) static DEFINE_SPINLOCK(ptype_lock); static struct list_head ptype_base[PTYPE_HASH_SIZE] __read_mostly; static struct list_head ptype_all __read_mostly; /* Taps */


这一张hash 表和 一个链表形成了数据链路层与网络层之间接收数据的接口。


linux 内核中使用如下函数进行协议的添加和删除:


添加协议:

void dev_add_pack(struct packet_type *pt) { int hash; /*ptype_all 链表使用ptype_lock 自旋锁来保护。 ptype_bash hash 表中写的时候用该自旋锁来保护, 读的时候用rcu 锁来保护,实质就是读的时候禁止抢占*/ spin_lock_bh(&ptype_lock); if (pt->type == htons(ETH_P_ALL)) list_add_rcu(&pt->list, &ptype_all); else { hash = ntohs(pt->type) & PTYPE_HASH_MASK; list_add_rcu(&pt->list, &ptype_base[hash]); } spin_unlock_bh(&ptype_lock); }


如果协议类型定义为 ETH_P_ALL,就是接收所有类型的报文。就把该packet_type加入到ptyte_all 链表上。


移除协议:

void __dev_remove_pack(struct packet_type *pt) { struct list_head *head; struct packet_type *pt1; spin_lock_bh(&ptype_lock); if (pt->type == htons(ETH_P_ALL)) head = &ptype_all; else head = &ptype_base[ntohs(pt->type) & PTYPE_HASH_MASK]; list_for_each_entry(pt1, head, list) { if (pt == pt1) { list_del_rcu(&pt->list); goto out; } } out: spin_unlock_bh(&ptype_lock); } void dev_remove_pack(struct packet_type *pt) { __dev_remove_pack(pt); /*移除ptype后要等所有cpu核上的进程都切换一遍后再返回, 这时说明被移除的ptype 已经没有进程在使用了,可以 放心的去做下一步动作,比如释放该结构体所占的内存*/ synchronize_net(); }


每个协议要自己定义自己的paket_type变量,初始化自己的数据。然后自协议模块初始化时调用

dev_add_packet把自己的packet_type 加入到 packet_base hash表中。


协议栈的真正入口函数 netif_receive_skb():


int netif_receive_skb(struct sk_buff *skb) { struct packet_type *ptype, *pt_prev; struct net_device *orig_dev; struct net_device *null_or_orig; int ret = NET_RX_DROP; __be16 type; /*给skb赋值接收时间戳*/ if (!skb->tstamp.tv64) net_timestamp(skb); /*如果带vlan tag,判断是否是到本机vlan 三层口的报文, 如果是,把接收dev 赋成vlan dev*/ if (skb->vlan_tci && vlan_hwaccel_do_receive(skb)) return NET_RX_SUCCESS; /*netpoll是用于在网络设备及I/O还没初始化好时 也能进行报文的收发处理的虚拟网卡的一种实现, 主要用于远程调试及网络控制终端*/ /* if we've gotten here through NAPI, check netpoll */ if (netpoll_receive_skb(skb)) return NET_RX_DROP; if (!skb->iif) skb->iif = skb->dev->ifindex; /*处理链路聚合,如果接收dev 加入了聚合组,把接收dev 替换成聚合口*/ null_or_orig = NULL; orig_dev = skb->dev; if (orig_dev->master) { if (skb_bond_should_drop(skb)) null_or_orig = orig_dev; /* deliver only exact match */ else skb->dev = orig_dev->master; } /*增加收包统计计数*/ __get_cpu_var(netdev_rx_stat).total++; /*初始化skb 中以下指针*/ skb_reset_network_header(skb); skb_reset_transport_header(skb); skb->mac_len = skb->network_header - skb->mac_header; pt_prev = NULL; /*在ptype_base 和 ptype_base中查找,要使用rcu 读锁保护临界区*/ rcu_read_lock();    /*如果内核注册了协议嗅探器,把skb 拷贝一份传给它进行处理。 注意: 经过这轮循环,最后一个协议嗅探器的执行函数是没有被调用的, 放在下面进行调用,因为报文处理的最后一个处理函数被调用时 不需要进行skb user 引用计数的加 1,所以,下一个处理函数会把 最后一个ptype 传递进去,如果该函数要处理掉该skb时,应该先执行 该ptype 处理函数后再执行自己的处理程序*/ list_for_each_entry_rcu(ptype, &ptype_all, list) { if (ptype->dev == null_or_orig || ptype->dev == skb->dev || ptype->dev == orig_dev) { if (pt_prev) ret = deliver_skb(skb, pt_prev, orig_dev); pt_prev = ptype; } } /*进入桥进行二层处理,如果返回skb == NULL,说明skb 被直接二层 转发走了,不用再送网络层了,函数直接返回*/ skb = handle_bridge(skb, &pt_prev, &ret, orig_dev); if (!skb) goto out; /*处理vlan*/ skb = handle_macvlan(skb, &pt_prev, &ret, orig_dev); if (!skb) goto out; /*经过以上处理,报文没被消化掉,就在 ptype_base hash 表中 找到该报文的协议接收函数,送给相应协议处理*/ type = skb->protocol; list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) { if (ptype->type == type && (ptype->dev == null_or_orig || ptype->dev == skb->dev || ptype->dev == orig_dev)) { if (pt_prev) ret = deliver_skb(skb, pt_prev, orig_dev); pt_prev = ptype; } } if (pt_prev) { ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev); } else { kfree_skb(skb); /* Jamal, now you will not able to escape explaining * me how you were going to use this. :-) */ ret = NET_RX_DROP; } out: rcu_read_unlock(); return ret; } EXPORT_SYMBOL(netif_receive_skb);


static inline int deliver_skb(struct sk_buff *skb, struct packet_type *pt_prev, struct net_device *orig_dev) { /*送入相应协议接收函数时要对skb 的引用计数加一, 防止正在使用时被释放。因为一个报文可以被多个协议 的接收函数处理。但最后一个协议接收函数不需要对skb 的引用计数加一,因为最后一个协议接收函数负责释放该 报文所占的内存*/ atomic_inc(&skb->users); return pt_prev->func(skb, skb->dev, pt_prev, orig_dev); }


上一篇:Linux批量部署工具Expect
下一篇:没有了
网友评论