Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
isedev opened this issue Jun 27, 2020 · 6 comments
Open

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

isedev opened this issue Jun 27, 2020 · 6 comments
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@isedev
Copy link

isedev commented Jun 27, 2020

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.

@gopherbot gopherbot added this to the Unreleased milestone Jun 27, 2020
@dmitshur
Copy link
Contributor

dmitshur commented Jul 1, 2020

/cc @mikioh per owners.

@dmitshur dmitshur added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Jul 1, 2020
@davecheney
Copy link
Contributor

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
Copy link
Author

isedev commented Jul 6, 2020

Apologies for the late replay.

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

  • One is using SOCK_RAW which does requires root privileges as you say.
  • The other is using SOCK_DGRAM / IPPROTO_ICMP (referred to as "udp4" in the code) which does not require root privileges. This has been supported in the kernel since 2.6.39. For IPv4, it is supported by the net.ipv4.ping_group_range kernel variable: "Specify the group range allowed to create non-raw icmp sockets".

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
Copy link
Author

isedev commented Jul 6, 2020

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
Copy link
Contributor

Ping @mikioh

@raylee
Copy link

raylee commented Feb 20, 2022

@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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

No branches or pull requests

5 participants