2

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.

Tom
  • 7,269
  • 1
  • 42
  • 69

1 Answers1

0

I was troubled by similar problem for the past few days. I didn't find many helpful information on the internet besides the common ones involves in rp_filter and iptables, which I'm sure not the real cause.

Fortunately, I finally get the problem solved. The root cause of my case was that the destination address (10.42.5.0) of the reply packet happens to be marked as a broadcast route in the local table.

$ ip route show table local
...
broadcast 10.42.5.0 dev cni0 proto kernel scope link src 10.42.5.1
...

Deleting the rule fixes the problem. If you experience the same issue, try look up ip rule and ip routes for all related routing tables. Perhaps there is a route that matches your reply packet so it's not delivered as expected.

shouya
  • 2,863
  • 1
  • 24
  • 45