首先來看一下基於3層的ipv4以及ipv6實現的一些4層的協議:
這裏要注意並沒有IGMPV6,這是因爲在ipv6中,它是作爲iCMPv6的一部分實現的.
首先我們要知道輸入數據包的ip頭中的protocol域標識了,將要傳遞的4層協議.
我們這裏主要介紹的是ip數據包從3層傳遞到4層的接口(也就是輸入幀接口).而輸出幀的處理,我前面的blog都已經有介紹,想了解的話,可以去看前面的blog.
先來看主要的數據結構,然後我們會分析ip_local_deliver_finish函數(也就是3層處理的出口函數).
在內核中,每一個4層協議都是一個net_protocol結構體,而內核會在啓動的時候將所有的4層協議都註冊到一個數組inet_protos中,然後根據數據包的ip頭來得到相應的handle函數:
- struct net_protocol {
- ///協議的處理函數,也就是將要處理輸入數據報的4層協議的處理函數.
- int (*handler)(struct sk_buff *skb);
- ///協議的錯誤處理函數.
- void (*err_handler)(struct sk_buff *skb, u32 info);
- ///gso相關的兩個函數.
- int (*gso_send_check)(struct sk_buff *skb);
- struct sk_buff *(*gso_segment)(struct sk_buff *skb,
- int features);
- ///主要是被ipsec所使用的兩個域
- unsigned int no_policy:1,
- netns_ok:1;
- };
L4的協議都是在linux/in.h這個文件中,都是以IPPROTO開頭的一些宏.由於ip頭中的4層協議域是8位,因此4層協議的最大數值也就是255.而在內核中,255是raw ip, IPPPROTO_RAW:
- enum {
- IPPROTO_IP = 0, /* Dummy protocol for TCP */
- IPPROTO_ICMP = 1, /* Internet Control Message Protocol */
- IPPROTO_IGMP = 2, /* Internet Group Management Protocol */
- IPPROTO_IPIP = 4, /* IPIP tunnels (older KA9Q tunnels use 94) */
- IPPROTO_TCP = 6, /* Transmission Control Protocol */
- IPPROTO_EGP = 8, /* Exterior Gateway Protocol */
- IPPROTO_PUP = 12, /* PUP protocol */
- IPPROTO_UDP = 17, /* User Datagram Protocol */
- IPPROTO_IDP = 22, /* XNS IDP protocol */
- IPPROTO_DCCP = 33, /* Datagram Congestion Control Protocol */
- IPPROTO_RSVP = 46, /* RSVP protocol */
- IPPROTO_GRE = 47, /* Cisco GRE tunnels (rfc 1701,1702) */
- IPPROTO_IPV6 = 41, /* IPv6-in-IPv4 tunnelling */
- IPPROTO_ESP = 50, /* Encapsulation Security Payload protocol */
- IPPROTO_AH = 51, /* Authentication Header protocol */
- IPPROTO_BEETPH = 94, /* IP option pseudo header for BEET */
- IPPROTO_PIM = 103, /* Protocol Independent Multicast */
- IPPROTO_COMP = 108, /* Compression Header protocol */
- IPPROTO_SCTP = 132, /* Stream Control Transport Protocol */
- IPPROTO_UDPLITE = 136, /* UDP-Lite (RFC 3828) */
- IPPROTO_RAW = 255, /* Raw IP packets */
- IPPROTO_MAX
- };
這裏要上面列出的協議,並不是所有的都在內核態handle的,其中一些經常在用戶態handle的例如(IPPROTO_RSVP).
內核是通過inet_add_protocol來添加協議到inet_protos數組中的,相應的還有一個刪除方法,我們先來看inet_protos的結構:
這裏要注意的就是讀寫inet_protos時,使用的是自旋鎖,而只讀時,使用的是RCU(Read-Copy Update).
然後來看inet_add_protocol的源碼:
- struct net_protocol *inet_protos[MAX_INET_PROTOS] ____cacheline_aligned_in_smp;
- ///這裏只是舉兩個例子,tcp和udp的協議註冊函數.我們這次暫時就不分析tcp和udp的處理函數了(我會在3層結束後,分析4層源碼)
- static struct net_protocol tcp_protocol = {
- .handler = tcp_v4_rcv,
- .err_handler = tcp_v4_err,
- .gso_send_check = tcp_v4_gso_send_check,
- .gso_segment = tcp_tso_segment,
- .no_policy = 1,
- .netns_ok = 1,
- };
- static struct net_protocol udp_protocol = {
- .handler = udp_rcv,
- .err_handler = udp_err,
- .no_policy = 1,
- .netns_ok = 1,
- };
- int inet_add_protocol(struct net_protocol *prot, unsigned char protocol)
- {
- int hash, ret;
- ///計算當前協議在數組中的slot.
- hash = protocol & (MAX_INET_PROTOS - 1);
- ///使用自旋鎖.
- spin_lock_bh(&inet_proto_lock);
- if (inet_protos[hash]) {
- ret = -1;
- } else {
- ///將相應的prot添加到數組
- inet_protos[hash] = prot;
- ret = 0;
- }
- spin_unlock_bh(&inet_proto_lock);
- return ret;
- }
然後這些協議的註冊都是在內核boot的時候在inet_init中初始化的,下面就是inet_init的代碼片段.:
- static int __init inet_init(void)
- {
- ...........................................
- /*
- * Add all the base protocols.
- */
- if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0)
- printk(KERN_CRIT "inet_init: Cannot add ICMP protocol\n");
- if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)
- printk(KERN_CRIT "inet_init: Cannot add UDP protocol\n");
- if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)
- printk(KERN_CRIT "inet_init: Cannot add TCP protocol\n");
- #ifdef CONFIG_IP_MULTICAST
- if (inet_add_protocol(&igmp_protocol, IPPROTO_IGMP) < 0)
- printk(KERN_CRIT "inet_init: Cannot add IGMP protocol\n");
- #endif
- ..................................
- }
知道協議如何註冊之後,我們來分析ip_local_deliver_finish函數,來看3層是如何將數據包發送到4層的.
1 我們知道linux支持raw數據包的發送,因此在這裏會對raw socket進行了特殊處理,它會clone一份數據包然後傳遞給相應的raw處理函數,然後再繼續後面的處理.
2 ipsec.這時還需要加上相應的ipsec頭,然後再傳給4層處理.看下面的圖:
- static int ip_local_deliver_finish(struct sk_buff *skb)
- {
- ///取出相應的net信息.
- struct net *net = dev_net(skb->dev);
- ///下面兩個主要是調整data指針,使data指針指向4層的數據開始處.
- __skb_pull(skb, ip_hdrlen(skb));
- skb_reset_transport_header(skb);
- ///加rcu鎖.
- rcu_read_lock();
- {
- ///取出ip頭中的協議.
- int protocol = ip_hdr(skb)->protocol;
- int hash, raw;
- struct net_protocol *ipprot;
- resubmit:
- ///得到raw socket, 如果不是raw socket,則返回0.
- raw = raw_local_deliver(skb, protocol);
- ///計算4層協議的slot.
- hash = protocol & (MAX_INET_PROTOS - 1);
- ///rcu讀取相應的協議處理結構.
- ipprot = rcu_dereference(inet_protos[hash]);
- ///主要是ipprot是否有被當前主機註冊.
- if (ipprot != NULL && (net == &init_net || ipprot->netns_ok)) {
- int ret;
- ///判斷ipsec,並進行相關處理.
- if (!ipprot->no_policy) {
- if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
- kfree_skb(skb);
- goto out;
- }
- nf_reset(skb);
- }
- ///調用handler,進入相應的4層協議的處理.
- ret = ipprot->handler(skb);
- if (ret < 0) {
- protocol = -ret;
- goto resubmit;
- }
- IP_INC_STATS_BH(net, IPSTATS_MIB_INDELIVERS);
- }
- ................................................
- out:
- rcu_read_unlock();
- return 0;
- }
最後來看一下raw socket的處理,通過上面我們知道,會調用raw_local_deliver來進行raw socket的相關處理(如果沒有raw socket,則會直接返回):
當應用程序使用raw ip socket,他只需要攢遞給內核協議id(4層的協議),以及目的地址.因此這裏存取sock的hash表使用的key就是4層協議id.
- ///相應的hash表,保存raw socket.
- struct raw_hashinfo {
- rwlock_t lock;
- struct hlist_head ht[RAW_HTABLE_SIZE];
- };
- static struct raw_hashinfo raw_v4_hashinfo = {
- .lock = __RW_LOCK_UNLOCKED(raw_v4_hashinfo.lock),
- };
- int raw_local_deliver(struct sk_buff *skb, int protocol)
- {
- int hash;
- struct sock *raw_sk;
- ///通過協議計算hash值(使用4層協議id).
- hash = protocol & (RAW_HTABLE_SIZE - 1);
- ///得到相應的raw_sk.
- raw_sk = sk_head(&raw_v4_hashinfo.ht[hash]);
- /* If there maybe a raw socket we must check - if not we
- * don't care less
- */
- ///交給raw socket的處理函數,raw_v4_input中會clone一個skb,然後交給最後的raw_rev函數去處理最終的數據包.
- if (raw_sk && !raw_v4_input(skb, ip_hdr(skb), hash))
- raw_sk = NULL;
- return raw_sk != NULL;
- }