1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
|
package ndp
import (
"errors"
"fmt"
"net"
"net/netip"
"runtime"
"time"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv6"
)
// HopLimit is the expected IPv6 hop limit for all NDP messages.
const HopLimit = 255
// A Conn is a Neighbor Discovery Protocol connection.
type Conn struct {
pc *ipv6.PacketConn
cm *ipv6.ControlMessage
ifi *net.Interface
addr netip.Addr
// icmpTest disables the self-filtering mechanism in ReadFrom.
icmpTest bool
}
// Listen creates a NDP connection using the specified interface and address
// type.
//
// As a special case, literal IPv6 addresses may be specified to bind to a
// specific address for an interface. If the IPv6 address does not exist on the
// interface, an error will be returned.
//
// Listen returns a Conn and the chosen IPv6 address of the interface.
func Listen(ifi *net.Interface, addr Addr) (*Conn, netip.Addr, error) {
addrs, err := ifi.Addrs()
if err != nil {
return nil, netip.Addr{}, err
}
ip, err := chooseAddr(addrs, ifi.Name, addr)
if err != nil {
return nil, netip.Addr{}, err
}
ic, err := icmp.ListenPacket("ip6:ipv6-icmp", ip.String())
if err != nil {
return nil, netip.Addr{}, err
}
pc := ic.IPv6PacketConn()
// Hop limit is always 255, per RFC 4861.
if err := pc.SetHopLimit(HopLimit); err != nil {
return nil, netip.Addr{}, err
}
if err := pc.SetMulticastHopLimit(HopLimit); err != nil {
return nil, netip.Addr{}, err
}
if runtime.GOOS != "windows" {
// Calculate and place ICMPv6 checksum at correct offset in all
// messages (not implemented by golang.org/x/net/ipv6 on Windows).
const chkOff = 2
if err := pc.SetChecksum(true, chkOff); err != nil {
return nil, netip.Addr{}, err
}
}
return newConn(pc, ip, ifi)
}
// newConn is an internal test constructor used for creating a Conn from an
// arbitrary ipv6.PacketConn.
func newConn(pc *ipv6.PacketConn, src netip.Addr, ifi *net.Interface) (*Conn, netip.Addr, error) {
c := &Conn{
pc: pc,
// The default control message used when none is specified.
cm: &ipv6.ControlMessage{
HopLimit: HopLimit,
Src: src.AsSlice(),
IfIndex: ifi.Index,
},
ifi: ifi,
addr: src,
}
return c, src, nil
}
// Close closes the Conn's underlying connection.
func (c *Conn) Close() error { return c.pc.Close() }
// SetDeadline sets the read and write deadlines for Conn. It is
// equivalent to calling both SetReadDeadline and SetWriteDeadline.
func (c *Conn) SetDeadline(t time.Time) error { return c.pc.SetDeadline(t) }
// SetReadDeadline sets a deadline for the next NDP message to arrive.
func (c *Conn) SetReadDeadline(t time.Time) error { return c.pc.SetReadDeadline(t) }
// SetWriteDeadline sets a deadline for the next NDP message to be written.
func (c *Conn) SetWriteDeadline(t time.Time) error { return c.pc.SetWriteDeadline(t) }
// JoinGroup joins the specified multicast group. If group contains an IPv6
// zone, it is overwritten by the zone of the network interface which backs
// Conn.
func (c *Conn) JoinGroup(group netip.Addr) error {
return c.pc.JoinGroup(c.ifi, &net.IPAddr{
IP: group.AsSlice(),
Zone: c.ifi.Name,
})
}
// LeaveGroup leaves the specified multicast group. If group contains an IPv6
// zone, it is overwritten by the zone of the network interface which backs
// Conn.
func (c *Conn) LeaveGroup(group netip.Addr) error {
return c.pc.LeaveGroup(c.ifi, &net.IPAddr{
IP: group.AsSlice(),
Zone: c.ifi.Name,
})
}
// SetICMPFilter applies the specified ICMP filter. This option can be used
// to ensure a Conn only accepts certain kinds of NDP messages.
func (c *Conn) SetICMPFilter(f *ipv6.ICMPFilter) error { return c.pc.SetICMPFilter(f) }
// SetControlMessage enables the reception of *ipv6.ControlMessages based on
// the specified flags.
func (c *Conn) SetControlMessage(cf ipv6.ControlFlags, on bool) error {
return c.pc.SetControlMessage(cf, on)
}
// ReadFrom reads a Message from the Conn and returns its control message and
// source network address. Messages sourced from this machine and malformed or
// unrecognized ICMPv6 messages are filtered.
//
// If more control and/or a more efficient low-level API are required, see
// ReadRaw.
func (c *Conn) ReadFrom() (Message, *ipv6.ControlMessage, netip.Addr, error) {
b := make([]byte, c.ifi.MTU)
for {
n, cm, ip, err := c.ReadRaw(b)
if err != nil {
return nil, nil, netip.Addr{}, err
}
// Filter if this address sent this message, but allow toggling that
// behavior in tests.
if !c.icmpTest && ip == c.addr {
continue
}
m, err := ParseMessage(b[:n])
if err != nil {
// Filter parsing errors on the caller's behalf.
if errors.Is(err, errParseMessage) {
continue
}
return nil, nil, netip.Addr{}, err
}
return m, cm, ip, nil
}
}
// ReadRaw reads ICMPv6 message bytes into b from the Conn and returns the
// number of bytes read, the control message, and the source network address.
//
// Most callers should use ReadFrom instead, which parses bytes into Messages
// and also handles malformed and unrecognized ICMPv6 messages.
func (c *Conn) ReadRaw(b []byte) (int, *ipv6.ControlMessage, netip.Addr, error) {
n, cm, src, err := c.pc.ReadFrom(b)
if err != nil {
return n, nil, netip.Addr{}, err
}
// We fully control the underlying ipv6.PacketConn, so panic if the
// conversions fail.
ip, ok := netip.AddrFromSlice(src.(*net.IPAddr).IP)
if !ok {
panicf("ndp: invalid source IP address: %s", src)
}
// Always apply the IPv6 zone of this interface.
return n, cm, ip.WithZone(c.ifi.Name), nil
}
// WriteTo writes a Message to the Conn, with an optional control message and
// destination network address. If dst contains an IPv6 zone, it is overwritten
// by the zone of the network interface which backs Conn.
//
// If cm is nil, a default control message will be sent.
func (c *Conn) WriteTo(m Message, cm *ipv6.ControlMessage, dst netip.Addr) error {
b, err := MarshalMessage(m)
if err != nil {
return err
}
return c.writeRaw(b, cm, dst)
}
// writeRaw allows writing raw bytes with a Conn.
func (c *Conn) writeRaw(b []byte, cm *ipv6.ControlMessage, dst netip.Addr) error {
// Set reasonable defaults if control message is nil.
if cm == nil {
cm = c.cm
}
_, err := c.pc.WriteTo(b, cm, &net.IPAddr{
IP: dst.AsSlice(),
Zone: c.ifi.Name,
})
return err
}
// SolicitedNodeMulticast returns the solicited-node multicast address for
// an IPv6 address.
func SolicitedNodeMulticast(ip netip.Addr) (netip.Addr, error) {
if err := checkIPv6(ip); err != nil {
return netip.Addr{}, err
}
// Fixed prefix, and low 24 bits taken from input address.
var (
// ff02::1:ff00:0/104
snm = [16]byte{0: 0xff, 1: 0x02, 11: 0x01, 12: 0xff}
ips = ip.As16()
)
for i := 13; i < 16; i++ {
snm[i] = ips[i]
}
return netip.AddrFrom16(snm), nil
}
func panicf(format string, a ...any) {
panic(fmt.Sprintf(format, a...))
}
|