• linux网络协议栈源码分析 - 网络层IP网际协议


    1、IP报文

    1.1、IP报文格式 

            更详细的介绍参考《TCP/IP详解卷 1:协议》第3章 IP:网际协议。

    1.2、IP首部结构体

    1. struct iphdr {
    2. #if defined(__LITTLE_ENDIAN_BITFIELD)
    3. __u8 ihl:4,
    4. version:4;
    5. #elif defined (__BIG_ENDIAN_BITFIELD)
    6. __u8 version:4,
    7. ihl:4;
    8. #else
    9. #error "Please fix "
    10. #endif
    11. __u8 tos;
    12. __be16 tot_len;
    13. __be16 id;
    14. __be16 frag_off;
    15. __u8 ttl;
    16. __u8 protocol;
    17. __sum16 check;
    18. __be32 saddr;
    19. __be32 daddr;
    20. /*The options start here. */
    21. };

            3位标志位:

    1. /* IP flags. */
    2. #define IP_CE 0x8000 /* Flag: "Congestion" */
    3. #define IP_DF 0x4000 /* Flag: "Don't Fragment" */
    4. #define IP_MF 0x2000 /* Flag: "More Fragments" */
    5. #define IP_OFFSET 0x1FFF /* "Fragment Offset" part */

    2、IP路由缓存查找

            (参考资料《Linux Kernel Networking -  Implementation and Theory》、机械工业出版社《Linux内核源码剖析:TCP/IP实现(上册)》)

    2.1、输出路由缓存查询(ip_route_output_key)

            __ip_route_output_key_hash返回路由rtable,rtable里面包含路由缓存项dst_entry,对于输出路由,主要用到了路由及路由缓存项的输出函数指针output、网卡设备net_device、网关rt_gateway,__ip_route_output_key_hash调用栈:

              dst_output调用路由缓存的输出函数指针output,然后调用ip_output、ip_finish_output、ip_finish_output2,ip_finish_output2找到路由缓的下一跳(网关或者局域网内的其他主机),然后调用邻居子系统的dst_neigh_output发送报文,ip_finish_output2邻居查找及报文发送代码实现如下:

            ip_finish_output2函数调用栈如下:

    2.2、输入路由缓存查询(ip_route_input/ip_route_input_noref)

          ip_route_input_slow调用skb_dst_set_noref设置skb的路由缓存,路由缓存dst包含一个输入函数指针,最终调用该输入函数处理输入报文:

            ip_route_input_slow调用栈:

             查找到路由缓存之后,调用dst_input函数,dst_input函数调用路由缓存的input函数处理报文,dst_input调用栈: 

    3、IP报文输出

    3.1、IP报文发送(ip_queue_xmit)

            ip_queue_xmit主要检查skb是否已经设置路由,如果没有就查找路由,如果找不到路由就丢弃报文;有路由就设置IP首部(IP首部的16位总长度、16位首部检验和在下一级函数设置),调用ip_local_out、rt->dst.output输出IP报文。

            ip_queue_xmit代码实现如下:

    1. int ip_queue_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl)
    2. {
    3. struct inet_sock *inet = inet_sk(sk);
    4. struct net *net = sock_net(sk);
    5. struct ip_options_rcu *inet_opt;
    6. struct flowi4 *fl4;
    7. struct rtable *rt;
    8. struct iphdr *iph;
    9. int res;
    10. /* Skip all of this if the packet is already routed,
    11. * f.e. by something like SCTP.
    12. */
    13. rcu_read_lock();
    14. inet_opt = rcu_dereference(inet->inet_opt);
    15. fl4 = &fl->u.ip4;
    16. rt = skb_rtable(skb); // skb路由项(输出网卡设备、下一跳地址等)
    17. if (rt)
    18. goto packet_routed; // 如果已经有路由,那么跳转到packet_routed,使用已经设置好的路由
    19. /* Make sure we can route this packet. */
    20. rt = (struct rtable *)__sk_dst_check(sk, 0); // 获取路由项缓存sk_dst_cache
    21. if (!rt) {
    22. __be32 daddr;
    23. /* Use correct destination address if we have options. */
    24. daddr = inet->inet_daddr;
    25. if (inet_opt && inet_opt->opt.srr)
    26. daddr = inet_opt->opt.faddr;
    27. /* If this fails, retransmit mechanism of transport layer will
    28. * keep trying until route appears or the connection times
    29. * itself out.
    30. */
    31. rt = ip_route_output_ports(net, fl4, sk,
    32. daddr, inet->inet_saddr,
    33. inet->inet_dport,
    34. inet->inet_sport,
    35. sk->sk_protocol,
    36. RT_CONN_FLAGS(sk),
    37. sk->sk_bound_dev_if); // 调用ip_route_output_ports、ip_route_output_flow查询输出路由缓存;参考机械工业出版社《Linux内核源码剖析:TCP IP实现(下册)》"第20章 路由缓存"
    38. if (IS_ERR(rt))
    39. goto no_route; // 没有路由则跳转到no_route,丢弃报文
    40. sk_setup_caps(sk, &rt->dst);
    41. }
    42. skb_dst_set_noref(skb, &rt->dst); // 设置skb->_skb_refdst
    43. packet_routed:
    44. if (inet_opt && inet_opt->opt.is_strictroute && rt->rt_uses_gateway)
    45. goto no_route;
    46. /* OK, we know where to send it, allocate and build IP header. */
    47. skb_push(skb, sizeof(struct iphdr) + (inet_opt ? inet_opt->opt.optlen : 0)); // 获取IP首部在skb里面的地址(如果有选项,需要计算选项的长度,否则为0;从传输层数据之前预留的就是IP首部地址空间)
    48. skb_reset_network_header(skb);
    49. iph = ip_hdr(skb); // 获取IP首部地址
    50. *((__be16 *)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff)); // IP首部前16位为: 4位版本号、4位首部长度、8位服务类型(TOS);(4 << 12)为4位版本号,IPv4,(5 << 8)为4位首部长度,5*4共20字节(此次没加上选项长度), (inet->tos & 0xff)为8位服务类型(TOS)
    51. if (ip_dont_fragment(sk, &rt->dst) && !skb->ignore_df)
    52. iph->frag_off = htons(IP_DF); // IP不分片(Don’t Fragment)
    53. else
    54. iph->frag_off = 0;
    55. iph->ttl = ip_select_ttl(inet, &rt->dst); // 8位生存时间(TTL)
    56. iph->protocol = sk->sk_protocol; // 8位协议(IPPROTO_TCP/IPPROTO_UDP)
    57. ip_copy_addrs(iph, fl4); // 32位源IP地址
    58. /* Transport layer set skb->h.foo itself. */
    59. if (inet_opt && inet_opt->opt.optlen) { // 选项(如果有)
    60. iph->ihl += inet_opt->opt.optlen >> 2; // 修正4位首部长度
    61. ip_options_build(skb, &inet_opt->opt, inet->inet_daddr, rt, 0); // 拷贝选项
    62. }
    63. ip_select_ident_segs(net, skb, sk,
    64. skb_shinfo(skb)->gso_segs ?: 1); // 16位标识(每个IP报文有唯一的标识,通过16位标识区分是否是一个IP报文的分片)
    65. /* TODO : should we use skb->sk here instead of sk ? */
    66. skb->priority = sk->sk_priority;
    67. skb->mark = sk->sk_mark;
    68. res = ip_local_out(net, sk, skb); // 调用ip_local_out发送IP报文(IP首部的16位总长度在函数__ip_local_out里面设置,16位首部检验和在函数ip_send_check设置)
    69. rcu_read_unlock();
    70. return res;
    71. no_route:
    72. rcu_read_unlock();
    73. IP_INC_STATS(net, IPSTATS_MIB_OUTNOROUTES);
    74. kfree_skb(skb);
    75. return -EHOSTUNREACH;
    76. }

     3.2、ip_queue_xmit调用栈

            ip_queue_xmit调用栈如下:

    4、IP报文输入

    4.1、IP报文输入流程

            ip_packet_type定义ETH_P_IP报文输入函数:

    1. static struct packet_type ip_packet_type __read_mostly = {
    2. .type = cpu_to_be16(ETH_P_IP),
    3. .func = ip_rcv,
    4. };

            以太网首部:

    1. struct ethhdr {
    2. unsigned char h_dest[ETH_ALEN]; /* destination eth addr */
    3. unsigned char h_source[ETH_ALEN]; /* source ether addr */
    4. __be16 h_proto; /* packet type ID field */
    5. } __attribute__((packed));

            smsc911x_poll调用eth_type_trans读取以太网首部类型字段并设置接收报文类型skb->protocol:

             __netif_receive_skb_core找到ip_packet_type,并调用输入函数ip_rcv处理输入报文:

             ip_rcv函数调用栈:

     4.2、IP报文校验(ip_rcv)

            ip_rcv校验IP首部各字段、校验和、总长度等,校验通过之后调用ip_rcv_finish继续处理IP报文。

            ip_rcv函数代码实现:

    1. int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
    2. {
    3. const struct iphdr *iph;
    4. struct net *net;
    5. u32 len;
    6. /* When the interface is in promisc. mode, drop all the crap
    7. * that it receives, do not try to analyse it.
    8. */
    9. if (skb->pkt_type == PACKET_OTHERHOST) // eth_type_trans比较以太网首部的目的地址是否是输入网卡的地址,如果不是,设置skb->pkt_type为PACKET_OTHERHOST
    10. goto drop; // 丢弃发往其他主机的IP报文
    11. net = dev_net(dev);
    12. IP_UPD_PO_STATS_BH(net, IPSTATS_MIB_IN, skb->len);
    13. skb = skb_share_check(skb, GFP_ATOMIC);
    14. if (!skb) {
    15. IP_INC_STATS_BH(net, IPSTATS_MIB_INDISCARDS);
    16. goto out;
    17. }
    18. if (!pskb_may_pull(skb, sizeof(struct iphdr))) // 报文长度检查(IP首部的长度)
    19. goto inhdr_error; // 报文的长度小于IP首部的长度(不包含选项),也就是不完整的IP报文,或者有其他错误,跳转到inhdr_error
    20. iph = ip_hdr(skb); // 获取IP首部地址
    21. /*
    22. * RFC1122: 3.2.1.2 MUST silently discard any IP frame that fails the checksum.
    23. *
    24. * Is the datagram acceptable?
    25. *
    26. * 1. Length at least the size of an ip header
    27. * 2. Version of 4
    28. * 3. Checksums correctly. [Speed optimisation for later, skip loopback checksums]
    29. * 4. Doesn't have a bogus length
    30. */
    31. if (iph->ihl < 5 || iph->version != 4) // 如果IP首部4位首部长度小于5(IP首部不包含选项至少有5*4个字节数据),那么4位首部长度错误,跳转到inhdr_error;如果IP首部4位版本不是4(不是IPv4),那么跳转到inhdr_error
    32. goto inhdr_error;
    33. BUILD_BUG_ON(IPSTATS_MIB_ECT1PKTS != IPSTATS_MIB_NOECTPKTS + INET_ECN_ECT_1);
    34. BUILD_BUG_ON(IPSTATS_MIB_ECT0PKTS != IPSTATS_MIB_NOECTPKTS + INET_ECN_ECT_0);
    35. BUILD_BUG_ON(IPSTATS_MIB_CEPKTS != IPSTATS_MIB_NOECTPKTS + INET_ECN_CE);
    36. IP_ADD_STATS_BH(net,
    37. IPSTATS_MIB_NOECTPKTS + (iph->tos & INET_ECN_MASK),
    38. max_t(unsigned short, 1, skb_shinfo(skb)->gso_segs));
    39. if (!pskb_may_pull(skb, iph->ihl*4)) // 报文长度检查(IP首部长度(包含选项))
    40. goto inhdr_error; // 报文长度不够IP首部长度(包含选项)或者其他错误,跳转到inhdr_error
    41. iph = ip_hdr(skb); // 获取IP首部地址
    42. if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl))) // 16位首部检验和校验(包含选项)
    43. goto csum_error; // 16位首部检验和校验失败,跳转到csum_error
    44. len = ntohs(iph->tot_len); // 16位总长度(字节数)
    45. if (skb->len < len) { // 报文总长度小于16位总长度
    46. IP_INC_STATS_BH(net, IPSTATS_MIB_INTRUNCATEDPKTS);
    47. goto drop; // 跳转到drop,丢弃报文
    48. } else if (len < (iph->ihl*4)) // 报文长度检查(IP首部长度(包含选项))
    49. goto inhdr_error; // 报文长度不够IP首部长度(包含选项)或者其他错误,跳转到inhdr_error
    50. /* Our transport medium may have padded the buffer out. Now we know it
    51. * is IP we can trim to the true length of the frame.
    52. * Note this now means skb->len holds ntohs(iph->tot_len).
    53. */
    54. if (pskb_trim_rcsum(skb, len)) { // 删除SKB尾部的数据(len之后的数据)
    55. IP_INC_STATS_BH(net, IPSTATS_MIB_INDISCARDS);
    56. goto drop;
    57. }
    58. skb->transport_header = skb->network_header + iph->ihl*4; // 获取传输层首部
    59. /* Remove any debris in the socket control block */
    60. memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));
    61. /* Must drop socket now because of tproxy. */
    62. skb_orphan(skb);
    63. return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,
    64. net, NULL, skb, dev, NULL,
    65. ip_rcv_finish); // 调用ip_rcv_finish
    66. csum_error:
    67. IP_INC_STATS_BH(net, IPSTATS_MIB_CSUMERRORS);
    68. inhdr_error:
    69. IP_INC_STATS_BH(net, IPSTATS_MIB_INHDRERRORS);
    70. drop:
    71. kfree_skb(skb);
    72. out:
    73. return NET_RX_DROP;
    74. }

    4.3、输入路由查找(ip_rcv_finish)

            物理层只管物理地址不管IP地址,IP报文校验完整之后还得调用ip_route_input_noref查找输入路由,看看是否有输入路由(发往本地或者转发),如果没有输入路由则丢弃报文,否则调用dst_input根据路由处理报文(发往本地或者转发)。

            ip_rcv_finish函数实现代码如下:

    1. static int ip_rcv_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
    2. {
    3. const struct iphdr *iph = ip_hdr(skb);
    4. struct rtable *rt;
    5. if (sysctl_ip_early_demux &&
    6. !skb_dst(skb) &&
    7. !skb->sk &&
    8. !ip_is_fragment(iph)) {
    9. const struct net_protocol *ipprot;
    10. int protocol = iph->protocol;
    11. ipprot = rcu_dereference(inet_protos[protocol]);
    12. if (ipprot && ipprot->early_demux) {
    13. ipprot->early_demux(skb);
    14. /* must reload iph, skb->head might have changed */
    15. iph = ip_hdr(skb);
    16. }
    17. }
    18. /*
    19. * Initialise the virtual path cache for the packet. It describes
    20. * how the packet travels inside Linux networking.
    21. */
    22. if (!skb_valid_dst(skb)) { // 没有输入路由缓存
    23. int err = ip_route_input_noref(skb, iph->daddr, iph->saddr,
    24. iph->tos, skb->dev); // 查找输入路由
    25. if (unlikely(err)) { // 没有输入路由
    26. if (err == -EXDEV)
    27. NET_INC_STATS_BH(net, LINUX_MIB_IPRPFILTER);
    28. goto drop; // 跳转到drop,丢弃报文
    29. }
    30. }
    31. #ifdef CONFIG_IP_ROUTE_CLASSID
    32. if (unlikely(skb_dst(skb)->tclassid)) {
    33. struct ip_rt_acct *st = this_cpu_ptr(ip_rt_acct);
    34. u32 idx = skb_dst(skb)->tclassid;
    35. st[idx&0xFF].o_packets++;
    36. st[idx&0xFF].o_bytes += skb->len;
    37. st[(idx>>16)&0xFF].i_packets++;
    38. st[(idx>>16)&0xFF].i_bytes += skb->len;
    39. }
    40. #endif
    41. if (iph->ihl > 5 && ip_rcv_options(skb)) // 4位首部长度大于5(包含选项),处理选项
    42. goto drop; // 选项处理失败,跳转到drop,丢弃报文
    43. rt = skb_rtable(skb);
    44. if (rt->rt_type == RTN_MULTICAST) {
    45. IP_UPD_PO_STATS_BH(net, IPSTATS_MIB_INMCAST, skb->len);
    46. } else if (rt->rt_type == RTN_BROADCAST)
    47. IP_UPD_PO_STATS_BH(net, IPSTATS_MIB_INBCAST, skb->len);
    48. return dst_input(skb); // 调用dst_input处理输入报文
    49. drop:
    50. kfree_skb(skb);
    51. return NET_RX_DROP;
    52. }

            ip_local_deliver调用栈:

    4.4、报文发送到传输层(ip_local_deliver_finish) 

            对于发送本地的报文,调用ip_local_deliver、ip_local_deliver_finish,ip_local_deliver_finish根据IP首部的8位协议找到传输层对应协议的输入处理函数,调用传输层的处理函数处理传输层报文,对于IPv4的TCP协议就是tcp_v4_rcv。

            ip_local_deliver_finish函数代码实现如下:

    1. static int ip_local_deliver_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
    2. {
    3. __skb_pull(skb, skb_network_header_len(skb));
    4. rcu_read_lock();
    5. {
    6. int protocol = ip_hdr(skb)->protocol; // 8位协议(IPPROTO_TCP/IPPROTO_UDP/IPPROTO_RAW...)
    7. const struct net_protocol *ipprot;
    8. int raw;
    9. resubmit:
    10. raw = raw_local_deliver(skb, protocol);
    11. ipprot = rcu_dereference(inet_protos[protocol]); // 获取传输层的net_protocol(tcp_protocol/udp_protocol...)
    12. if (ipprot) {
    13. int ret;
    14. if (!ipprot->no_policy) {
    15. if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
    16. kfree_skb(skb);
    17. goto out;
    18. }
    19. nf_reset(skb);
    20. }
    21. ret = ipprot->handler(skb); // 调用传输层的处理函数(tcp_v4_rcv/udp_rcv...)
    22. if (ret < 0) {
    23. protocol = -ret;
    24. goto resubmit;
    25. }
    26. IP_INC_STATS_BH(net, IPSTATS_MIB_INDELIVERS);
    27. } else {
    28. if (!raw) {
    29. if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
    30. IP_INC_STATS_BH(net, IPSTATS_MIB_INUNKNOWNPROTOS);
    31. icmp_send(skb, ICMP_DEST_UNREACH,
    32. ICMP_PROT_UNREACH, 0);
    33. }
    34. kfree_skb(skb);
    35. } else {
    36. IP_INC_STATS_BH(net, IPSTATS_MIB_INDELIVERS);
    37. consume_skb(skb);
    38. }
    39. }
    40. }
    41. out:
    42. rcu_read_unlock();
    43. return 0;
    44. }

            ip_local_deliver_finish调用栈:

     

  • 相关阅读:
    2022年11月21日13:32:00——T5——JS对象与Date日期函数
    13个培训师游戏
    汽车称重软件系统配置(一)
    什么是 SEO 垃圾邮件攻击?
    20221121 秩相同,值域不一定相同
    蓝桥杯中级题目之组合(c++)
    【C/C++笔试练习】常见进制转换、宏的定义和特点、sizeof与strlen、字符串函数、统计回文、连续最大和
    《热题100》字符串、双指针、贪心算法篇
    MySQL锁(乐观锁、悲观锁、多粒度锁)
    C++ map和set的使用
  • 原文地址:https://blog.csdn.net/arm7star/article/details/126130896