核心版本:2.6.34
ICMP模組比較簡單,要注意的是icmp的速率限制策略,向IP層傳輸資料ip_append_data()和 ip_push_pending_frames()。
在net/ipv4/af_inet.c中的inet_init()註冊icmp協議,從這裡也可以看出,ICMP模組是綁 定在IP模組之上的。inet_add_protocol()會將icmp_protocol加入到全域量inet_protos中。
if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0) printk(KERN_CRIT "inet_init: Cannot add ICMP protocol\n"); icmp_protocol定義如下: static const struct net_protocol icmp_protocol = { .handler = icmp_rcv, .no_policy = 1, .netns_ok = 1, };
除了註冊icmp協議,還要對icmp模組初始化,這部分由icmp_init()完成。
if (icmp_init() < 0) panic("Failed to create the ICMP control socket.\n");
icmp_init()函數做的事很簡單,register_pernet_subsys(&icmp_sk_ops),而註冊icmp網路子系統過程中會調用 icmp_sk_ops.init(即icmp_sk_init函數)來完成它的初始化,下面具體看icmp_sk_init()函數。
首先為net為配CPU數目 (nr_cpu_ids)個struct sock結構體空間,這裡的net是全域的網路名稱,一般是init_inet。
net->ipv4.icmp_sk = kzalloc(nr_cpu_ids * sizeof(struct sock *), GFP_KERNEL);
每個CPU i,它的sock結構體位於net中的icmp_sk[i]。於每 個CPU i,初始化剛剛分配的icmp_sk[i]:
-第一步,inet_ctl_sock_create()建立sk,並在net->ipv4.icmp_sk[i] = sk中將其賦值給icmp_sk[i]。
-第二步:ICMP發送緩衝區大小sk_sndbuf設定為128K
for_each_possible_cpu(i) { struct sock *sk; err = inet_ctl_sock_create(&sk, PF_INET, SOCK_RAW, IPPROTO_ICMP, net); if (err < 0) goto fail; net->ipv4.icmp_sk[i] = sk; sk->sk_sndbuf = (2 * ((64 * 1024) + sizeof(struct sk_buff))); sock_set_flag(sk, SOCK_USE_WRITE_QUEUE); inet_sk(sk)->pmtudisc = IP_PMTUDISC_DONT; }
忽略發往廣播位址的icmp echo報文;忽略發往廣播位址的錯誤的響應報文;
net->ipv4.sysctl_icmp_echo_ignore_all = 0; net->ipv4.sysctl_icmp_echo_ignore_broadcasts = 1; net->ipv4.sysctl_icmp_ignore_bogus_error_responses = 1;
設定icmp處理速率,這裡的ratelimit和ratemask參 數在後面限速處理時會具體用到。
net->ipv4.sysctl_icmp_ratelimit = 1 * HZ; net->ipv4.sysctl_icmp_ratemask = 0x1818; net->ipv4.sysctl_icmp_errors_use_inbound_ifaddr = 0;
初始化工作完成後,還是從icmp的接收開始,icmp_rcv 完成icmp報文的處理。
取得icmp前序,此時skb->transport_header是在IP模組處理中的ip_local_deliver_finish() 將其設定為了指向icmp前序的位置。
icmph = icmp_hdr(skb);
根據icmp的類型type交由不同的處理函數去完成。
icmp_pointers[icmph->type].handler(skb);
icmp_pointers是在icmp.c中定義的全域量,部分如下:
static const struct icmp_control icmp_pointers[NR_ICMP_TYPES + 1] = { [ICMP_ECHOREPLY] = { .handler = icmp_discard, }, [1] = { .handler = icmp_discard, .error = 1, }, …… }
比如對於收到的icmp報文type為0或1(響應回覆或目的不可達),協議棧要做的就是丟棄掉它 – icmp_discard()。下 面以icmp echo和icmp timestamp為例說明。
收到icmp echo報文執行icmp_echo()
icmp_param是回複時資訊,它直接 拷貝了echo的ICMP前序icmp_hdr(skb),僅僅改變了前序的type = ICMP_ECHO_REPLY,然後調用icmp_reply()處理髮送。
struct icmp_bxm icmp_param; icmp_param.data.icmph = *icmp_hdr(skb); icmp_param.data.icmph.type = ICMP_ECHOREPLY; icmp_param.skb = skb; icmp_param.offset = 0; icmp_param.data_len = skb->len; icmp_param.head_len = sizeof(struct icmphdr); icmp_reply(&icmp_param, skb);
收到icmp timestamp報文後執行icmp_timestamp()