报文目的ip被占用_为什么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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值