IP 层收发报文简要剖析4--ip 报文发送
阅读原文时间:2023年07月09日阅读:1

无论是从本地输出的数据还是转发的数据报文,经过路由后都要输出到网络设备,而输出到网络设备的接口就是dst_output(output)函数

路由的时候,dst_output函数设置为ip_output ip_mc_output等

1、TCP输出接口

L4 层在发送数据时会根据协议的不同调用上面提到的几个辅助函数之一,tcp协议打包成ip数据包文的方法根据tcp段的不同而选择不同的接口,

其中ip_queue_xmit为常用接口,ip_build_and_send_pkt、ip_send_reply只有在发送特定段的时候才使用

1-1 int ip_queue_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl)

int ip_queue_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl)
{
struct inet_sock *inet = inet_sk(sk);
struct net *net = sock_net(sk);
struct ip_options_rcu *inet_opt;
struct flowi4 *fl4;
struct rtable *rt;
struct iphdr *iph;
int res;

/\* Skip all of this if the packet is already routed,  
 \* f.e. by something like SCTP.  
 \*/  
rcu\_read\_lock();  
/\*  
 \* 如果待输出的数据包已准备好路由缓存,  
 \* 则无需再查找路由,直接跳转到packet\_routed  
 \* 处作处理。  
 \*/  
inet\_opt = rcu\_dereference(inet->inet\_opt);  
fl4 = &fl->u.ip4;  
rt = skb\_rtable(skb);  
if (rt)  
    goto packet\_routed;

/\* Make sure we can route this packet. \*/  
/\*  
 \* 如果输出该数据包的传输控制块中  
 \* 缓存了输出路由缓存项,则需检测  
 \* 该路由缓存项是否过期。  
 \* 如果过期,重新通过输出网络设备、  
 \* 目的地址、源地址等信息查找输出  
 \* 路由缓存项。如果查找到对应的路  
 \* 由缓存项,则将其缓存到传输控制  
 \* 块中,否则丢弃该数据包。  
 \* 如果未过期,则直接使用缓存在  
 \* 传输控制块中的路由缓存项。  
 \*/  
rt = (struct rtable \*)\_\_sk\_dst\_check(sk, 0);  
if (!rt) { /\* 缓存过期 \*/  
    \_\_be32 daddr;

    /\* Use correct destination address if we have options. \*/  
    daddr = inet->inet\_daddr; /\* 目的地址 \*/  
    if (inet\_opt && inet\_opt->opt.srr)  
        daddr = inet\_opt->opt.faddr; /\* 严格路由选项 \*/

    /\* If this fails, retransmit mechanism of transport layer will  
     \* keep trying until route appears or the connection times  
     \* itself out.  
     \*/ /\* 查找路由缓存 \*/  
    rt = ip\_route\_output\_ports(net, fl4, sk,  
                   daddr, inet->inet\_saddr,  
                   inet->inet\_dport,  
                   inet->inet\_sport,  
                   sk->sk\_protocol,  
                   RT\_CONN\_FLAGS(sk),  
                   sk->sk\_bound\_dev\_if);  
    if (IS\_ERR(rt))  
        goto no\_route;  
    sk\_setup\_caps(sk, &rt->dst);  /\* 设置控制块的路由缓存 \*/  
}  
skb\_dst\_set\_noref(skb, &rt->dst);/\* 将路由设置到skb中 \*/

packet_routed:
/*
* 查找到输出路由后,先进行严格源路由
* 选项的处理。如果存在严格源路由选项,
* 并且数据包的下一跳地址和网关地址不
* 一致,则丢弃该数据包。
*/
if (inet_opt && inet_opt->opt.is_strictroute && rt->rt_uses_gateway)
goto no_route;

/\* OK, we know where to send it, allocate and build IP header. \*/  
/\*  
 \* 设置IP首部中各字段的值。如果存在IP选项,  
 \* 则在IP数据包首部中构建IP选项。  
 \*/  
skb\_push(skb, sizeof(struct iphdr) + (inet\_opt ? inet\_opt->opt.optlen : 0));  
skb\_reset\_network\_header(skb);  
iph = ip\_hdr(skb);/\* 构造ip头 \*/  
\*((\_\_be16 \*)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff));  
if (ip\_dont\_fragment(sk, &rt->dst) && !skb->ignore\_df)  
    iph->frag\_off = htons(IP\_DF);  
