golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
122.19k stars 17.46k forks source link

x/net/icmp: bind fails SOCK_DGRAM IPPROTO_ICMP #39891

Open isedev opened 4 years ago

isedev commented 4 years ago

What version of Go are you using (go version)?

$ go version
go version go1.14.2 linux/amd64

Does this issue reproduce with the latest release?

Yes (tested against go1.14.4)

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build062765693=/tmp/go-build -gno-record-gcc-switches"

What did you do?

The following simple test program fails with "bind: permission denied" on Linux 5.0.16-100.fc28.x86_64.

package main

import (
    "fmt"
    "os"
    "time"

    "github.com/sparrc/go-ping"
)

func main() {
    pinger, err := ping.NewPinger("www.google.com")
    if err != nil {
        fmt.Println(err.Error())
        os.Exit(1)
    }

    pinger.Count = 5
    pinger.Timeout = 1500 * time.Millisecond
    pinger.Interval = 200 * time.Millisecond
    pinger.Source = "192.168.0.36"
    pinger.SetPrivileged(false)
    pinger.Run()

    s := pinger.Statistics()
    fmt.Printf("Packets send    : %d\n", s.PacketsSent)
    fmt.Printf("Packets received: %d\n", s.PacketsRecv)
    fmt.Printf("Min roundtrip   : %.2fms\n", float64(s.MinRtt.Microseconds())/1000.0)
    fmt.Printf("Max roundtrip   : %.2fms\n", float64(s.MaxRtt.Microseconds())/1000.0)
}

The ping library used above depends on golang.org/x/net/icmp.

Not sure if this is relevant or not, but the system where the above was tested uses a bridge interface with an IP address configured and the physical network interface is attached to the bridge:

2: enp32s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master phy0 state UP group default qlen 1000
    link/ether 00:26:b9:a3:5a:b9 brd ff:ff:ff:ff:ff:ff
3: phy0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 00:26:b9:a3:5a:b9 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.36/24 brd 192.168.0.255 scope global dynamic noprefixroute phy0
       valid_lft 77073sec preferred_lft 77073sec

What did you expect to see?

$ go build .
$ ./ping
Packets send    : 5                                                                                                              
Packets received: 5                                                                                                              
Min roundtrip   : 9.89ms                                                                                                         
Max roundtrip   : 10.72ms                                                                                                        

What did you see instead?

$ go build .
$ ./ping
Error listening for ICMP packets: bind: permission denied                           
Packets send    : 0                                                      
Packets received: 0                                           
Min roundtrip   : 0.00ms                                       
Max roundtrip   : 0.00ms                                                                                                         

Making the following change to x/net/icmp/listen_posix.go results in expected behaviour:

--- listen_posix.go.orig    2020-06-27 17:27:59.013649798 +0100
+++ listen_posix.go 2020-06-27 17:28:38.219901503 +0100
@@ -74,15 +74,17 @@
                return nil, os.NewSyscallError("setsockopt", err)
            }
        }
-       sa, err := sockaddr(family, address)
-       if err != nil {
-           syscall.Close(s)
-           return nil, err
-       }
-       if err := syscall.Bind(s, sa); err != nil {
-           syscall.Close(s)
-           return nil, os.NewSyscallError("bind", err)
-       }
+       /*
+           sa, err := sockaddr(family, address)
+           if err != nil {
+               syscall.Close(s)
+               return nil, err
+           }
+           if err := syscall.Bind(s, sa); err != nil {
+               syscall.Close(s)
+               return nil, os.NewSyscallError("bind", err)
+           }
+       */
        f := os.NewFile(uintptr(s), "datagram-oriented icmp")
        c, cerr = net.FilePacketConn(f)
        f.Close()

Using "0.0.0.0" or "" as source address makes no difference.

dmitshur commented 4 years ago

/cc @mikioh per owners.

davecheney commented 4 years ago

I’m pretty sure you need to run your process as root to bind to ICMP.

ping is usually setuid for this reason, https://askubuntu.com/questions/789938/why-do-mount-ping-and-su-have-a-sticky-bit-set

Have you tried building your program and running it as root (note, don’t use go run, that won’t give the correct results)

isedev commented 4 years ago

Apologies for the late replay.

As far as I am aware, Linux supports two ways of performing ICMP echo/reply:

And it does works. When commenting out the socket bind code above, I am able to perform pings as non-root. The issue I think is that the UDP ICMP socket does not support binding.

isedev commented 4 years ago

I see from the x/net/listen_posix.go code that it does try to support the non-root feature since it explicitly distinguishes between "ip4" and "udp4" networks (the latter being in AF_INET family).

davecheney commented 4 years ago

Ping @mikioh

raylee commented 2 years ago

@isedev At least of Go 1.17.5 and the current version of go-ping, this works correctly as an unprivileged user. I tested the example code under Darwin and Linux successfully.

Give it another try?