-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
350 lines (294 loc) · 9.61 KB
/
main.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
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
package main
import (
"encoding/json"
"fmt"
"net"
"os"
"strings"
"time"
)
// Message represents a BPDU or data message sent on the wire
type Message struct {
Source string `json:"source"`
Dest string `json:"dest"`
Type string `json:"type"` // this could be an enum
Message map[string]interface{} `json:"message"`
}
// BPDU represents the core information of a BPDU message
type BPDU struct {
RootID string `json:"root"`
Cost int `json:"cost"`
BridgeID string `json:"id"`
}
// LANForwardingEntry represents a LANID to forward data packets to and the time the entry was created
type LANForwardingEntry struct {
LANID string
CreatedAt time.Time
}
// IncomingBPDU represents a BPDU Message and the LANID it came from
type IncomingBPDU struct {
BPDU Message
LANID string
}
// BPDUTableEntry represents a BPDU, the LANID it came from, and the time it was created at
type BPDUTableEntry struct {
BPDU BPDU
IncomingLAN string
CreatedAt time.Time
}
// BPDUTableKey represents the BridgeID and LANID that a BPDU came from
type BPDUTableKey struct {
BridgeID string
LANID string
}
// global state variables
// LANConns maps LANIDs to socket Conns
var LANConns = make(map[string]net.Conn)
// initialBPDU contains our starting BPDU message that is initially broadcast to other bridges
var initialBPDU BPDU
// outgoingBPDU contains the best BPDU message that we have seen, that we will currently broadcast to other bridges
var outgoingBPDU BPDU
// deignatedBridgeID contains the bridgeID of the next bridge that leads us to the root
var designatedBridgeID string
// rootPort contains the port (LANID) that leads us to the root
var rootPort string
// enabledLANConns maps LANIDs to booleans indicating whether we send and receive data for them
var enabledLANConns = make(map[string]bool)
// baseLANs contains all of the LANIDs that are connected to our bridge at startup
var baseLANs = []string{}
// forwardingTableMap maps LANIDs to their forwarding table entries, indicating where we should send data packets
var forwardingTableMap = make(map[string]LANForwardingEntry)
// BPDUTable keeps track of all the BPDUs we have seen for a given (BridgeID, LANID) pair and when we have seen them
// this allows us to easily compute the best scoring BPDU we have seen recently
var BPDUTable = make(map[BPDUTableKey]BPDUTableEntry)
// receivedBPDUs acts as a singular store for BPDUs that we have received from LANs so one routine can process them
var receivedBPDUs = make(chan IncomingBPDU)
// adds null bytes to the LANID so we can use it as a unix domain socket
func padLANID(LANID string) (paddedID string) {
return fmt.Sprintf("%c", '\x00') + LANID + strings.Repeat(fmt.Sprintf("%c", '\x00'), 106-len(LANID))
}
func main() {
myBridgeID := os.Args[1]
designatedBridgeID = myBridgeID
outgoingBPDU = BPDU{myBridgeID, 0, myBridgeID}
initialBPDU = BPDU{myBridgeID, 0, myBridgeID}
LANIDs := os.Args[2:]
fmt.Printf("Bridge %s starting up\n", myBridgeID)
for _, LANID := range LANIDs {
// ignore duplicate LANIDs
if _, ok := LANConns[LANID]; ok {
continue
}
conn, err := net.Dial("unixpacket", padLANID(LANID))
if err != nil {
fmt.Println("TERRIBLE ERROR")
fmt.Println(err)
}
LANConns[LANID] = conn
enabledLANConns[LANID] = true
baseLANs = append(baseLANs, LANID)
}
go func() {
for {
broadcastBPDU(outgoingBPDU)
time.Sleep(500 * time.Millisecond)
}
}()
for LANID, LANConn := range LANConns {
go listenForMessage(LANID, LANConn)
}
for {
incomingBPDU := <-receivedBPDUs
BPDUMessage := incomingBPDU.BPDU.Message
cost := BPDUMessage["cost"].(float64)
intCost := int(cost)
receivedBPDU := BPDU{RootID: BPDUMessage["root"].(string),
Cost: intCost,
BridgeID: BPDUMessage["id"].(string),
}
// build potential table entry
tableKey := BPDUTableKey{
BridgeID: receivedBPDU.BridgeID,
LANID: incomingBPDU.LANID,
}
potentialTableEntry := BPDUTableEntry{
BPDU: receivedBPDU,
IncomingLAN: incomingBPDU.LANID,
CreatedAt: time.Now(),
}
// check for previous entry with lesser LANID
BPDUTable[tableKey] = potentialTableEntry
var currentBestBPDU BPDU
newEnabledLANConns, newRootPort, currentBestBPDU := updateBPDU()
if compare(newEnabledLANConns, enabledLANConns) ||
newRootPort != rootPort {
for k := range forwardingTableMap {
delete(forwardingTableMap, k)
}
}
if newRootPort != rootPort {
fmt.Printf("New Root: %s/%s\n", initialBPDU.BridgeID, newRootPort)
}
for port, enabled := range newEnabledLANConns {
// if newly enabled or disabled
if enabled != enabledLANConns[port] && port != newRootPort {
if enabled {
fmt.Printf("Designated Port: %s/%s\n", initialBPDU.BridgeID, port)
} else {
fmt.Printf("Disabled Port: %s/%s\n", initialBPDU.BridgeID, port)
}
}
}
enabledLANConns = newEnabledLANConns
rootPort = newRootPort
bestCost := currentBestBPDU.Cost
if initialBPDU.RootID != currentBestBPDU.RootID {
bestCost++
}
outgoingBPDU = BPDU{
RootID: currentBestBPDU.RootID,
Cost: bestCost,
BridgeID: initialBPDU.BridgeID,
}
}
}
// reads in messages from a LAN connection, parsing BPDUs and forwarding data messages
func listenForMessage(LANID string, LANConn net.Conn) {
d := json.NewDecoder(LANConn)
for {
var unknownMessage Message
err := d.Decode(&unknownMessage)
if err != nil {
fmt.Printf("horrible error")
panic(err)
}
if unknownMessage.Type == "bpdu" {
receivedBPDUs <- IncomingBPDU{BPDU: unknownMessage,
LANID: LANID,
}
} else {
fmt.Printf("Received message %v on port %s from %s to %s\n", unknownMessage.Message["id"], LANID, unknownMessage.Source, unknownMessage.Dest)
if enabledLANConns[LANID] {
forwardingTableMap[unknownMessage.Source] = LANForwardingEntry{
LANID: LANID,
CreatedAt: time.Now(),
}
sendData(unknownMessage, LANID)
}
}
}
}
// forwards or broadcasts data messages
func sendData(message Message, incomingLan string) {
if tableEntry, ok := forwardingTableMap[message.Dest]; ok && time.Since(tableEntry.CreatedAt).Seconds() < 5.0 {
if tableEntry.LANID != incomingLan { // if where we would forward to is where we got the message from
conn, _ := LANConns[tableEntry.LANID]
bytes, _ := json.Marshal(message)
fmt.Fprintf(conn, string(bytes))
fmt.Printf("Forwarding message %s to port %s", message.Message["id"], tableEntry.LANID)
} else {
fmt.Printf("Not forwarding message %s", message.Message["id"])
}
} else { // we don't know where to send our message, so we send it everywhere except the incoming port
fmt.Printf("Broadcasting message %s to all ports", message.Message["id"])
// for each active port, send the message
for k, v := range enabledLANConns {
if k != incomingLan && v {
conn, _ := LANConns[k]
bytes, _ := json.Marshal(message)
fmt.Fprintf(conn, string(bytes))
}
}
}
}
// broadcasts BPDUs on all LANs
func broadcastBPDU(bpdu BPDU) {
dataMessage := make(map[string]interface{})
dataMessage["id"] = outgoingBPDU.BridgeID
dataMessage["root"] = outgoingBPDU.RootID
dataMessage["cost"] = outgoingBPDU.Cost
message := Message{Source: bpdu.BridgeID,
Dest: "ffff",
Type: "bpdu",
Message: dataMessage}
for LANID, conn := range LANConns {
bytes, err := json.Marshal(message)
if err != nil {
fmt.Println("marshal error")
fmt.Println(err)
}
fmt.Println(LANID, "sent bpdu")
fmt.Fprintf(conn, string(bytes))
}
}
// computes enabled/disabledLANs, our designated port/LANID, and the BPDU we should broadcast to others
func updateBPDU() (enabledLANs map[string]bool, currentBestLANID string, currentBestBPDU BPDU) {
currentBestBPDU = initialBPDU
BPDULans := make(map[string][]BPDU)
enabledLANs = make(map[string]bool)
// initialize BPDULans
for _, LANID := range baseLANs {
BPDULans[LANID] = []BPDU{}
}
// delete expired entries
for key, tableEntry := range BPDUTable {
if time.Since(tableEntry.CreatedAt).Seconds()*1000.0 > 750.0 {
delete(BPDUTable, key)
} else { // compare to find best one
BPDULans[tableEntry.IncomingLAN] = append(BPDULans[tableEntry.IncomingLAN], tableEntry.BPDU)
if (currentBestBPDU.RootID < tableEntry.BPDU.RootID) ||
(currentBestBPDU.RootID == tableEntry.BPDU.RootID &&
currentBestBPDU.Cost < tableEntry.BPDU.Cost) ||
(currentBestBPDU.RootID == tableEntry.BPDU.RootID &&
currentBestBPDU.Cost == tableEntry.BPDU.Cost &&
currentBestBPDU.BridgeID < tableEntry.BPDU.BridgeID) {
// do nothing
} else {
currentBestBPDU = tableEntry.BPDU
currentBestLANID = tableEntry.IncomingLAN
}
}
}
for LANID, BPDUs := range BPDULans {
if len(BPDUs) == 0 {
// enable LANs we got no BPDUs from
enabledLANs[LANID] = true
} else {
lowestCostBPDU := lowestCost(BPDUs, currentBestBPDU.RootID)
if currentBestBPDU.Cost+1 < lowestCostBPDU.Cost ||
(currentBestBPDU.Cost+1 == lowestCostBPDU.Cost &&
initialBPDU.BridgeID < lowestCostBPDU.BridgeID) {
// we are designated
enabledLANs[LANID] = true
} else {
enabledLANs[LANID] = false
}
}
}
// enable our root port
if currentBestLANID != "" {
enabledLANs[currentBestLANID] = true
}
return
}
// computes the lowest cost BPDU from a list of BPDUs for a given rootID
func lowestCost(BPDUList []BPDU, rootID string) BPDU {
var lowestCostBPDU BPDU
foundThing := false
for _, b := range BPDUList {
if b.RootID != rootID {
continue
}
if !foundThing {
lowestCostBPDU = b
foundThing = true
} else {
if b.Cost < lowestCostBPDU.Cost ||
(b.Cost == lowestCostBPDU.Cost &&
b.BridgeID < lowestCostBPDU.BridgeID) {
lowestCostBPDU = b
}
}
}
return lowestCostBPDU
}