我有一个Linux系统:
$ uname -a
Linux mybox 4.14.77-v7+ #1 SMP Mon Jan 7 10:09:57 GMT 2019 armv7l Linux
用户空间是Alpine Linux 3.8,而libc是musl-libc。
系统具有两个以太网接口eth0和eth2。默认路由的配置如下:
$ ip route
default via 100.119.124.45 dev eth2 metric 103
default via 10.40.0.1 dev eth0 metric 201
如果更改优先级,以使eth0路由成为优先级路由,则可以在其上路由流量。如图所示,我也可以使用路由表ping 8.8.8.8
。
但是,当我尝试通过8.8.8.8
ping eth0
时,tcpdump -nni eth0 icmp
显示我收到了响应数据包,但是ping
报告没有收到数据包。这是ping
的输出:
$ sudo ping -I eth0 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
^C
--- 8.8.8.8 ping statistics ---
7 packets transmitted, 0 packets received, 100% packet loss
这是相应的tcpdump
输出:
$ tcpdump -nni eth0 icmp
10:02:42.312257 IP 10.40.16.110 > google-public-dns-a.google.com: ICMP echo request, id 3146, seq 0, length 64
10:02:42.318189 IP google-public-dns-a.google.com > 10.40.16.110: ICMP echo reply, id 3146, seq 0, length 64
10:02:43.312525 IP 10.40.16.110 > google-public-dns-a.google.com: ICMP echo request, id 3146, seq 1, length 64
10:02:43.318321 IP google-public-dns-a.google.com > 10.40.16.110: ICMP echo reply, id 3146, seq 1, length 64
10:02:44.312792 IP 10.40.16.110 > google-public-dns-a.google.com: ICMP echo request, id 3146, seq 2, length 64
10:02:44.318632 IP google-public-dns-a.google.com > 10.40.16.110: ICMP echo reply, id 3146, seq 2, length 64
10:02:45.313066 IP 10.40.16.110 > google-public-dns-a.google.com: ICMP echo request, id 3146, seq 3, length 64
10:02:45.318802 IP google-public-dns-a.google.com > 10.40.16.110: ICMP echo reply, id 3146, seq 3, length 64
10:02:46.313327 IP 10.40.16.110 > google-public-dns-a.google.com: ICMP echo request, id 3146, seq 4, length 64
10:02:46.319310 IP google-public-dns-a.google.com > 10.40.16.110: ICMP echo reply, id 3146, seq 4, length 64
10:02:47.313595 IP 10.40.16.110 > google-public-dns-a.google.com: ICMP echo request, id 3146, seq 5, length 64
10:02:47.319366 IP google-public-dns-a.google.com > 10.40.16.110: ICMP echo reply, id 3146, seq 5, length 64
10:02:48.313822 IP 10.40.16.110 > google-public-dns-a.google.com: ICMP echo request, id 3146, seq 6, length 64
10:02:48.319652 IP google-public-dns-a.google.com > 10.40.16.110: ICMP echo reply, id 3146, seq 6, length 64
10.40.16.110
是eth0
的IP地址。
这是怎么回事?数据包如何到达内核,却在内核和用户空间之间丢失?
我已经用自己的ping实现对其进行了测试:
package main
import (
"encoding/binary"
"errors"
"time"
"os"
"syscall"
"net"
"fmt"
"flag"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"github.com/vishvananda/netlink"
)
func sockaddr(address string) (syscall.Sockaddr, error) {
a, err := net.ResolveIPAddr("ip4", address)
if err != nil { return nil, err }
a.IP = a.IP.To4()
sa := &syscall.SockaddrInet4{}
copy(sa.Addr[:], a.IP)
return sa, nil
}
func ListenPacket(address, iface string) (net.PacketConn, error) {
s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_ICMP)
fmt.Println("trace")
if err != nil { fmt.Println("Failed to open socket."); return nil, err }
fmt.Println("trace")
err = syscall.SetsockoptString(s, syscall.SOL_SOCKET, syscall.SO_BINDTODEVICE, iface)
if err != nil { return nil, err }
sa, err := sockaddr(address)
if err != nil { return nil, err }
err = syscall.Bind(s, sa)
if err != nil { return nil, err }
f := os.NewFile(uintptr(s), "datagram-oriented icmp")
defer f.Close()
c, _ := net.FilePacketConn(f)
return c, nil
}
func valid_ip_address(ping_iface string) (net.IP, error) {
link, err := netlink.LinkByName(ping_iface)
if err != nil {
fmt.Println(err)
return net.ParseIP("0.0.0.0"), err
}
addresses, err := netlink.AddrList(link, syscall.AF_INET)
for _, address := range addresses {
if address.Scope == 253 {
continue
}
if ms0, ms1 := address.Mask.Size(); ms0 == ms1 {
continue
}
return address.IP, nil
}
return net.ParseIP("0.0.0.0"), errors.New("No IP4 address found on interface")
}
func main() {
ping_iface := ""
flag.StringVar(&ping_iface, "p", "wlan0", "The interface over which to send ping packets")
flag.Parse()
local_address, err := valid_ip_address(ping_iface)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("Pinging through interface %s address %s\n", ping_iface, local_address.String())
c, err := ListenPacket(local_address.String(), ping_iface)
if err != nil {
fmt.Println(err)
return
}
defer c.Close()
data := make([]byte, 16)
binary.BigEndian.PutUint64(data[0:8], uint64(time.Now().Unix()))
for ii := 8; ii < len(data); ii += 1 {
data[ii] = byte('V')
}
m := icmp.Message {
Type: ipv4.ICMPTypeEcho,
Code: 0,
Body: &icmp.Echo {
ID: os.Getpid() & 0xffff,
Seq: 1,
Data: data,
},
}
message, _ := m.Marshal(nil)
remote := net.IPAddr {
IP: net.ParseIP("8.8.8.8"),
}
fmt.Println(remote)
n, err := c.WriteTo(message, &remote)
if err != nil {
fmt.Println(err)
}
rmessage := make([]byte, 2000)
timeout := time.Now().Add(10 * time.Second)
c.SetReadDeadline(timeout)
n, _, err = c.ReadFrom(rmessage)
if err != nil {
fmt.Println(err)
return
}
msg, err:= icmp.ParseMessage(1, rmessage[:n])
if err != nil {
fmt.Println(err)
return
}
switch msg.Type {
case ipv4.ICMPTypeEchoReply:
fmt.Println("Received response to ping")
}
}
这给出了相同的结果; tcpdump
看到了数据包,但它们再也没有回到用户空间客户端。
更新,我已将iptables默认策略设置为ACCEPT,并从iptables(iptables-save | grep '\(^*\)\|\(COMMIT\)' | iptables-restore
)中删除了所有内容,并禁用了rp_filter(echo 0 > /proc/sys/net/ipv4/conf/default/rp_filter
),并得到了相同的结果。>