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

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

IP 层收发报文简要剖析4--ip 报文发送

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);
}
View Code

相关文章: