diff --git a/.gitignore b/.gitignore index 2c7a47d..b0bf8cd 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,9 @@ lib/python* \.pydevproject \.project +.idea/workspace.xml +.idea/vcs.xml +.idea/modules.xml +.idea/misc.xml +.idea/encodings.xml +.idea/CSCI_466_Programming_Assignments.iml diff --git a/.project b/.project new file mode 100644 index 0000000..01a42ac --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + CSCI_466_PAs + + + + + + org.python.pydev.PyDevBuilder + + + + + + org.python.pydev.pythonNature + + diff --git a/.pydevproject b/.pydevproject new file mode 100644 index 0000000..2899bac --- /dev/null +++ b/.pydevproject @@ -0,0 +1,8 @@ + + + +/${PROJECT_DIR_NAME} + +python 3.6 +python37 + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..179bf68 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +encoding/network.py=UTF-8 +encoding/network_1.py=UTF-8 diff --git a/images/Drawing2.vsdx b/images/Drawing2.vsdx new file mode 100644 index 0000000..ada3628 Binary files /dev/null and b/images/Drawing2.vsdx differ diff --git a/images/network.png b/images/network.png new file mode 100644 index 0000000..43e9cbc Binary files /dev/null and b/images/network.png differ diff --git a/link.py b/link_1.py similarity index 100% rename from link.py rename to link_1.py diff --git a/link_2.py b/link_2.py new file mode 100644 index 0000000..e488016 --- /dev/null +++ b/link_2.py @@ -0,0 +1,75 @@ +import queue +import threading + +## An abstraction of a link between router interfaces +class Link: + + ## creates a link between two objects by looking up and linking node interfaces. + # @param node_1: node from which data will be transfered + # @param node_1_intf: number of the interface on that node + # @param node_2: node to which data will be transfered + # @param node_2_intf: number of the interface on that node + def __init__(self, node_1, node_1_intf, node_2, node_2_intf): + self.node_1 = node_1 + self.node_1_intf = node_1_intf + self.node_2 = node_2 + self.node_2_intf = node_2_intf + print('Created link %s' % self.__str__()) + + ## called when printing the object + def __str__(self): + return 'Link %s-%d - %s-%d' % (self.node_1, self.node_1_intf, self.node_2, self.node_2_intf) + + ##transmit a packet between interfaces in each direction + def tx_pkt(self): + for (node_a, node_a_intf, node_b, node_b_intf) in \ + [(self.node_1, self.node_1_intf, self.node_2, self.node_2_intf), + (self.node_2, self.node_2_intf, self.node_1, self.node_1_intf)]: + intf_a = node_a.intf_L[node_a_intf] + intf_b = node_b.intf_L[node_b_intf] + pkt_S = intf_a.get('out') + if pkt_S is None: + continue #continue if no packet to transfer + #otherwise transmit the packet + try: + intf_b.put(pkt_S, 'in') + print('%s: direction %s-%s -> %s-%s: transmitting packet "%s"' % \ + (self, node_a, node_a_intf, node_b, node_b_intf, pkt_S)) + except queue.Full: + print('%s: direction %s-%s -> %s-%s: packet lost' % \ + (self, node_a, node_a_intf, node_b, node_b_intf)) + pass + + +## An abstraction of the link layer +class LinkLayer: + + def __init__(self): + ## list of links in the network + self.link_L = [] + self.stop = False #for thread termination + + ## called when printing the object + def __str__(self): + return 'Network' + + ##add a Link to the network + def add_link(self, link): + self.link_L.append(link) + + ##transfer a packet across all links + def transfer(self): + for link in self.link_L: + link.tx_pkt() + + ## thread target for the network to keep transmitting data across links + def run(self): + print (threading.currentThread().getName() + ': Starting') + while True: + #transfer one packet on all the links + self.transfer() + #terminate + if self.stop: + print (threading.currentThread().getName() + ': Ending') + return + \ No newline at end of file diff --git a/link_3.py b/link_3.py new file mode 100644 index 0000000..e488016 --- /dev/null +++ b/link_3.py @@ -0,0 +1,75 @@ +import queue +import threading + +## An abstraction of a link between router interfaces +class Link: + + ## creates a link between two objects by looking up and linking node interfaces. + # @param node_1: node from which data will be transfered + # @param node_1_intf: number of the interface on that node + # @param node_2: node to which data will be transfered + # @param node_2_intf: number of the interface on that node + def __init__(self, node_1, node_1_intf, node_2, node_2_intf): + self.node_1 = node_1 + self.node_1_intf = node_1_intf + self.node_2 = node_2 + self.node_2_intf = node_2_intf + print('Created link %s' % self.__str__()) + + ## called when printing the object + def __str__(self): + return 'Link %s-%d - %s-%d' % (self.node_1, self.node_1_intf, self.node_2, self.node_2_intf) + + ##transmit a packet between interfaces in each direction + def tx_pkt(self): + for (node_a, node_a_intf, node_b, node_b_intf) in \ + [(self.node_1, self.node_1_intf, self.node_2, self.node_2_intf), + (self.node_2, self.node_2_intf, self.node_1, self.node_1_intf)]: + intf_a = node_a.intf_L[node_a_intf] + intf_b = node_b.intf_L[node_b_intf] + pkt_S = intf_a.get('out') + if pkt_S is None: + continue #continue if no packet to transfer + #otherwise transmit the packet + try: + intf_b.put(pkt_S, 'in') + print('%s: direction %s-%s -> %s-%s: transmitting packet "%s"' % \ + (self, node_a, node_a_intf, node_b, node_b_intf, pkt_S)) + except queue.Full: + print('%s: direction %s-%s -> %s-%s: packet lost' % \ + (self, node_a, node_a_intf, node_b, node_b_intf)) + pass + + +## An abstraction of the link layer +class LinkLayer: + + def __init__(self): + ## list of links in the network + self.link_L = [] + self.stop = False #for thread termination + + ## called when printing the object + def __str__(self): + return 'Network' + + ##add a Link to the network + def add_link(self, link): + self.link_L.append(link) + + ##transfer a packet across all links + def transfer(self): + for link in self.link_L: + link.tx_pkt() + + ## thread target for the network to keep transmitting data across links + def run(self): + print (threading.currentThread().getName() + ': Starting') + while True: + #transfer one packet on all the links + self.transfer() + #terminate + if self.stop: + print (threading.currentThread().getName() + ': Ending') + return + \ No newline at end of file diff --git a/network.py b/network.py deleted file mode 100644 index 0bb95f0..0000000 --- a/network.py +++ /dev/null @@ -1,223 +0,0 @@ -import queue -import threading - - -## wrapper class for a queue of packets -class Interface: - ## @param maxsize - the maximum size of the queue storing packets - def __init__(self, maxsize=0): - self.in_queue = queue.Queue(maxsize) - self.out_queue = queue.Queue(maxsize) - - ##get packet from the queue interface - # @param in_or_out - use 'in' or 'out' interface - def get(self, in_or_out): - try: - if in_or_out == 'in': - pkt_S = self.in_queue.get(False) - # if pkt_S is not None: - # print('getting packet from the IN queue') - return pkt_S - else: - pkt_S = self.out_queue.get(False) - # if pkt_S is not None: - # print('getting packet from the OUT queue') - return pkt_S - except queue.Empty: - return None - - ##put the packet into the interface queue - # @param pkt - Packet to be inserted into the queue - # @param in_or_out - use 'in' or 'out' interface - # @param block - if True, block until room in queue, if False may throw queue.Full exception - def put(self, pkt, in_or_out, block=False): - if in_or_out == 'out': - # print('putting packet in the OUT queue') - self.out_queue.put(pkt, block) - else: - # print('putting packet in the IN queue') - self.in_queue.put(pkt, block) - - -## Implements a network layer packet. -class NetworkPacket: - ## packet encoding lengths - dst_S_length = 5 - prot_S_length = 1 - - ##@param dst: address of the destination host - # @param data_S: packet payload - # @param prot_S: upper layer protocol for the packet (data, or control) - def __init__(self, dst, prot_S, data_S): - self.dst = dst - self.data_S = data_S - self.prot_S = prot_S - - ## called when printing the object - def __str__(self): - return self.to_byte_S() - - ## convert packet to a byte string for transmission over links - def to_byte_S(self): - byte_S = str(self.dst).zfill(self.dst_S_length) - if self.prot_S == 'data': - byte_S += '1' - elif self.prot_S == 'control': - byte_S += '2' - else: - raise('%s: unknown prot_S option: %s' %(self, self.prot_S)) - byte_S += self.data_S - return byte_S - - ## extract a packet object from a byte string - # @param byte_S: byte string representation of the packet - @classmethod - def from_byte_S(self, byte_S): - dst = byte_S[0 : NetworkPacket.dst_S_length].strip('0') - prot_S = byte_S[NetworkPacket.dst_S_length : NetworkPacket.dst_S_length + NetworkPacket.prot_S_length] - if prot_S == '1': - prot_S = 'data' - elif prot_S == '2': - prot_S = 'control' - else: - raise('%s: unknown prot_S field: %s' %(self, prot_S)) - data_S = byte_S[NetworkPacket.dst_S_length + NetworkPacket.prot_S_length : ] - return self(dst, prot_S, data_S) - - - - -## Implements a network host for receiving and transmitting data -class Host: - - ##@param addr: address of this node represented as an integer - def __init__(self, addr): - self.addr = addr - self.intf_L = [Interface()] - self.stop = False #for thread termination - - ## called when printing the object - def __str__(self): - return self.addr - - ## create a packet and enqueue for transmission - # @param dst: destination address for the packet - # @param data_S: data being transmitted to the network layer - def udt_send(self, dst, data_S): - p = NetworkPacket(dst, 'data', data_S) - print('%s: sending packet "%s"' % (self, p)) - self.intf_L[0].put(p.to_byte_S(), 'out') #send packets always enqueued successfully - - ## receive packet from the network layer - def udt_receive(self): - pkt_S = self.intf_L[0].get('in') - if pkt_S is not None: - print('%s: received packet "%s"' % (self, pkt_S)) - - ## thread target for the host to keep receiving data - def run(self): - print (threading.currentThread().getName() + ': Starting') - while True: - #receive data arriving to the in interface - self.udt_receive() - #terminate - if(self.stop): - print (threading.currentThread().getName() + ': Ending') - return - - - -## Implements a multi-interface router -class Router: - - ##@param name: friendly router name for debugging - # @param cost_D: cost table to neighbors {neighbor: {interface: cost}} - # @param max_queue_size: max queue length (passed to Interface) - def __init__(self, name, cost_D, max_queue_size): - self.stop = False #for thread termination - self.name = name - #create a list of interfaces - self.intf_L = [Interface(max_queue_size) for _ in range(len(cost_D))] - #save neighbors and interfeces on which we connect to them - self.cost_D = cost_D # {neighbor: {interface: cost}} - #TODO: set up the routing table for connected hosts - self.rt_tbl_D = {} # {destination: {router: cost}} - print('%s: Initialized routing table' % self) - self.print_routes() - - - ## Print routing table - def print_routes(self): - #TODO: print the routes as a two dimensional table - print(self.rt_tbl_D) - - - ## called when printing the object - def __str__(self): - return self.name - - - ## look through the content of incoming interfaces and - # process data and control packets - def process_queues(self): - for i in range(len(self.intf_L)): - pkt_S = None - #get packet from interface i - pkt_S = self.intf_L[i].get('in') - #if packet exists make a forwarding decision - if pkt_S is not None: - p = NetworkPacket.from_byte_S(pkt_S) #parse a packet out - if p.prot_S == 'data': - self.forward_packet(p,i) - elif p.prot_S == 'control': - self.update_routes(p, i) - else: - raise Exception('%s: Unknown packet type in packet %s' % (self, p)) - - - ## forward the packet according to the routing table - # @param p Packet to forward - # @param i Incoming interface number for packet p - def forward_packet(self, p, i): - try: - # TODO: Here you will need to implement a lookup into the - # forwarding table to find the appropriate outgoing interface - # for now we assume the outgoing interface is 1 - self.intf_L[1].put(p.to_byte_S(), 'out', True) - print('%s: forwarding packet "%s" from interface %d to %d' % \ - (self, p, i, 1)) - except queue.Full: - print('%s: packet "%s" lost on interface %d' % (self, p, i)) - pass - - - ## send out route update - # @param i Interface number on which to send out a routing update - def send_routes(self, i): - # TODO: Send out a routing table update - #create a routing table update packet - p = NetworkPacket(0, 'control', 'DUMMY_ROUTING_TABLE') - try: - print('%s: sending routing update "%s" from interface %d' % (self, p, i)) - self.intf_L[i].put(p.to_byte_S(), 'out', True) - except queue.Full: - print('%s: packet "%s" lost on interface %d' % (self, p, i)) - pass - - - ## forward the packet according to the routing table - # @param p Packet containing routing information - def update_routes(self, p, i): - #TODO: add logic to update the routing tables and - # possibly send out routing updates - print('%s: Received routing update %s from interface %d' % (self, p, i)) - - - ## thread target for the host to keep forwarding data - def run(self): - print (threading.currentThread().getName() + ': Starting') - while True: - self.process_queues() - if self.stop: - print (threading.currentThread().getName() + ': Ending') - return \ No newline at end of file diff --git a/network.pyc b/network.pyc new file mode 100644 index 0000000..9681158 Binary files /dev/null and b/network.pyc differ diff --git a/network_1.py b/network_1.py new file mode 100644 index 0000000..5c5f9b7 --- /dev/null +++ b/network_1.py @@ -0,0 +1,374 @@ +import queue +import threading +from collections import defaultdict +import sys +import math + + +# wrapper class for a queue of packets +class Interface: + # @param maxsize - the maximum size of the queue storing packets + def __init__(self, maxsize=0): + self.in_queue = queue.Queue(maxsize) + self.out_queue = queue.Queue(maxsize) + + # get packet from the queue interface + # @param in_or_out - use 'in' or 'out' interface + def get(self, in_or_out): + try: + if in_or_out == 'in': + pkt_S = self.in_queue.get(False) + # if pkt_S is not None: + # print('getting packet from the IN queue') + return pkt_S + else: + pkt_S = self.out_queue.get(False) + # if pkt_S is not None: + # print('getting packet from the OUT queue') + return pkt_S + except queue.Empty: + return None + + # put the packet into the interface queue + # @param pkt - Packet to be inserted into the queue + # @param in_or_out - use 'in' or 'out' interface + # @param block - if True, block until room in queue, if False may throw queue.Full exception + def put(self, pkt, in_or_out, block=False): + if in_or_out == 'out': + # print('putting packet in the OUT queue') + self.out_queue.put(pkt, block) + else: + # print('putting packet in the IN queue') + self.in_queue.put(pkt, block) + + +# Implements a network layer packet. +class NetworkPacket: + # packet encoding lengths + dst_S_length = 5 + prot_S_length = 1 + + ##@param dst: address of the destination host + # @param data_S: packet payload + # @param prot_S: upper layer protocol for the packet (data, or control) + def __init__(self, dst, prot_S, data_S): + self.dst = dst + self.data_S = data_S + self.prot_S = prot_S + + # called when printing the object + def __str__(self): + return self.to_byte_S() + + # convert packet to a byte string for transmission over links + def to_byte_S(self): + byte_S = str(self.dst).zfill(self.dst_S_length) + if self.prot_S == 'data': + byte_S += '1' + elif self.prot_S == 'control': + byte_S += '2' + else: + raise ('%s: unknown prot_S option: %s' % (self, self.prot_S)) + byte_S += self.data_S + return byte_S + + ## extract a packet object from a byte string + # @param byte_S: byte string representation of the packet + @classmethod + def from_byte_S(self, byte_S): + dst = byte_S[0: NetworkPacket.dst_S_length].strip('0') + prot_S = byte_S[NetworkPacket.dst_S_length: NetworkPacket.dst_S_length + NetworkPacket.prot_S_length] + if prot_S == '1': + prot_S = 'data' + elif prot_S == '2': + prot_S = 'control' + else: + raise ('%s: unknown prot_S field: %s' % (self, prot_S)) + data_S = byte_S[NetworkPacket.dst_S_length + NetworkPacket.prot_S_length:] + return self(dst, prot_S, data_S) + + +## Implements a network host for receiving and transmitting data +class Host: + + ##@param addr: address of this node represented as an integer + def __init__(self, addr): + self.addr = addr + self.intf_L = [Interface()] + self.stop = False # for thread termination + + ## called when printing the object + def __str__(self): + return self.addr + + ## create a packet and enqueue for transmission + # @param dst: destination address for the packet + # @param data_S: data being transmitted to the network layer + def udt_send(self, dst, data_S): + p = NetworkPacket(dst, 'data', data_S) + print('%s: sending packet "%s"' % (self, p)) + self.intf_L[0].put(p.to_byte_S(), 'out') # send packets always enqueued successfully + + ## receive packet from the network layer + def udt_receive(self): + pkt_S = self.intf_L[0].get('in') + if pkt_S is not None: + print('%s: received packet "%s"' % (self, pkt_S)) + + ## thread target for the host to keep receiving data + def run(self): + print(threading.currentThread().getName() + ': Starting') + while True: + # receive data arriving to the in interface + self.udt_receive() + # terminate + if (self.stop): + print(threading.currentThread().getName() + ': Ending') + return + + +## Implements a multi-interface router +class Router: + + ##@param name: friendly router name for debugging + # @param cost_D: cost table to neighbors {neighbor: {interface: cost}} + # @param max_queue_size: max queue length (passed to Interface) + def __init__(self, name, cost_D, max_queue_size): + self.stop = False # for thread termination + self.name = name + # create a list of interfaces + self.intf_L = [Interface(max_queue_size) for _ in range(len(cost_D))] + # save neighbors and interfeces on which we connect to them + self.cost_D = cost_D # cost_D {neighbor: {interface: cost}} + # TODO: set up the routing table for connected hosts + # {destination: {router: cost}} ##Initial setup + self.rt_tbl_D = {name: {name: 0}} + keys = list(cost_D.keys()) + values = list(cost_D.values()) + for i in range(len(keys)): + self.rt_tbl_D[keys[i]] = {name: list(values[i].values())[0]} + self.print_routes() + print('%s: Initialized routing table' % self) + + def getCurrentRoutingTable(self): + routingTableString = self.name + "-" + values = list(self.rt_tbl_D.values()) + keys = list(self.rt_tbl_D.keys()) + first = True + for i in range(len(keys)): + if first: + first = False + routingTableString += keys[i] + "," + str(list(values[i])[0]) + "," + str(list(values[i].values())[0]) + else: + routingTableString += ":" + keys[i] + "," + str(list(values[i])[0]) + "," + str( + list(values[i].values())[0]) + print(routingTableString) + return routingTableString + + ## Print routing table + def updateUniqueRouters(self): + self.uniqueRouters = [] + values = self.rt_tbl_D.values() + routers = {} + for i in range(len(values)): + if list(list(values)[i].keys())[0] not in routers: + routers[list(list(values)[i].keys())[0]] = "" + for item in routers: + self.uniqueRouters.append(item) + + def print_routes(self): + keys = self.rt_tbl_D.keys() + values = self.rt_tbl_D.values() + columns = len(keys) + 1 + keyString = "" + topTableString = "╒" + headerBottomTableString = "╞" + tableRowSeperator = "├" + tableBottom = "╘" + # //Setting up table + for i in range(columns): + if i + 1 != columns: + topTableString += "══════╤" + headerBottomTableString += "══════╪" + tableRowSeperator += "──────┼" + tableBottom += "══════╧" + else: + topTableString += "══════╕\n" + headerBottomTableString += "══════╡\n" + tableRowSeperator += "──────┤\n" + tableBottom += "══════╛\n" + itemSpace = " " + for item in keys: + keyString += " " + item + " │" + costRows = [] + changed = [] + self.updateUniqueRouters() + for item in self.uniqueRouters: + costRows.append("│ " + item + " │") + for i in range(len(values)): + changedFlag = False + for j in range(len(costRows)): + for k in range(len(list(values)[i].keys())): + if list(list(values)[i].keys())[k] == self.uniqueRouters[j]: + formattedVal = itemSpace[0:len(itemSpace) - len(str(list(list(values)[i].values())[k]))] + str( + list(list(values)[i].values())[k]) + costRows[j] += formattedVal + "│" + changed.append(j) + changedFlag = True + if changedFlag: + changedFlag = False + for l in range(len(costRows)): + if (l in changed): + continue + else: + costRows[l] += " │" + changed = [] + + sys.stdout.write(topTableString + "│ " + self.name + " │" + keyString + "\n" + headerBottomTableString) + for i in range(len(costRows)): + if i + 1 != len(costRows): + sys.stdout.write(costRows[i] + "\n" + tableRowSeperator) + else: + sys.stdout.write(costRows[i] + "\n") + sys.stdout.write(tableBottom) + + ## called when printing the object + def __str__(self): + return self.name + + ## look through the content of incoming interfaces and + # process data and control packets + def process_queues(self): + for i in range(len(self.intf_L)): + pkt_S = None + # get packet from interface i + pkt_S = self.intf_L[i].get('in') + # if packet exists make a forwarding decision + if pkt_S is not None: + p = NetworkPacket.from_byte_S(pkt_S) # parse a packet out + if p.prot_S == 'data': + self.forward_packet(p, i) + elif p.prot_S == 'control': + self.update_routes(p, i) + else: + raise Exception('%s: Unknown packet type in packet %s' % (self, p)) + + ## forward the packet according to the routing table + # @param p Packet to forward + # @param i Incoming interface number for packet p + def forward_packet(self, p, i): + try: + # TODO: Here you will need to implement a lookup into the + # forwarding table to find the appropriate outgoing interface + # for now we assume the outgoing interface is 1 + self.intf_L[1].put(p.to_byte_S(), 'out', True) # send out + print("Index out of range, {}".format(1)) + print('%s: forwarding packet "%s" from interface %d to %d' % \ + (self, p, i, 1)) + except queue.Full: + print('%s: packet "%s" lost on interface %d' % (self, p, i)) + pass + + ## send out route update + # @param i Interface number on which to send out a routing update + def send_routes(self, i): + # TODO: Send out a routing table update + # create a routing table update packet + p = NetworkPacket(0, 'control', self.getCurrentRoutingTable()) + try: + print('%s: sending routing update "%s" from interface %d' % (self, p, i)) + self.intf_L[i].put(p.to_byte_S(), 'out', True) + except queue.Full: + print('%s: packet "%s" lost on interface %d' % (self, p, i)) + pass + + ## forward the packet according to the routing table + # @param p Packet containing routing information + def update_routes(self, p, i): + # TODO: add logic to update the routing tables and + # possibly send out routing updates + updates = p.to_byte_S()[6:].split('-') + name = updates[0] + update = updates[1].split(":") + # Raw updating + for j in update: # for each update + items = j.split(",") # items: 0=dest 1 1=dest 2 2=cost between dest 1 and dest 2 + if items[0] in self.rt_tbl_D: # if dest 1 is in table headers + values = list(self.rt_tbl_D.values()) # values is a list of dicts of form {router: cost} + exists = False # assume that it doesn't exist + # already in table + for i in range(len(values)): # for as many values(which are mappings of dests to routers) + vks = list(values[i].keys()) # vks = list of routers in + for vk in vks: # for each router in the router list, + if vk == items[1]: # if the router is dest 2 + self.rt_tbl_D[items[0]][items[1]] = items[2] # set the cost of dest 1 to dest 2 in the table to the cost in items + self.rt_tbl_D[items[1]][items[0]] = items[2] # set the cost of dest 1 to dest 2 in the table to the cost in items + # do stuff/compare + exists = True + if not exists: # will always default to this + self.rt_tbl_D[items[0]][items[1]] = items[ + 2] # set the cost of dest 1 to dest 2 in the table to the cost in items + self.rt_tbl_D[items[1]][items[0]] = items[ + 2] # set the cost of dest 1 to dest 2 in the table to the cost in items + else: + self.rt_tbl_D[items[0]] = {items[1]: items[2]} + + '''for header in self.rt_tbl_D: #see if header is missing routers in its dict + for router in self.uniqueRouters: #for each router, + if router not in self.rt_tbl_D[header]: #if the router is NOT in the dict of the header + #put it in the header's dict, set cost to inf + self.rt_tbl_D[header][router] = 999 #basically infinity, right? + self.rt_tbl_D[router][header] = 999''' + + self.updateUniqueRouters() + # run the algorithm on each router in the table + router_count = len(self.uniqueRouters) + print(router_count) + for j in range(router_count): # for every router (row) in the network, + # step 1: set all unknowns to infinity + for header in self.rt_tbl_D: + # print("Detecting gaps for {} to {}".format(header,self.uniqueRouters[j])) + if self.uniqueRouters[j] not in self.rt_tbl_D[header]: # if the router is NOT in the dict of the header + # print("Gap filled {} to {}".format(header, self.uniqueRouters[j])) + # put it in the header's dict, set cost to inf + self.rt_tbl_D[header][self.uniqueRouters[j]] = 999 # basically infinity, right? + # {header: {router: cost}} + # bellman ford starts here + i = 1 + # http://courses.csail.mit.edu/6.006/spring11/lectures/lec15.pdf + # rt_tbl is a list of edges. + updated = False + # step 2: relax edges |V|-1 times + for i in range(len(self.rt_tbl_D)): + # for V-1 (the number of verticies minus one + for u in self.rt_tbl_D: + # relax edge, represented as a call with the header + # for each vertex's neighbor, + for v in self.rt_tbl_D[u]: # iterate through each outgoing edge + edge_distance = int(self.rt_tbl_D[u][v]) + u_dist = int(self.rt_tbl_D[u][self.uniqueRouters[j]]) # distance to u vertex + v_dist = int(self.rt_tbl_D[v][self.uniqueRouters[j]]) # distance to v vertex + try: + if (u_dist > (v_dist + edge_distance)): + # if the edge plus the distance to vertex v is greater than the distance to u + self.rt_tbl_D[u][ + self.uniqueRouters[j]] = v_dist + edge_distance # update the distance to u + updated = True + self.updateUniqueRouters() + except KeyError: + print("Key error exception occurred") + if updated: + # cost_D {neighbor: {interface: cost}} + for i in range(len(self.cost_D.values())): # for all values + for x in range(len(list(self.cost_D.values())[i].keys())): + interface = list(list(self.cost_D.values())[i].keys())[x] + self.send_routes(interface) + + ## thread target for the host to keep forwarding data + def run(self): + print(threading.currentThread().getName() + ': Starting') + while True: + self.process_queues() + if self.stop: + print(threading.currentThread().getName() + ': Ending') + return diff --git a/network_2.py b/network_2.py new file mode 100644 index 0000000..fc683ae --- /dev/null +++ b/network_2.py @@ -0,0 +1,420 @@ +import queue +import threading +from collections import defaultdict +import sys +import math + + +## wrapper class for a queue of packets +class Interface: + ## @param maxsize - the maximum size of the queue storing packets + def __init__(self, maxsize=0): + self.in_queue = queue.Queue(maxsize) + self.out_queue = queue.Queue(maxsize) + + ##get packet from the queue interface + # @param in_or_out - use 'in' or 'out' interface + def get(self, in_or_out): + try: + if in_or_out == 'in': + pkt_S = self.in_queue.get(False) + # if pkt_S is not None: + # print('getting packet from the IN queue') + return pkt_S + else: + pkt_S = self.out_queue.get(False) + # if pkt_S is not None: + # print('getting packet from the OUT queue') + return pkt_S + except queue.Empty: + return None + + ##put the packet into the interface queue + # @param pkt - Packet to be inserted into the queue + # @param in_or_out - use 'in' or 'out' interface + # @param block - if True, block until room in queue, if False may throw queue.Full exception + def put(self, pkt, in_or_out, block=False): + if in_or_out == 'out': + # print('putting packet in the OUT queue') + self.out_queue.put(pkt, block) + else: + # print('putting packet in the IN queue') + self.in_queue.put(pkt, block) + + +## Implements a network layer packet. +class NetworkPacket: + ## packet encoding lengths + dst_S_length = 5 + prot_S_length = 1 + + ##@param dst: address of the destination host + # @param data_S: packet payload + # @param prot_S: upper layer protocol for the packet (data, or control) + def __init__(self, dst, prot_S, data_S): + self.dst = dst + self.data_S = data_S + self.prot_S = prot_S + + ## called when printing the object + def __str__(self): + return self.to_byte_S() + + ## convert packet to a byte string for transmission over links + def to_byte_S(self): + byte_S = str(self.dst).zfill(self.dst_S_length) + if self.prot_S == 'data': + byte_S += '1' + elif self.prot_S == 'control': + byte_S += '2' + else: + raise ('%s: unknown prot_S option: %s' % (self, self.prot_S)) + byte_S += self.data_S + return byte_S + + ## extract a packet object from a byte string + # @param byte_S: byte string representation of the packet + @classmethod + def from_byte_S(self, byte_S): + dst = byte_S[0: NetworkPacket.dst_S_length].strip('0') + prot_S = byte_S[NetworkPacket.dst_S_length: NetworkPacket.dst_S_length + NetworkPacket.prot_S_length] + if prot_S == '1': + prot_S = 'data' + elif prot_S == '2': + prot_S = 'control' + else: + raise ('%s: unknown prot_S field: %s' % (self, prot_S)) + data_S = byte_S[NetworkPacket.dst_S_length + NetworkPacket.prot_S_length:] + return self(dst, prot_S, data_S) + + +## Implements a network host for receiving and transmitting data +class Host: + + ##@param addr: address of this node represented as an integer + def __init__(self, addr): + self.addr = addr + self.intf_L = [Interface()] + self.stop = False # for thread termination + + ## called when printing the object + def __str__(self): + return self.addr + + ## create a packet and enqueue for transmission + # @param dst: destination address for the packet + # @param data_S: data being transmitted to the network layer + def udt_send(self, dst, data_S): + p = NetworkPacket(dst, 'data', data_S) + print('%s: sending packet "%s"' % (self, p)) + self.intf_L[0].put(p.to_byte_S(), 'out') # send packets always enqueued successfully + + ## receive packet from the network layer + def udt_receive(self): + pkt_S = self.intf_L[0].get('in') + if pkt_S is not None: + print('%s: received packet "%s"' % (self, pkt_S)) + + ## thread target for the host to keep receiving data + def run(self): + print(threading.currentThread().getName() + ': Starting') + while True: + # receive data arriving to the in interface + self.udt_receive() + # terminate + if (self.stop): + print(threading.currentThread().getName() + ': Ending') + return + + +## Implements a multi-interface router +class Router: + + ##@param name: friendly router name for debugging + # @param cost_D: cost table to neighbors {neighbor: {interface: cost}} + # @param max_queue_size: max queue length (passed to Interface) + def __init__(self, name, cost_D, max_queue_size): + self.stop = False # for thread termination + self.name = name + # create a list of interfaces + self.intf_L = [Interface(max_queue_size) for _ in range(len(cost_D))] + # save neighbors and interfeces on which we connect to them + self.cost_D = cost_D # cost_D {neighbor: {interface: cost}} + # TODO: set up the routing table for connected hosts + + # {destination: {router: cost}} ##Initial setup + self.rt_tbl_D = {name: {name: 0}} + keys = list(cost_D.keys()) + values = list(cost_D.values()) + for i in range(len(keys)): + self.rt_tbl_D[keys[i]] = {name: list(values[i].values())[0]} + self.print_routes() + print('%s: Initialized routing table' % self) + + def getCurrentRoutingTable(self): + routingTableString = self.name + "-" + values = list(self.rt_tbl_D.values()) + keys = list(self.rt_tbl_D.keys()) + first = True + for i in range(len(keys)): + if first: + first = False + routingTableString += keys[i] + "," + str(list(values[i])[0]) + "," + str(list(values[i].values())[0]) + else: + routingTableString += ":" + keys[i] + "," + str(list(values[i])[0]) + "," + str( + list(values[i].values())[0]) + print(routingTableString) + return routingTableString + + ## Print routing table + def updateUniqueRouters(self): + self.uniqueRouters = [] + values = self.rt_tbl_D.values() + routers = {} + for i in range(len(values)): + if list(list(values)[i].keys())[0] not in routers: + routers[list(list(values)[i].keys())[0]] = "" + for item in routers: + self.uniqueRouters.append(item) + + def print_routes(self): + keys = self.rt_tbl_D.keys() + values = self.rt_tbl_D.values() + columns = len(keys) + 1 + keyString = "" + topTableString = "╒" + headerBottomTableString = "╞" + tableRowSeperator = "├" + tableBottom = "╘" + # //Setting up table + for i in range(columns): + if (i + 1 != columns): + topTableString += "══════╤" + headerBottomTableString += "══════╪" + tableRowSeperator += "──────┼" + tableBottom += "══════╧" + else: + topTableString += "══════╕\n" + headerBottomTableString += "══════╡\n" + tableRowSeperator += "──────┤\n" + tableBottom += "══════╛\n" + itemSpace = " " + for item in keys: + keyString += " " + item + " │" + costRows = [] + changed = [] + self.updateUniqueRouters() + for item in self.uniqueRouters: + costRows.append("│ " + item + " │") + for i in range(len(values)): + changedFlag = False + for j in range(len(costRows)): + for k in range(len(list(values)[i].keys())): + if list(list(values)[i].keys())[k] == self.uniqueRouters[j]: + formattedVal = itemSpace[0:len(itemSpace) - len(str(list(list(values)[i].values())[k]))] + str( + list(list(values)[i].values())[k]) + costRows[j] += formattedVal + "│" + changed.append(j) + changedFlag = True + if changedFlag: + changedFlag = False + for l in range(len(costRows)): + if (l in changed): + continue + else: + costRows[l] += " │" + changed = [] + + sys.stdout.write(topTableString + "│ " + self.name + " │" + keyString + "\n" + headerBottomTableString) + for i in range(len(costRows)): + if i + 1 != len(costRows): + sys.stdout.write(costRows[i] + "\n" + tableRowSeperator) + else: + sys.stdout.write(costRows[i] + "\n") + sys.stdout.write(tableBottom) + + ## called when printing the object + def __str__(self): + return self.name + + ## look through the content of incoming interfaces and + # process data and control packets + def process_queues(self): + for i in range(len(self.intf_L)): + pkt_S = None + # get packet from interface i + pkt_S = self.intf_L[i].get('in') + # if packet exists make a forwarding decision + if pkt_S is not None: + p = NetworkPacket.from_byte_S(pkt_S) # parse a packet out + if p.prot_S == 'data': + self.forward_packet(p, i) + elif p.prot_S == 'control': + self.update_routes(p, i) + else: + raise Exception('%s: Unknown packet type in packet %s' % (self, p)) + + ## forward the packet according to the routing table + # @param p Packet to forward + # @param i Incoming interface number for packet p + def forward_packet(self, p, i): + try: + # TODO: Here you will need to implement a lookup into the + # forwarding table to find the appropriate outgoing interface + # for now we assume the outgoing interface is 1 + + # we know the length of the shortest path + # we know how many edges and verticies there are + # we don't know what the shortest path is... like how is the program going to trace the path?? + # simple: we use the bellman ford equation as a verification instead of an algorithm + + # first, let's make it easy. + dest = p.dst + + # then we'll set aside some variable for the node to forward to, let's call it v + v_d = 999 # distance to v + v = dest + + # cost_D {neighbor: {interface: cost}} + # okay, so now we know where we're going. + for header in self.rt_tbl_D: + # for every node in the routing table, + if header in self.cost_D: # narrow it down to only neighbors + # header is in routing table and is reachable by the node + dest_d = int(self.rt_tbl_D[dest][self.name]) # distance to the destination + node_d = int(self.rt_tbl_D[header][self.name]) # distance to potential outgoing node + try: + node_dest_d = int(self.rt_tbl_D[header][dest]) # distance from the potential outgoing node to the destination + if v_d > (node_d + node_dest_d): # find the minimum + # new minimum + v_d = node_d + v = header + except KeyError: + continue; + #print("Key Error: Neighbor is likely host") + # new addition + chosenVal = 999 + chosenRoute = "" + if v not in self.cost_D: # if v is NOT a neighbor + for value in self.rt_tbl_D[v]: # iterate through values + cost = self.rt_tbl_D[v][value] # get cost in routing table + if int(cost) < int(chosenVal): # find lowest cost router + chosenRoute = value + chosenVal = cost + for key in self.cost_D[chosenRoute]: # set the chosenRoutes interface + out_intf = key # set the outgoing interface to the result. + else: # is a neighbor + # @param cost_D: cost table to neighbors {neighbor: {interface: cost}} + for key in self.cost_D[v]: # iterate through values + out_intf = key # set the outgoing interface to the result. + try: + self.intf_L[out_intf].put(p.to_byte_S(), 'out', True) # send out + except IndexError: + print("Index out of range, %i" % out_intf) + print('%s: forwarding packet "%s" from interface %d to %d' % \ + (self, p, i, 1)) + except queue.Full: + print('%s: packet "%s" lost on interface %d' % (self, p, i)) + pass + + ## send out route update + # @param i Interface number on which to send out a routing update + def send_routes(self, i): + # TODO: Send out a routing table update + # create a routing table update packet + p = NetworkPacket(0, 'control', self.getCurrentRoutingTable()) + try: + print('%s: sending routing update "%s" from interface %d' % (self, p, i)) + self.intf_L[i].put(p.to_byte_S(), 'out', True) + except queue.Full: + print('%s: packet "%s" lost on interface %d' % (self, p, i)) + pass + + ## forward the packet according to the routing table + # @param p Packet containing routing information + def update_routes(self, p, i): + # TODO: add logic to update the routing tables and + # possibly send out routing updates + updates = p.to_byte_S()[6:].split('-') + name = updates[0] + update = updates[1].split(":") + # Raw updating + for j in update: # for each update + items = j.split(",") # items: 0=dest 1 1=dest 2 2=cost between dest 1 and dest 2 + if items[0] in self.rt_tbl_D: # if dest 1 is in table headers + values = list(self.rt_tbl_D.values()) # values is a list of dicts of form {router: cost} + exists = False # assume that it doesn't exist + # already in table + for i in range(len(values)): # for as many values(which are mappings of dests to routers) + vks = list(values[i].keys()) # vks = list of routers in + for vk in vks: # for each router in the router list, + if vk == items[1]: # if the router is dest 2 + self.rt_tbl_D[items[0]][items[1]] = items[ + 2] # set the cost of dest 1 to dest 2 in the table to the cost in items + # do stuff/compare + exists = True + if not exists: # will always default to this + self.rt_tbl_D[items[0]][items[1]] = items[ + 2] # set the cost of dest 1 to dest 2 in the table to the cost in items + else: + self.rt_tbl_D[items[0]] = {items[1]: items[2]} + + '''for header in self.rt_tbl_D: #see if header is missing routers in its dict + for router in self.uniqueRouters: #for each router, + if router not in self.rt_tbl_D[header]: #if the router is NOT in the dict of the header + #put it in the header's dict, set cost to inf + self.rt_tbl_D[header][router] = 999 #basically infinity, right? + self.rt_tbl_D[router][header] = 999''' + + self.updateUniqueRouters() + # run the algorithm on each router in the table + router_count = len(self.uniqueRouters) + for j in range(router_count): # for every router (row) in the network, + # step 1: set all unknowns to infinity + for header in self.rt_tbl_D: + # print("Detecting gaps for {} to {}".format(header,self.uniqueRouters[j])) + if self.uniqueRouters[j] not in self.rt_tbl_D[header]: # if the router is NOT in the dict of the header + # print("Gap filled {} to {}".format(header, self.uniqueRouters[j])) + # put it in the header's dict, set cost to inf + self.rt_tbl_D[header][self.uniqueRouters[j]] = 999 # basically infinity, right? + # {header: {router: cost}} + # bellman ford starts here + i = 1 + # http://courses.csail.mit.edu/6.006/spring11/lectures/lec15.pdf + # rt_tbl is a list of edges. + updated = False + # step 2: relax edges |V|-1 times + for i in range(len(self.rt_tbl_D)): + # for V-1 (the number of verticies minus one + for u in self.rt_tbl_D: + # relax edge, represented as a call with the header + # for each vertex's neighbor, + for v in self.rt_tbl_D[u]: # iterate through each outgoing edge + edge_distance = int(self.rt_tbl_D[u][v]) + u_dist = int(self.rt_tbl_D[u][self.uniqueRouters[j]]) # distance to u vertex + v_dist = int(self.rt_tbl_D[v][self.uniqueRouters[j]]) # distance to v vertex + try: + if (u_dist > (v_dist + edge_distance)): + # if the edge plus the distance to vertex v is greater than the distance to u + self.rt_tbl_D[u][ + self.uniqueRouters[j]] = v_dist + edge_distance # update the distance to u + updated = True + self.updateUniqueRouters() + except KeyError: + continue + #print("Key error exception occurred") + if (updated): + # cost_D {neighbor: {interface: cost}} + for i in range(len(self.cost_D.values())): # for all values + for x in range(len(list(self.cost_D.values())[i].keys())): + interface = list(list(self.cost_D.values())[i].keys())[x] + self.send_routes(interface) + + ## thread target for the host to keep forwarding data + def run(self): + print(threading.currentThread().getName() + ': Starting') + while True: + self.process_queues() + if self.stop: + print(threading.currentThread().getName() + ': Ending') + return diff --git a/network_3.py b/network_3.py new file mode 100644 index 0000000..1c3468a --- /dev/null +++ b/network_3.py @@ -0,0 +1,419 @@ +import queue +import threading +from collections import defaultdict +import sys +import math + + +## wrapper class for a queue of packets +class Interface: + ## @param maxsize - the maximum size of the queue storing packets + def __init__(self, maxsize=0): + self.in_queue = queue.Queue(maxsize) + self.out_queue = queue.Queue(maxsize) + + ##get packet from the queue interface + # @param in_or_out - use 'in' or 'out' interface + def get(self, in_or_out): + try: + if in_or_out == 'in': + pkt_S = self.in_queue.get(False) + # if pkt_S is not None: + # print('getting packet from the IN queue') + return pkt_S + else: + pkt_S = self.out_queue.get(False) + # if pkt_S is not None: + # print('getting packet from the OUT queue') + return pkt_S + except queue.Empty: + return None + + ##put the packet into the interface queue + # @param pkt - Packet to be inserted into the queue + # @param in_or_out - use 'in' or 'out' interface + # @param block - if True, block until room in queue, if False may throw queue.Full exception + def put(self, pkt, in_or_out, block=False): + if in_or_out == 'out': + # print('putting packet in the OUT queue') + self.out_queue.put(pkt, block) + else: + # print('putting packet in the IN queue') + self.in_queue.put(pkt, block) + + +## Implements a network layer packet. +class NetworkPacket: + ## packet encoding lengths + dst_S_length = 5 + prot_S_length = 1 + + ##@param dst: address of the destination host + # @param data_S: packet payload + # @param prot_S: upper layer protocol for the packet (data, or control) + def __init__(self, dst, prot_S, data_S): + self.dst = dst + self.data_S = data_S + self.prot_S = prot_S + + ## called when printing the object + def __str__(self): + return self.to_byte_S() + + ## convert packet to a byte string for transmission over links + def to_byte_S(self): + byte_S = str(self.dst).zfill(self.dst_S_length) + if self.prot_S == 'data': + byte_S += '1' + elif self.prot_S == 'control': + byte_S += '2' + else: + raise ('%s: unknown prot_S option: %s' % (self, self.prot_S)) + byte_S += self.data_S + return byte_S + + ## extract a packet object from a byte string + # @param byte_S: byte string representation of the packet + @classmethod + def from_byte_S(self, byte_S): + dst = byte_S[0: NetworkPacket.dst_S_length].strip('0') + prot_S = byte_S[NetworkPacket.dst_S_length: NetworkPacket.dst_S_length + NetworkPacket.prot_S_length] + if prot_S == '1': + prot_S = 'data' + elif prot_S == '2': + prot_S = 'control' + else: + raise ('%s: unknown prot_S field: %s' % (self, prot_S)) + data_S = byte_S[NetworkPacket.dst_S_length + NetworkPacket.prot_S_length:] + return self(dst, prot_S, data_S) + + +## Implements a network host for receiving and transmitting data +class Host: + + ##@param addr: address of this node represented as an integer + def __init__(self, addr): + self.addr = addr + self.intf_L = [Interface()] + self.stop = False # for thread termination + + ## called when printing the object + def __str__(self): + return self.addr + + ## create a packet and enqueue for transmission + # @param dst: destination address for the packet + # @param data_S: data being transmitted to the network layer + def udt_send(self, dst, data_S): + p = NetworkPacket(dst, 'data', data_S) + print('%s: sending packet "%s"' % (self, p)) + self.intf_L[0].put(p.to_byte_S(), 'out') # send packets always enqueued successfully + + ## receive packet from the network layer + def udt_receive(self): + pkt_S = self.intf_L[0].get('in') + if pkt_S is not None: + print('%s: received packet "%s"' % (self, pkt_S)) + + ## thread target for the host to keep receiving data + def run(self): + print(threading.currentThread().getName() + ': Starting') + while True: + # receive data arriving to the in interface + self.udt_receive() + # terminate + if (self.stop): + print(threading.currentThread().getName() + ': Ending') + return + + +## Implements a multi-interface router +class Router: + + ##@param name: friendly router name for debugging + # @param cost_D: cost table to neighbors {neighbor: {interface: cost}} + # @param max_queue_size: max queue length (passed to Interface) + def __init__(self, name, cost_D, max_queue_size): + self.stop = False # for thread termination + self.name = name + # create a list of interfaces + self.intf_L = [Interface(max_queue_size) for _ in range(len(cost_D))] + # save neighbors and interfeces on which we connect to them + self.cost_D = cost_D # cost_D {neighbor: {interface: cost}} + # TODO: set up the routing table for connected hosts + + # {destination: {router: cost}} ##Initial setup + self.rt_tbl_D = {name: {name: 0}} + keys = list(cost_D.keys()) + values = list(cost_D.values()) + for i in range(len(keys)): + self.rt_tbl_D[keys[i]] = {name: list(values[i].values())[0]} + self.print_routes() + print('%s: Initialized routing table' % self) + + def getCurrentRoutingTable(self): + routingTableString = self.name + "-" + values = list(self.rt_tbl_D.values()) + keys = list(self.rt_tbl_D.keys()) + first = True + for i in range(len(keys)): + if first: + first = False + routingTableString += keys[i] + "," + str(list(values[i])[0]) + "," + str(list(values[i].values())[0]) + else: + routingTableString += ":" + keys[i] + "," + str(list(values[i])[0]) + "," + str( + list(values[i].values())[0]) + print(routingTableString) + return routingTableString + + ## Print routing table + def updateUniqueRouters(self): + self.uniqueRouters = [] + values = self.rt_tbl_D.values() + routers = {} + for i in range(len(values)): + if list(list(values)[i].keys())[0] not in routers: + routers[list(list(values)[i].keys())[0]] = "" + for item in routers: + self.uniqueRouters.append(item) + + def print_routes(self): + keys = self.rt_tbl_D.keys() + values = self.rt_tbl_D.values() + columns = len(keys) + 1 + keyString = "" + topTableString = "╒" + headerBottomTableString = "╞" + tableRowSeperator = "├" + tableBottom = "╘" + # //Setting up table + for i in range(columns): + if (i + 1 != columns): + topTableString += "══════╤" + headerBottomTableString += "══════╪" + tableRowSeperator += "──────┼" + tableBottom += "══════╧" + else: + topTableString += "══════╕\n" + headerBottomTableString += "══════╡\n" + tableRowSeperator += "──────┤\n" + tableBottom += "══════╛\n" + itemSpace = " " + for item in keys: + keyString += " " + item + " │" + costRows = [] + changed = [] + self.updateUniqueRouters() + for item in self.uniqueRouters: + costRows.append("│ " + item + " │") + for i in range(len(values)): + changedFlag = False + for j in range(len(costRows)): + for k in range(len(list(values)[i].keys())): + if list(list(values)[i].keys())[k] == self.uniqueRouters[j]: + formattedVal = itemSpace[0:len(itemSpace) - len(str(list(list(values)[i].values())[k]))] + str( + list(list(values)[i].values())[k]) + costRows[j] += formattedVal + "│" + changed.append(j) + changedFlag = True + if changedFlag: + changedFlag = False + for l in range(len(costRows)): + if (l in changed): + continue + else: + costRows[l] += " │" + changed = [] + + sys.stdout.write(topTableString + "│ " + self.name + " │" + keyString + "\n" + headerBottomTableString) + for i in range(len(costRows)): + if i + 1 != len(costRows): + sys.stdout.write(costRows[i] + "\n" + tableRowSeperator) + else: + sys.stdout.write(costRows[i] + "\n") + sys.stdout.write(tableBottom) + + ## called when printing the object + def __str__(self): + return self.name + + ## look through the content of incoming interfaces and + # process data and control packets + def process_queues(self): + for i in range(len(self.intf_L)): + pkt_S = None + # get packet from interface i + pkt_S = self.intf_L[i].get('in') + # if packet exists make a forwarding decision + if pkt_S is not None: + p = NetworkPacket.from_byte_S(pkt_S) # parse a packet out + if p.prot_S == 'data': + self.forward_packet(p, i) + elif p.prot_S == 'control': + self.update_routes(p, i) + else: + raise Exception('%s: Unknown packet type in packet %s' % (self, p)) + + ## forward the packet according to the routing table + # @param p Packet to forward + # @param i Incoming interface number for packet p + def forward_packet(self, p, i): + try: + # TODO: Here you will need to implement a lookup into the + # forwarding table to find the appropriate outgoing interface + # for now we assume the outgoing interface is 1 + + # we know the length of the shortest path + # we know how many edges and verticies there are + # we don't know what the shortest path is... like how is the program going to trace the path?? + # simple: we use the bellman ford equation as a verification instead of an algorithm + + # first, let's make it easy. + dest = p.dst + + # then we'll set aside some variable for the node to forward to, let's call it v + v_d = 999 # distance to v + v = dest + # cost_D {neighbor: {interface: cost}} + # okay, so now we know where we're going. + for header in self.rt_tbl_D: + # for every node in the routing table, + if header in self.cost_D: # narrow it down to only neighbors + # header is in routing table and is reachable by the node + dest_d = int(self.rt_tbl_D[dest][self.name]) # distance to the destination + node_d = int(self.rt_tbl_D[header][self.name]) # distance to potential outgoing node + try: + if v_d > (dest_d + node_d): # find the minimum + # new minimum + v_d = dest_d + v = header + except KeyError: + print("Key Error: Neighbor is likely host") + # new addition + chosenVal = 999 + chosenRoute = "" + if v not in self.cost_D: # if v is NOT a neighbor + for value in self.rt_tbl_D[v]: # iterate through values + cost = self.rt_tbl_D[v][value] # get cost in routing table + if int(cost) < int(chosenVal): # find lowest cost router + chosenRoute = value + chosenVal = cost + for key in self.cost_D[chosenRoute]: # set the chosenRoutes interface + out_intf = key # set the outgoing interface to the result. + else: # is a neighbor + # @param cost_D: cost table to neighbors {neighbor: {interface: cost}} + for key in self.cost_D[v]: # iterate through values + out_intf = key # set the outgoing interface to the result. + try: + self.intf_L[out_intf].put(p.to_byte_S(), 'out', True) # send out + except IndexError: + print("Index out of range, %i" % out_intf) + print('%s: forwarding packet "%s" from interface %d to %d' % \ + (self, p, i, 1)) + except queue.Full: + print('%s: packet "%s" lost on interface %d' % (self, p, i)) + pass + + ## send out route update + # @param i Interface number on which to send out a routing update + def send_routes(self, i): + # TODO: Send out a routing table update + # create a routing table update packet + p = NetworkPacket(0, 'control', self.getCurrentRoutingTable()) + try: + print('%s: sending routing update "%s" from interface %d' % (self, p, i)) + self.intf_L[i].put(p.to_byte_S(), 'out', True) + except queue.Full: + print('%s: packet "%s" lost on interface %d' % (self, p, i)) + pass + + ## forward the packet according to the routing table + # @param p Packet containing routing information + def update_routes(self, p, i): + # TODO: add logic to update the routing tables and + # possibly send out routing updates + updates = p.to_byte_S()[6:].split('-') + name = updates[0] + update = updates[1].split(":") + updated = False + # Raw updating + for j in update: # for each update + items = j.split(",") # items: 0=dest 1 1=dest 2 2=cost between dest 1 and dest 2 + if items[0] in self.rt_tbl_D: # if dest 1 is in table headers + values = list(self.rt_tbl_D.values()) # values is a list of dicts of form {router: cost} + exists = False # assume that it doesn't exist + # already in table + for i in range(len(values)): # for as many values(which are mappings of dests to routers) + vks = list(values[i].keys()) # vks = list of routers in + for vk in vks: # for each router in the router list, + if vk == items[1]: # if the router is dest 2 + self.rt_tbl_D[items[0]][items[1]] = items[2] # set the cost of dest 1 to dest 2 in the table to the cost in items + # do stuff/compare + exists = True + + if not exists: # will always default to this + self.rt_tbl_D[items[0]][items[1]] = items[2] # set the cost of dest 1 to dest 2 in the table to the cost in items + updated = True + else: + self.rt_tbl_D[items[0]] = {items[1]: items[2]} + updated = True + + '''for header in self.rt_tbl_D: #see if header is missing routers in its dict + for router in self.uniqueRouters: #for each router, + if router not in self.rt_tbl_D[header]: #if the router is NOT in the dict of the header + #put it in the header's dict, set cost to inf + self.rt_tbl_D[header][router] = 999 #basically infinity, right? + self.rt_tbl_D[router][header] = 999''' + + self.updateUniqueRouters() + # run the algorithm on each router in the table + router_count = len(self.uniqueRouters) + print(router_count) + for j in range(router_count): # for every router (row) in the network, + # step 1: set all unknowns to infinity + for header in self.rt_tbl_D: + # print("Detecting gaps for {} to {}".format(header,self.uniqueRouters[j])) + if self.uniqueRouters[j] not in self.rt_tbl_D[header]: # if the router is NOT in the dict of the header + # print("Gap filled {} to {}".format(header, self.uniqueRouters[j])) + # put it in the header's dict, set cost to inf + self.rt_tbl_D[header][self.uniqueRouters[j]] = 999 # basically infinity, right? + # {header: {router: cost}} + # bellman ford starts here + # http://courses.csail.mit.edu/6.006/spring11/lectures/lec15.pdf + # rt_tbl is a list of edges. + self.updateUniqueRouters() + # step 2: relax edges |V|-1 times + for i in range(len(self.rt_tbl_D)-1): + # for V-1 (the number of verticies minus one + for u in self.rt_tbl_D: + # relax edge, represented as a call with the header + # for each vertex's neighbor, + for v in self.rt_tbl_D[u]: # iterate through each outgoing edge + edge_distance = int(self.rt_tbl_D[u][v]) + u_dist = int(self.rt_tbl_D[u][self.uniqueRouters[j]]) # distance to u vertex + v_dist = int(self.rt_tbl_D[v][self.uniqueRouters[j]]) # distance to v vertex + try: + if (u_dist > (v_dist + edge_distance)): + # if the edge plus the distance to vertex v is greater than the distance to u + self.rt_tbl_D[u][ + self.uniqueRouters[j]] = v_dist + edge_distance # update the distance to u + updated = True + self.updateUniqueRouters() + except KeyError: + print("Key error exception occurred") + if (updated): + # cost_D {neighbor: {interface: cost}} + for i in range(len(self.cost_D.values())): # for all values + for x in range(len(list(self.cost_D.values())[i].keys())): + interface = list(list(self.cost_D.values())[i].keys())[x] + self.send_routes(interface) + self.updateUniqueRouters() + + ## thread target for the host to keep forwarding data + def run(self): + print(threading.currentThread().getName() + ': Starting') + while True: + self.process_queues() + if self.stop: + print(threading.currentThread().getName() + ': Ending') + return diff --git a/partners.txt b/partners.txt new file mode 100644 index 0000000..632a8a7 --- /dev/null +++ b/partners.txt @@ -0,0 +1,2 @@ +Kendal Black +Keefer Sands \ No newline at end of file diff --git a/simulation.py b/simulation.py deleted file mode 100644 index b9c412b..0000000 --- a/simulation.py +++ /dev/null @@ -1,71 +0,0 @@ -import network -import link -import threading -from time import sleep -import sys - -##configuration parameters -router_queue_size = 0 #0 means unlimited -simulation_time = 1 #give the network sufficient time to execute transfers - -if __name__ == '__main__': - object_L = [] #keeps track of objects, so we can kill their threads at the end - - #create network hosts - host_1 = network.Host('H1') - object_L.append(host_1) - host_2 = network.Host('H2') - object_L.append(host_2) - - #create routers and cost tables for reaching neighbors - cost_D = {'H1': {0: 1}, 'RB': {1: 1}} # {neighbor: {interface: cost}} - router_a = network.Router(name='RA', - cost_D = cost_D, - max_queue_size=router_queue_size) - object_L.append(router_a) - - cost_D = {'H2': {1: 3}, 'RA': {0: 1}} # {neighbor: {interface: cost}} - router_b = network.Router(name='RB', - cost_D = cost_D, - max_queue_size=router_queue_size) - object_L.append(router_b) - - #create a Link Layer to keep track of links between network nodes - link_layer = link.LinkLayer() - object_L.append(link_layer) - - #add all the links - need to reflect the connectivity in cost_D tables above - link_layer.add_link(link.Link(host_1, 0, router_a, 0)) - link_layer.add_link(link.Link(router_a, 1, router_b, 0)) - link_layer.add_link(link.Link(router_b, 1, host_2, 0)) - - - #start all the objects - thread_L = [] - for obj in object_L: - thread_L.append(threading.Thread(name=obj.__str__(), target=obj.run)) - - for t in thread_L: - t.start() - - ## compute routing tables - router_a.send_routes(1) #one update starts the routing process - sleep(simulation_time) #let the tables converge - print("Converged routing tables") - for obj in object_L: - if str(type(obj)) == "": - obj.print_routes() - - #send packet from host 1 to host 2 - host_1.udt_send('H2', 'MESSAGE_FROM_H1') - sleep(simulation_time) - - - #join all threads - for o in object_L: - o.stop = True - for t in thread_L: - t.join() - - print("All simulation threads joined") - diff --git a/simulation_1.py b/simulation_1.py new file mode 100644 index 0000000..a73d9dc --- /dev/null +++ b/simulation_1.py @@ -0,0 +1,64 @@ +import network_1 +import link_1 +import threading +from time import sleep +import sys + +# configuration parameters +router_queue_size = 0 # 0 means unlimited +simulation_time = 7 # give the network_1 sufficient time to execute transfers +if __name__ == '__main__': + object_L = [] # keeps track of objects, so we can kill their threads at the end + # create network_1 hosts + host_1 = network_1.Host('H1') + object_L.append(host_1) + host_2 = network_1.Host('H2') + object_L.append(host_2) + # create routers and cost tables for reaching neighbors + cost_D = {'H1': {0: 1}, 'RB': {1: 1}} # {neighbor: {interface: cost}} + router_a = network_1.Router(name='RA', + cost_D=cost_D, + max_queue_size=router_queue_size) + object_L.append(router_a) + cost_D = {'H2': {1: 3}, 'RA': {0: 1}} # {neighbor: {interface: cost}} + router_b = network_1.Router(name='RB', + cost_D=cost_D, + max_queue_size=router_queue_size) + object_L.append(router_b) + # create a Link Layer to keep track of links between network_1 nodes + link_layer = link_1.LinkLayer() + object_L.append(link_layer) + + # add all the links - need to reflect the connectivity in cost_D tables above + link_layer.add_link(link_1.Link(host_1, 0, router_a, 0)) + link_layer.add_link(link_1.Link(router_a, 1, router_b, 0)) + link_layer.add_link(link_1.Link(router_b, 1, host_2, 0)) + + # start all the objects + thread_L = [] + for obj in object_L: + thread_L.append(threading.Thread(name=obj.__str__(), target=obj.run)) + sys.stdout.write("\n") + for t in thread_L: + t.start() + + # compute routing tables + router_a.send_routes(1) # one update starts the routing process + sleep(simulation_time) # let the tables converge + print("Converged routing tables") + # Table Header Bottom + for i in range(len(object_L)): + if str(type(object_L[i])) == "": + object_L[i].print_routes() + + # send packet from host 1 to host 2 + host_1.udt_send('H2', 'MESSAGE_FROM_H1') + sleep(simulation_time) + + # join all threads + for o in object_L: + o.stop = True + for t in thread_L: + t.join() + + print("All simulation threads joined") diff --git a/simulation_2.py b/simulation_2.py new file mode 100644 index 0000000..4e90d46 --- /dev/null +++ b/simulation_2.py @@ -0,0 +1,67 @@ +import network_2 +import link_2 +import threading +from time import sleep +import sys + +# configuration parameters +router_queue_size = 0 # 0 means unlimited +simulation_time = 5 # give the network_2 sufficient time to execute transfers +if __name__ == '__main__': + object_L = [] # keeps track of objects, so we can kill their threads at the end + # create network_2 hosts + host_1 = network_2.Host('H1') + object_L.append(host_1) + host_2 = network_2.Host('H2') + object_L.append(host_2) + # create routers and cost tables for reaching neighbors + cost_D = {'H1': {0: 1}, 'RB': {1: 1}} # {neighbor: {interface: cost}} + router_a = network_2.Router(name='RA', + cost_D=cost_D, + max_queue_size=router_queue_size) + object_L.append(router_a) + cost_D = {'H2': {1: 3}, 'RA': {0: 1}} # {neighbor: {interface: cost}} + router_b = network_2.Router(name='RB', + cost_D=cost_D, + max_queue_size=router_queue_size) + object_L.append(router_b) + # create a link_2 Layer to keep track of link_2s between network_2 nodes + link_layer = link_2.LinkLayer() + object_L.append(link_layer) + + # add all the links - need to reflect the connectivity in cost_D tables above + link_layer.add_link(link_2.Link(host_1, 0, router_a, 0)) + link_layer.add_link(link_2.Link(router_a, 0, host_1, 0)) + link_layer.add_link(link_2.Link(router_a, 1, router_b, 0)) + link_layer.add_link(link_2.Link(router_b, 1, host_2, 0)) + link_layer.add_link(link_2.Link(router_b, 0, router_a, 1)) + link_layer.add_link(link_2.Link(host_2, 0, router_b, 1)) + # start all the objects + thread_L = [] + for obj in object_L: + thread_L.append(threading.Thread(name=obj.__str__(), target=obj.run)) + sys.stdout.write("\n") + for t in thread_L: + t.start() + + # compute routing tables + router_a.send_routes(1) # one update starts the routing process + sleep(simulation_time) # let the tables converge + print("Converged routing tables") + # Table Header Bottom + for i in range(len(object_L)): + if str(type(object_L[i])) == "": + object_L[i].print_routes() + + # send packet from host 1 to host 2 + host_1.udt_send('H2', 'MESSAGE_FROM_H1') + host_2.udt_send('H1', 'MESSAGE FROM H2') + sleep(simulation_time) + + # join all threads + for o in object_L: + o.stop = True + for t in thread_L: + t.join() + + print("All simulation threads joined") diff --git a/simulation_3.py b/simulation_3.py new file mode 100644 index 0000000..70f01d0 --- /dev/null +++ b/simulation_3.py @@ -0,0 +1,72 @@ +import network_3 +import link_3 +import threading +from time import sleep +import sys + +# configuration parameters +router_queue_size = 0 # 0 means unlimited +simulation_time = 8 # give the network_3 sufficient time to execute transfers +if __name__ == '__main__': + object_L = [] # keeps track of objects, so we can kill their threads at the end + # create network_3 hosts + host_1 = network_3.Host('H1') + host_2 = network_3.Host('H2') + # {neighbor: {interface: cost}} + cost_d_a = {'H1': {0: 5}, 'RB': {1: 4}, 'RC': {2: 1}} + cost_d_b = {'RA': {0: 3}, 'RD': {1: 1}} + cost_d_c = {'RA': {0: 1}, 'RD': {1: 3}} + cost_d_d = {'RB': {0: 1}, 'RC': {1: 1}, 'H2': {2: 5}} + router_a = network_3.Router('RA', cost_d_a, router_queue_size) + router_b = network_3.Router('RB', cost_d_b, router_queue_size) + router_c = network_3.Router('RC', cost_d_c, router_queue_size) + router_d = network_3.Router('RD', cost_d_d, router_queue_size) + object_L.append(host_1) + object_L.append(host_2) + object_L.append(router_a) + object_L.append(router_b) + object_L.append(router_c) + object_L.append(router_d) + # create a link_3 Layer to keep track of link_3s between network_3 nodes + link_layer = link_3.LinkLayer() + object_L.append(link_layer) + + # add all the links - need to reflect the connectivity in cost_D tables above + link_layer.add_link(link_3.Link(host_1, 0, router_a, 0)) + link_layer.add_link(link_3.Link(router_a, 1, router_b, 0)) + link_layer.add_link(link_3.Link(router_a, 2, router_c, 0)) + link_layer.add_link(link_3.Link(router_c, 1, router_d, 1)) + link_layer.add_link(link_3.Link(router_b, 1, router_d, 0)) + link_layer.add_link(link_3.Link(router_d, 2, host_2, 0)) + + + # start all the objects + thread_L = [] + for obj in object_L: + thread_L.append(threading.Thread(name=obj.__str__(), target=obj.run)) + sys.stdout.write("\n") + for t in thread_L: + t.start() + + # compute routing tables + router_d.send_routes(1) # one update starts the routing process + router_a.send_routes(1) + sleep(simulation_time) # let the tables converge + print("Converged routing tables") + # Table Header Bottom + for i in range(len(object_L)): + if str(type(object_L[i])) == "": + object_L[i].print_routes() + + # send packet from host 1 to host 2 + host_1.udt_send('H2', 'MESSAGE_FROM_H1') + host_2.udt_send('H1', 'MESSAGE FROM H2') + sleep(simulation_time) + + # join all threads + for o in object_L: + o.stop = True + for t in thread_L: + t.join() + + print("All simulation threads joined") diff --git a/youtube_video.txt b/youtube_video.txt new file mode 100644 index 0000000..2cdf379 --- /dev/null +++ b/youtube_video.txt @@ -0,0 +1 @@ +https://www.youtube.com/watch?v=YSov91NWGeQ&feature=youtu.be \ No newline at end of file