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

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

原文地址:https://www.cnblogs.com/codestack/p/9195001.html

时间: 2024-08-04 19:08:51

IP 层收发报文简要剖析4--ip 报文发送的相关文章

IP 层收发报文简要剖析3--ip输入报文分片重组

在ip_local_deliver中,如果检测到是分片包,则需要将报文进行重组.其所有的分片被重新组合后才能提交到上层协议,每一个被重新组合的数据包文用ipq结构实例来表示 struct ipq { struct inet_frag_queue q; u32 user;//分片来源 __be32 saddr;//原地址 __be32 daddr;//目的地址 __be16 id;//ip报文序列号 u8 protocol;//上层协议号 //这四个字段来自ip首部是为了确定来自哪个ip数据报文

IP 层收发报文简要剖析2--ip报文的输入ip_local_deliver

ip报文根据路由结果:如果发往本地则调用ip_local_deliver处理报文:如果是转发出去,则调用ip_forward 处理报文. 一.ip报文转发到本地: /* * Deliver IP Packets to the higher protocol layers. */ /* * 在ip_route_input_noref进行路由选择后,如果接收的包 * 是发送给本机,则调用ip_local_deliver来传递给上层协议 */ int ip_local_deliver(struct s

IP 层收发报文简要剖析1-ip报文的输入

ip层数据包处理场景如下: 网络层处理数据包文时需要和路由表以及邻居系统打交道.输入数据时,提供输入接口给链路层调用,并调用传输层的输入接口将数据输入到传输层. 在输出数据时,提供输出接口给传输层,并调用链路层的输出接口将数据输出到链路层.在输入输出数据时,需要查找路由表 通过netfiler处理等操作. 一.ip数据报文输入 ip_rcv &ip_rcv_finish &ip_rcv_finish2 1.在inet_init中注册了类型为ETH_P_IP协议的数据包的回调ip_rcv 2

IP 层收发报文简要剖析6--ip报文输出3 ip_push_pending_frames

L4层的协议会把数据通过ip_append_data或ip_append_page把数据线放在缓冲区,然后再显示调用ip_push_pending_frames传送数据. 把数据放在缓冲区有两个优点,一方面,缓冲区的数据可以被后续的一些函数使用,构成一些片段:另一方面,把数据放缓冲区,等缓冲区满了(达到PMTU)再传送数据,可以更有效率. 如果在一些情况下,L4层希望去放在缓冲区的数据立即被传输,那么在调用ip_append_data把数据放缓冲区后,立即调用ip_push_pending_fr

IP 层收发报文简要剖析6--ip_forward 报文转发

//在函数ip_route_input_slow->ip_mkroute_input注册, /* * IP数据包的转发是由ip_forward()处理,该函数在ip_rcv_finish() * 通过输入路由缓存被调用. */ int ip_forward(struct sk_buff *skb) { u32 mtu; struct iphdr *iph; /* Our header */ struct rtable *rt; /* Route we use */ struct ip_optio

Linux 网卡驱动学习(六)(应用层、tcp 层、ip 层、设备层和驱动层作用解析)

本文将介绍网络连接建立的过程.收发包流程,以及当中应用层.tcp层.ip层.设备层和驱动层各层发挥的作用. 1.应用层 对于使用socket进行网络连接的server端程序.我们会先调用socket函数创建一个套接字: fd = socket(AF_INET, SOCK_STREAM, 0); 以上指定了连接协议,socket调用返回一个文件句柄,与socket文件相应的inode不在磁盘上,而是存在于内存. 之后我们指定监听的port.同意与哪些ip建立连接,并调用bind完毕port绑定:

老斜两宗事-七层代理模式还是IP层VPN

1.七层代理模式还是IP层VPN 很多人会问,我到底是使用代理模式呢,还是使用VPN模式,如果我想数据在中间不安全的链路上实现加密保护的话.这个问题有一个背景,那就是,你想保护你的数据,可以使用VPN,但是有时候,第七层的代理模式或许更好,比如SSL卸载器,比如内置SSL处理的代理,分为正向代理和反向代理.正向代理:代理的是访问者.一般位于访问者一端,访问者能意识到代理的存在,直接访问代理,由代理向服务器发起访问.反向代理:反向代理代理的是被访问者.位于被访问者一端,访问者意识不到代理的存在,访

第二十二章 TCP/IP层的实现

                      第二十二章    TCP/IP层的实现        我比较喜欢先难后易,如果把GPU显示管理.和网络管理拿下后:我会从头整理.改写一遍APO操作系统.这样,就会形成APO操作系统的锥形.也获得了全局观.内核CPU线路.和用户CPU线路,你可以将它们看成是独立的2个32位CPU核:内核CPU主要任务是实时处理.硬件中断,256个实时线程包含了一些中断程序的后半部.用户CPU主要是动态优先级进程.线程调度,各种应用程序的运行:2个核之间是通过消息交互.句

用Netcat,SSH构建的IP层加密隧道搭建VPN

[关于题外话在最后] 写作本文主要基于两点,首先是因为我前段时间写了几篇关于VPN的新解,收到了很多的邮件反馈,我也思考了很多,另一个方面是因为很多人问我怎么用QQ,P2P搭建一个IP层的VPN,我的回答是"我也不知道".我确实不知道,根本就没有试过,只是有个这样那样的想法...我主要是没有能力去Hack这些非Linux上的东西...所以说,我写这篇文章,用UNIX的方法"将多个小工具结合起来"实现我的那些没有实现的想法,抛砖引玉一下. 声明: 本文没有技术含量,甚