zoukankan      html  css  js  c++  java
  • (OK) 第五章 传输层(tcp)到网络层(ip)--基于Linux3.10


    http://blog.csdn.net/shichaog/article/details/44600453


    根据数据的流向跟踪代码,由于数据发送是从tcp层到网络层再到网络到主机层,所以先来看tcp层向ip层发送数据的函数。

    tcp的发送函数和接收函数一样位于net/ipv4/文件夹,文件名是tcp_output.c文件,传输层和网络层联系的函数是tcp_transmit_skb(...):

    在进入该函数时,先看该函数用到的一个重要的数据结构,其定义于net/dccp/ipv4.c文件,919行是发送时用到的函数。

    1. 918 static const struct inet_connection_sock_af_ops dccp_ipv4_af_ops = {  
    2. 919     .queue_xmit    = ip_queue_xmit,  
    3. 920     .send_check    = dccp_v4_send_check,  
    4. 921     .rebuild_header    = inet_sk_rebuild_header,  
    5. 922     .conn_request      = dccp_v4_conn_request,  
    6. 923     .syn_recv_sock     = dccp_v4_request_recv_sock,  
    7. 924     .net_header_len    = sizeof(struct iphdr),  
    8. 925     .setsockopt    = ip_setsockopt,  
    9. 926     .getsockopt    = ip_getsockopt,  
    10. 927     .addr2sockaddr     = inet_csk_addr2sockaddr,  
    11. 928     .sockaddr_len      = sizeof(struct sockaddr_in),  
    12. 929     .bind_conflict     = inet_csk_bind_conflict,  
    13. 934 };  

    919行的函数服务于ip层,也就是四层模型中的网络层,我们先看传输层的函数tcp_transmit_skb,该函数会建立IP层的头,并将该头传递给网络层。

    1. net/ipv4/tcp_output.c  
    2.  840 static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it,  
    3.  841                 gfp_t gfp_mask)  
    4.  842 {  
    5.  843     const struct inet_connection_sock *icsk = inet_csk(sk);  
    6.  844     struct inet_sock *inet;  
    7.  845     struct tcp_sock *tp;   
    8.  961     err = icsk->icsk_af_ops->queue_xmit(skb, &inet->cork.fl);   
    9. }  

    icsk_af_ops->queue_xmit的两个参数,structsk_buff *skb已经在第二章叙述过了,inet的原型是inet_sock是inet的套接字的代表。

    1. struct inet_sock {  
    2. /* sk and pinet6 has to be the first two members of inet_sock */  
    3. struct sock  sk;   //祖先的类  
    4. /* Socket demultiplex comparisons on incoming packets. */  
    5. #define inet_daddr sk.__sk_common.skc_daddr //外部ipv4地址  
    6. #define inet_rcv_saddr sk.__sk_common.skc_rcv_saddr//绑定的本地ipv4地址  
    7. #define inet_addrpair sk.__sk_common.skc_addrpair  
    8. #define inet_dport sk.__sk_common.skc_dport//目的端口号  
    9. #define inet_num sk.__sk_common.skc_num//本地端口号  
    10. #define inet_portpair sk.__sk_common.skc_portpair  
    11. __be32  inet_saddr;  
    12. __s16 uc_ttl; //单播的TTL,time to live  
    13. __u16 cmsg_flags;  
    14. __be16  inet_sport;  
    15. __u16 inet_id;  
    16. struct ip_options_rcu __rcu  
    17. *inet_opt;  
    18. int rx_dst_ifindex;  
    19. __u8 tos;  
    20. __u8 min_ttl;  
    21. __u8 mc_ttl;   
    22. __u8 pmtudisc;  
    23. __u8 recverr:1,  
    24. is_icsk:1,  
    25. freebind:1,  
    26. hdrincl:1,  
    27. mc_loop:1,  
    28. transparent:1,  
    29. mc_all:1,  
    30. nodefrag:1;  
    31. __u8 rcv_tos;  
    32. int uc_index;  
    33. int mc_index;//多播设备索引  
    34. __be32  mc_addr;  
    35. struct ip_mc_socklist __rcu  
    36. *mc_list;  
    37. struct inet_cork_full  
    38. cork; //每个分片的ip packet构建ip头时用到的数据结构  
    39. };  

    ip_queue_xmit这个函数就是网络层的传输函数了;

    1. 326 int ip_queue_xmit(struct sk_buff *skb, struct flowi *fl)   
    2. 327 {  
    3. 328     struct sock *sk = skb->sk;  
    4. 329     struct inet_sock *inet = inet_sk(sk);   
    5. 360         rt = ip_route_output_ports(sock_net(sk), fl4, sk,  //获取输出信息的路由表,路由信息会填充到skb的dst字段去。  
    6. 361                        daddr, inet->inet_saddr,  
    7. 362                        inet->inet_dport,  
    8. 363                        inet->inet_sport,  
    9. 364                        sk->sk_protocol,  
    10. 365                        RT_CONN_FLAGS(sk),  
    11. 366                        sk->sk_bound_dev_if);   
    12. 403     res = ip_local_out(skb);   //发送出去  
    13. 412 }  

    第403行是发送packet的函数,   res = ip_local_out(skb);   //发送出去

    1. static inline int dst_output(struct sk_buff *skb)  
    2. {  
    3. return skb_dst(skb)->output(skb);  
    4. }  
    5. int ip_local_out(struct sk_buff *skb)   
    6. {  
    7. err = __ip_local_out(skb);  
    8. if (likely(err == 1))  
    9. err = dst_output(skb);  
    10. }  

    由上面两个函数,真正调用的其实是rth->dst.output= ip_output指向的函数,该函数将主要工作放到ip_finish_output去完成,这和接收时的方法很类似,并且这里也有一个钩子函数。

    1. 298 int ip_output(struct sk_buff *skb)   
    2. 299 {  
    3. 300     struct net_device *dev = skb_dst(skb)->dev;  
    4. 301   
    5. 302     IP_UPD_PO_STATS(dev_net(dev), IPSTATS_MIB_OUT, skb->len);  
    6. 303   
    7. 304     skb->dev = dev;  
    8. 305     skb->protocol = htons(ETH_P_IP);  
    9. 306   
    10. 307     return NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING, skb, NULL, dev,  
    11. 308                 ip_finish_output,  
    12. 309                 !(IPCB(skb)->flags & IPSKB_REROUTED));  
    13. 310 }  

    ip_finish_output函数的定义如下,CONFIG_NETFILTER和CONFIG_XFRM都是和安全相关的机制。231行判断ip是否被分片了,通常以太网上的数据没有经过分片操作,如果分片了使用ip_fragment来处理,处理完了调用ip_finish_output2完成实质的发送工作。

    1. 222 static int ip_finish_output(struct sk_buff *skb)  
    2. 223 {  
    3. 224 #if defined(CONFIG_NETFILTER) && defined(CONFIG_XFRM)  
    4. 225     /* Policy lookup after SNAT yielded a new policy */  
    5. 226     if (skb_dst(skb)->xfrm != NULL) {  
    6. 227         IPCB(skb)->flags |= IPSKB_REROUTED;  
    7. 228         return dst_output(skb);  
    8. 229     }  
    9. 230 #endif  
    10. 231     if (skb->len > ip_skb_dst_mtu(skb) && !skb_is_gso(skb))  
    11. 232         return ip_fragment(skb, ip_finish_output2);  
    12. 233     else  
    13. 234         return ip_finish_output2(skb);  
    14. 235 }  

    ip_finish_output2对packet的头进行处理,然后根据路由表寻找下一跳地址,这里还涉及一层邻居协议,这个没有路由那么庞大。

    1. 166 static inline int ip_finish_output2(struct sk_buff *skb)  
    2. 167 {  
    3. 196     nexthop = (__force u32) rt_nexthop(rt, ip_hdr(skb)->daddr);  
    4. 197     neigh = __ipv4_neigh_lookup_noref(dev, nexthop);  
    5. 198     if (unlikely(!neigh))  
    6. 199         neigh = __neigh_create(&arp_tbl, &nexthop, dev, false);  
    7. 200     if (!IS_ERR(neigh)) {  
    8. 201         int res = dst_neigh_output(dst, neigh, skb);   
    9. 212 }  

    neigh是structneighbour类型的结构体,这个数据邻居协议的范畴。197是邻居协议查找合适的网卡设备,198行如果找不到则执行199行的函数创建一个,在获得邻居项之后执行201行的函数。该函数位于include/net/dst.h,该函数未定义于ipv4的目录下,这也意味着数据将传递到主机到网络层了。

    1. 393 static inline int dst_neigh_output(struct dst_entry *dst, struct neighbour *n,  
    2. 394                    struct sk_buff *skb)  
    3. 395 {  
    4. 396     const struct hh_cache *hh;  
    5. 397   
    6. 398     if (dst->pending_confirm) {  
    7. 399         unsigned long now = jiffies;  
    8. 400   
    9. 401         dst->pending_confirm = 0;  
    10. 402         /* avoid dirtying neighbour */  
    11. 403         if (n->confirmed != now)  
    12. 404             n->confirmed = now;  
    13. 405     }  
    14. 406   
    15. 407     hh = &n->hh;  
    16. 408     if ((n->nud_state & NUD_CONNECTED) && hh->hh_len)  
    17. 409         return neigh_hh_output(hh, skb);  //支持硬件缓存头方法的发送  
    18. 410     else  
    19. 411         return n->output(n, skb);  //不支持硬件缓存头的方法的发送。  
    20. 412 }  

    第411行调用的是neigh_resolve_output,该函数是通过路由表查到的,不论硬件支不支持硬件头缓存,neigh_resolve_output都会被调用到,它来源如下:

    1. static const struct neigh_ops arp_generic_ops = {  
    2. .family =  AF_INET,  
    3. .solicit =  arp_solicit,  
    4. .error_report =arp_error_report,  
    5. .output =  neigh_resolve_output,  
    6. .connected_output =  
    7. neigh_connected_output,  
    8. };  
    9. //有硬件头缓存的函数操作集  
    10. static const struct neigh_ops arp_hh_ops = {  
    11. .family =  AF_INET,  
    12. .solicit =  arp_solicit,  
    13. .error_report =arp_error_report,  
    14. .output =  neigh_resolve_output,  
    15. .connected_output =neigh_resolve_output,  
    16. };  

    获得上述output函数的过程就是路由的过程,还是来看看neigh_resolve_output,它定义于net/core/neighbour.c文件。这时真正的离开了IP层,该函数的1310会调用之一文章讲述的函数将数据实际的发送出去。

    1. 1286 int neigh_resolve_output(struct neighbour *neigh, struct sk_buff *skb)  
    2. 1287 {  
    3. 1288     struct dst_entry *dst = skb_dst(skb);  
    4. 1289     int rc = 0;  
    5. 1290   
    6. 1291     if (!dst)  
    7. 1292         goto discard;  
    8. 1293   
    9. 1294     if (!neigh_event_send(neigh, skb)) {  
    10. 1295         int err;  
    11. 1296         struct net_device *dev = neigh->dev;  
    12. 1297         unsigned int seq;  
    13. 1298   
    14. 1299         if (dev->header_ops->cache && !neigh->hh.hh_len)  
    15. 1300             neigh_hh_init(neigh, dst);  
    16. 1301   
    17. 1302         do {  
    18. 1303             __skb_pull(skb, skb_network_offset(skb));  
    19. 1304             seq = read_seqbegin(&neigh->ha_lock);  
    20. 1305             err = dev_hard_header(skb, dev, ntohs(skb->protocol),  
    21. 1306                           neigh->ha, NULL, skb->len);  
    22. 1307         } while (read_seqretry(&neigh->ha_lock, seq));  
    23. 1308   
    24. 1309         if (err >= 0)  
    25. 1310             rc = dev_queue_xmit(skb);  
    26.   
    27. 1313     }  
    28. }  

    最后还是看一张图来回顾tcp/ip发送数据的流程。


    图5.1 网络层函数调用流程


  • 相关阅读:
    JavaScript怎么让字符串和JSON相互转化
    golang怎么使用redis,最基础的有效的方法
    SmartGit过期后破解方法
    Mac下安装ElasticSearch
    浏览器滚动条拉底部的方法
    git 管理
    MAC远程连接服务器,不需要输入密码的配置方式
    centos6.5下使用yum完美搭建LNMP环境(php5.6) 无脑安装
    【笔记】LAMP 环境无脑安装配置 Centos 6.3
    vs2008不能创建C#项目的解决方法
  • 原文地址:https://www.cnblogs.com/ztguang/p/12644605.html
Copyright © 2011-2022 走看看