-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathip.go
More file actions
1583 lines (1498 loc) · 51.1 KB
/
Copy pathip.go
File metadata and controls
1583 lines (1498 loc) · 51.1 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
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package mipstack
import (
"context"
"encoding/binary"
"errors"
"net"
"net/netip"
"os"
"strconv"
"strings"
"sync"
"sync/atomic"
"syscall"
"time"
)
const (
// ipDefaultReceiveCapacity bounds retained raw payload and metadata per
// protocol socket.
ipDefaultReceiveCapacity = 4 * 1024 * 1024
// ipDatagramMetadataSize accounts for endpoints, header options, and queue
// storage. Empty payloads must still consume capacity.
ipDatagramMetadataSize = 96
)
// ipDatagram is one validated, reassembled protocol payload.
type ipDatagram struct {
payload []byte
source netip.Addr
target netip.Addr
options ipPacketOptions
}
// IPInfo is a point-in-time diagnostic snapshot of one IP protocol socket.
// Traffic byte counters measure protocol payload; receive-queue byte values
// also include the stack's per-datagram accounting overhead.
type IPInfo struct {
// LocalAddress is the bound local address; an unspecified address denotes
// a wildcard binding.
LocalAddress netip.Addr
// RemoteAddress is the connected peer, or an invalid address for an
// unconnected socket.
RemoteAddress netip.Addr
// Protocol is the IANA IP protocol number carried by the socket.
Protocol uint8
// Closed reports whether the socket was closed when the snapshot was taken.
Closed bool
// ReceiveQueuePackets is the number of complete payloads awaiting a read.
ReceiveQueuePackets int
// ReceiveQueueBytes is the accounted payload and metadata retained by the
// receive queue.
ReceiveQueueBytes int
// ReceiveQueueCapacity is the configured accounting-byte limit of the
// combined payload and error queues, not an exact heap-allocation limit.
ReceiveQueueCapacity int
// ReceiveErrors reports whether asynchronous network errors are reserved
// for ReadError instead of being returned by ordinary reads.
ReceiveErrors bool
// ErrorQueueEntries is the number of asynchronous network errors awaiting
// ReadError or, when ReceiveErrors is false, an ordinary read.
ErrorQueueEntries int
// ErrorQueueBytes is the accounted metadata and quoted packet data retained
// by the asynchronous error queue.
ErrorQueueBytes int
// ErrorsDropped counts asynchronous network errors discarded because the
// configured receive-buffer budget was exhausted.
ErrorsDropped uint64
// PacketsSent counts successfully emitted protocol payloads.
PacketsSent uint64
// BytesSent counts successfully emitted protocol payload bytes.
BytesSent uint64
// PacketsReceived counts protocol payloads accepted into the receive queue.
PacketsReceived uint64
// BytesReceived counts protocol payload bytes accepted into the receive
// queue.
BytesReceived uint64
// PacketsDropped counts payloads rejected because the socket was closed or
// its receive queue lacked capacity.
PacketsDropped uint64
// ICMPErrors counts matching asynchronous ICMP errors delivered to the
// socket.
ICMPErrors uint64
// PathMTU is the complete-IP-packet PMTU for a connected unicast peer, or
// zero when no such path exists.
PathMTU int
// PathMTUDiscovery is the Linux-compatible source-fragmentation and PMTU
// policy used by subsequent writes.
PathMTUDiscovery PathMTUDiscovery
// HopLimit is the default unicast IPv4 TTL or IPv6 Hop Limit.
HopLimit int
// MulticastHopLimit is the default multicast IPv4 TTL or IPv6 Hop Limit.
MulticastHopLimit int
// MulticastLoopback reports whether transmitted multicast is delivered to
// matching local memberships.
MulticastLoopback bool
// Broadcast reports whether IPv4 broadcast output is permitted.
Broadcast bool
// TrafficClass is the default IPv4 TOS or IPv6 Traffic Class byte.
TrafficClass uint8
// FlowLabel is the effective IPv6 Flow Label; it is zero for IPv4 sockets.
FlowLabel uint32
// LastError is the most recently recorded socket operation or asynchronous
// network error.
LastError error
}
// ipKey identifies one specific or wildcard raw protocol binding.
type ipKey struct {
address netip.Addr
protocol byte
}
// ipEndpoints is the optional raw-protocol dispatcher retained by Stack. Its
// concrete implementation is created only by DialIP or ListenIP, allowing raw
// socket parsing, queues, and typed methods to be removed from TCP/UDP-only
// binaries.
type ipEndpoints interface {
// deliver dispatches one protocol payload to matching raw sockets.
deliver(stack *Stack, packet ipPacket) bool
// deliverError dispatches one correlated ICMP error to matching sockets.
deliverError(stack *Stack, networkError ICMPError) bool
// updateConfig closes raw sockets invalidated by new network policy.
updateConfig(stack *Stack, network *networkState)
// closeAll closes every raw socket retained by the dispatcher.
closeAll()
}
// ipEndpointState owns raw-protocol fan-out maps. Stack.mu protects them.
type ipEndpointState struct {
bindings map[ipKey]map[*IPConn]struct{}
}
// IPConn is a connected or unconnected userspace IP protocol socket. It
// exchanges protocol payloads; mipstack owns the IPv4 or IPv6 header.
type IPConn struct {
stack *Stack
net string
protocol byte
v6 bool
dual bool
local netip.Addr
remote netip.Addr
closed chan struct{}
once sync.Once
mu sync.Mutex
receive datagramQueue[ipDatagram]
receiveSpare []byte
receiveNotify chan struct{}
receiveCapacity int
queuedBytes int
errorQueue datagramQueue[queuedSocketError]
errorQueuedBytes int
receiveErrors bool
readDeadline socketDeadline
writeDeadline socketDeadline
recentTargets recentDestinationCache[netip.Addr]
defaultOptions ipPacketOptions
pathMTUDiscovery PathMTUDiscovery
multicastHopLimit byte
multicastLoopback bool
broadcast bool
lastError error
packetsSent atomic.Uint64
bytesSent atomic.Uint64
packetsReceived atomic.Uint64
bytesReceived atomic.Uint64
packetsDropped atomic.Uint64
icmpErrors atomic.Uint64
errorsDropped atomic.Uint64
}
// ipWriteParameters is one validated output-policy snapshot shared by
// contiguous and scatter/gather writes.
type ipWriteParameters struct {
source netip.Addr
target netip.Addr
options ipPacketOptions
pathMTUDiscovery PathMTUDiscovery
nonUnicast bool
}
// ListenIP creates an unconnected IPv4 or IPv6 protocol socket. Network must
// be an IP network with a numeric or well-known protocol, such as ip4:icmp or
// ip:99. An empty Local selects the network's wildcard address; a generic ip
// wildcard is dual-stack when both address families are configured.
func (s *Stack) ListenIP(ctx context.Context, network string, local netip.Addr) (*IPConn, error) {
local = local.Unmap()
target := ipNetAddr(local)
wrap := func(err error) (*IPConn, error) {
return nil, socketOperationError("listen", network, nil, target, err)
}
protocol, err := parseIPNetwork(network, local)
if err != nil {
return wrap(err)
}
if local.IsValid() && (local.IsMulticast() || local.Zone() != "") {
return wrap(errors.New("mipstack: invalid IP listen address"))
}
if local.IsValid() && !local.IsUnspecified() && !s.isLocal(local) {
return wrap(syscall.EADDRNOTAVAIL)
}
if err := ctx.Err(); err != nil {
return wrap(err)
}
if err := s.ready(); err != nil {
return wrap(err)
}
s.mu.Lock()
defer s.mu.Unlock()
if s.closed {
return wrap(ErrClosed)
}
state := s.network.Load()
family := network[:strings.LastIndexByte(network, ':')]
local, dual, err := listenAddress(state, family, "ip", local)
if err != nil {
return wrap(err)
}
if !local.IsUnspecified() && !networkStateHasLocal(state, local) {
return wrap(syscall.EADDRNOTAVAIL)
}
connection := newIPConn(s, network, protocol, local, netip.Addr{})
connection.dual = dual
s.ipEndpointStateLocked().register(connection)
s.stats.activeIPSockets.Add(1)
return connection, nil
}
// DialIP creates a connected IPv4 or IPv6 protocol socket. Network must be an
// IP network with a numeric or well-known protocol, such as ip6:ipv6-icmp or
// ip:99. An invalid or unspecified source selects a managed address using the
// route table.
func (s *Stack) DialIP(ctx context.Context, network string, source, remote netip.Addr) (net.Conn, error) {
remote = remote.Unmap()
target := ipNetAddr(remote)
wrap := func(local net.Addr, err error) (net.Conn, error) {
return nil, socketOperationError("dial", network, local, target, err)
}
protocol, err := parseIPNetwork(network, remote)
if err != nil {
return wrap(nil, err)
}
if !remote.IsValid() || remote.IsUnspecified() || remote.Zone() != "" {
return wrap(nil, errors.New("mipstack: invalid IP destination"))
}
if err := ctx.Err(); err != nil {
return wrap(nil, err)
}
if err := s.ready(); err != nil {
return wrap(nil, err)
}
source = source.Unmap()
if source.IsValid() && source.Zone() != "" {
return wrap(nil, syscall.EINVAL)
}
family := network[:strings.LastIndexByte(network, ':')]
if source.IsValid() && source.IsUnspecified() && source.Is6() != remote.Is6() && family != "ip" {
addressFamily := "IPv6"
if remote.Is4() {
addressFamily = "IPv4"
}
return wrap(ipNetAddr(source), &net.AddrError{Err: "non-" + addressFamily + " address", Addr: source.String()})
}
s.mu.Lock()
defer s.mu.Unlock()
if s.closed {
return wrap(nil, ErrClosed)
}
local, err := s.sourceForRequested(remote, source)
if err != nil {
return wrap(ipNetAddr(source), err)
}
connection := newIPConn(s, network, protocol, local, remote)
s.ipEndpointStateLocked().register(connection)
s.stats.activeIPSockets.Add(1)
return connection, nil
}
// parseIPNetwork implements the numeric netip DialIP network form introduced
// by the net package without requiring a newer Go toolchain at build time.
func parseIPNetwork(network string, address netip.Addr) (byte, error) {
separator := strings.LastIndexByte(network, ':')
if separator < 0 {
return 0, net.UnknownNetworkError(network)
}
family, protocolName := network[:separator], network[separator+1:]
switch family {
case "ip":
case "ip4":
if address.IsValid() && address.Is6() {
return 0, syscall.EAFNOSUPPORT
}
case "ip6":
if address.IsValid() && address.Is4() {
return 0, syscall.EAFNOSUPPORT
}
default:
return 0, net.UnknownNetworkError(network)
}
var protocol byte
switch strings.ToLower(protocolName) {
case "icmp":
protocol = protocolICMPv4
case "igmp":
protocol = 2
case "tcp":
protocol = protocolTCP
case "udp":
protocol = protocolUDP
case "ipv6-icmp":
protocol = protocolICMPv6
default:
for _, character := range protocolName {
if character < '0' || character > '9' {
return 0, &net.AddrError{Err: "unknown IP protocol specified", Addr: protocolName}
}
}
value, err := strconv.ParseUint(protocolName, 10, 8)
if err != nil {
return 0, &net.AddrError{Err: "unknown IP protocol specified", Addr: protocolName}
}
protocol = byte(value)
}
if err := validateIPProtocol(protocol); err != nil {
return 0, err
}
return protocol, nil
}
// validateIPProtocol rejects values that cannot identify a payload header.
func validateIPProtocol(protocol byte) error {
if protocol == 0 {
return errors.New("mipstack: invalid IP protocol")
}
return nil
}
// newIPConn allocates one unregistered protocol socket.
func newIPConn(stack *Stack, network string, protocol byte, local, remote netip.Addr) *IPConn {
defaults := DatagramSocketDefaults{ReceiveBuffer: ipDefaultReceiveCapacity, HopLimit: 64, MulticastHopLimit: 1}
if stack != nil {
defaults = stack.network.Load().ipDefaults
}
connection := &IPConn{
stack: stack, net: network, protocol: protocol, v6: local.Is6(), local: local, remote: remote,
closed: make(chan struct{}), receiveNotify: make(chan struct{}, 1), receiveCapacity: defaults.ReceiveBuffer,
defaultOptions: ipPacketOptions{
hopLimit: byte(defaults.HopLimit), trafficClass: defaults.TrafficClass,
flowLabel: defaults.FlowLabel, flowLabelSet: defaults.FlowLabel != 0,
},
pathMTUDiscovery: defaults.PathMTUDiscovery,
multicastHopLimit: byte(defaults.MulticastHopLimit), multicastLoopback: !defaults.DisableMulticastLoopback,
broadcast: !defaults.DisableBroadcast,
}
return connection
}
// ipEndpointStateLocked returns the lazily allocated raw dispatcher while
// Stack.mu is held.
func (s *Stack) ipEndpointStateLocked() *ipEndpointState {
if s.ip == nil {
state := &ipEndpointState{bindings: make(map[ipKey]map[*IPConn]struct{})}
s.ip = state
return state
}
return s.ip.(*ipEndpointState)
}
// register adds a connection to protocol fan-out while Stack.mu is held.
func (state *ipEndpointState) register(connection *IPConn) {
key := ipKey{address: connection.local, protocol: connection.protocol}
bindings := state.bindings[key]
if bindings == nil {
bindings = make(map[*IPConn]struct{})
state.bindings[key] = bindings
}
bindings[connection] = struct{}{}
}
// deliver copies a valid protocol payload to every matching raw socket. It
// reports a consumer even when that socket's bounded queue drops the payload.
func (state *ipEndpointState) deliver(stack *Stack, packet ipPacket) bool {
stack.mu.RLock()
if stack.ip != state {
stack.mu.RUnlock()
return false
}
connections := state.connectionsForLocked(packet.target, packet.protocol)
stack.mu.RUnlock()
accepted := false
options := ipPacketOptions{hopLimit: packet.hopLimit, trafficClass: packet.trafficClass, flowLabel: packet.flowLabel}
for _, connection := range connections {
if connection.remote.IsValid() && connection.remote != packet.source {
continue
}
accepted = true
connection.enqueue(packet.payload, packet.source, packet.target, options)
}
return accepted
}
// connectionsForLocked returns exact, family-wildcard, and dual-stack raw
// bindings while Stack.mu is held.
func (state *ipEndpointState) connectionsForLocked(address netip.Addr, protocol byte) []*IPConn {
connections := make([]*IPConn, 0)
for connection := range state.bindings[ipKey{address: address, protocol: protocol}] {
connections = append(connections, connection)
}
wildcard := netip.IPv4Unspecified()
if address.Is6() {
wildcard = netip.IPv6Unspecified()
}
for connection := range state.bindings[ipKey{address: wildcard, protocol: protocol}] {
connections = append(connections, connection)
}
if address.Is4() {
for connection := range state.bindings[ipKey{address: netip.IPv6Unspecified(), protocol: protocol}] {
if connection.dual {
connections = append(connections, connection)
}
}
}
return connections
}
// deliverError correlates an ICMP quote with recent writes by matching raw
// protocol sockets before it changes shared path state.
func (state *ipEndpointState) deliverError(stack *Stack, networkError ICMPError) bool {
stack.mu.RLock()
if stack.ip != state {
stack.mu.RUnlock()
return false
}
connections := state.connectionsForLocked(networkError.QuotedSource, networkError.QuotedProtocol)
stack.mu.RUnlock()
accepted := false
acceptedPathMTU := false
for _, connection := range connections {
if !connection.acceptsError(networkError.QuotedTarget) {
continue
}
accepted = true
acceptedPathMTU = acceptedPathMTU || connection.acceptsPathMTU()
connectionError := networkError
connectionError.QuotedPayload = append([]byte(nil), networkError.QuotedPayload...)
connection.deliverError(networkError.QuotedTarget, connectionError)
}
if acceptedPathMTU && networkError.MTU != 0 && stack.observePathMTU(networkError.QuotedTarget, networkError.MTU) {
stack.notifyTCPPathMTU(networkError.QuotedTarget, nil)
}
return accepted
}
// empty reports whether no raw protocol bindings remain.
func (state *ipEndpointState) empty() bool { return len(state.bindings) == 0 }
// connections returns all raw sockets while Stack.mu is held.
func (state *ipEndpointState) connections() []*IPConn {
var connections []*IPConn
for _, bindings := range state.bindings {
for connection := range bindings {
connections = append(connections, connection)
}
}
return connections
}
// updateConfig closes raw sockets whose binding or route was removed.
func (state *ipEndpointState) updateConfig(stack *Stack, network *networkState) {
stack.mu.RLock()
if stack.ip != state {
stack.mu.RUnlock()
return
}
connections := state.connections()
stack.mu.RUnlock()
for _, connection := range connections {
if connection.dual && !networkStateHasFamily(network, false) && !networkStateHasFamily(network, true) ||
!connection.dual && connection.local.IsUnspecified() && !networkStateHasFamily(network, connection.v6) ||
!connection.local.IsUnspecified() && !networkStateHasLocal(network, connection.local) {
stack.closeIP(connection)
continue
}
if connection.remote.IsValid() {
if !network.hasOutputPath(connection.remote) {
stack.closeIP(connection)
}
}
}
}
// closeAll publishes stack closure to every socket in a detached raw
// dispatcher.
func (state *ipEndpointState) closeAll() {
connections := state.connections()
state.bindings = nil
for _, connection := range connections {
connection.closeFromStack()
}
}
// remove unregisters a raw socket while Stack.mu is held.
func (state *ipEndpointState) remove(connection *IPConn) bool {
key := ipKey{address: connection.local, protocol: connection.protocol}
bindings := state.bindings[key]
if _, exists := bindings[connection]; !exists {
return false
}
delete(bindings, connection)
if len(bindings) == 0 {
delete(state.bindings, key)
}
return true
}
// enqueue copies one payload unless the configured receive capacity is full.
func (c *IPConn) enqueue(payload []byte, source, target netip.Addr, options ipPacketOptions) {
size := ipDatagramMetadataSize + len(payload)
c.mu.Lock()
select {
case <-c.closed:
c.mu.Unlock()
c.stack.stats.inboundDroppedPackets.Add(1)
c.packetsDropped.Add(1)
return
default:
}
if size > c.receiveCapacity || c.queuedBytes+c.errorQueuedBytes > c.receiveCapacity-size {
c.mu.Unlock()
c.stack.stats.inboundDroppedPackets.Add(1)
c.packetsDropped.Add(1)
return
}
var retained []byte
if len(payload) != 0 {
if cap(c.receiveSpare) >= len(payload) {
retained = c.receiveSpare[:len(payload)]
c.receiveSpare = nil
copy(retained, payload)
} else {
retained = append([]byte(nil), payload...)
}
}
datagram := ipDatagram{payload: retained, source: source, target: target, options: options}
c.receive.push(datagram)
c.queuedBytes += size
c.packetsReceived.Add(1)
c.bytesReceived.Add(uint64(len(payload)))
c.notifyReceiveLocked()
c.mu.Unlock()
}
// notifyReceiveLocked keeps one edge notification armed while queued data
// remains and removes a stale token when the queue becomes empty.
func (c *IPConn) notifyReceiveLocked() {
if c.receive.len() != 0 || !c.receiveErrors && c.errorQueue.len() != 0 {
select {
case c.receiveNotify <- struct{}{}:
default:
}
return
}
select {
case <-c.receiveNotify:
default:
}
}
// ReadFrom implements net.PacketConn.
func (c *IPConn) ReadFrom(buffer []byte) (int, net.Addr, error) {
n, datagram, _, err := c.readDatagram(buffer)
address := ipNetAddr(datagram.source)
if err != nil {
return n, address, c.operationError("read", err)
}
return n, address, nil
}
// ReadFromIP acts like ReadFrom but returns an IPAddr.
func (c *IPConn) ReadFromIP(buffer []byte) (int, *net.IPAddr, error) {
n, datagram, _, err := c.readDatagram(buffer)
address := ipNetAddr(datagram.source)
if err != nil {
return n, address, c.operationError("read", err)
}
return n, address, nil
}
// ReadMsgIP reads one protocol payload and Linux-compatible packet info,
// hop-limit, and traffic-class ancillary data.
func (c *IPConn) ReadMsgIP(buffer, oob []byte) (n, oobn, flags int, address *net.IPAddr, err error) {
var datagram ipDatagram
var truncated bool
n, datagram, truncated, err = c.readDatagram(buffer)
address = ipNetAddr(datagram.source)
if truncated {
flags |= MessageTruncated
}
if err != nil {
err = c.operationError("read", err)
return
}
control, controlErr := controlMessageForRead(datagram.target, datagram.options)
if controlErr != nil {
err = c.operationError("read", controlErr)
return
}
oobn = copy(oob, control)
if oobn < len(control) {
flags |= MessageControlTruncated
}
return
}
// ReadBatch reads one or more IP protocol messages using the Message layout
// shared by x/net/ipv4 and x/net/ipv6. The first message follows the socket's
// blocking and deadline semantics; after it succeeds, the method drains only
// messages already queued. MessageDontWait also makes the first read
// nonblocking.
func (c *IPConn) ReadBatch(messages []Message, flags int) (int, error) {
if flags&^MessageDontWait != 0 {
return 0, c.operationError("read", syscall.EOPNOTSUPP)
}
for index := range messages {
wait := index == 0 && flags&MessageDontWait == 0
err := c.readBatchMessage(&messages[index], wait, index == 0)
if err != nil {
// recvmmsg reports a completed prefix without the error that stopped
// the next message. A retry starting at index exposes that error.
if index != 0 {
return index, nil
}
return index, err
}
}
return len(messages), nil
}
// readBatchMessage receives one scatter/gather message without waiting when
// wait is false. consumeErrors is false after a successful prefix so an
// asynchronous error remains available to the next socket operation.
func (c *IPConn) readBatchMessage(message *Message, wait, consumeErrors bool) error {
if _, err := messageBufferLength(message.Buffers); err != nil {
return c.operationError("read", err)
}
n, datagram, truncated, err := c.readDatagramBuffers(message.Buffers, wait, consumeErrors)
if err != nil {
return c.operationError("read", err)
}
control, err := controlMessageForRead(datagram.target, datagram.options)
if err != nil {
return c.operationError("read", err)
}
flags := 0
if truncated {
flags |= MessageTruncated
}
oobn := copy(message.OOB, control)
if oobn < len(control) {
flags |= MessageControlTruncated
}
message.N, message.NN, message.Flags, message.Addr = n, oobn, flags, ipNetAddr(datagram.source)
return nil
}
// Read receives from a connected remote endpoint.
func (c *IPConn) Read(buffer []byte) (int, error) {
n, _, _, err := c.readDatagram(buffer)
if err != nil {
return n, c.operationError("read", err)
}
return n, nil
}
// readDatagram returns one payload without adding the public operation wrapper.
func (c *IPConn) readDatagram(buffer []byte) (n int, datagram ipDatagram, truncated bool, err error) {
return c.readDatagramBuffers([][]byte{buffer}, true, true)
}
// readDatagramBuffers is the scatter/gather and nonblocking form used by
// ReadBatch. It returns EAGAIN without consuming state when wait is false and
// neither a payload nor an ordinary-read error is ready.
func (c *IPConn) readDatagramBuffers(buffers [][]byte, wait, consumeErrors bool) (n int, datagram ipDatagram, truncated bool, err error) {
for {
c.mu.Lock()
select {
case <-c.closed:
c.mu.Unlock()
return 0, ipDatagram{}, false, net.ErrClosed
default:
}
timeout := c.readDeadline.wait()
select {
case <-timeout:
c.mu.Unlock()
return 0, ipDatagram{}, false, os.ErrDeadlineExceeded
default:
}
if queued, ok := c.receive.pop(); ok {
datagram = queued
c.queuedBytes -= ipDatagramMetadataSize + len(datagram.payload)
c.notifyReceiveLocked()
c.mu.Unlock()
n = copyMessagePayload(buffers, datagram.payload)
if cap(datagram.payload) != 0 && cap(datagram.payload) <= datagramReusablePayloadLimit {
c.mu.Lock()
select {
case <-c.closed:
default:
if cap(datagram.payload) > cap(c.receiveSpare) {
c.receiveSpare = datagram.payload[:0]
}
}
c.mu.Unlock()
}
return n, datagram, n < len(datagram.payload), nil
}
if !c.receiveErrors && consumeErrors {
if queued, ok := c.errorQueue.pop(); ok {
c.errorQueuedBytes -= queued.size
c.notifyReceiveLocked()
c.mu.Unlock()
return 0, ipDatagram{}, false, queued.err
}
}
if !wait {
c.mu.Unlock()
return 0, ipDatagram{}, false, syscall.EAGAIN
}
notified := c.receiveNotify
c.mu.Unlock()
select {
case <-notified:
case <-timeout:
return 0, ipDatagram{}, false, os.ErrDeadlineExceeded
case <-c.closed:
return 0, ipDatagram{}, false, net.ErrClosed
}
}
}
// WriteTo sends one payload to an unconnected destination.
func (c *IPConn) WriteTo(payload []byte, address net.Addr) (int, error) {
ipAddress, ok := address.(*net.IPAddr)
if !ok {
return 0, c.operationErrorTo("write", address, syscall.EINVAL)
}
return c.WriteToIP(payload, ipAddress)
}
// WriteToIP acts like WriteTo but accepts an IPAddr directly.
func (c *IPConn) WriteToIP(payload []byte, address *net.IPAddr) (int, error) {
netAddress := ipAddrNet(address)
if c.remote.IsValid() {
return 0, c.operationErrorTo("write", netAddress, net.ErrWriteToConnected)
}
target, err := ipAddr(address)
if err != nil {
return 0, c.operationErrorTo("write", netAddress, err)
}
n, err := c.writeTo(payload, target, netip.Addr{}, ipPacketOptions{})
if err != nil {
return n, c.operationErrorTo("write", netAddress, err)
}
return n, nil
}
// Write sends one payload to the connected endpoint.
func (c *IPConn) Write(payload []byte) (int, error) {
if !c.remote.IsValid() {
return 0, c.operationError("write", errors.New("mipstack: IP socket is not connected"))
}
n, err := c.writeTo(payload, c.remote, netip.Addr{}, ipPacketOptions{})
if err != nil {
return n, c.operationError("write", err)
}
return n, nil
}
// WritePathMTUProbe sends one connected protocol payload without IPv4 or
// IPv6 source fragmentation. The complete packet may exceed the confirmed
// PMTU but cannot exceed the first-hop MTU.
func (c *IPConn) WritePathMTUProbe(payload []byte) (int, error) {
if !c.remote.IsValid() {
return 0, c.operationError("write", errors.New("mipstack: IP socket is not connected"))
}
if c.remote.IsMulticast() || c.stack.network.Load().broadcastDestination(c.remote) {
return 0, c.operationError("write", syscall.EOPNOTSUPP)
}
n, err := c.writeToWith(payload, c.remote, netip.Addr{}, ipPacketOptions{}, c.writePathMTUProbePayload)
if err != nil {
return n, c.operationError("write", err)
}
return n, nil
}
// WritePathMTUProbeTo is the unconnected netip form of WritePathMTUProbe.
func (c *IPConn) WritePathMTUProbeTo(payload []byte, target netip.Addr) (int, error) {
if c.remote.IsValid() {
return 0, c.operationErrorTo("write", ipNetAddr(target), net.ErrWriteToConnected)
}
if target.IsMulticast() || c.stack.network.Load().broadcastDestination(target) {
return 0, c.operationErrorTo("write", ipNetAddr(target), syscall.EOPNOTSUPP)
}
n, err := c.writeToWith(payload, target, netip.Addr{}, ipPacketOptions{}, c.writePathMTUProbePayload)
if err != nil {
return n, c.operationErrorTo("write", ipNetAddr(target), err)
}
return n, nil
}
// ConfirmPathMTU records application-level acknowledgement of a connected
// protocol probe. mtu is the complete IP packet size, not the payload size.
func (c *IPConn) ConfirmPathMTU(mtu int) error {
if !c.remote.IsValid() {
return c.operationError("set", errors.New("mipstack: IP socket is not connected"))
}
if err := c.stack.ConfirmPathMTU(c.remote, mtu); err != nil {
return c.setOperationError(err)
}
return nil
}
// ConfirmPathMTUFor is the unconnected form of ConfirmPathMTU.
func (c *IPConn) ConfirmPathMTUFor(target netip.Addr, mtu int) error {
if c.remote.IsValid() {
return c.setOperationError(net.ErrWriteToConnected)
}
if err := c.stack.ConfirmPathMTU(target, mtu); err != nil {
return c.setOperationError(err)
}
return nil
}
// WriteMsgIP writes one payload with Linux-compatible source, hop-limit, and
// traffic-class ancillary data. Like net.IPConn, it requires an unconnected
// socket and a non-nil destination.
func (c *IPConn) WriteMsgIP(payload, oob []byte, address *net.IPAddr) (n, oobn int, err error) {
netAddress := ipAddrNet(address)
if c.remote.IsValid() {
return 0, 0, c.operationErrorTo("write", netAddress, net.ErrWriteToConnected)
}
target, err := ipAddr(address)
if err != nil {
return 0, 0, c.operationErrorTo("write", netAddress, err)
}
target, err = c.validateWriteTarget(target)
if err != nil {
return 0, 0, c.operationErrorTo("write", netAddress, err)
}
// Match net.IPConn: destination conversion precedes poll state, while an
// expired deadline or closed descriptor precedes ancillary-data parsing.
if err = (socketWriteState{deadline: &c.writeDeadline, closed: c.closed}).err(); err != nil {
return 0, 0, c.operationErrorTo("write", netAddress, err)
}
source, options, err := parseControlMessageForWrite(oob, target.Is6())
if err != nil {
return 0, 0, c.operationErrorTo("write", netAddress, err)
}
n, err = c.writeTo(payload, target, source, options)
if err != nil {
return n, 0, c.operationErrorTo("write", netAddress, err)
}
return n, len(oob), nil
}
// WriteBatch writes a prefix of IP protocol messages using scatter/gather
// payloads. Flags other than zero are unsupported because packet-queue
// backpressure and deadlines are expressed by the socket rather than an
// operating-system fd.
func (c *IPConn) WriteBatch(messages []Message, flags int) (int, error) {
if flags != 0 {
return 0, c.operationError("write", syscall.EOPNOTSUPP)
}
for index := range messages {
message := &messages[index]
n, oobn, err := c.writeBatchMessage(message)
if err != nil {
// sendmmsg reports a completed prefix without the error that stopped
// the next message. A retry starting at index exposes that error.
if index != 0 {
return index, nil
}
return index, err
}
message.N, message.NN, message.Flags = n, oobn, 0
}
return len(messages), nil
}
// writeBatchMessage validates one destination and sends a scatter/gather
// payload through the ordinary ancillary-data and output policy.
func (c *IPConn) writeBatchMessage(message *Message) (int, int, error) {
var target netip.Addr
var address net.Addr
if c.remote.IsValid() {
if message.Addr != nil {
return 0, 0, c.operationErrorTo("write", message.Addr, net.ErrWriteToConnected)
}
target, address = c.remote, c.remoteAddr()
} else {
address = message.Addr
ipAddress, ok := address.(*net.IPAddr)
if !ok || ipAddress == nil {
return 0, 0, c.operationErrorTo("write", address, syscall.EINVAL)
}
var err error
target, err = ipAddr(ipAddress)
if err != nil {
return 0, 0, c.operationErrorTo("write", address, err)
}
}
validated, err := c.validateWriteTarget(target)
if err != nil {
return 0, 0, c.operationErrorTo("write", address, err)
}
maximum := 65535
if validated.Is4() {
maximum -= 20
}
payloadSize, err := messageBufferLength(message.Buffers)
if err != nil {
return 0, 0, c.operationErrorTo("write", address, err)
}
if payloadSize > maximum {
return 0, 0, c.operationErrorTo("write", address, syscall.EMSGSIZE)
}
if len(message.Buffers) == 1 {
if err = (socketWriteState{deadline: &c.writeDeadline, closed: c.closed}).err(); err != nil {
return 0, 0, c.operationErrorTo("write", address, err)
}
source, options, parseErr := parseControlMessageForWrite(message.OOB, validated.Is6())
if parseErr != nil {
return 0, 0, c.operationErrorTo("write", address, parseErr)
}
n, writeErr := c.writeTo(message.Buffers[0], validated, source, options)
if writeErr != nil {
return n, 0, c.operationErrorTo("write", address, writeErr)
}
return n, len(message.OOB), nil
}
if err = (socketWriteState{deadline: &c.writeDeadline, closed: c.closed}).err(); err != nil {
return 0, 0, c.operationErrorTo("write", address, err)
}
source, options, err := parseControlMessageForWrite(message.OOB, validated.Is6())
if err != nil {
return 0, 0, c.operationErrorTo("write", address, err)
}
n, err := c.writeBuffersTo(message.Buffers, payloadSize, validated, source, options)
if err != nil {
return n, 0, c.operationErrorTo("write", address, err)
}
return n, len(message.OOB), nil
}
// writeTo selects a source, repairs ICMPv6 checksum, and emits one ordinary
// fragmentable payload.
func (c *IPConn) writeTo(payload []byte, target netip.Addr, packetInfoSource netip.Addr, options ipPacketOptions) (int, error) {
return c.writeToWith(payload, target, packetInfoSource, options, c.writePayload)
}
// prepareWrite snapshots socket policy and selects the source for one output
// operation without retaining any caller payload.
func (c *IPConn) prepareWrite(target, packetInfoSource netip.Addr, options ipPacketOptions) (ipWriteParameters, error) {
target, err := c.validateWriteTarget(target)
if err != nil {
return ipWriteParameters{}, err
}
writeState, options, pathMTUDiscovery := c.writeStateAndOptions(options)
if err = writeState.err(); err != nil {
return ipWriteParameters{}, err
}
requestedSource := c.local
packetInfoSource = packetInfoSource.Unmap()
if packetInfoSource.IsValid() && !packetInfoSource.IsUnspecified() {
if !c.local.IsUnspecified() && packetInfoSource != c.local {
return ipWriteParameters{}, syscall.EADDRNOTAVAIL
}
requestedSource = packetInfoSource
}
source, nonUnicast, err := c.stack.sourceForOutput(target, requestedSource)
if err != nil {
return ipWriteParameters{}, err
}
return ipWriteParameters{
source: source, target: target, options: options,
pathMTUDiscovery: pathMTUDiscovery, nonUnicast: nonUnicast,
}, nil
}
// writeToWith keeps routing, checksums, deadlines, accounting, and ICMP
// correlation shared between ordinary writes and PLPMTUD probes.
func (c *IPConn) writeToWith(payload []byte, target netip.Addr, packetInfoSource netip.Addr, options ipPacketOptions, write func(netip.Addr, netip.Addr, []byte, ipPacketOptions, PathMTUDiscovery, bool) error) (int, error) {
parameters, err := c.prepareWrite(target, packetInfoSource, options)
if err != nil {
return 0, err
}