IP 層收發報文簡要剖析4--ip 報文發送


無論是從本地輸出的數據還是轉發的數據報文,經過路由后都要輸出到網絡設備,而輸出到網絡設備的接口就是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);
}
View Code

 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 流程圖

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM