I have a Linux system:
$ uname -a
Linux mybox 4.14.77-v7+ #1 SMP Mon Jan 7 10:09:57 GMT 2019 armv7l Linux
The userspace is Alpine Linux 3.8 and libc is musl-libc.
The system has two ethernet interfaces, eth0 and eth2. The default routes are configured like this:
$ ip route
default via 100.119.124.45 dev eth2 metric 103
default via 10.40.0.1 dev eth0 metric 201
If I change the priorities so that the eth0 route is the priority route, I can route traffic over it. I can also ping 8.8.8.8
with the routing table as shown.
But when I try to ping 8.8.8.8
specifically over eth0
, tcpdump -nni eth0 icmp
shows that I get a response packet, but ping
reports that no packets are received. Here is the ping
output:
$ 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
And here is the corresponding tcpdump
output:
$ 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
is the IP address of eth0
.
What is going on here? How do packets arrive in the kernel, but get lost between there and userspace?
I've tested this with my own implementation of 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")
}
}
This gives the same results; tcpdump
sees the packets, but they never arrive back at the userspace client.
Update I have set iptables default policy to ACCEPT and deleted everything out of iptables (iptables-save | grep '\(^*\)\|\(COMMIT\)' | iptables-restore
) and disabled rp_filter (echo 0 > /proc/sys/net/ipv4/conf/default/rp_filter
) and get the same result.