-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
x/net/internal/icmp: add support for non-privileged ICMP endpoint, kn…
…own as ping socket This CL adds PacketConn struct that implements net.PacketConn interface. Update golang/go#9166 LGTM=iant R=iant CC=golang-codereviews https://golang.org/cl/182110043
- Loading branch information
Showing
6 changed files
with
510 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
// Copyright 2014 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package icmp | ||
|
||
import ( | ||
"net" | ||
"syscall" | ||
"time" | ||
|
||
"golang.org/x/net/ipv4" | ||
"golang.org/x/net/ipv6" | ||
) | ||
|
||
var _ net.PacketConn = &PacketConn{} | ||
|
||
type ipc interface{} | ||
|
||
// A PacketConn represents a packet network endpoint that uses either | ||
// ICMPv4 or ICMPv6. | ||
type PacketConn struct { | ||
c net.PacketConn | ||
ipc // either ipv4.PacketConn or ipv6.PacketConn | ||
} | ||
|
||
func (c *PacketConn) ok() bool { return c != nil && c.c != nil } | ||
|
||
// IPv4PacketConn returns the ipv4.PacketConn of c. | ||
// It returns nil when c is not created as the endpoint for ICMPv4. | ||
func (c *PacketConn) IPv4PacketConn() *ipv4.PacketConn { | ||
if !c.ok() { | ||
return nil | ||
} | ||
p, _ := c.ipc.(*ipv4.PacketConn) | ||
return p | ||
} | ||
|
||
// IPv6PacketConn returns the ipv6.PacketConn of c. | ||
// It returns nil when c is not created as the endpoint for ICMPv6. | ||
func (c *PacketConn) IPv6PacketConn() *ipv6.PacketConn { | ||
if !c.ok() { | ||
return nil | ||
} | ||
p, _ := c.ipc.(*ipv6.PacketConn) | ||
return p | ||
} | ||
|
||
// ReadFrom reads an ICMP message from the connection. | ||
func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) { | ||
if !c.ok() { | ||
return 0, nil, syscall.EINVAL | ||
} | ||
return c.c.ReadFrom(b) | ||
} | ||
|
||
// WriteTo writes the ICMP message b to dst. | ||
// Dst must be net.UDPAddr when c is a non-privileged | ||
// datagram-oriented ICMP endpoint. Otherwise it must be net.IPAddr. | ||
func (c *PacketConn) WriteTo(b []byte, dst net.Addr) (int, error) { | ||
if !c.ok() { | ||
return 0, syscall.EINVAL | ||
} | ||
return c.c.WriteTo(b, dst) | ||
} | ||
|
||
// Close closes the endpoint. | ||
func (c *PacketConn) Close() error { | ||
if !c.ok() { | ||
return syscall.EINVAL | ||
} | ||
return c.c.Close() | ||
} | ||
|
||
// LocalAddr returns the local network address. | ||
func (c *PacketConn) LocalAddr() net.Addr { | ||
if !c.ok() { | ||
return nil | ||
} | ||
return c.c.LocalAddr() | ||
} | ||
|
||
// SetDeadline sets the read and write deadlines associated with the | ||
// endpoint. | ||
func (c *PacketConn) SetDeadline(t time.Time) error { | ||
if !c.ok() { | ||
return syscall.EINVAL | ||
} | ||
return c.c.SetDeadline(t) | ||
} | ||
|
||
// SetReadDeadline sets the read deadline associated with the | ||
// endpoint. | ||
func (c *PacketConn) SetReadDeadline(t time.Time) error { | ||
if !c.ok() { | ||
return syscall.EINVAL | ||
} | ||
return c.c.SetReadDeadline(t) | ||
} | ||
|
||
// SetWriteDeadline sets the write deadline associated with the | ||
// endpoint. | ||
func (c *PacketConn) SetWriteDeadline(t time.Time) error { | ||
if !c.ok() { | ||
return syscall.EINVAL | ||
} | ||
return c.c.SetWriteDeadline(t) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// Copyright 2014 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package icmp_test | ||
|
||
import ( | ||
"log" | ||
"net" | ||
"os" | ||
|
||
"golang.org/x/net/internal/iana" | ||
"golang.org/x/net/internal/icmp" | ||
"golang.org/x/net/ipv6" | ||
) | ||
|
||
func ExamplePacketConn_nonPrivilegedPing() { | ||
c, err := icmp.ListenPacket("udp6", "fe80::1%en0") | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
defer c.Close() | ||
|
||
wm := icmp.Message{ | ||
Type: ipv6.ICMPTypeEchoRequest, Code: 0, | ||
Body: &icmp.Echo{ | ||
ID: os.Getpid() & 0xffff, Seq: 1, | ||
Data: []byte("HELLO-R-U-THERE"), | ||
}, | ||
} | ||
wb, err := wm.Marshal(nil) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
if _, err := c.WriteTo(wb, &net.UDPAddr{IP: net.ParseIP("ff02::1"), Zone: "en0"}); err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
rb := make([]byte, 1500) | ||
n, peer, err := c.ReadFrom(rb) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
rm, err := icmp.ParseMessage(iana.ProtocolIPv6ICMP, rb[:n]) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
switch rm.Type { | ||
case ipv6.ICMPTypeEchoReply: | ||
log.Printf("got reflection from %v", peer) | ||
default: | ||
log.Printf("got %+v; want echo reply", rm) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// Copyright 2014 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
// +build darwin dragonfly freebsd linux netbsd openbsd solaris | ||
|
||
package icmp | ||
|
||
import ( | ||
"net" | ||
"syscall" | ||
) | ||
|
||
func sockaddr(family int, address string) (syscall.Sockaddr, error) { | ||
switch family { | ||
case syscall.AF_INET: | ||
a, err := net.ResolveIPAddr("ip4", address) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if len(a.IP) == 0 { | ||
a.IP = net.IPv4zero | ||
} | ||
if a.IP = a.IP.To4(); a.IP == nil { | ||
return nil, net.InvalidAddrError("non-ipv4 address") | ||
} | ||
sa := &syscall.SockaddrInet4{} | ||
copy(sa.Addr[:], a.IP) | ||
return sa, nil | ||
case syscall.AF_INET6: | ||
a, err := net.ResolveIPAddr("ip6", address) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if len(a.IP) == 0 { | ||
a.IP = net.IPv6unspecified | ||
} | ||
if a.IP.Equal(net.IPv4zero) { | ||
a.IP = net.IPv6unspecified | ||
} | ||
if a.IP = a.IP.To16(); a.IP == nil || a.IP.To4() != nil { | ||
return nil, net.InvalidAddrError("non-ipv6 address") | ||
} | ||
sa := &syscall.SockaddrInet6{ZoneId: zoneToUint32(a.Zone)} | ||
copy(sa.Addr[:], a.IP) | ||
return sa, nil | ||
default: | ||
return nil, net.InvalidAddrError("unexpected family") | ||
} | ||
} | ||
|
||
func zoneToUint32(zone string) uint32 { | ||
if zone == "" { | ||
return 0 | ||
} | ||
if ifi, err := net.InterfaceByName(zone); err == nil { | ||
return uint32(ifi.Index) | ||
} | ||
n, _, _ := dtoi(zone, 0) | ||
return uint32(n) | ||
} | ||
|
||
func last(s string, b byte) int { | ||
i := len(s) | ||
for i--; i >= 0; i-- { | ||
if s[i] == b { | ||
break | ||
} | ||
} | ||
return i | ||
} | ||
|
||
const big = 0xFFFFFF | ||
|
||
func dtoi(s string, i0 int) (n int, i int, ok bool) { | ||
n = 0 | ||
for i = i0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { | ||
n = n*10 + int(s[i]-'0') | ||
if n >= big { | ||
return 0, i, false | ||
} | ||
} | ||
if i == i0 { | ||
return 0, i, false | ||
} | ||
return n, i, true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// Copyright 2014 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
// +build nacl plan9 windows | ||
|
||
package icmp | ||
|
||
// ListenPacket listens for incoming ICMP packets addressed to | ||
// address. See net.Dial for the syntax of address. | ||
// | ||
// For non-privileged datagram-oriented ICMP endpoints, network must | ||
// be "udp4" or "udp6". The endpoint allows to read, write a few | ||
// limited ICMP messages such as echo request and echo reply. | ||
// Currently only Dariwn and Linux support this. | ||
// | ||
// Examples: | ||
// ListenPacket("udp4", "192.168.0.1") | ||
// ListenPacket("udp4", "0.0.0.0") | ||
// ListenPacket("udp6", "fe80::1%en0") | ||
// ListenPacket("udp6", "::") | ||
// | ||
// For privileged raw ICMP endpoints, network must be "ip4" or "ip6" | ||
// followed by a colon and an ICMP protocol number or name. | ||
// | ||
// Examples: | ||
// ListenPacket("ip4:icmp", "192.168.0.1") | ||
// ListenPacket("ip4:1", "0.0.0.0") | ||
// ListenPacket("ip6:ipv6-icmp", "fe80::1%en0") | ||
// ListenPacket("ip6:58", "::") | ||
func ListenPacket(network, address string) (*PacketConn, error) { | ||
return nil, errOpNoSupport | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// Copyright 2014 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
// +build darwin dragonfly freebsd linux netbsd openbsd solaris | ||
|
||
package icmp | ||
|
||
import ( | ||
"net" | ||
"os" | ||
"runtime" | ||
"syscall" | ||
|
||
"golang.org/x/net/internal/iana" | ||
"golang.org/x/net/ipv4" | ||
"golang.org/x/net/ipv6" | ||
) | ||
|
||
const sysIP_STRIPHDR = 0x17 // for now only darwin supports this option | ||
|
||
// ListenPacket listens for incoming ICMP packets addressed to | ||
// address. See net.Dial for the syntax of address. | ||
// | ||
// For non-privileged datagram-oriented ICMP endpoints, network must | ||
// be "udp4" or "udp6". The endpoint allows to read, write a few | ||
// limited ICMP messages such as echo request and echo reply. | ||
// Currently only Dariwn and Linux support this. | ||
// | ||
// Examples: | ||
// ListenPacket("udp4", "192.168.0.1") | ||
// ListenPacket("udp4", "0.0.0.0") | ||
// ListenPacket("udp6", "fe80::1%en0") | ||
// ListenPacket("udp6", "::") | ||
// | ||
// For privileged raw ICMP endpoints, network must be "ip4" or "ip6" | ||
// followed by a colon and an ICMP protocol number or name. | ||
// | ||
// Examples: | ||
// ListenPacket("ip4:icmp", "192.168.0.1") | ||
// ListenPacket("ip4:1", "0.0.0.0") | ||
// ListenPacket("ip6:ipv6-icmp", "fe80::1%en0") | ||
// ListenPacket("ip6:58", "::") | ||
func ListenPacket(network, address string) (*PacketConn, error) { | ||
var family, proto int | ||
switch network { | ||
case "udp4": | ||
family, proto = syscall.AF_INET, iana.ProtocolICMP | ||
case "udp6": | ||
family, proto = syscall.AF_INET6, iana.ProtocolIPv6ICMP | ||
default: | ||
i := last(network, ':') | ||
switch network[:i] { | ||
case "ip4": | ||
proto = iana.ProtocolICMP | ||
case "ip6": | ||
proto = iana.ProtocolIPv6ICMP | ||
} | ||
} | ||
var err error | ||
var c net.PacketConn | ||
switch family { | ||
case syscall.AF_INET, syscall.AF_INET6: | ||
s, err := syscall.Socket(family, syscall.SOCK_DGRAM, proto) | ||
if err != nil { | ||
return nil, os.NewSyscallError("socket", err) | ||
} | ||
defer syscall.Close(s) | ||
if runtime.GOOS == "darwin" && family == syscall.AF_INET { | ||
if err := syscall.SetsockoptInt(s, iana.ProtocolIP, sysIP_STRIPHDR, 1); err != nil { | ||
return nil, os.NewSyscallError("setsockopt", err) | ||
} | ||
} | ||
sa, err := sockaddr(family, address) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if err := syscall.Bind(s, sa); err != nil { | ||
return nil, os.NewSyscallError("bind", err) | ||
} | ||
f := os.NewFile(uintptr(s), "datagram-oriented icmp") | ||
defer f.Close() | ||
c, err = net.FilePacketConn(f) | ||
default: | ||
c, err = net.ListenPacket(network, address) | ||
} | ||
if err != nil { | ||
return nil, err | ||
} | ||
switch proto { | ||
case iana.ProtocolICMP: | ||
return &PacketConn{c: c, ipc: ipv4.NewPacketConn(c)}, nil | ||
case iana.ProtocolIPv6ICMP: | ||
return &PacketConn{c: c, ipc: ipv6.NewPacketConn(c)}, nil | ||
default: | ||
return &PacketConn{c: c}, nil | ||
} | ||
} |
Oops, something went wrong.