generated from soypat/go-module-template
-
Notifications
You must be signed in to change notification settings - Fork 3
/
arp.go
166 lines (143 loc) · 5.05 KB
/
arp.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
package stacks
import (
"errors"
"log/slog"
"net/netip"
"github.com/soypat/seqs/eth"
)
// ARP returns the ARP client for this stack. The type is not exported since
// it's implementation is experimental.
func (ps *PortStack) ARP() *arpClient {
return &ps.arpClient
}
var (
errARPUnsupported = errors.New("unsupported ARP request")
errNoARPInProgress = errors.New("no ARP in progress")
errARPResponsePending = errors.New("ARP response pending")
errARPRequestPending = errors.New("ARP request not yet sent")
)
/*
ARP PortStack state machine:
# ARP User Request (outgoing request)
PortStack.arpResult contains state:
1. Upon user request `BeginResolveARPv4`: PortStack.arpResult.Operation = 1 (request), which means request has not been sent out.
2. Upon `handleARP`: PortStack.arpResult.Operation = 0xffff (wait)
3. Upon corresponding ARP reply in `recvARP`: PortStack.arpResult.Operation = 2 (reply).
If `BeginResolveARPv4` is called at any point in resolution, the state is reset to 1.
# External ARP incoming request
PortStack.pendingARPresponse contains state:
1. Upon ARP request in `recvARP`, case 1: Store outgoing ARP response in PortStack.pendingARPresponse. PortStack.pendingARPresponse.Operation = 2 (reply)
2. Upon `handleARP`: Packet sent out. PortStack.pendingARPresponse.Operation = 0 (no pending response) Ready to receive.
*/
type arpClient struct {
stack *PortStack
result eth.ARPv4Header
pendingResponse eth.ARPv4Header
}
func (c *arpClient) ResultAs6() (netip.Addr, [6]byte, error) {
switch c.result.Operation {
case 0:
return netip.Addr{}, [6]byte{}, errNoARPInProgress
case 1:
return netip.Addr{}, [6]byte{}, errARPRequestPending
case arpOpWait:
return netip.Addr{}, [6]byte{}, errARPResponsePending
}
return netip.AddrFrom4(c.result.ProtoSender), c.result.HardwareSender, nil
}
func (c *arpClient) BeginResolve(addr netip.Addr) error {
if !addr.Is4() {
return errIPVersion
}
c.result = eth.ARPv4Header{
Operation: 1, // Request.
HardwareType: 1, // Ethernet.
ProtoType: uint16(eth.EtherTypeIPv4),
HardwareLength: 6,
ProtoLength: 4,
HardwareSender: c.stack.HardwareAddr6(),
ProtoSender: c.stack.ip,
HardwareTarget: [6]byte{}, // Zeroes, is filled by target.
ProtoTarget: addr.As4(),
}
return nil
}
func (c *arpClient) Abort() {
c.result = eth.ARPv4Header{}
}
func (c *arpClient) IsDone() bool {
return c.result.Operation == 2
}
func (c *arpClient) isPending() bool {
return c.pendingReplyToARP() || c.pendingOutReqARPv4()
}
func (c *arpClient) pendingReplyToARP() bool {
return c.pendingResponse.Operation == 2 // 2 means reply.
}
func (c *arpClient) pendingOutReqARPv4() bool {
return c.result.Operation == 1 // User asked for a ARP request.
}
func (c *arpClient) handle(dst []byte) (n int) {
pendingOutReq := c.pendingOutReqARPv4()
switch {
case pendingOutReq:
// We have a pending request from user to perform ARP.
ehdr := eth.EthernetHeader{
Destination: eth.BroadcastHW6(),
Source: c.stack.HardwareAddr6(),
SizeOrEtherType: uint16(eth.EtherTypeARP),
}
ehdr.Put(dst)
c.result.Put(dst[eth.SizeEthernetHeader:])
c.result.Operation = arpOpWait // Clear pending ARP to not loop.
n = eth.SizeEthernetHeader + eth.SizeARPv4Header
case c.pendingReplyToARP():
// We need to respond to an ARP request that queries our address.
ehdr := eth.EthernetHeader{
Destination: c.pendingResponse.HardwareTarget,
Source: c.stack.HardwareAddr6(),
SizeOrEtherType: uint16(eth.EtherTypeARP),
}
ehdr.Put(dst)
c.pendingResponse.Put(dst[eth.SizeEthernetHeader:])
c.pendingResponse.Operation = 0 // Clear pending ARP.
n = eth.SizeEthernetHeader + eth.SizeARPv4Header
default:
// return 0 // Nothing to do, n=0.
}
if n > 0 && c.stack.isLogEnabled(slog.LevelDebug) {
c.stack.debug("ARP:send", slog.Bool("isReply", !pendingOutReq))
}
return n
}
func (c *arpClient) recv(ahdr *eth.ARPv4Header) error {
if ahdr.HardwareLength != 6 || ahdr.ProtoLength != 4 || ahdr.HardwareType != 1 || ahdr.AssertEtherType() != eth.EtherTypeIPv4 {
return errARPUnsupported // Ignore ARP unsupported requests.
}
switch ahdr.Operation {
case 1: // We received ARP request.
if c.pendingReplyToARP() || ahdr.ProtoTarget != c.stack.ip {
return nil // ARP reply pending or not for us.
}
// We need to respond to this ARP request by inverting Sender/Target fields.
ahdr.HardwareTarget = ahdr.HardwareSender
ahdr.ProtoTarget = ahdr.ProtoSender
ahdr.HardwareSender = c.stack.HardwareAddr6()
ahdr.ProtoSender = c.stack.ip
ahdr.Operation = 2 // Set as reply. This also flags the packet as pending.
c.pendingResponse = *ahdr
case 2: // We received ARP reply.
if c.result.Operation != arpOpWait || // Result already received
ahdr.ProtoTarget != c.stack.ip || // Not meant for us.
ahdr.ProtoSender != c.result.ProtoTarget { // does not correspond to last request.
return nil
}
c.result = *ahdr
default:
return errARPUnsupported
}
if c.stack.isLogEnabled(slog.LevelDebug) {
c.stack.debug("ARP:recv", slog.Int("op", int(ahdr.Operation)))
}
return nil
}