Пакеты эхо-ответа ICMP исчезают между ядром и пользовательским пространством - PullRequest
0 голосов
/ 18 января 2019

У меня есть система 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.

Система имеет два интерфейса Ethernet, 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 был приоритетным маршрутом, я могу направить трафик по нему. Я также могу пропинговать 8.8.8.8 с таблицей маршрутизации, как показано.

Но когда я пытаюсь пинговать 8.8.8.8, в частности, по 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 - это IP-адрес eth0.

Что здесь происходит? Как пакеты попадают в ядро, но теряются между ними и пользовательским пространством?

Я протестировал это с моей собственной реализацией 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 по умолчанию значение ПРИНЯТЬ, удалил все из iptables (iptables-save | grep '\(^*\)\|\(COMMIT\)' | iptables-restore) и отключил rp_filter (echo 0 > /proc/sys/net/ipv4/conf/default/rp_filter) и получил тот же результат.

...