forked from mdlayher/ndp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
conn.go
249 lines (208 loc) · 7.04 KB
/
conn.go
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
247
248
249
package ndp
import (
"errors"
"fmt"
"net"
"net/netip"
"runtime"
"strconv"
"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
}
// Specify the zone index to bind to the correct interface.
// The zone index is cached in https://github.com/golang/go/blob/go1.23.1/src/net/interface.go#L192
// and may result in binding on the wrong interface if using the interface name as zone.
ic, err := icmp.ListenPacket("ip6:ipv6-icmp", ip.WithZone(strconv.Itoa(ifi.Index)).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...))
}