最新消息:XAMPP默认安装之后是很不安全的,我们只需要点击左方菜单的 "安全"选项,按照向导操作即可完成安全设置。

Linux 内核网络之 网络层发送消息[1]

XAMPP相关 admin 79浏览 0评论

在TCP 中,将 TCP 段打包成 IP 数据报的方法根据 TCP 段类型的不同而有多种接口。其中最常用的就是 ip_queue_xmit( ), 而 ip_build_and_send_pkt( ) 和 ip_sned_reply( ) 只有在发送特定段时才会被调用。

TCP 层发送数据报文中,通过 tcp_transmit_skb  调用 ip_queue_xmit 把TCP 段打包成 IP 数据报。

ip_queue_xmit

/*该方法时tcp传输中最长调用的,普通数据的输出最后都是有它进行打包处理的。
而ip_build_and_send_pkt 和 ip_send_reply 只有在发送特定的段时才会被调用
skb 待封装成ip数据报的tcp段
ipfragok 标识待输出的数据是否已经完成分片。由于在调用该函数时ipfragok总为0,
因此输出的ip数据报是否已经分片完全取决于是否启用PMTU发现
*/
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;

    //若数据报已准备好路由缓存,则无需再查找路由,直接跳转处理
    rt = (struct rtable *) skb->dst;
    if (rt != NULL)
        goto packet_routed;

    /* Make sure we can route this packet. */
    /*若数据报的传输控制块中缓存了输出路由缓存项,则需检查缓存项是否过期。
    若过期,重新通过输出网络设备、目的地址、源地址等信息查找输出路由缓存项。若查找到对应的路由缓存项,
    则将其缓存到控制块中,否则丢弃该数据包。
    若未过期,则直接使用缓存在传输控制块中的路由缓存项*/
    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,
                        .uli_u = { .ports =
                               { .sport = inet->sport,
                             .dport = inet->dport } } };


            security_sk_classify_flow(sk, &fl);
            if (ip_route_output_flow(&rt, &fl, sk, 0))
                goto no_route;
        }
        sk_setup_caps(sk, &rt->u.dst);
    }
    skb->dst = dst_clone(&rt->u.dst);

packet_routed:
    /* 查找到输出路由后,先进行严格源路由选项的处理。若存在严格源路由选项,
       并且数据包的下一跳地址和网关地址不一致,则丢弃该数据报 
     */
    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. */
    //设置ip首部中各字段的值。若存在ip选项,则在ip数据报首部中构建ip选项
    iph = (struct iphdr *) skb_push(skb, sizeof(struct iphdr) + (opt ? opt->optlen : 0));
    *((__be16 *)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff));
    iph->tot_len = htons(skb->len);
    if (ip_dont_fragment(sk, &rt->u.dst) && !ipfragok)
        iph->frag_off = htons(IP_DF);
    else
        iph->frag_off = 0;
    iph->ttl      = ip_select_ttl(inet, &rt->u.dst);
    iph->protocol = sk->sk_protocol;
    iph->saddr    = rt->rt_src;
    iph->daddr    = rt->rt_dst;
    skb->nh.iph   = iph;
    /* Transport layer set skb->h.foo itself. */

    if (opt && opt->optlen) {
        iph->ihl += opt->optlen >> 2;
        ip_options_build(skb, opt, inet->daddr, rt, 0);
    }

    ip_select_ident_more(iph, &rt->u.dst, sk,
                 (skb_shinfo(skb)->gso_segs ?: 1) - 1);

    /* Add an IP checksum. */
    ip_send_check(iph);
    //设置输出数据报的QOS 类别
    skb->priority = sk->sk_priority;

    // 通过netfilter处理后,由dst_output输出
    return NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,
               dst_output);

//若查不到对应的路由缓存项,则丢弃数据报
no_route:
    IP_INC_STATS(IPSTATS_MIB_OUTNOROUTES);
    kfree_skb(skb);
    return -EHOSTUNREACH;
}

关于 ip_queue_xmit 流程如下:

该方法的主要作用就是封装 IP 数据报,然后把封装好的 IP 数据报经过 netfilter 处理后,由 dst_output 输出。

dst_output

//封装了输出数据报目的路由缓存项中的输出接口
static inline int dst_output(struct sk_buff *skb)
{
    //对于单播为ip_output, 组播的输出函数ip_mc_output
    return skb->dst->output(skb);
}

对于单播来讲,其输出接口为  ip_output。

 ip_output

int ip_output(struct sk_buff *skb)
{
    struct net_device *dev = skb->dst->dev;

    IP_INC_STATS(IPSTATS_MIB_OUTREQUESTS);
    // 设置数据报的输出网络设备和数据报网络层协议类型
    skb->dev = dev;
    skb->protocol = htons(ETH_P_IP);

    //通过netfilter处理后,调用ip_finish_output 继续ip数据报的输出
    return NF_HOOK_COND(PF_INET, NF_IP_POST_ROUTING, skb, NULL, dev,
                    ip_finish_output,
                !(IPCB(skb)->flags & IPSKB_REROUTED));
}

该方法也是先经过 netfilter 处理,对所有马上便要通过网络设备出去的包通过此检测点处理,然后通过  ip_finish_output 把数据包发送出去。

ip_finish_output

//该函数的功能: 若数据报大于MTU,调用ip_fragment分片,否则调用ip_finish_output2输出
static inline int ip_finish_output(struct sk_buff *skb)
{
    ...

    //若数据报大于MTU,调用ip_fragment分片
    if (skb->len > dst_mtu(skb->dst) && !skb_is_gso(skb))
        return ip_fragment(skb, ip_finish_output2);
    else
        return ip_finish_output2(skb);
}

ip_finish_output2

//此函数通过邻居子系统将数据输出到网络设备
static inline int ip_finish_output2(struct sk_buff *skb)
{
    struct dst_entry *dst = skb->dst;
    struct net_device *dev = dst->dev;
    int hh_len = LL_RESERVED_SPACE(dev);

    /* Be paranoid, rather than too clever. */
    /*检测skb的前部空间是否还能存储链路层首部,若不够,重新分配更大存储区的skb,并释放源skb*/
    if (unlikely(skb_headroom(skb) < hh_len && dev->hard_header)) {
        struct sk_buff *skb2;

        skb2 = skb_realloc_headroom(skb, LL_RESERVED_SPACE(dev));
        if (skb2 == NULL) {
            kfree_skb(skb);
            return -ENOMEM;
        }
        if (skb->sk)
            skb_set_owner_w(skb2, skb->sk);
        kfree_skb(skb);
        skb = skb2;
    }
    //若缓存了链路层的首部,则输出数据报
    if (dst->hh)
        return neigh_hh_output(dst->hh, skb); //dev_queue_xmit
    //否则,若存在对应的邻居项,则通过邻居项的输出方法输出数据报
    else if (dst->neighbour)
        return dst->neighbour->output(skb); // neigh_resolve_output
    //若既没有缓存链路层的首部,又不存在对应的邻居项,则不能输出,释放该数据报
    if (net_ratelimit())
        printk(KERN_DEBUG "ip_finish_output2: No header cache and no neighbour!\n");
    kfree_skb(skb);
    return -EINVAL;
}

最终通过邻居子系统调用 dev_queue_xmit 把 IP报文输出到网络设备上。

网络层发送报文处理流程如下:

相关推荐:Linux 内核网络之 传输层接收消息[2]

转载请注明:XAMPP中文组官网 » Linux 内核网络之 网络层发送消息[1]

您必须 登录 才能发表评论!