else  
    iph->frag\_off = 0;  
iph->ttl      = ip\_select\_ttl(inet, &rt->dst);  
iph->protocol = sk->sk\_protocol;  
ip\_copy\_addrs(iph, fl4);

/\* Transport layer set skb->h.foo itself. \*/  
 /\* 构造ip选项 \*/  
if (inet\_opt && inet\_opt->opt.optlen) {  
    iph->ihl += inet\_opt->opt.optlen >> 2;  
    ip\_options\_build(skb, &inet\_opt->opt, inet->inet\_daddr, rt, 0);  
}

ip\_select\_ident\_segs(net, skb, sk,  
             skb\_shinfo(skb)->gso\_segs ?: 1);

/\* TODO : should we use skb->sk here instead of sk ? \*/  
/\*  
 \* 设置输出数据包的QoS类型。  
 \*/  
skb->priority = sk->sk\_priority;  
skb->mark = sk->sk\_mark;

res = ip\_local\_out(net, sk, skb);  /\* 输出 \*/  
rcu\_read\_unlock();  
return res;

no_route:
rcu_read_unlock();
/*
* 如果查找不到对应的路由缓存项,
* 在此处理,将该数据包丢弃。
*/
IP_INC_STATS(net, IPSTATS_MIB_OUTNOROUTES);
kfree_skb(skb);
return -EHOSTUNREACH;
}

1、2 ip_build_and_send_pkt

在tcp建立连接过程中,内核封包输出syn+ack类型的tcp报文。用此函数封包ip报文段

int ip_build_and_send_pkt(struct sk_buff *skb, const struct sock *sk,
__be32 saddr, __be32 daddr, struct ip_options_rcu *opt)
{
struct inet_sock *inet = inet_sk(sk);
struct rtable *rt = skb_rtable(skb);
struct net *net = sock_net(sk);
struct iphdr *iph;

/\* Build the IP header. 构造ip首部\*/  
skb\_push(skb, sizeof(struct iphdr) + (opt ? opt->opt.optlen : 0));  
skb\_reset\_network\_header(skb);  
iph = ip\_hdr(skb);  
iph->version  = 4;  
iph->ihl      = 5;  
iph->tos      = inet->tos;  
iph->ttl      = ip\_select\_ttl(inet, &rt->dst);  
iph->daddr    = (opt && opt->opt.srr ? opt->opt.faddr : daddr);  
iph->saddr    = saddr;  
iph->protocol = sk->sk\_protocol;  
/\* 分片与否 \*/  
if (ip\_dont\_fragment(sk, &rt->dst)) {  
    iph->frag\_off = htons(IP\_DF);  
    iph->id = 0;  
} else {  
    iph->frag\_off = 0;  
    \_\_ip\_select\_ident(net, iph, 1);  
}  
    /\*处理ip选项字段\*/  
if (opt && opt->opt.optlen) {  
    iph->ihl += opt->opt.optlen>>2;  
    ip\_options\_build(skb, &opt->opt, daddr, rt, 0);  
}  
 /\* QOS优先级 \*/  
skb->priority = sk->sk\_priority;  
skb->mark = sk->sk\_mark;

/\* Send it out. \*/  
return ip\_local\_out(net, skb->sk, skb);  

}

1.3 ip_send_reply

此函数主要用于输出rst 以及ack段报文 在tcp_v4_send_reset 和tcp_v4_send_ack 中调用

