Skip to content

Commit

Permalink
x/net/internal/icmp: add support for non-privileged ICMP endpoint, kn…
Browse files Browse the repository at this point in the history
…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
cixtor committed Dec 3, 2014
1 parent 2a8857c commit fc168c3
Show file tree
Hide file tree
Showing 6 changed files with 510 additions and 0 deletions.
108 changes: 108 additions & 0 deletions internal/icmp/endpoint.go
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)
}
54 changes: 54 additions & 0 deletions internal/icmp/example_test.go
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)
}
}
87 changes: 87 additions & 0 deletions internal/icmp/helper_unix.go
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
}
33 changes: 33 additions & 0 deletions internal/icmp/listen_stub.go
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
}
98 changes: 98 additions & 0 deletions internal/icmp/listen_unix.go
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
}
}
Loading

0 comments on commit fc168c3

Please sign in to comment.