linux 协议栈学习 第五节 链路层和网络层的接口

会根据该字段来确定把报文送给那个协议模块进一步处理。

以太网的设备调用 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;elseskb->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);}

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;elsehead = &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 */elseskb->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);

人,总是很难改正自己的缺点,

linux 协议栈学习 第五节 链路层和网络层的接口

相关文章:

你感兴趣的文章:

标签云: