第五章 传输层(tcp)到网络层(ip)--基于Linux3.10
2015-03-24 22:30
1271 查看
根据数据的流向跟踪代码,由于数据发送是从tcp层到网络层再到网络到主机层,所以先来看tcp层向ip层发送数据的函数。
tcp的发送函数和接收函数一样位于net/ipv4/文件夹,文件名是tcp_output.c文件,传输层和网络层联系的函数是tcp_transmit_skb(...):
在进入该函数时,先看该函数用到的一个重要的数据结构,其定义于net/dccp/ipv4.c文件,919行是发送时用到的函数。
919行的函数服务于ip层,也就是四层模型中的网络层,我们先看传输层的函数tcp_transmit_skb,该函数会建立IP层的头,并将该头传递给网络层。
icsk_af_ops->queue_xmit的两个参数,structsk_buff *skb已经在第二章叙述过了,inet的原型是inet_sock是inet的套接字的代表。
ip_queue_xmit这个函数就是网络层的传输函数了;
第403行是发送packet的函数, res = ip_local_out(skb); //发送出去
由上面两个函数,真正调用的其实是rth->dst.output= ip_output指向的函数,该函数将主要工作放到ip_finish_output去完成,这和接收时的方法很类似,并且这里也有一个钩子函数。
ip_finish_output函数的定义如下,CONFIG_NETFILTER和CONFIG_XFRM都是和安全相关的机制。231行判断ip是否被分片了,通常以太网上的数据没有经过分片操作,如果分片了使用ip_fragment来处理,处理完了调用ip_finish_output2完成实质的发送工作。
ip_finish_output2对packet的头进行处理,然后根据路由表寻找下一跳地址,这里还涉及一层邻居协议,这个没有路由那么庞大。
neigh是structneighbour类型的结构体,这个数据邻居协议的范畴。197是邻居协议查找合适的网卡设备,198行如果找不到则执行199行的函数创建一个,在获得邻居项之后执行201行的函数。该函数位于include/net/dst.h,该函数未定义于ipv4的目录下,这也意味着数据将传递到主机到网络层了。
第411行调用的是neigh_resolve_output,该函数是通过路由表查到的,不论硬件支不支持硬件头缓存,neigh_resolve_output都会被调用到,它来源如下:
获得上述output函数的过程就是路由的过程,还是来看看neigh_resolve_output,它定义于net/core/neighbour.c文件。这时真正的离开了IP层,该函数的1310会调用之一文章讲述的函数将数据实际的发送出去。
最后还是看一张图来回顾tcp/ip发送数据的流程。
图5.1 网络层函数调用流程
tcp的发送函数和接收函数一样位于net/ipv4/文件夹,文件名是tcp_output.c文件,传输层和网络层联系的函数是tcp_transmit_skb(...):
在进入该函数时,先看该函数用到的一个重要的数据结构,其定义于net/dccp/ipv4.c文件,919行是发送时用到的函数。
918 static const struct inet_connection_sock_af_ops dccp_ipv4_af_ops = { 919 .queue_xmit = ip_queue_xmit, 920 .send_check = dccp_v4_send_check, 921 .rebuild_header = inet_sk_rebuild_header, 922 .conn_request = dccp_v4_conn_request, 923 .syn_recv_sock = dccp_v4_request_recv_sock, 924 .net_header_len = sizeof(struct iphdr), 925 .setsockopt = ip_setsockopt, 926 .getsockopt = ip_getsockopt, 927 .addr2sockaddr = inet_csk_addr2sockaddr, 928 .sockaddr_len = sizeof(struct sockaddr_in), 929 .bind_conflict = inet_csk_bind_conflict, 934 };
919行的函数服务于ip层,也就是四层模型中的网络层,我们先看传输层的函数tcp_transmit_skb,该函数会建立IP层的头,并将该头传递给网络层。
net/ipv4/tcp_output.c 840 static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it, 841 gfp_t gfp_mask) 842 { 843 const struct inet_connection_sock *icsk = inet_csk(sk); 844 struct inet_sock *inet; 845 struct tcp_sock *tp; 961 err = icsk->icsk_af_ops->queue_xmit(skb, &inet->cork.fl); }
icsk_af_ops->queue_xmit的两个参数,structsk_buff *skb已经在第二章叙述过了,inet的原型是inet_sock是inet的套接字的代表。
struct inet_sock { /* sk and pinet6 has to be the first two members of inet_sock */ struct sock sk; //祖先的类 /* Socket demultiplex comparisons on incoming packets. */ #define inet_daddr sk.__sk_common.skc_daddr //外部ipv4地址 #define inet_rcv_saddr sk.__sk_common.skc_rcv_saddr//绑定的本地ipv4地址 #define inet_addrpair sk.__sk_common.skc_addrpair #define inet_dport sk.__sk_common.skc_dport//目的端口号 #define inet_num sk.__sk_common.skc_num//本地端口号 #define inet_portpair sk.__sk_common.skc_portpair __be32 inet_saddr; __s16 uc_ttl; //单播的TTL,time to live __u16 cmsg_flags; __be16 inet_sport; __u16 inet_id; struct ip_options_rcu __rcu *inet_opt; int rx_dst_ifindex; __u8 tos; __u8 min_ttl; __u8 mc_ttl; __u8 pmtudisc; __u8 recverr:1, is_icsk:1, freebind:1, hdrincl:1, mc_loop:1, transparent:1, mc_all:1, nodefrag:1; __u8 rcv_tos; int uc_index; int mc_index;//多播设备索引 __be32 mc_addr; struct ip_mc_socklist __rcu *mc_list; struct inet_cork_full cork; //每个分片的ip packet构建ip头时用到的数据结构 };
ip_queue_xmit这个函数就是网络层的传输函数了;
326 int ip_queue_xmit(struct sk_buff *skb, struct flowi *fl) 327 { 328 struct sock *sk = skb->sk; 329 struct inet_sock *inet = inet_sk(sk); 360 rt = ip_route_output_ports(sock_net(sk), fl4, sk, //获取输出信息的路由表,路由信息会填充到skb的dst字段去。 361 daddr, inet->inet_saddr, 362 inet->inet_dport, 363 inet->inet_sport, 364 sk->sk_protocol, 365 RT_CONN_FLAGS(sk), 366 sk->sk_bound_dev_if); 403 res = ip_local_out(skb); //发送出去 412 }
第403行是发送packet的函数, res = ip_local_out(skb); //发送出去
static inline int dst_output(struct sk_buff *skb) { return skb_dst(skb)->output(skb); } int ip_local_out(struct sk_buff *skb) { err = __ip_local_out(skb); if (likely(err == 1)) err = dst_output(skb); }
由上面两个函数,真正调用的其实是rth->dst.output= ip_output指向的函数,该函数将主要工作放到ip_finish_output去完成,这和接收时的方法很类似,并且这里也有一个钩子函数。
298 int ip_output(struct sk_buff *skb) 299 { 300 struct net_device *dev = skb_dst(skb)->dev; 301 302 IP_UPD_PO_STATS(dev_net(dev), IPSTATS_MIB_OUT, skb->len); 303 304 skb->dev = dev; 305 skb->protocol = htons(ETH_P_IP); 306 307 return NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING, skb, NULL, dev, 308 ip_finish_output, 309 !(IPCB(skb)->flags & IPSKB_REROUTED)); 310 }
ip_finish_output函数的定义如下,CONFIG_NETFILTER和CONFIG_XFRM都是和安全相关的机制。231行判断ip是否被分片了,通常以太网上的数据没有经过分片操作,如果分片了使用ip_fragment来处理,处理完了调用ip_finish_output2完成实质的发送工作。
222 static int ip_finish_output(struct sk_buff *skb) 223 { 224 #if defined(CONFIG_NETFILTER) && defined(CONFIG_XFRM) 225 /* Policy lookup after SNAT yielded a new policy */ 226 if (skb_dst(skb)->xfrm != NULL) { 227 IPCB(skb)->flags |= IPSKB_REROUTED; 228 return dst_output(skb); 229 } 230 #endif 231 if (skb->len > ip_skb_dst_mtu(skb) && !skb_is_gso(skb)) 232 return ip_fragment(skb, ip_finish_output2); 233 else 234 return ip_finish_output2(skb); 235 }
ip_finish_output2对packet的头进行处理,然后根据路由表寻找下一跳地址,这里还涉及一层邻居协议,这个没有路由那么庞大。
166 static inline int ip_finish_output2(struct sk_buff *skb) 167 { 196 nexthop = (__force u32) rt_nexthop(rt, ip_hdr(skb)->daddr); 197 neigh = __ipv4_neigh_lookup_noref(dev, nexthop); 198 if (unlikely(!neigh)) 199 neigh = __neigh_create(&arp_tbl, &nexthop, dev, false); 200 if (!IS_ERR(neigh)) { 201 int res = dst_neigh_output(dst, neigh, skb); 212 }
neigh是structneighbour类型的结构体,这个数据邻居协议的范畴。197是邻居协议查找合适的网卡设备,198行如果找不到则执行199行的函数创建一个,在获得邻居项之后执行201行的函数。该函数位于include/net/dst.h,该函数未定义于ipv4的目录下,这也意味着数据将传递到主机到网络层了。
393 static inline int dst_neigh_output(struct dst_entry *dst, struct neighbour *n, 394 struct sk_buff *skb) 395 { 396 const struct hh_cache *hh; 397 398 if (dst->pending_confirm) { 399 unsigned long now = jiffies; 400 401 dst->pending_confirm = 0; 402 /* avoid dirtying neighbour */ 403 if (n->confirmed != now) 404 n->confirmed = now; 405 } 406 407 hh = &n->hh; 408 if ((n->nud_state & NUD_CONNECTED) && hh->hh_len) 409 return neigh_hh_output(hh, skb); //支持硬件缓存头方法的发送 410 else 411 return n->output(n, skb); //不支持硬件缓存头的方法的发送。 412 }
第411行调用的是neigh_resolve_output,该函数是通过路由表查到的,不论硬件支不支持硬件头缓存,neigh_resolve_output都会被调用到,它来源如下:
static const struct neigh_ops arp_generic_ops = { .family = AF_INET, .solicit = arp_solicit, .error_report =arp_error_report, .output = neigh_resolve_output, .connected_output = neigh_connected_output, }; //有硬件头缓存的函数操作集 static const struct neigh_ops arp_hh_ops = { .family = AF_INET, .solicit = arp_solicit, .error_report =arp_error_report, .output = neigh_resolve_output, .connected_output =neigh_resolve_output, };
获得上述output函数的过程就是路由的过程,还是来看看neigh_resolve_output,它定义于net/core/neighbour.c文件。这时真正的离开了IP层,该函数的1310会调用之一文章讲述的函数将数据实际的发送出去。
1286 int neigh_resolve_output(struct neighbour *neigh, struct sk_buff *skb) 1287 { 1288 struct dst_entry *dst = skb_dst(skb); 1289 int rc = 0; 1290 1291 if (!dst) 1292 goto discard; 1293 1294 if (!neigh_event_send(neigh, skb)) { 1295 int err; 1296 struct net_device *dev = neigh->dev; 1297 unsigned int seq; 1298 1299 if (dev->header_ops->cache && !neigh->hh.hh_len) 1300 neigh_hh_init(neigh, dst); 1301 1302 do { 1303 __skb_pull(skb, skb_network_offset(skb)); 1304 seq = read_seqbegin(&neigh->ha_lock); 1305 err = dev_hard_header(skb, dev, ntohs(skb->protocol), 1306 neigh->ha, NULL, skb->len); 1307 } while (read_seqretry(&neigh->ha_lock, seq)); 1308 1309 if (err >= 0) 1310 rc = dev_queue_xmit(skb); 1313 } }
最后还是看一张图来回顾tcp/ip发送数据的流程。
图5.1 网络层函数调用流程
相关文章推荐
- (OK) 第五章 传输层(tcp)到网络层(ip)--基于Linux3.10 - dst_neigh_output() - neigh_resolve_output() 真正的离开了IP层
- 第八章 tcp接(传输层)--基于Linux 3.10
- linux网络编程之TCP/IP基础(五):分析一帧基于UDP的TFTP协议帧
- TCP/IP 网络子系统 在Linux 内核中实现详解 (基于2.6.35版)
- 基于tcp/ip的数据网络数据传输发送端
- Linux 基于TCP/IP的文件传输系统
- 基于tcp/ip的网络数据传输接收端
- 第七章 tcp发送(传输层)--基于Linux3.10
- 百度笔试题2005题目大致是这样的: 第一部分选择题: 有几道网络相关的题目,巨简单,比如第一题是TCP、RIP、IP、FTP中哪个协议是传输层的......。有一道linux的 chown使用题目。其他的全是数据结构的题目!什么链,表
- 基于linux简单的TCP/IP网络通信代码
- 未安装TCP/IP 网络传输
- Linux下高性能网络编程中的几个TCP/IP选项
- [Linux网络编程笔记]传输协议TCP和UDP简介
- Linux内核网络系统结构图(TCP/IP)和源代码的布局
- Linux网络管理员手册(2) 第二章 TCP/IP网络的问题 IP地址 子网(Subnetworks) 域名服务器 解析
- WINDOWS (服务器) 和 DOS(客户端) 网络互连 基于TCP/IP的编程实现
- 基于TCP/IP的SOCKET接口实现网络通信
- WINDOWS (服务器) 和 DOS(客户端) 网络互连 基于TCP/IP的编程实现
- 基于TCP、UDP网络传输的实例分析