void ip_send_unicast_reply(struct sock *sk, struct sk_buff *skb,
const struct ip_options *sopt,
__be32 daddr, __be32 saddr,
const struct ip_reply_arg *arg,
unsigned int len)
{
struct ip_options_data replyopts;
struct ipcm_cookie ipc;
struct flowi4 fl4;
struct rtable *rt = skb_rtable(skb);
struct net *net = sock_net(sk);
struct sk_buff *nskb;
int err;
int oif;
/* 获取ip选项用于处理原路由选项 */
if (__ip_options_echo(&replyopts.opt.opt, skb, sopt))
return;

ipc.addr = daddr;  
ipc.opt = NULL;  
ipc.tx\_flags = 0;  
ipc.ttl = 0;  
ipc.tos = -1;  

/* 如果输入的ip 数据包文启用了路由选项
将得到的下一跳地址作为目的ip地址*/
if (replyopts.opt.opt.optlen) {
ipc.opt = &replyopts.opt;

    if (replyopts.opt.opt.srr)  
        daddr = replyopts.opt.opt.faddr;  
}

oif = arg->bound\_dev\_if; /\*设置 输出接口 \*/  
if (!oif && netif\_index\_is\_l3\_master(net, skb->skb\_iif))  
    oif = skb->skb\_iif;  

/* 查路由 根据目的地址 原地址 查找 */
flowi4_init_output(&fl4, oif,
IP4_REPLY_MARK(net, skb->mark),
RT_TOS(arg->tos),
RT_SCOPE_UNIVERSE, ip_hdr(skb)->protocol,
ip_reply_arg_flowi_flags(arg),
daddr, saddr,
tcp_hdr(skb)->source, tcp_hdr(skb)->dest);
security_skb_classify_flow(skb, flowi4_to_flowi(&fl4));
rt = ip_route_output_key(net, &fl4);
if (IS_ERR(rt))/*如果没有命中路由,终止*/
return;

inet\_sk(sk)->tos = arg->tos;

sk->sk\_priority = skb->priority;  
sk->sk\_protocol = ip\_hdr(skb)->protocol;  
sk->sk\_bound\_dev\_if = arg->bound\_dev\_if;  
sk->sk\_sndbuf = sysctl\_wmem\_default;  
/\*将数据报文添加到队列末尾中或者复制到新生成的  
skb中去 并添加到输出队列\*/  
err = ip\_append\_data(sk, &fl4, ip\_reply\_glue\_bits, arg->iov->iov\_base,  
             len, 0, &ipc, &rt, MSG\_DONTWAIT);  
if (unlikely(err)) {  
    ip\_flush\_pending\_frames(sk);  
    goto out;  
}

nskb = skb\_peek(&sk->sk\_write\_queue);  
if (nskb) {/\* 如果发送队列有skb,则计算校验和,发送 \*/  
    if (arg->csumoffset >= 0)  
        \*((\_\_sum16 \*)skb\_transport\_header(nskb) +  
          arg->csumoffset) = csum\_fold(csum\_add(nskb->csum,  
                            arg->csum));  
    nskb->ip\_summed = CHECKSUM\_NONE;  
    ip\_push\_pending\_frames(sk, &fl4);// 发送数据  
}  

out:
ip_rt_put(rt);
}
int ip_send_skb(struct net *net, struct sk_buff *skb)
{
int err;

err = ip\_local\_out(net, skb->sk, skb);  
if (err) {  
    if (err > 0)  
        err = net\_xmit\_errno(err);  
    if (err)  
        IP\_INC\_STATS(net, IPSTATS\_MIB\_OUTDISCARDS);  
}

return err;  

}

int ip_push_pending_frames(struct sock *sk, struct flowi4 *fl4)
{
struct sk_buff *skb;

skb = ip\_finish\_skb(sk, fl4);  
if (!skb)  
    return 0;

/\* Netfilter gets whole the not fragmented skb. \*/  
return ip\_send\_skb(sock\_net(sk), skb);  

}

ip_queue_xmit 流程图