记得初学网络之时就曾困惑于ping 本机地址与 ping 127.0.0.1之间的区别与联系,也试图在网上搜寻答案,但终究还是雾里看花,似是而非。
“源码之前,了无秘密”,这次就深入Linux内核协议栈,来看看这两者之间到底是什么样的关系
凭直觉首先会想到这一定和路由表的查找有关,更进一步来说是和路由类型及外出接口有关。在Linux系统中,与外界通信相关的路由条目存储在主路由表(main)中,除此之外还有一张local表,在测试机上这两张表中的内容如下:
$ ip route
default via 192.168.28.1 dev eth0 proto static
169.254.0.0/16 dev eth0 scope link metric 1000
192.168.28.0/24 dev eth0 proto kernel scope link src 192.168.28.67 metric 1
$ ip route show table local
broadcast 127.0.0.0 dev lo proto kernel scope link src 127.0.0.1
local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1
local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1
broadcast 127.255.255.255 dev lo proto kernel scope link src 127.0.0.1
broadcast 192.168.28.0 dev eth0 proto kernel scope link src 192.168.28.67
local 192.168.28.67 dev eth0 proto kernel scope host src 192.168.28.67
broadcast 192.168.28.255 dev eth0 proto kernel scope link src 192.168.28.67
可以看到,本机地址192.168.28.67和127.0.0.1对应的路由条目都存在于local表中,对应的类型为RTN_LOCAL,但相应的外出接口则不同,分别为eth0和lo。
在__ip_route_output_key函数(Kernel 3.13)中有如下的代码:
/*
* Major route resolver routine.
*/
struct rtable *__ip_route_output_key(struct net *net, struct flowi4 *fl4)
{
...
if (res.type == RTN_LOCAL) {
if (!fl4->saddr) {
if (res.fi->fib_prefsrc)
fl4->saddr = res.fi->fib_prefsrc;
else
fl4->saddr = fl4->daddr;
}
dev_out = net->loopback_dev;
fl4->flowi4_oif = dev_out->ifindex;
flags |= RTCF_LOCAL;
goto make_route;
}
...
}
可见,当路由类型为RTN_LOCAL时,dev_out会被设置为loopback_dev,从而使用loopback设备的传输函数:
static netdev_tx_t loopback_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct pcpu_lstats *lb_stats;
int len;
skb_orphan(skb);
/* Before queueing this packet to netif_rx(),
* make sure dst is refcounted.
*/
skb_dst_force(skb);
skb->protocol = eth_type_trans(skb, dev);
/* it's OK to use per_cpu_ptr() because BHs are off */
lb_stats = this_cpu_ptr(dev->lstats);
len = skb->len;
if (likely(netif_rx(skb) == NET_RX_SUCCESS)) {
u64_stats_update_begin(&lb_stats->syncp);
lb_stats->bytes += len;
lb_stats->packets++;
u64_stats_update_end(&lb_stats->syncp);
}
return NETDEV_TX_OK;
}
loopback环回设备的输出函数最终是调用了netif_rx(),又将数据包送入协议栈的接收处理流程中,从而实现了“环回“的功能。
至此,已经从源代码的级别分析了ping本机地址和127.0.0.1的实现,实际数据都是通过loopback设备流转的。
延伸分析: 有网友分析ping本机地址与127.0.0.1的不同之处时指出当网线断开时127.0.0.1仍然是通的,但ping本机地址失败:
$ ping 192.168.28.67
connect: Network is unreachable
strace看一下是由于connect函数返回网络不可达导致的:
$ strace ping 192.168.28.67
...
...
socket(PF_INET, SOCK_RAW, IPPROTO_ICMP) = -1 EPERM (Operation not permitted)
getuid() = 1001
setuid(1001) = 0
socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(1025), sin_addr=inet_addr("192.168.28.67")}, 16) = -1 ENETUNREACH (Network is unreachable)
dup(2) = 4
fcntl(4, F_GETFL) = 0x8002 (flags O_RDWR|O_LARGEFILE)
brk(0) = 0x1f4e000
brk(0x1f6f000) = 0x1f6f000
fstat(4, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 6), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fcd94dda000
lseek(4, 0, SEEK_CUR) = -1 ESPIPE (Illegal seek)
write(4, "connect: Network is unreachable\n", 32connect: Network is unreachable
) = 32
close(4) = 0
munmap(0x7fcd94dda000, 4096) = 0
exit_group(2) = ?
其主要原因还是在于路由表查询:
$ ip route show table local
broadcast 127.0.0.0 dev lo proto kernel scope link src 127.0.0.1
local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1
local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1
broadcast 127.255.255.255 dev lo proto kernel scope link src 127.0.0.1
当断开网线时,接口上的ip地址和相关的路由条目都被移除,而127.0.0.1则不受影响(只要协议栈本身正常)。
一切都很明了了。
PS. 细心的你可能会发现,在strace的结果中其实还有一个EPERM的错误,但在网络正常的情况下ping程序是可以正常运行的,那么strace中显示的这个错误是什么原因产生的呢,下一篇文章中将会专门介绍。