Repository navigation
Expand file tree
/
Copy pathtcp.go
More file actions
535 lines (457 loc) · 18 KB
/
Copy pathtcp.go
File metadata and controls
535 lines (457 loc) · 18 KB
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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
package bgp
import (
"context"
"errors"
"fmt"
"math"
"net"
"net/netip"
"syscall"
"time"
)
// Port is the well-known TCP port for BGP, as assigned by IANA. A Dialer
// dials it when its own Port field is zero.
const Port = 179
// useMultipathTCP determines whether this package's connections may use
// Multipath TCP (RFC 8684). They may not: BGP is a single-path control
// protocol, the net package listens with MPTCP by default, and an MPTCP socket
// rejects every socket option a BGP speaker sets: TCP_MD5SIG, the TTL
// options behind GTSM, IP_TOS, and TCP_USER_TIMEOUT among them.
const useMultipathTCP = false
// DSCPCS6 is the Class Selector 6 Differentiated Services Code Point (RFC
// 2474). RFC 4594 assigns it to network control traffic, and routers
// conventionally apply it to BGP. It is the usual value for TCPOptions.DSCP.
const DSCPCS6 = 48
// maxDSCP is the largest DSCP value: the code point is six bits wide.
const maxDSCP = 63
// maxUserTimeout is the largest TCP_USER_TIMEOUT, which the kernel takes as
// an unsigned 32 bit count of milliseconds.
const maxUserTimeout = math.MaxUint32 * time.Millisecond
// TCPOptions carries the socket options a BGP speaker sets on both the active
// and the passive open. Embedded in Dialer, the options apply to each dialed
// connection. Embedded in ListenConfig, they apply to the listening socket,
// and every accepted connection inherits them. Each option is a whole-socket
// property: peers with different needs must be split across listeners. The
// zero value sets nothing.
//
// With the exception of KeepAlive, these options are only supported on Linux.
// Elsewhere, setting any of them makes [Dialer.Dial] and ListenConfig.Listen
// return an error which wraps [errors.ErrUnsupported].
type TCPOptions struct {
// GTSM optionally enables the Generalized TTL Security Mechanism (RFC
// 5082): outgoing packets are sent with a TTL of 255, and incoming
// packets with a lower TTL are dropped by the kernel.
//
// GTSM only makes sense for a directly connected peer. Multihop peering
// is its explicit opposite and needs no option at all, since the kernel
// default TTL already crosses any reasonable multihop distance.
GTSM bool
// DSCP optionally marks outgoing packets with a Differentiated Services
// Code Point (RFC 2474), a value from 0 to 63, so that routers along the
// path can prioritize the session's traffic. The conventional marking
// for BGP is DSCPCS6. The zero value leaves packets unmarked, which is
// the kernel default.
DSCP uint8
// UserTimeout optionally bounds how long transmitted data may remain
// unacknowledged before the kernel closes the connection
// (TCP_USER_TIMEOUT). The failure surfaces on the next read or write.
// The zero value leaves the kernel default, which gives up only after
// tcp_retries2 exhausts: roughly fifteen minutes.
//
// UserTimeout is not a liveness mechanism; the BGP hold timer is, and
// the FSM closes a session whose hold timer expires regardless of this
// option. What UserTimeout buys is agreement between the kernel and
// the session. Without it, a peer which stops acknowledging mid-write
// keeps the kernel retransmitting long after BGP has declared the
// session dead. Conventionally it is set to the hold time. The same
// bound also caps how long TCP keepalive probes may go unanswered on
// an idle connection, overriding the probe count.
//
// A positive value is rounded up to a whole millisecond. A negative
// value is an error.
UserTimeout time.Duration
// SendBuffer optionally sets the size in bytes of the kernel's send
// buffer (SO_SNDBUF). The zero value leaves the kernel to size the
// buffer itself, adaptively. A nonzero value turns that off, and the
// kernel may still round or clamp it. A negative value is an error.
//
// Operators tune the send buffer for the burst of a full-table push.
// A larger buffer trades later detection of a stalled peer for
// throughput. A write completes as soon as the kernel has buffered
// it, so a larger buffer lets more of a push complete before a peer
// which stopped reading becomes visible.
SendBuffer int
// RecvBuffer optionally sets the size in bytes of the kernel's
// receive buffer (SO_RCVBUF), with SendBuffer's zero, rounding, and
// negative-value semantics. It is set before the socket carries a
// connection: the only point at which the buffer can influence the
// window scale TCP negotiates. On a listening socket it shapes the
// connections accepted afterwards, an adopted socket's included.
RecvBuffer int
// KeepAlive optionally configures TCP keepalive probes. The nil value
// leaves the net package's default, which enables probes at its own
// idle time, interval, and count. A non-nil value with Enable set
// replaces that configuration. A non-nil value with Enable clear
// disables probes entirely.
//
// TCP keepalive is not BGP's liveness mechanism; the hold timer is,
// and it detects a dead peer on its own. Probes are a backstop for
// the one case the hold timer cannot see from inside the kernel: a
// peer which vanished without a reset. The hold timer reports that
// silence only when it expires, while probes can fail the connection
// sooner. Callers who align the probes with the hold time should note
// that UserTimeout, when set, caps unanswered probes as well.
//
// Unlike every other option, KeepAlive is portable and never yields
// [errors.ErrUnsupported]: the net package applies it to dialed and
// accepted connections alike.
KeepAlive *net.KeepAliveConfig
}
// keepAlive applies o.KeepAlive to the net package's pair of keepalive fields,
// which Dialer and ListenConfig share. The pair's semantics are the net
// package's: the config is honored when its Enable is set, and probes are
// disabled only by a negative interval, not by a clear Enable.
//
// The option is the net package's to apply rather than this package's because
// it applies its own keepalive configuration to every accepted connection: a
// value set on the listening socket would not survive Accept.
func (o TCPOptions) keepAlive(interval *time.Duration, config *net.KeepAliveConfig) {
switch {
case o.KeepAlive == nil:
return
case o.KeepAlive.Enable:
*config = *o.KeepAlive
default:
*interval = -1
}
}
// check validates the options before any socket exists to apply them to.
func (o TCPOptions) check() error {
if o.DSCP > maxDSCP {
return fmt.Errorf("bgp: DSCP %d is not between 0 and %d", o.DSCP, maxDSCP)
}
if o.UserTimeout < 0 || o.UserTimeout > maxUserTimeout {
return fmt.Errorf("bgp: user timeout %s is not between 0 and %s", o.UserTimeout, maxUserTimeout)
}
if o.SendBuffer < 0 {
return fmt.Errorf("bgp: send buffer size %d is negative", o.SendBuffer)
}
if o.RecvBuffer < 0 {
return fmt.Errorf("bgp: receive buffer size %d is negative", o.RecvBuffer)
}
return nil
}
// control applies the options to the socket underlying c, which belongs to
// the IPv4 address family when ipv4 is true and IPv6 otherwise. The
// IP-level options are each a level/option pair selected by that family;
// the TCP-level options are the same in both.
func (o TCPOptions) control(ipv4 bool, c syscall.RawConn) error {
if o == (TCPOptions{}) {
return nil
}
if o.GTSM {
if err := setGTSM(c, ipv4); err != nil {
return err
}
}
if o.DSCP != 0 {
if err := setDSCP(c, ipv4, o.DSCP); err != nil {
return err
}
}
if o.UserTimeout != 0 {
if err := setUserTimeout(c, o.UserTimeout); err != nil {
return err
}
}
if o.SendBuffer != 0 {
if err := setBuffer(c, true, o.SendBuffer); err != nil {
return err
}
}
if o.RecvBuffer != 0 {
if err := setBuffer(c, false, o.RecvBuffer); err != nil {
return err
}
}
return nil
}
// A Dialer creates BGP connections by performing an active open. It applies
// the TCP socket options a BGP speaker typically needs. The zero value is
// usable and produces a plain TCP connection.
type Dialer struct {
// TCPOptions are the socket options applied to each dialed connection.
TCPOptions
// LocalAddr optionally binds the local side of the connection to a
// specific address, port, or both. The zero value binds nothing, the
// normal posture for an active open: BGP speakers connect from an
// ephemeral port.
LocalAddr netip.AddrPort
// Port is the TCP port the peer is dialed on. The zero value is Port,
// the well-known 179.
Port uint16
}
// Dial performs an active open to the BGP speaker at addr, on the Dialer's
// Port, and returns a Conn over the resulting connection.
func (d *Dialer) Dial(ctx context.Context, addr netip.Addr) (*Conn, error) {
return d.dial(ctx, addr, "")
}
// dial implements Dial, optionally authenticating the connection with a
// TCP-MD5 key: the peering's key from PeerConfig.MD5Password on the Peer's
// active open path. An empty md5 disables TCP-MD5.
func (d *Dialer) dial(ctx context.Context, addr netip.Addr, md5 string) (*Conn, error) {
addr = addr.Unmap()
if !addr.IsValid() {
return nil, fmt.Errorf("bgp: invalid remote address: %s", addr)
}
port := d.Port
if port == 0 {
port = Port
}
if err := d.check(); err != nil {
return nil, err
}
nd := &net.Dialer{
Control: func(_, _ string, c syscall.RawConn) error {
if md5 != "" {
if err := setMD5(c, addr, md5); err != nil {
return err
}
}
return d.control(addr.Is4(), c)
},
}
nd.SetMultipathTCP(useMultipathTCP)
d.keepAlive(&nd.KeepAlive, &nd.KeepAliveConfig)
if d.LocalAddr != (netip.AddrPort{}) {
nd.LocalAddr = net.TCPAddrFromAddrPort(d.LocalAddr)
}
c, err := nd.DialContext(ctx, tcpNetwork(addr), netip.AddrPortFrom(addr, port).String())
if err != nil {
return nil, err
}
if err := setNoDelay(c); err != nil {
_ = c.Close()
return nil, err
}
return NewConn(c), nil
}
// A ListenConfig contains options for a Listener. The zero value is usable
// and produces a plain TCP listener.
type ListenConfig struct {
// TCPOptions are the socket options applied to the listening socket,
// which every accepted connection inherits.
TCPOptions
}
// Listen begins listening for BGP connections on laddr, which must carry a
// valid address. A Listener is always bound to exactly one address family,
// so that socket options such as GTSM apply to every connection it accepts.
// To listen on every address of one family, use 0.0.0.0 or ::. A port of
// zero selects an ephemeral port, which Listener.Addr reports.
func (lc *ListenConfig) Listen(ctx context.Context, laddr netip.AddrPort) (*Listener, error) {
addr := laddr.Addr().Unmap()
if !addr.IsValid() {
return nil, fmt.Errorf("bgp: listen address must carry an IPv4 or IPv6 address: %s", laddr)
}
laddr = netip.AddrPortFrom(addr, laddr.Port())
if err := lc.check(); err != nil {
return nil, err
}
nlc := &net.ListenConfig{
Control: func(_, _ string, c syscall.RawConn) error {
return lc.control(addr.Is4(), c)
},
}
nlc.SetMultipathTCP(useMultipathTCP)
lc.keepAlive(&nlc.KeepAlive, &nlc.KeepAliveConfig)
l, err := nlc.Listen(ctx, tcpNetwork(addr), laddr.String())
if err != nil {
return nil, err
}
tl, ok := l.(*net.TCPListener)
if !ok {
_ = l.Close()
return nil, fmt.Errorf("bgp: unexpected listener type %T", l)
}
rc, err := tl.SyscallConn()
if err != nil {
_ = tl.Close()
return nil, err
}
return &Listener{
l: tl,
rc: rc,
v4: addr.Is4(),
}, nil
}
// NewListener adopts an existing listening socket and returns a Listener
// which serves it, applying the ListenConfig's options as Listen does. It is
// intended for advanced use cases such as systemd socket activation.
// [net.FileListener] turns a file descriptor into the *net.TCPListener this
// method takes.
//
// The socket must be listening, must not use Multipath TCP, and must serve
// exactly one address family. A dual-stack socket is rejected: a Listener
// installs TCP-MD5 keys and socket options for one family, so a socket
// serving both would leave an IPv4 peer's key uninstalled and its signed SYN
// dropped by the kernel. Bind one socket per family instead.
//
// On success the Listener owns the socket and Close closes it; a failure
// never closes it. The socket may already have accepted connections, which
// met no TCP-MD5 key; see [Server.Run] for the same window on a new socket.
//
// Adoption is only supported on Linux. Elsewhere, NewListener returns an
// error which wraps [errors.ErrUnsupported].
func (lc *ListenConfig) NewListener(l *net.TCPListener) (*Listener, error) {
if l == nil {
return nil, errors.New("bgp: a Listener cannot adopt a nil socket")
}
if err := lc.check(); err != nil {
return nil, err
}
rc, err := l.SyscallConn()
if err != nil {
return nil, err
}
fam, err := listenerSocket(rc)
if err != nil {
return nil, err
}
if fam.dual {
return nil, errors.New("bgp: the socket accepts both IPv4 and IPv6 connections, and a Listener serves exactly one address family; bind one socket per family instead")
}
if err := lc.control(fam.v4, rc); err != nil {
return nil, err
}
// The net package applies its keepalive configuration to the connections
// accepted from a listener it created, and an adopted socket has none, so
// Accept applies the caller's instead.
return &Listener{
l: l,
rc: rc,
v4: fam.v4,
keepAlive: lc.KeepAlive,
}, nil
}
// A listenerFamily is the address family posture of a listening socket, read
// from the socket itself by listenerSocket.
type listenerFamily struct {
// v4 is set for an IPv4 socket.
v4 bool
// dual is set for an IPv6 socket which also accepts IPv4 connections,
// as v4-mapped ones. A Listener cannot serve one; see NewListener.
dual bool
}
// A Listener accepts BGP connections opened by a peer: the passive open. It
// serves exactly one address family, so that socket options such as GTSM
// apply to every connection it accepts.
type Listener struct {
l *net.TCPListener
// v4 records the single address family the socket is bound to, which
// scopes a Server's key installation; see serves.
v4 bool
// rc is the listening socket itself, on which TCP-MD5 keys are installed:
// the kernel requires a key to be present before a peer's SYN arrives, and
// accepted connections inherit the listening socket's keys.
rc syscall.RawConn
// keepAlive is the caller's keepalive configuration, applied to each
// accepted connection by Accept. Only an adopted Listener sets it: a
// listener the net package bound carries the configuration itself and
// applies it on accept, while an adopted socket carries none, so the
// caller's option would otherwise be silently ignored. A nil value
// leaves the net package's default in place either way.
keepAlive *net.KeepAliveConfig
}
// Accept waits for and returns the next connection to the Listener, mirroring
// the net.Listener method of the same name. Close unblocks a pending Accept.
func (l *Listener) Accept() (*Conn, error) {
for {
c, err := l.l.AcceptTCP()
if err != nil {
return nil, err
}
if err := l.setup(c); err != nil {
// A connection which cannot take socket options is already
// dead. Its failure belongs to the connection, not the
// listener, so it must not surface as an Accept error: close
// it and accept the next.
_ = c.Close()
continue
}
return NewConn(c), nil
}
}
// setup applies to an accepted connection the options which do not survive
// the kernel's inheritance from the listening socket.
func (l *Listener) setup(c *net.TCPConn) error {
if err := setNoDelay(c); err != nil {
return err
}
if l.keepAlive == nil {
return nil
}
return c.SetKeepAliveConfig(*l.keepAlive)
}
// SetMD5 installs a TCP-MD5 (RFC 2385) key for the speaker at peer on the
// listening socket, authenticating the connections accepted from peer. The
// password must not be empty, and installing a key for a peer which already has
// one replaces it. The key must be installed before the peer's SYN arrives.
//
// SetMD5 covers accepted connections only. On the Peer path, set
// PeerConfig.MD5Password instead: it signs the peer's dialed connections, and a
// Server installs it on its listeners via this method.
//
// TCP-MD5 is only supported on Linux. Elsewhere, SetMD5 returns an error which
// wraps [errors.ErrUnsupported].
func (l *Listener) SetMD5(peer netip.Addr, password string) error {
if password == "" {
return errors.New("bgp: a TCP-MD5 password is required; RemoveMD5 removes a key")
}
peer = peer.Unmap()
if !peer.IsValid() {
return fmt.Errorf("bgp: invalid peer address: %s", peer)
}
return setMD5(l.rc, peer, password)
}
// RemoveMD5 removes the TCP-MD5 key installed for peer by SetMD5, restoring
// plain TCP for the speaker at peer. Removing a key which was never installed
// is not an error.
//
// RemoveMD5 is only supported on Linux, and elsewhere returns an error which
// wraps [errors.ErrUnsupported].
func (l *Listener) RemoveMD5(peer netip.Addr) error {
peer = peer.Unmap()
if !peer.IsValid() {
return fmt.Errorf("bgp: invalid peer address: %s", peer)
}
// The kernel removes a key by installing a zero length one.
return setMD5(l.rc, peer, "")
}
// serves reports whether the Listener is bound to peer's address family:
// IPv4 and IPv6 alike, a peer's key belongs on exactly the listeners its
// SYN could reach.
func (l *Listener) serves(peer netip.Addr) bool { return l.v4 == peer.Is4() }
// Addr returns the Listener's network address.
func (l *Listener) Addr() net.Addr { return l.l.Addr() }
// Close closes the Listener, unblocking any pending Accept. Connections
// already returned by Accept are unaffected.
func (l *Listener) Close() error { return l.l.Close() }
// tcpNetwork returns the single-family network name for addr, which must be
// valid. The IPv6 network "tcp6" never produces a dual-stack socket: the net
// package binds it v6-only.
func tcpNetwork(addr netip.Addr) string {
if addr.Is4() {
return "tcp4"
}
return "tcp6"
}
// setNoDelay disables Nagle's algorithm on c. BGP is a control protocol where
// a delayed KEEPALIVE or NOTIFICATION costs far more than a small packet;
// message batching, when it is wanted, belongs in the write path instead.
func setNoDelay(c net.Conn) error {
tc, ok := c.(*net.TCPConn)
if !ok {
return fmt.Errorf("bgp: unexpected connection type %T", c)
}
return tc.SetNoDelay(true)
}