1// Copyright 2014 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows
6// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris windows
7
8package icmp
9
10import (
11	"net"
12	"os"
13	"runtime"
14	"syscall"
15
16	"golang.org/x/net/internal/iana"
17	"golang.org/x/net/ipv4"
18	"golang.org/x/net/ipv6"
19)
20
21const sysIP_STRIPHDR = 0x17 // for now only darwin supports this option
22
23// ListenPacket listens for incoming ICMP packets addressed to
24// address. See net.Dial for the syntax of address.
25//
26// For non-privileged datagram-oriented ICMP endpoints, network must
27// be "udp4" or "udp6". The endpoint allows to read, write a few
28// limited ICMP messages such as echo request and echo reply.
29// Currently only Darwin and Linux support this.
30//
31// Examples:
32//	ListenPacket("udp4", "192.168.0.1")
33//	ListenPacket("udp4", "0.0.0.0")
34//	ListenPacket("udp6", "fe80::1%en0")
35//	ListenPacket("udp6", "::")
36//
37// For privileged raw ICMP endpoints, network must be "ip4" or "ip6"
38// followed by a colon and an ICMP protocol number or name.
39//
40// Examples:
41//	ListenPacket("ip4:icmp", "192.168.0.1")
42//	ListenPacket("ip4:1", "0.0.0.0")
43//	ListenPacket("ip6:ipv6-icmp", "fe80::1%en0")
44//	ListenPacket("ip6:58", "::")
45func ListenPacket(network, address string) (*PacketConn, error) {
46	var family, proto int
47	switch network {
48	case "udp4":
49		family, proto = syscall.AF_INET, iana.ProtocolICMP
50	case "udp6":
51		family, proto = syscall.AF_INET6, iana.ProtocolIPv6ICMP
52	default:
53		i := last(network, ':')
54		if i < 0 {
55			i = len(network)
56		}
57		switch network[:i] {
58		case "ip4":
59			proto = iana.ProtocolICMP
60		case "ip6":
61			proto = iana.ProtocolIPv6ICMP
62		}
63	}
64	var cerr error
65	var c net.PacketConn
66	switch family {
67	case syscall.AF_INET, syscall.AF_INET6:
68		s, err := syscall.Socket(family, syscall.SOCK_DGRAM, proto)
69		if err != nil {
70			return nil, os.NewSyscallError("socket", err)
71		}
72		if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && family == syscall.AF_INET {
73			if err := syscall.SetsockoptInt(s, iana.ProtocolIP, sysIP_STRIPHDR, 1); err != nil {
74				syscall.Close(s)
75				return nil, os.NewSyscallError("setsockopt", err)
76			}
77		}
78		sa, err := sockaddr(family, address)
79		if err != nil {
80			syscall.Close(s)
81			return nil, err
82		}
83		if err := syscall.Bind(s, sa); err != nil {
84			syscall.Close(s)
85			return nil, os.NewSyscallError("bind", err)
86		}
87		f := os.NewFile(uintptr(s), "datagram-oriented icmp")
88		c, cerr = net.FilePacketConn(f)
89		f.Close()
90	default:
91		c, cerr = net.ListenPacket(network, address)
92	}
93	if cerr != nil {
94		return nil, cerr
95	}
96	switch proto {
97	case iana.ProtocolICMP:
98		return &PacketConn{c: c, p4: ipv4.NewPacketConn(c)}, nil
99	case iana.ProtocolIPv6ICMP:
100		return &PacketConn{c: c, p6: ipv6.NewPacketConn(c)}, nil
101	default:
102		return &PacketConn{c: c}, nil
103	}
104}
105