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;
}