-
Notifications
You must be signed in to change notification settings - Fork 1
/
router.go
213 lines (177 loc) · 4.87 KB
/
router.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
package netem
//
// Packet routing
//
import (
"errors"
"sync"
)
// RouterPort is a port of a [Router]. The zero value is invalid, use
// the [NewRouterPort] constructor to instantiate.
type RouterPort struct {
// closeOnce provides once semantics for the Close method
closeOnce sync.Once
// closed is closed when we close this port
closed chan any
// ifaceName is the interface name
ifaceName string
// logger is the logger to use
logger Logger
// outgoingMu protects outgoingQueue
outgoingMu sync.Mutex
// outgoingNotify is posted each time a new packet is queued
outgoingNotify chan any
// outgoingQueue is the outgoing queue
outgoingQueue [][]byte
// router is the router.
router *Router
}
// NewRouterPort creates a new [RouterPort] for a given [Router].
func NewRouterPort(router *Router) *RouterPort {
const maxNotifications = 1024
port := &RouterPort{
closeOnce: sync.Once{},
closed: make(chan any),
logger: router.logger,
ifaceName: newNICName(),
outgoingMu: sync.Mutex{},
outgoingNotify: make(chan any, maxNotifications),
outgoingQueue: [][]byte{},
router: router,
}
port.logger.Debugf("netem: ifconfig %s up", port.ifaceName)
return port
}
var _ NIC = &RouterPort{}
// writeOutgoingPacket is the function a [Router] calls
// to write an outgoing packet of this port.
func (sp *RouterPort) writeOutgoingPacket(packet []byte) error {
// enqueue
sp.outgoingMu.Lock()
sp.outgoingQueue = append(sp.outgoingQueue, packet)
sp.outgoingMu.Unlock()
// notify
select {
case <-sp.closed:
return ErrStackClosed
case sp.outgoingNotify <- true:
return nil
default:
return ErrPacketDropped
}
}
// FrameAvailable implements NIC
func (sp *RouterPort) FrameAvailable() <-chan any {
return sp.outgoingNotify
}
// ReadFrameNonblocking implements NIC
func (sp *RouterPort) ReadFrameNonblocking() (*Frame, error) {
// honour the port-closed flag
select {
case <-sp.closed:
return nil, ErrStackClosed
default:
// fallthrough
}
// check whether we can read from the queue
defer sp.outgoingMu.Unlock()
sp.outgoingMu.Lock()
if len(sp.outgoingQueue) <= 0 {
return nil, ErrNoPacket
}
// dequeue packet
packet := sp.outgoingQueue[0]
sp.outgoingQueue = sp.outgoingQueue[1:]
// wrap packet with a frame
frame := NewFrame(packet)
return frame, nil
}
// StackClosed implements NIC
func (sp *RouterPort) StackClosed() <-chan any {
return sp.closed
}
// Close implements NIC
func (sp *RouterPort) Close() error {
sp.closeOnce.Do(func() {
sp.logger.Debugf("netem: ifconfig %s down", sp.ifaceName)
close(sp.closed)
})
return nil
}
// IPAddress implements NIC
func (sp *RouterPort) IPAddress() string {
return "0.0.0.0"
}
// InterfaceName implements NIC
func (sp *RouterPort) InterfaceName() string {
return sp.ifaceName
}
// ErrPacketDropped indicates that a packet was dropped.
var ErrPacketDropped = errors.New("netem: packet was dropped")
// WriteFrame implements NIC
func (sp *RouterPort) WriteFrame(frame *Frame) error {
return sp.router.tryRoute(frame)
}
// Router routes traffic between [RouterPort]s. The zero value of this
// structure isn't invalid; construct using [NewRouter].
type Router struct {
// logger is the Logger we're using.
logger Logger
// mu provides mutual exclusion.
mu sync.Mutex
// table is the routing table.
table map[string]*RouterPort
}
// NewRouter creates a new [Router] instance.
func NewRouter(logger Logger) *Router {
return &Router{
logger: logger,
mu: sync.Mutex{},
table: map[string]*RouterPort{},
}
}
// AddRoute adds a route to the routing table.
func (r *Router) AddRoute(destIP string, destPort *RouterPort) {
r.logger.Debugf("netem: route add %s/32 %s", destIP, destPort.ifaceName)
r.mu.Lock()
r.table[destIP] = destPort
r.mu.Unlock()
}
// tryRoute attempts to route a raw packet.
func (r *Router) tryRoute(frame *Frame) error {
// parse the packet
packet, err := DissectPacket(frame.Payload)
if err != nil {
r.logger.Warnf("netem: tryRoute: %s", err.Error())
return err
}
// check whether we should drop this packet
if ttl := packet.TimeToLive(); ttl <= 0 {
r.logger.Warn("netem: tryRoute: TTL exceeded in transit")
return ErrPacketDropped
}
packet.DecrementTimeToLive()
// check whether we should spoof packets
if frame.Flags&FrameFlagSpoof != 0 {
for _, spoofed := range frame.Spoofed {
_ = r.tryRoute(NewFrame(spoofed))
}
// fallthrough
}
// figure out the interface where to emit the packet
destAddr := packet.DestinationIPAddress()
r.mu.Lock()
destPort := r.table[destAddr]
r.mu.Unlock()
if destPort == nil {
r.logger.Warnf("netem: tryRoute: %s: no route to host", destAddr)
return ErrPacketDropped
}
// serialize a TCP or UDP packet while ignoring other protocols
rawOutput, err := packet.Serialize()
if err != nil {
r.logger.Warnf("netem: tryRoute: %s", err.Error())
return err
}
return destPort.writeOutgoingPacket(rawOutput)
}