自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

從Linux5.9看Icmp的處理流程

系統(tǒng) Linux
昨天有個同學(xué)碰到發(fā)送udp包時收到destination unreachable的icmp包問題,本文簡單介紹一下linux5.9中icmp包的處理流程。

[[397621]]

 本文轉(zhuǎn)載自微信公眾號「編程雜技」,作者theanarkh。轉(zhuǎn)載本文請聯(lián)系編程雜技公眾號。

昨天有個同學(xué)碰到發(fā)送udp包時收到destination unreachable的icmp包問題,本文簡單介紹一下linux5.9中icmp包的處理流程。

發(fā)送icmp包的流程

下面以udp為例看看什么時候會發(fā)送destination unreachable包。我們從收到一個udp包開始分析,具體函數(shù)是udp_rcv。

  1. int udp_rcv(struct sk_buff *skb){ 
  2.     return __udp4_lib_rcv(skb, &udp_table, IPPROTO_UDP); 
  3.  
  4. int __udp4_lib_rcv(struct sk_buff *skb, struct udp_table *udptable, 
  5.            int proto){ 
  6.     struct sock *sk; 
  7.     struct udphdr *uh; 
  8.     unsigned short ulen; 
  9.     struct rtable *rt = skb_rtable(skb); 
  10.     __be32 saddr, daddr; 
  11.     struct net *net = dev_net(skb->dev); 
  12.     bool refcounted; 
  13.     // udp頭 
  14.     uh   = udp_hdr(skb); 
  15.     ulen = ntohs(uh->len); 
  16.     // 源目的ip 
  17.     saddr = ip_hdr(skb)->saddr; 
  18.     daddr = ip_hdr(skb)->daddr; 
  19.     // 頭部指示大小比實際數(shù)據(jù)小 
  20.     if (ulen > skb->len) 
  21.         goto short_packet; 
  22.  
  23.     if (proto == IPPROTO_UDP) { 
  24.         uh = udp_hdr(skb); 
  25.     } 
  26.  
  27.     sk = skb_steal_sock(skb, &refcounted); 
  28.  
  29.     // 廣播或多播 
  30.     if (rt->rt_flags & (RTCF_BROADCAST|RTCF_MULTICAST)) 
  31.         return __udp4_lib_mcast_deliver(net, skb, uh, 
  32.                         saddr, daddr, udptable, proto); 
  33.     // 單播,根據(jù)地址信息找到對應(yīng)的socket 
  34.     sk = __udp4_lib_lookup_skb(skb, uh->source, uh->dest, udptable); 
  35.     // 找到則掛到socket下 
  36.     if (sk) 
  37.         return udp_unicast_rcv_skb(sk, skb, uh); 
  38.  
  39.     // 找不到socket則回復(fù)一個ICMP_DEST_UNREACH icmp包 
  40.     icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0); 
  41.  
  42.     kfree_skb(skb); 
  43.     return 0; 

我們看到當(dāng)通過ip包信息找不到對應(yīng)socket的時候,就會發(fā)送一個icmp包給發(fā)送端。icmp包結(jié)構(gòu)如下。

收到icmp包的處理流程

我們從收到ip包開始分析。

  1. int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, 
  2.        struct net_device *orig_dev){ 
  3.     struct net *net = dev_net(dev); 
  4.  
  5.     skb = ip_rcv_core(skb, net); 
  6.     if (skb == NULL
  7.         return NET_RX_DROP; 
  8.  
  9.     return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, 
  10.                net, NULL, skb, dev, NULL
  11.                ip_rcv_finish); 

ip層收到包后會繼續(xù)執(zhí)行ip_rcv_finish。

  1. static int ip_rcv_finish(struct net *net, struct sock *sk, struct sk_buff *skb){ 
  2.     struct net_device *dev = skb->dev; 
  3.     int ret; 
  4.  
  5.     ret = ip_rcv_finish_core(net, sk, skb, dev, NULL); 
  6.     if (ret != NET_RX_DROP) 
  7.         ret = dst_input(skb); 
  8.     return ret; 

接著執(zhí)行dst_input

  1. static inline int dst_input(struct sk_buff *skb){ 
  2.     return skb_dst(skb)->input(skb); 

input對應(yīng)的是ip_local_deliver。

  1. int ip_local_deliver(struct sk_buff *skb){ 
  2.     struct net *net = dev_net(skb->dev); 
  3.     return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN, 
  4.                net, NULL, skb, skb->dev, NULL
  5.                ip_local_deliver_finish); 

接著執(zhí)行ip_local_deliver_finish。

  1. static int ip_local_deliver_finish(struct net *net, struct sock *sk, struct sk_buff *skb){ 
  2.     __skb_pull(skb, skb_network_header_len(skb)); 
  3.  
  4.     rcu_read_lock(); 
  5.     ip_protocol_deliver_rcu(net, skb, ip_hdr(skb)->protocol); 
  6.     rcu_read_unlock(); 
  7.  
  8.     return 0; 

ip_local_deliver_finish會執(zhí)行ip_protocol_deliver_rcu進一步處理,ip_protocol_deliver_rcu的最后一個入?yún)⑹莍p包里的協(xié)議字段(上層協(xié)議)。

  1. void ip_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int protocol){ 
  2.     const struct net_protocol *ipprot; 
  3.     int raw, ret; 
  4.  
  5. resubmit: 
  6.     // 根據(jù)協(xié)議找到對應(yīng)的處理函數(shù),這里是icmp 
  7.     ipprot = rcu_dereference(inet_protos[protocol]); 
  8.     if (ipprot) { 
  9.         ret = INDIRECT_CALL_2(ipprot->handler, tcp_v4_rcv, udp_rcv, 
  10.                       skb); 
  11.         if (ret < 0) { 
  12.             protocol = -ret; 
  13.             goto resubmit; 
  14.         } 
  15.         __IP_INC_STATS(net, IPSTATS_MIB_INDELIVERS); 
  16.     } 

INDIRECT_CALL_2是一個宏。

  1. #define INDIRECT_CALL_1(f, f1, ...)                 \ 
  2.     ({                              \ 
  3.         likely(f == f1) ? f1(__VA_ARGS__) : f(__VA_ARGS__); \ 
  4.     })#define INDIRECT_CALL_2(f, f2, f1, ...)                 \ 
  5.     ({                              \ 
  6.         likely(f == f2) ? f2(__VA_ARGS__) :         \ 
  7.                   INDIRECT_CALL_1(f, f1, __VA_ARGS__);  \ 
  8.     }) 

因為這里的protocol是icmp協(xié)議。所以會執(zhí)行icmp對應(yīng)的handler。那么對應(yīng)的是哪個函數(shù)呢?我們看看inet_protos是什么。

  1. struct net_protocol __rcu *inet_protos[MAX_INET_PROTOS] __read_mostly; 
  2. int inet_add_protocol(const struct net_protocol *prot, unsigned char protocol){ 
  3.     return !cmpxchg((const struct net_protocol **)&inet_protos[protocol], 
  4.             NULL, prot) ? 0 : -1; 

我們看到inet_add_protocol函數(shù)是注冊協(xié)議和對應(yīng)處理函數(shù)的。我們再來看看哪里會調(diào)用這個函數(shù)。

  1. static int __init inet_init(void) { 
  2.     inet_add_protocol(&icmp_protocol, IPPROTO_ICMP); 
  3.     inet_add_protocol(&udp_protocol, IPPROTO_UDP); 
  4.     ... 

在內(nèi)核初始化的時候會注冊一系列的協(xié)議和處理函數(shù)。下面我們看看icmp的函數(shù)集。

  1. static const struct net_protocol icmp_protocol = { 
  2.     .handler =  icmp_rcv, 
  3.     .err_handler =  icmp_err, 
  4.     .no_policy =    1, 
  5.     .netns_ok = 1, 
  6. }; 

我們看到handler是icmp_rcv。

  1. int icmp_rcv(struct sk_buff *skb){ 
  2.     struct icmphdr *icmph; 
  3.     struct rtable *rt = skb_rtable(skb); 
  4.     struct net *net = dev_net(rt->dst.dev); 
  5.     bool success; 
  6.     // icmp頭 
  7.     icmph = icmp_hdr(skb); 
  8.     success = icmp_pointers[icmph->type].handler(skb); 

icmp_rcv根據(jù)icmp包的信息做進一步處理。我看看icmp_pointers的定義。

  1. static const struct icmp_control icmp_pointers[NR_ICMP_TYPES + 1] = { 
  2.     ... 
  3.     [ICMP_DEST_UNREACH] = { 
  4.         .handler = icmp_unreach, 
  5.         .error = 1, 
  6.     }, 
  7. }; 

這里我們只關(guān)注ICMP_DEST_UNREACH的處理。

  1. static bool icmp_unreach(struct sk_buff *skb){ 
  2.     ... 
  3.     icmp_socket_deliver(skb, info); 

繼續(xù)看icmp_socket_deliver

  1. static void icmp_socket_deliver(struct sk_buff *skb, u32 info){ 
  2.     const struct iphdr *iph = (const struct iphdr *) skb->data; 
  3.     const struct net_protocol *ipprot; 
  4.     int protocol = iph->protocol; 
  5.     // 根據(jù)ip頭的協(xié)議字段找到對應(yīng)協(xié)議處理,這里的iph是觸發(fā)錯誤的原始ip頭,不是收到icmp包的ip頭,所以protocol是udp 
  6.     ipprot = rcu_dereference(inet_protos[protocol]); 
  7.     if (ipprot && ipprot->err_handler) 
  8.         ipprot->err_handler(skb, info); 

接著執(zhí)行udp的err_handler,是udp_err

  1. int udp_err(struct sk_buff *skb, u32 info){ 
  2.     return __udp4_lib_err(skb, info, &udp_table);}int __udp4_lib_err(struct sk_buff *skb, u32 info, struct udp_table *udptable){ 
  3.     struct inet_sock *inet; 
  4.     const struct iphdr *iph = (const struct iphdr *)skb->data; 
  5.     struct udphdr *uh = (struct udphdr *)(skb->data+(iph->ihl<<2)); 
  6.     const int type = icmp_hdr(skb)->type; 
  7.     const int code = icmp_hdr(skb)->code; 
  8.     bool tunnel = false
  9.     struct sock *sk; 
  10.     int harderr; 
  11.     int err; 
  12.     struct net *net = dev_net(skb->dev); 
  13.     // 根據(jù)報文信息找到對應(yīng)socket 
  14.     sk = __udp4_lib_lookup(net, iph->daddr, uh->dest, 
  15.                    iph->saddr, uh->source, skb->dev->ifindex, 
  16.                    inet_sdif(skb), udptable, NULL); 
  17.     err = 0; 
  18.     harderr = 0; 
  19.     inet = inet_sk(sk); 
  20.  
  21.     switch (type) { 
  22.     case ICMP_DEST_UNREACH: 
  23.         err = EHOSTUNREACH; 
  24.         if (code <= NR_ICMP_UNREACH) { 
  25.             harderr = icmp_err_convert[code].fatal; 
  26.             err = icmp_err_convert[code].errno; 
  27.         } 
  28.         break; 
  29.         ... 
  30.     } 
  31.  
  32.     // 設(shè)置錯誤信息到socket 
  33.     sk->sk_err = err; 
  34.     sk->sk_error_report(sk); 
  35. out
  36.     return 0; 

__udp4_lib_err設(shè)置了錯誤信息,然后調(diào)用sk_error_report。sk_error_report是在調(diào)用socket函數(shù)時賦值的(具體在sock_init_data函數(shù))。

  1. sk->sk_error_report =   sock_def_error_report; 

接著看sock_def_error_report

  1. static void sock_def_error_report(struct sock *sk){ 
  2.     struct socket_wq *wq; 
  3.  
  4.     rcu_read_lock(); 
  5.     wq = rcu_dereference(sk->sk_wq); 
  6.     if (skwq_has_sleeper(wq)) 
  7.         wake_up_interruptible_poll(&wq->wait, EPOLLERR); 
  8.     sk_wake_async(sk, SOCK_WAKE_IO, POLL_ERR); 
  9.     rcu_read_unlock();}static inline void sk_wake_async(const struct sock *sk, int how, int band){ 
  10.     if (sock_flag(sk, SOCK_FASYNC)) { 
  11.         rcu_read_lock(); 
  12.         sock_wake_async(rcu_dereference(sk->sk_wq), how, band); 
  13.         rcu_read_unlock(); 
  14.     } 

我們看到如果進程阻塞在socket則會被喚醒,或者設(shè)置了SOCK_FASYNC標(biāo)記則收到信號。

后記:本文簡單介紹了icmp的產(chǎn)生和處理過程,后面有時間再細化一下。

 

責(zé)任編輯:武曉燕 來源: 編程雜技
相關(guān)推薦

2021-01-12 09:05:11

Linus Torva進程頁面鎖

2010-06-24 14:58:06

ICMP協(xié)議消息

2021-07-15 14:27:47

LinuxSocketClose

2021-07-14 09:48:15

Linux源碼Epoll

2010-07-28 17:19:28

ICMP協(xié)議

2020-10-10 07:00:16

LinuxSocketTCP

2021-06-10 09:52:33

LinuxTCPAccept

2010-08-02 14:29:46

LinuxPingICMP

2010-08-02 14:36:52

ICMPLinux

2012-09-28 09:27:50

LinuxRed Hat

2021-10-21 05:41:27

QueenSono安全工具提取數(shù)據(jù)

2022-03-25 10:16:40

CentOSLinux開源

2020-10-15 10:10:31

Linux數(shù)據(jù)中心IT

2010-04-02 17:44:45

MiniPCI無線網(wǎng)卡

2010-07-28 17:36:17

2020-10-14 14:31:37

LinuxTCP連接

2010-07-13 22:16:30

INBOUND ICM

2009-06-14 18:43:57

LinuxWindows對比

2019-04-28 16:10:50

設(shè)計Redux前端

2021-07-01 09:00:14

LSMtreeWiscKey 機制
點贊
收藏

51CTO技術(shù)棧公眾號