在__netif_receive_skb_core中,我看着原来经常使用的tcpdump的抓包点,很是激动,看来读一遍源代码时间真的没白浪费 。接着__netif_receive_skb_core取出protocol,它会从数据包中取出协议信息,然后遍历注册在这个协议上的回调函数列表 。ptype_base 是一个 hash table,在协议注册小节我们提到过 。ip_rcv 函数地址就是存在这个 hash table中的 。
//file: net/core/dev.cstatic inline int deliver_skb(struct sk_buff *skb, struct packet_type *pt_prev, struct net_device *orig_dev){ ...... return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);}pt_prev->func这一行就调用到了协议层注册的处理函数了 。对于ip包来讲,就会进入到ip_rcv(如果是arp包的话,会进入到arp_rcv) 。
3.4 IP协议层处理我们再来大致看一下linux在ip协议层都做了什么,包又是怎么样进一步被送到udp或tcp协议处理函数中的 。
//file: net/ipv4/ip_input.cint ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev){ ...... return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish);}这里NF_HOOK是一个钩子函数,当执行完注册的钩子后就会执行到最后一个参数指向的函数ip_rcv_finish 。
static int ip_rcv_finish(struct sk_buff *skb){ ...... if (!skb_dst(skb)) { int err = ip_route_input_noref(skb, iph->daddr, iph->saddr, iph->tos, skb->dev); ... } ...... return dst_input(skb);}跟踪ip_route_input_noref 后看到它又调用了 ip_route_input_mc 。在ip_route_input_mc中,函数ip_local_deliver被赋值给了dst.input, 如下:
//file: net/ipv4/route.cstatic int ip_route_input_mc(struct sk_buff *skb, __be32 daddr, __be32 saddr,u8 tos, struct net_device *dev, int our){ if (our) { rth->dst.input= ip_local_deliver; rth->rt_flags |= RTCF_LOCAL; }}所以回到ip_rcv_finish中的return dst_input(skb); 。
/* Input packet from network to transport. */static inline int dst_input(struct sk_buff *skb){ return skb_dst(skb)->input(skb);}skb_dst(skb)->input调用的input方法就是路由子系统赋的ip_local_deliver 。
//file: net/ipv4/ip_input.cint ip_local_deliver(struct sk_buff *skb){ /* * Reassemble IP fragments. */ if (ip_is_fragment(ip_hdr(skb))) { if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER)) return 0; } return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN, skb, skb->dev, NULL, ip_local_deliver_finish);}static int ip_local_deliver_finish(struct sk_buff *skb){ ...... int protocol = ip_hdr(skb)->protocol; const struct net_protocol *ipprot; ipprot = rcu_dereference(inet_protos[protocol]); if (ipprot != NULL) { ret = ipprot->handler(skb); }}如协议注册小节看到inet_protos中保存着tcp_rcv()和udp_rcv()的函数地址 。这里将会根据包中的协议类型选择进行分发,在这里skb包将会进一步被派送到更上层的协议中,udp和tcp 。
3.5 UDP协议层处理在协议注册小节的时候我们说过,udp协议的处理函数是udp_rcv 。
//file: net/ipv4/udp.cint 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){ sk = __udp4_lib_lookup_skb(skb, uh->source, uh->dest, udptable); if (sk != NULL) { int ret = udp_queue_rcv_skb(sk, skb } icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);}__udp4_lib_lookup_skb是根据skb来寻找对应的socket,当找到以后将数据包放到socket的缓存队列里 。如果没有找到,则发送一个目标不可达的icmp包 。
//file: net/ipv4/udp.cint udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb){ ...... if (sk_rcvqueues_full(sk, skb, sk->sk_rcvbuf)) goto drop; rc = 0; ipv4_pktinfo_prepare(skb); bh_lock_sock(sk); if (!sock_owned_by_user(sk)) rc = __udp_queue_rcv_skb(sk, skb); else if (sk_add_backlog(sk, skb, sk->sk_rcvbuf)) { bh_unlock_sock(sk); goto drop; } bh_unlock_sock(sk); return rc;}
推荐阅读
- 三种使用AI攻击网络安全的方法
- 理解了Linux I/O机制,才能真的明白“什么是多线程”
- Linux网络配置
- 带你重新认识Linux系统的inode
- 古剑奇谭网络版野外pvp 古剑奇谭ol剧情
- 从Linux源码看Socket的listen及连接队列
- linux网络编程常见API详解
- 了解神经网络和模型泛化
- TCP/IP 基础知识总结
- 如何在电脑上安装网络打印机?详细教程全部教给你
