/
dv_router.py
221 lines (187 loc) · 8.71 KB
/
dv_router.py
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
"""
Your awesome Distance Vector router for CS 168
Based on skeleton code by:
MurphyMc, zhangwen0411, lab352
"""
import sim.api as api
from cs168.dv import RoutePacket, \
Table, TableEntry, \
DVRouterBase, Ports, \
FOREVER, INFINITY
class DVRouter(DVRouterBase):
# A route should time out after this interval
ROUTE_TTL = 15
# Dead entries should time out after this interval
GARBAGE_TTL = 10
# -----------------------------------------------
# At most one of these should ever be on at once
SPLIT_HORIZON = False
POISON_REVERSE = False
# -----------------------------------------------
# Determines if you send poison for expired routes
POISON_EXPIRED = False
# Determines if you send updates when a link comes up
SEND_ON_LINK_UP = False
# Determines if you send poison when a link goes down
POISON_ON_LINK_DOWN = False
def __init__(self):
"""
Called when the instance is initialized.
DO NOT remove any existing code from this method.
However, feel free to add to it for memory purposes in the final stage!
"""
assert not (self.SPLIT_HORIZON and self.POISON_REVERSE), \
"Split horizon and poison reverse can't both be on"
self.start_timer() # Starts signaling the timer at correct rate.
# Contains all current ports and their latencies.
# See the write-up for documentation.
self.ports = Ports()
# This is the table that contains all current routes
self.table = Table()
self.table.owner = self
self.history = {}
def add_static_route(self, host, port):
"""
Adds a static route to this router's table.
Called automatically by the framework whenever a host is connected
to this router.
:param host: the host.
:param port: the port that the host is attached to.
:returns: nothing.
"""
# `port` should have been added to `peer_tables` by `handle_link_up`
# when the link came up.
assert port in self.ports.get_all_ports(), "Link should be up, but is not."
# TODO: fill this in!
self.table[host] = TableEntry(host, port, self.ports.get_latency(port), FOREVER)
def handle_data_packet(self, packet, in_port):
"""
Called when a data packet arrives at this router.
You may want to forward the packet, drop the packet, etc. here.
:param packet: the packet that arrived.
:param in_port: the port from which the packet arrived.
:return: nothing.
"""
# TODO: fill this in!
for host, entry in self.table.items(): # iterate through my table
if packet.dst in entry:
if entry.latency < INFINITY:
self.send(packet, entry.port, False)
"""
Helper functions that will decide what to advertise. It will either not advertise or advertise a latency of infinity.
If the algorithm decides to advertise, the sendAd will send the advertisement
"""
def skipOrSend(self, advertisedPort, currPort, sh, pr):
if sh is True and pr is False:
return self.SPLIT_HORIZON is True and currPort == advertisedPort
else:
return self.POISON_REVERSE is True and currPort == advertisedPort
def isSame(self, force, entry, port, latency):
return force is False and entry.dst in self.history and port in self.history[entry.dst] and self.history[entry.dst][port] == latency
def sendAd(self, port, dst, latency):
self.history.setdefault(dst, {})[port] = latency
self.send_route(port, dst, latency)
def send_routes(self, force=False, single_port=None):
"""
Send route advertisements for all routes in the table.
:param force: if True, advertises ALL routes in the table;
otherwise, advertises only those routes that have
changed since the last advertisement.
single_port: if not None, sends updates only to that port; to
be used in conjunction with handle_link_up.
:return: nothing.
"""
# TODO: fill this in!
if single_port is not None:
for entry in self.table.values():
latency = entry.latency
self.sendAd(single_port, host, latency)
else:
for host, entry in self.table.items():
for port in self.ports.get_all_ports():
latency = entry.latency
switcher = {
1: self.skipOrSend(port, entry.port, True, False),
2: self.skipOrSend(port, entry.port, False, True),
3: self.isSame(force, entry, port, latency)
}
splitHorizon = switcher.get(1, lambda: "Invalid Choice")
positionReverse = switcher.get(2, lambda: "Invalid Choice")
checkHistory = switcher.get(3, lambda: "Invalid Choice")
if splitHorizon:
continue
if positionReverse:
latency = INFINITY
if self.isSame(force, entry, port, latency) or checkHistory:
continue
self.sendAd(port, host, latency)
# Will delete the routes that I appended to toDelete
def deleteRoutes(self, toDelete):
for host in toDelete:
del self.table[host]
def expire_routes(self):
"""
Clears out expired routes from table.
accordingly.
"""
# TODO: fill this in!
toDelete = []
if self.POISON_EXPIRED is True:
for host, entry in self.table.items():
if entry.has_expired:
self.table[host] = TableEntry(host, entry.port, INFINITY, api.current_time())
else:
for host, entry in self.table.items():
if entry.has_expired:
toDelete.append(host)
self.deleteRoutes(toDelete)
def handle_route_advertisement(self, route_dst, route_latency, port):
"""
Called when the router receives a route advertisement from a neighbor.
:param route_dst: the destination of the advertised route.
:param route_latency: latency from the neighbor to the destination.
:param port: the port that the advertisement arrived on.
:return: nothing.
"""
# TODO: fill this in!
# if its not in the table entry then I need to add it since it is a new destination
if route_dst not in self.table.keys():
self.table[route_dst] = TableEntry(route_dst, port, self.ports.get_latency(port) + route_latency, api.current_time() + self.ROUTE_TTL)
else:
for host, entry in self.table.items():
if route_dst == host: # if my destination is in my table entry then maybe I have found a better path and must update my existing path
if port == entry.port and route_latency >= INFINITY:
self.table[host] = TableEntry(route_dst, port, INFINITY, api.current_time())
self.send_routes(False)
elif port == entry.port or entry.latency > route_latency + self.ports.get_latency(port):
self.table[host] = TableEntry(route_dst, port, route_latency + self.ports.get_latency(port), api.current_time() + self.ROUTE_TTL)
self.send_routes(False)
def handle_link_up(self, port, latency):
"""
Called by the framework when a link attached to this router goes up.
:param port: the port that the link is attached to.
:param latency: the link latency.
:returns: nothing.
"""
self.ports.add_port(port, latency)
# TODO: fill in the rest!
if self.SEND_ON_LINK_UP:
self.send_routes(False, port);
def handle_link_down(self, port):
"""
Called by the framework when a link attached to this router does down.
:param port: the port number used by the link.
:returns: nothing.
"""
self.ports.remove_port(port)
# TODO: fill this in!
toDelete = []
for host, entry in self.table.items():
if entry.port == port:
if self.POISON_ON_LINK_DOWN:
self.table[host] = TableEntry(host, port, INFINITY, api.current_time() + self.ROUTE_TTL)
self.send_routes(False);
else:
toDelete.append(host)
self.deleteRoutes(toDelete)
# Feel free to add any helper methods!