記得初學網絡之時就曾困惑於ping 本機地址與 ping 127.0.0.1之間的區別與聯繫,也試圖在網上搜尋答案,但終究仍是霧裏看花,似是而非。
網絡
「源碼以前,了無祕密」,此次就深刻Linux內核協議棧,來看看這二者之間究竟是什麼樣的關係socket
憑直覺首先會想到這必定和路由表的查找有關,更進一步來講是和路由類型及外出接口有關。在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。
oop
在__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設備的傳輸函數:ui
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(),又將數據包送入協議棧的接收處理流程中,從而實現了「環回「的功能。this
至此,已經從源代碼的級別分析了ping本機地址和127.0.0.1的實現,實際數據都是經過loopback設備流轉的。code
延伸分析: 有網友分析ping本機地址與127.0.0.1的不一樣之處時指出當網線斷開時127.0.0.1仍然是通的,但ping本機地址失敗:接口
$ ping 192.168.28.67 connect: Network is unreachable
strace看一下是因爲connect函數返回網絡不可達致使的:ip
$ 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中顯示的這個錯誤是什麼緣由產生的呢,下一篇文章中將會專門介紹。