ip_queue_xmit函数
在ULNI上图中写的清楚,此函数一般由tcp或sctp调用
上层工作都已经做好了,只差ip头及以下部分的填充
tcp可以做到这一点,因为有mss的限制以及自己的一些控制包大小的算法
而udp不一样,它的数据包分段没有完成需要下层帮忙,
因此udp使用的ip_append_data函数要复杂得多。
下面简单注释ip_queue_xmit函数
int ip_queue_xmit(struct sk_buff *skb, int ipfragok) { struct sock *sk = skb->sk; struct inet_sock *inet = inet_sk(sk); struct ip_options *opt = inet->opt; struct rtable *rt; struct iphdr *iph; /* Skip all of this if the packet is already routed, * f.e. by something like SCTP. */ //首先检测skb->rtable是否为空,不为空说明已经指定了路由,跳到packet_routed继续执行 //根据上面注释,似乎sctp可能提前指定路由 rt = skb->rtable; if (rt != NULL) goto packet_routed; /* Make sure we can route this packet. */ //检测socket路由合法性,如果不合法也需要重新查找路由 rt = (struct rtable *)__sk_dst_check(sk, 0); if (rt == NULL) { __be32 daddr; /* Use correct destination address if we have options. */ daddr = inet->daddr; if(opt && opt->srr) daddr = opt->faddr; { struct flowi fl = { .oif = sk->sk_bound_dev_if, .nl_u = { .ip4_u = { .daddr = daddr, .saddr = inet->saddr, .tos = RT_CONN_FLAGS(sk) } }, .proto = sk->sk_protocol, .flags = inet_sk_flowi_flags(sk), .uli_u = { .ports = { .sport = inet->sport, .dport = inet->dport } } }; /* If this fails, retransmit mechanism of transport layer will * keep trying until route appears or the connection times * itself out. */ security_sk_classify_flow(sk, &fl); //下面是主要的出口路由查找函数,等看完路由这一章再回来补充 if (ip_route_output_flow(sock_net(sk), &rt, &fl, sk, 0)) goto no_route; } //下面函数做的其中一件事是sk->sk_dst_cache = dst;并释放旧的dst缓存 sk_setup_caps(sk, &rt->u.dst); } //增加路由缓存引用计数 skb->dst = dst_clone(&rt->u.dst); packet_routed: //如果sk_buff指向的sock的opt中包含严格源站路由选项, //而刚刚查找到的路由项目标地址又不等于网关地址的话前往no_route //说明严格源站路由无法满足 if (opt && opt->is_strictroute && rt->rt_dst != rt->rt_gateway) goto no_route; /* OK, we know where to send it, allocate and build IP header. */ //在skb的数据中预留出ip首部包括选项的空间给ip报头,并将 //skb->network_header指向它 skb_push(skb, sizeof(struct iphdr) + (opt ? opt->optlen : 0)); skb_reset_network_header(skb); iph = ip_hdr(skb); //在ip首部填入版本号4,ip首部长度5(20字节,这个值在后面要根据选项 //的长度增加),以及服务类型 *((__be16 *)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff)); //如果socket要求ip不分片(这是通过检测sock->pmtudisc做到的, //如果使用路径mtu发现则说明要求不分片,否则允许分片)并且参数ipfragok等于0, //那么将DF标志置1,否则清0 if (ip_dont_fragment(sk, &rt->u.dst) && !ipfragok) iph->frag_off = htons(IP_DF); else iph->frag_off = 0; //设置ip首部的ttl(从sock的uc_ttl获得,如果小于0则从路由项的metrics获得), //protocol(从sock->sk_protocol),源地址,目标地址(两者都从路由项获得) iph->ttl = ip_select_ttl(inet, &rt->u.dst); iph->protocol = sk->sk_protocol; iph->saddr = rt->rt_src; iph->daddr = rt->rt_dst; /* Transport layer set skb->h.foo itself. */ //若opt不为NULL,则在ip首部长度中加上选项长度, //并且调用ip_options_build向IP首部中写入ip选项 if (opt && opt->optlen) { iph->ihl += opt->optlen >> 2; //这个函数值得一看,opt是从inet_sock中获得的 ip_options_build(skb, opt, inet->daddr, rt, 0); } //调用ip_select_ident_more填入IP首部的id字段 //关于ip的id在ULNI上讲得很清楚,Linux为了防止id回绕采取的策略是对于每一个ip //分配一个inet_peer结构,在这个inet_peer中记录针对这个ip的id号, //这样可以很大程度上减缓id回绕的速度,但是仍不能完全避免 ip_select_ident_more(iph, &rt->u.dst, sk, (skb_shinfo(skb)->gso_segs ?: 1) - 1); skb->priority = sk->sk_priority; skb->mark = sk->sk_mark; return ip_local_out(skb); no_route: IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTNOROUTES); kfree_skb(skb); return -EHOSTUNREACH; }