您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關從Linux5.9看Icmp的處理流程是怎樣的,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
下面以udp為例看看什么時候會發送destination unreachable包。我們從收到一個udp包開始分析,具體函數是udp_rcv。
int udp_rcv(struct sk_buff *skb){ return __udp4_lib_rcv(skb, &udp_table, IPPROTO_UDP); } int __udp4_lib_rcv(struct sk_buff *skb, struct udp_table *udptable, int proto){ struct sock *sk; struct udphdr *uh; unsigned short ulen; struct rtable *rt = skb_rtable(skb); __be32 saddr, daddr; struct net *net = dev_net(skb->dev); bool refcounted; // udp頭 uh = udp_hdr(skb); ulen = ntohs(uh->len); // 源目的ip saddr = ip_hdr(skb)->saddr; daddr = ip_hdr(skb)->daddr; // 頭部指示大小比實際數據小 if (ulen > skb->len) goto short_packet; if (proto == IPPROTO_UDP) { uh = udp_hdr(skb); } sk = skb_steal_sock(skb, &refcounted); // 廣播或多播 if (rt->rt_flags & (RTCF_BROADCAST|RTCF_MULTICAST)) return __udp4_lib_mcast_deliver(net, skb, uh, saddr, daddr, udptable, proto); // 單播,根據地址信息找到對應的socket sk = __udp4_lib_lookup_skb(skb, uh->source, uh->dest, udptable); // 找到則掛到socket下 if (sk) return udp_unicast_rcv_skb(sk, skb, uh); // 找不到socket則回復一個ICMP_DEST_UNREACH icmp包 icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0); kfree_skb(skb); return 0; }
我們看到當通過ip包信息找不到對應socket的時候,就會發送一個icmp包給發送端。icmp包結構如下。
我們從收到ip包開始分析。
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev){ struct net *net = dev_net(dev); skb = ip_rcv_core(skb, net); if (skb == NULL) return NET_RX_DROP; return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, net, NULL, skb, dev, NULL, ip_rcv_finish); }
ip層收到包后會繼續執行ip_rcv_finish。
static int ip_rcv_finish(struct net *net, struct sock *sk, struct sk_buff *skb){ struct net_device *dev = skb->dev; int ret; ret = ip_rcv_finish_core(net, sk, skb, dev, NULL); if (ret != NET_RX_DROP) ret = dst_input(skb); return ret; }
接著執行dst_input
static inline int dst_input(struct sk_buff *skb){ return skb_dst(skb)->input(skb); }
input對應的是ip_local_deliver。
int ip_local_deliver(struct sk_buff *skb){ struct net *net = dev_net(skb->dev); return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN, net, NULL, skb, skb->dev, NULL, ip_local_deliver_finish); }
接著執行ip_local_deliver_finish。
static int ip_local_deliver_finish(struct net *net, struct sock *sk, struct sk_buff *skb){ __skb_pull(skb, skb_network_header_len(skb)); rcu_read_lock(); ip_protocol_deliver_rcu(net, skb, ip_hdr(skb)->protocol); rcu_read_unlock(); return 0; }
ip_local_deliver_finish會執行ip_protocol_deliver_rcu進一步處理,ip_protocol_deliver_rcu的最后一個入參是ip包里的協議字段(上層協議)。
void ip_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int protocol){ const struct net_protocol *ipprot; int raw, ret; resubmit: // 根據協議找到對應的處理函數,這里是icmp ipprot = rcu_dereference(inet_protos[protocol]); if (ipprot) { ret = INDIRECT_CALL_2(ipprot->handler, tcp_v4_rcv, udp_rcv, skb); if (ret < 0) { protocol = -ret; goto resubmit; } __IP_INC_STATS(net, IPSTATS_MIB_INDELIVERS); } }
INDIRECT_CALL_2是一個宏。
#define INDIRECT_CALL_1(f, f1, ...) \ ({ \ likely(f == f1) ? f1(__VA_ARGS__) : f(__VA_ARGS__); \ })#define INDIRECT_CALL_2(f, f2, f1, ...) \ ({ \ likely(f == f2) ? f2(__VA_ARGS__) : \ INDIRECT_CALL_1(f, f1, __VA_ARGS__); \ })
因為這里的protocol是icmp協議。所以會執行icmp對應的handler。那么對應的是哪個函數呢?我們看看inet_protos是什么。
struct net_protocol __rcu *inet_protos[MAX_INET_PROTOS] __read_mostly; int inet_add_protocol(const struct net_protocol *prot, unsigned char protocol){ return !cmpxchg((const struct net_protocol **)&inet_protos[protocol], NULL, prot) ? 0 : -1; }
我們看到inet_add_protocol函數是注冊協議和對應處理函數的。我們再來看看哪里會調用這個函數。
static int __init inet_init(void) { inet_add_protocol(&icmp_protocol, IPPROTO_ICMP); inet_add_protocol(&udp_protocol, IPPROTO_UDP); ... }
在內核初始化的時候會注冊一系列的協議和處理函數。下面我們看看icmp的函數集。
static const struct net_protocol icmp_protocol = { .handler = icmp_rcv, .err_handler = icmp_err, .no_policy = 1, .netns_ok = 1, };
我們看到handler是icmp_rcv。
int icmp_rcv(struct sk_buff *skb){ struct icmphdr *icmph; struct rtable *rt = skb_rtable(skb); struct net *net = dev_net(rt->dst.dev); bool success; // icmp頭 icmph = icmp_hdr(skb); success = icmp_pointers[icmph->type].handler(skb); }
icmp_rcv根據icmp包的信息做進一步處理。我看看icmp_pointers的定義。
static const struct icmp_control icmp_pointers[NR_ICMP_TYPES + 1] = { ... [ICMP_DEST_UNREACH] = { .handler = icmp_unreach, .error = 1, }, };
這里我們只關注ICMP_DEST_UNREACH的處理。
static bool icmp_unreach(struct sk_buff *skb){ ... icmp_socket_deliver(skb, info); }
繼續看icmp_socket_deliver
static void icmp_socket_deliver(struct sk_buff *skb, u32 info){ const struct iphdr *iph = (const struct iphdr *) skb->data; const struct net_protocol *ipprot; int protocol = iph->protocol; // 根據ip頭的協議字段找到對應協議處理,這里的iph是觸發錯誤的原始ip頭,不是收到icmp包的ip頭,所以protocol是udp ipprot = rcu_dereference(inet_protos[protocol]); if (ipprot && ipprot->err_handler) ipprot->err_handler(skb, info); }
接著執行udp的err_handler,是udp_err
int udp_err(struct sk_buff *skb, u32 info){ return __udp4_lib_err(skb, info, &udp_table);}int __udp4_lib_err(struct sk_buff *skb, u32 info, struct udp_table *udptable){ struct inet_sock *inet; const struct iphdr *iph = (const struct iphdr *)skb->data; struct udphdr *uh = (struct udphdr *)(skb->data+(iph->ihl<<2)); const int type = icmp_hdr(skb)->type; const int code = icmp_hdr(skb)->code; bool tunnel = false; struct sock *sk; int harderr; int err; struct net *net = dev_net(skb->dev); // 根據報文信息找到對應socket sk = __udp4_lib_lookup(net, iph->daddr, uh->dest, iph->saddr, uh->source, skb->dev->ifindex, inet_sdif(skb), udptable, NULL); err = 0; harderr = 0; inet = inet_sk(sk); switch (type) { case ICMP_DEST_UNREACH: err = EHOSTUNREACH; if (code <= NR_ICMP_UNREACH) { harderr = icmp_err_convert[code].fatal; err = icmp_err_convert[code].errno; } break; ... } // 設置錯誤信息到socket sk->sk_err = err; sk->sk_error_report(sk); out: return 0; }
__udp4_lib_err設置了錯誤信息,然后調用sk_error_report。sk_error_report是在調用socket函數時賦值的(具體在sock_init_data函數)。
sk->sk_error_report = sock_def_error_report;
接著看sock_def_error_report
static void sock_def_error_report(struct sock *sk){ struct socket_wq *wq; rcu_read_lock(); wq = rcu_dereference(sk->sk_wq); if (skwq_has_sleeper(wq)) wake_up_interruptible_poll(&wq->wait, EPOLLERR); sk_wake_async(sk, SOCK_WAKE_IO, POLL_ERR); rcu_read_unlock();}static inline void sk_wake_async(const struct sock *sk, int how, int band){ if (sock_flag(sk, SOCK_FASYNC)) { rcu_read_lock(); sock_wake_async(rcu_dereference(sk->sk_wq), how, band); rcu_read_unlock(); } }
我們看到如果進程阻塞在socket則會被喚醒,或者設置了SOCK_FASYNC標記則收到信號。
以上就是從Linux5.9看Icmp的處理流程是怎樣的,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。