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 · 5 comments
Open

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

isedev opened this issue Jun 27, 2020 · 5 comments

Comments

@isedev
Copy link

@isedev 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 dmitshur commented Jul 1, 2020

/cc @mikioh per owners.

Loading

@davecheney
Copy link
Contributor

@davecheney davecheney commented Jul 1, 2020

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)

Loading

@isedev
Copy link
Author

@isedev 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.

Loading

@isedev
Copy link
Author

@isedev 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).

Loading

@davecheney
Copy link
Contributor

@davecheney davecheney commented Jul 6, 2020

Ping @mikioh

Loading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
4 participants