在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;
- }
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请
点击举报。