zoukankan      html  css  js  c++  java
  • 为什么ip层收到的报文可能已经设置了路由

    一、收到报文对于网络地址的判断
    对于刚收到的网络数据,经过了NF_IP_PRE_ROUTING过滤之后,开始到达了ip_rcv_finish函数,在该函数的开始做了一个看起来比较诡异的操作,就是判断了这个数据包中的路由dst是否已经设置过了,如果没有设置过则进行路由;这也就是反过来说,一些收到的报文是可能已经设置过了了路由。那么为什么会有这样的报文,它们从哪里来,又是在哪里设置了这些路由项?
    static inline int ip_rcv_finish(struct sk_buff *skb)
    {
    struct iphdr *iph = skb->nh.iph;
     
    /*
     * Initialise the virtual path cache for the packet. It describes
     * how the packet travels inside Linux networking.
     */
    if (skb->dst == NULL) {//在什么情况下这个指针非空
    int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos,
     skb->dev);
    if (unlikely(err)) {
    if (err == -EHOSTUNREACH)
    IP_INC_STATS_BH(IPSTATS_MIB_INADDRERRORS);
    goto drop;
    }
    }
    ……
    }        
    二、一个典型的场景
    其实这个最为典型的场景发生在一个机器上的不同进程通过tcp通讯数据,也就是说当一个报文的发送目的地是本机的时候,此时这个报文就不再是从机器外部过来的数据,而是从本机内部的数据,所以它的目的地址在output的时候已经被设置过了,当然,如果你ping一个本地的loopback网卡设备,同样也是下面说的流程。假设说发送的目的地是在本机,此时经过的路由为
    ip_route_output_flow-->>__ip_route_output_key-->>ip_route_output_slow-->>
    if (res.type == RTN_LOCAL) {
    if (!fl.fl4_src)
    fl.fl4_src = fl.fl4_dst;
    if (dev_out)
    dev_put(dev_out);
    dev_out = &loopback_dev;
    如果网络发送的目的地址经过路由表判断之后为一个本地的地址,则此时“发送”时使用的网卡就是虚拟的loopback网卡,
    ip_route_output_slow-->>ip_mkroute_output-->>ip_mkroute_output_def-->>__mkroute_output
    rth->u.dst.output=ip_output;
     
    RT_CACHE_STAT_INC(out_slow_tot);
     
    if (flags & RTCF_LOCAL) {
    rth->u.dst.input = ip_local_deliver;
    rth->rt_spec_dst = fl->fl4_dst;
    }ip_output-->>ip_finish_output-->>ip_finish_output2-->>dev_queue_xmit-->>dev_hard_start_xmit-->>loopback_xmit
    static int loopback_xmit(struct sk_buff *skb, struct net_device *dev)
    {
    ……
    /* it's OK to use __get_cpu_var() because BHs are off */
    lb_stats = &__get_cpu_var(pcpu_lstats);
    lb_stats->bytes += skb->len;
    lb_stats->packets++;
     
    netif_rx(skb);再次掉头进入本机的网络协议栈
     
    return 0;
    }
    也就是这里的确使用了一个“具体的”网卡loopback将数据“发送”出去,但是这个设备非常特殊,它的发送接口是将所有被要求发送的报文进行了一个180°大转弯,调转netif_rx之后一头再次扎进了系统的协议栈中,这里的报文结构并没有作任何修改,所以它的dst指针依然有效,它的输入接口依然保留着在__mkroute_output函数中设置的方法,也就是ip_local_deliver,所以在ip_rcv_finish-->>dst_input
    /* Input packet from network to transport.  */
    static inline int dst_input(struct sk_buff *skb)
    {
    int err;
     
    for (;;) {
    err = skb->dst->input(skb);
     
    if (likely(err == 0))
    return err;
    /* Oh, Jamal... Seems, I will not forgive you this mess. :-) */
    if (unlikely(err != NET_XMIT_BYPASS))
    return err;
    }
    }
    此时的skb->dst->input方法就是__mkroute_output中设置的ip_local_deliver。
    三、再看一个“反面”的例子
    除了loopback这样的设备之外,系统中还有一些其他的设备类型也是需要进行这么一番周折的,例如我们在lvs可能使用的ipip设备。它的数据包在系统协议栈中的流转也比较坎坷,因为它包含了两个iphdr(想起了《国产零零七》中的“痰盂子母弹”),这意味着这个数据包也有机会两次进入系统协议栈,此时它是否也存在这样的问题呢?对于ipip协议数据的处理主要在函数ipip_rcv中完成:
    if ((tunnel = ipip_tunnel_lookup(iph->saddr, iph->daddr)) != NULL) {
    ……
    dst_release(skb->dst);
    skb->dst = NULL;
                    ……
               netif_rx(skb);
     
    return 0;
    }
    由于这里的例子并不适应,所以对于需要再次进入系统网络协议栈的报文,这里ipip进行了主动的有意识的重新归零,也就是为了避这个报文再次进入协议栈时有残留的路由信息,而这个信息又是一个错误的路由信息,所以这个地方要主动的将这个路由信息清除。
    四、路由转发对于traceroute实现的支持
    在看这个代码的时候,顺便看到了对于报文转发的处理。其实这个代码处理非常简单,就是对于过了的数据包进行路由计算并转发。这个数据包进入内核协议栈之后将会被设置为ip_forward,这个函数的功能其实非常简单,因为__mkroute_input已经为它选好了output使用的dev和gateway信息,所以它只是执行了ip_forward函数就可以返回了。在这个简短的函数中,除了执行我们最为关心的netfilter的钩子调用之外,它还有一个重要的功能就是递减这个被转发的报文的生存期,也就是ttl(Time To Live)字段。当这个值减少为1之后,这个报文将会被丢弃,并且给谁发送源发送一个ICMP_TIME_EXCEEDED类型的icmp报文。据说在windows下的troucert,linux下的traceroute工具都是利用了这个特性来进行路由检测,方法就是以1为ttl初始值来发送探测数据包,然后侦听回报中的ICMP_TIME_EXCEEDED报文,通过这样来逐渐尝试到达目的地址经过的各个站点。
    int ip_forward(struct sk_buff *skb)
    {
    ……
    /*
     * According to the RFC, we must first decrease the TTL field. If
     * that reaches zero, we must reply an ICMP control message telling
     * that the packet's lifetime expired.
     */
    if (skb->nh.iph->ttl <= 1)
    goto too_many_hops;
    ……
    /* Decrease ttl after skb cow done */
    ip_decrease_ttl(iph);
    ……
    too_many_hops:
    /* Tell the sender its packet died... */
    IP_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
    icmp_send(skb, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL, 0);
            
    在icmp_send的回包中,使用的源地址是检测到ttl归零的主机地址(而不是被转发报文的最终目的地址)。这个转换在icmp_send中可以看到:
    void icmp_send(struct sk_buff *skb_in, int type, int code, __be32 info)
    {
    ……
    /*
     * Construct source address and options.
     */
     
    saddr = iph->daddr;
    if (!(rt->rt_flags & RTCF_LOCAL)) {
    if (sysctl_icmp_errors_use_inbound_ifaddr)
    saddr = inet_select_addr(skb_in->dev, 0, RT_SCOPE_LINK);
    else
    saddr = 0;
    }                                                
    ……
    struct flowi fl = {
    .nl_u = {
    .ip4_u = {
    .daddr = icmp_param.replyopts.srr ?
    icmp_param.replyopts.faddr :
    iph->saddr,
    .saddr = saddr,//如果源地址为0,则由路由层自主选择出口网卡的IP地址
    .tos = RT_TOS(tos)
    }
    },
    .proto = IPPROTO_ICMP,
    .uli_u = {
    .icmpt = {
    .type = type,
    .code = code
    }
    }
    };
    在最终的发送接口中,其填充的源地址为ip_push_pending_frames
     ……
      iph->ttl = ttl;
    iph->protocol = sk->sk_protocol;
    iph->saddr = rt->rt_src;
    iph->daddr = rt->rt_dst;
    ip_send_check(iph);
    ……
    五、系统默认TTL的值
    static void rt_set_nexthop(struct rtable *rt, struct fib_result *res, u32 itag)
    if (rt->u.dst.metrics[RTAX_HOPLIMIT-1] == 0)
    rt->u.dst.metrics[RTAX_HOPLIMIT-1] = sysctl_ip_default_ttl;
                    
    tsecer@harry: cat /proc/sys/net/ipv4/ip_default_ttl 
    64
    通过tcpdump抓一个ping本地地址的数据包,可以看到ttl为默认的64
    08:43:38.626117 IP (tos 0x0, ttl 64, id 31217, offset 0, flags [DF], proto UDP (17), length 118)        
  • 相关阅读:
    mysql中的内连接and 多表查询
    webdriver中的三大等待及窗口的切换
    postman断言
    postman数据驱动ddt
    postman环境变量和全局变量的使用
    postman 接口请求过程
    垃圾脑瘫的坑
    待填的坑
    CF185D
    CF235E 题解(转)
  • 原文地址:https://www.cnblogs.com/tsecer/p/10487693.html
Copyright © 2011-2022 走看看