From 9051ccb4268850f8bf5866a4367df4a32f706ae2 Mon Sep 17 00:00:00 2001 From: mwittie Date: Thu, 26 Oct 2017 16:49:45 -0600 Subject: [PATCH 01/40] Create README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..bb3d8d8 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# CSCI 466 Programming Assignment 3 - Data Plane + From 1231c2730cb9baff068086903fe21bbc57f6c830 Mon Sep 17 00:00:00 2001 From: mwittie Date: Mon, 15 Oct 2018 20:01:06 -0600 Subject: [PATCH 02/40] Update README.md In the middle of converting to PA3 test --- README.md | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 122 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bb3d8d8..d8580ee 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,123 @@ -# CSCI 466 Programming Assignment 3 - Data Plane +# CSCI 466 PA3 - Network Layer: Data Plane + +## Instructions + +Complete the following assignment in pairs, or groups of three. +Submit your work on D2L into the "Programming Assignment 3" folder before its due date. +All partners will submit the same solution and we will only grade one solution for each group. + + +## Learning Objectives + +In this programming assignment you will: + +- Packetize streams at the network layer +- Implement packet segmentation +- Implement forwarding through routing tables + + +## Assignment + +During this project, you will implement several key data plane functions of a router, including stream packetization, packet segmentation, and forwarding. +The next assignment will complement these functions at the control plane. + + +### Starting Code + +The starting code for this project provides you with the implementation several network layers that cooperate to provide end-to-end communication. + +``` +NETWORK LAYER (network.py) +DATA LINK LAYER (link.py) +``` + +The code also includes `simulation.py` that manages the threads running the different network objects. +Currently, `simulation.py` defines the following network. + +![Image of Yaktocat](https://octodex.github.com/images/yaktocat.png) + +### Program Invocation + +To run the starting code you may run: + +``` +python server.py 5000 +``` + +and + +``` +python client.py localhost 5000 +``` + +in separate terminal windows. +Be sure to start the server first, to allow it to start listening on a socket, and start the client soon after, before the server times out. + + +## BONUS + +We will award __one bonus point__ for each of the following: + +* The network layer may also reorder packets. +If you set `prob_pkt_reorder` to a non-zero probability you will start seeing packet that are reordered. +Implement RDT 3.1, which delivers packets in the correct order. + +* RDT 3.1 is a stop and wait protocol. +Implements RDT 4.0 - a pipelined reliable data transmission protocol based on either Go-back-N (GBN), or Selective Repeat (SR) mechanisms. + + +## What to Submit + +You will submit different versions of `rdt.py`, which implements the send and receive functions for RDT 2.1, and RDT 3.0. +RDT 2.1 tolerates corrupted packets through retransmission. +RDT 3.0 tolerates corrupted and lost packets through retransmission. +The necessary functions prototypes are already included in `rdt.py`. +For the purposes of testing you may modify `client.py` and `server.py` to use these functions instead of those of RDT 1.0. +You will also submit a link to a YouTube video showing an execution of your code for each version of the protocol. +Videos longer than 5 minutes will not be graded. + +## Grading Rubric + +We will grade the assignment as follows: + +* \[2 points\] `partners.txt` with your partner's first and last name. + +* \[10 points\] `rdt_2_1.py`, `client_2_1.py`, `server_2_1.py`, `network_2_1.py` that correctly implement RDT 2.1 and a link to a YouTube video showing the execution of your program. + + * \[2 points\] RDT 2.1 delivers data under no corruption in the network + + * \[2 points\] RDT 2.1 uses a modified Packet class to send ACKs + + * \[2 points\] RDT 2.1 does not deliver corrupt packets + + * \[2 points\] RDT 2.1 uses modified Packet class to send NAKs for corrupt packets + + * \[2 points\] RDT 2.1 resends data following a NAK + +* \[13 points\] `rdt_3_0.py`, `client_3_0.py`, `server_3_0.py`, `network_3_0.py` that correctly implement RDT 3.0 and a link to a YouTube video showing the execution of your program. + + * \[2 points\] RDT 3.0 delivers data under no corruption or loss in the network and uses a modified Packet class to send ACKs + + * \[2 points\] RDT 3.0 does not deliver corrupt packets and uses modified Packet class to send NAKs + + * \[2 points\] RDT 3.0 resends data following a NAK + + * \[2 points\] RDT 3.0 retransmits a lost packet after a timeout + + * \[2 points\] RDT 3.0 retransmits a packet after a lost ACK + + * \[3 points\] RDT 3.0 ignores a duplicate packet after a premature timeout (or after a lost ACK) + +* \[1 points\] (BONUS) `rdt_3_1.py`, `client_3_1.py`, `server_3_1.py`, `network_3_1.py` that correctly implement RDT 3.1 and a link to a YouTube video showing the execution of your program. + +* \[1 points\] (BONUS) `rdt_4_0.py`, `client_4_0.py`, `server_4_0.py`, `network_4_0.py` that correctly implement RDT 4.0 and a link to a YouTube video showing the execution of your program. + + + +## Acknowledgements + +This project was adapted from Kishore Ramachandran version of this assignment. + + + From 08b3b836d4a27be688745b5374788b13475c5ef2 Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 15 Oct 2018 20:06:58 -0600 Subject: [PATCH 03/40] added images for readme.md --- .gitignore | 10 ++++++++++ images/Drawing2.vsdx | Bin 0 -> 61217 bytes images/network.png | Bin 0 -> 32174 bytes images/simple.png | Bin 0 -> 8018 bytes 4 files changed, 10 insertions(+) create mode 100644 images/Drawing2.vsdx create mode 100644 images/network.png create mode 100644 images/simple.png diff --git a/.gitignore b/.gitignore index e32cabc..542ec04 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,11 @@ __pycache* + +*.sublime-project + +*.sublime-workspace + +\.python-version + +bin/ + +lib/python3\.6/ diff --git a/images/Drawing2.vsdx b/images/Drawing2.vsdx new file mode 100644 index 0000000000000000000000000000000000000000..ada3628b9e03f11b2c285f9857c7434f45e89986 GIT binary patch literal 61217 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 literal 0 HcmV?d00001 diff --git a/images/network.png b/images/network.png new file mode 100644 index 0000000000000000000000000000000000000000..43e9cbc8c6cea24f272fc92a2a665df352ed5d59 GIT binary patch literal 32174 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 literal 0 HcmV?d00001 diff --git a/images/simple.png b/images/simple.png new file mode 100644 index 0000000000000000000000000000000000000000..75d586249ba591641282d90add7d381a12de3a06 GIT binary patch literal 8018 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 kD-Qq956BF%`In%M literal 0 HcmV?d00001 From 28665c80e0bbc4172e28aaddf78adc2e6c13fe89 Mon Sep 17 00:00:00 2001 From: mwittie Date: Mon, 15 Oct 2018 20:21:31 -0600 Subject: [PATCH 04/40] Update README.md Added a picture and video link --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d8580ee..5af7a71 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,14 @@ DATA LINK LAYER (link.py) The code also includes `simulation.py` that manages the threads running the different network objects. Currently, `simulation.py` defines the following network. -![Image of Yaktocat](https://octodex.github.com/images/yaktocat.png) +![network_1](https://github.com/msu-netlab/MSU_CSCI_466_PAs/blob/data_plane/images/simple.png) + +At a high level a network defined in `simulation.py` includes hosts, routers and links. +`Hosts` generate and receive traffic. +`Routers` forward traffic from one `Interface` to another based on routing tables that you will implement. +`Links` connect network interfaces of routers and hosts. +Finally, the `LinkLayer` forwards traffic along links. +Please consult the [video lecture](https://youtu.be/-JXvrxjPo7o) for a more in-depth explanation of the code. ### Program Invocation From da1f20050cb1e466a0f56e1bfaa26d31949fe02d Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 15 Oct 2018 20:38:06 -0600 Subject: [PATCH 05/40] added the rest of the latex --- README.md | 84 +++++++++++++++++++++++-------------------------------- 1 file changed, 35 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 5af7a71..f0d8378 100644 --- a/README.md +++ b/README.md @@ -45,10 +45,10 @@ Please consult the [video lecture](https://youtu.be/-JXvrxjPo7o) for a more in-d ### Program Invocation -To run the starting code you may run: +To run the starting code you may execute: ``` -python server.py 5000 +python simulation.py ``` and @@ -57,74 +57,60 @@ and python client.py localhost 5000 ``` -in separate terminal windows. -Be sure to start the server first, to allow it to start listening on a socket, and start the client soon after, before the server times out. +The current `simulation_time' in `simulation.py' is one second. +As the network becomes more complex and takes longer to execute, you may need to extend the simulation to allow all the packets to be transfered. -## BONUS - -We will award __one bonus point__ for each of the following: - -* The network layer may also reorder packets. -If you set `prob_pkt_reorder` to a non-zero probability you will start seeing packet that are reordered. -Implement RDT 3.1, which delivers packets in the correct order. - -* RDT 3.1 is a stop and wait protocol. -Implements RDT 4.0 - a pipelined reliable data transmission protocol based on either Go-back-N (GBN), or Selective Repeat (SR) mechanisms. - - -## What to Submit +## Grading Rubric -You will submit different versions of `rdt.py`, which implements the send and receive functions for RDT 2.1, and RDT 3.0. -RDT 2.1 tolerates corrupted packets through retransmission. -RDT 3.0 tolerates corrupted and lost packets through retransmission. -The necessary functions prototypes are already included in `rdt.py`. -For the purposes of testing you may modify `client.py` and `server.py` to use these functions instead of those of RDT 1.0. -You will also submit a link to a YouTube video showing an execution of your code for each version of the protocol. -Videos longer than 5 minutes will not be graded. +Your task is to extend the given code to implement several data link router functions. -## Grading Rubric +* \[2 points\] Currently `simulation.py` is configured to send three very short messages. +Instead, generate a message for `Host_2` that's at least 80 characters long. +You will notice that this messages is to large for the link MTUs. +Your first task is to break this message up into two separate packets. -We will grade the assignment as follows: +Implement your solution in files `link_1.py`, `network_1.py`, and `simulation_1.py`. -* \[2 points\] `partners.txt` with your partner's first and last name. -* \[10 points\] `rdt_2_1.py`, `client_2_1.py`, `server_2_1.py`, `network_2_1.py` that correctly implement RDT 2.1 and a link to a YouTube video showing the execution of your program. +* \[10 points\] The packets you created are small enough to pass over the links. +However, if we change the MTU of the second link (between `Router_A` and `Host_2`) to 30, the packets will now need to be segmented. - * \[2 points\] RDT 2.1 delivers data under no corruption in the network +Your task is to extend the network layer to support segmentation. +Study lecture notes and the book on how IP implements segmentation. +Extend the classes (including packet format) in `network.py` to match IP's mechanisms for packet segmentation and reconstruction. - * \[2 points\] RDT 2.1 uses a modified Packet class to send ACKs +Implement your solution in files `link_2.py`, `network_2.py`, and `simulation_2.py`. - * \[2 points\] RDT 2.1 does not deliver corrupt packets - * \[2 points\] RDT 2.1 uses modified Packet class to send NAKs for corrupt packets +* \[13 points\] The current router implementation supports very simple forwarding. +The router has only one input and one output interface and just forwards packets from one to the other. +Your tasks is to implement forwarding of packets within routers based on routing tables. - * \[2 points\] RDT 2.1 resends data following a NAK +First configure `simulation.py` to reflect the following network topology. -* \[13 points\] `rdt_3_0.py`, `client_3_0.py`, `server_3_0.py`, `network_3_0.py` that correctly implement RDT 3.0 and a link to a YouTube video showing the execution of your program. +![network_2](https://github.com/msu-netlab/MSU_CSCI_466_PAs/blob/data_plane/images/network.png) - * \[2 points\] RDT 3.0 delivers data under no corruption or loss in the network and uses a modified Packet class to send ACKs - - * \[2 points\] RDT 3.0 does not deliver corrupt packets and uses modified Packet class to send NAKs - - * \[2 points\] RDT 3.0 resends data following a NAK - - * \[2 points\] RDT 3.0 retransmits a lost packet after a timeout - - * \[2 points\] RDT 3.0 retransmits a packet after a lost ACK - - * \[3 points\] RDT 3.0 ignores a duplicate packet after a premature timeout (or after a lost ACK) +Second, create routing tables so that both Host 1 and Host 2 can send packets to Host 3 and Host 4 respectively. +The routing table for each router should be passed into the `Router` constructor, and so should be defined in `simulation.py`. +The format of these is up to you. +You will also need to modify the `Router` class to forward the packets correctly between interfaces according to your routing tables. -* \[1 points\] (BONUS) `rdt_3_1.py`, `client_3_1.py`, `server_3_1.py`, `network_3_1.py` that correctly implement RDT 3.1 and a link to a YouTube video showing the execution of your program. +Finally, third extend `NetworkPacket` with a source address and a destination address. +Configure the routing tables to forward packets from Host 1 through Router B and from Host 2 through Router C. -* \[1 points\] (BONUS) `rdt_4_0.py`, `client_4_0.py`, `server_4_0.py`, `network_4_0.py` that correctly implement RDT 4.0 and a link to a YouTube video showing the execution of your program. +Implement your solution in files `link_3.py`, `network_3.py`, and `simulation_3.py`. +* \[1 point\] BONUS: The communication in the network above is one directional. +Extend the network to carry packets both ways and have Host 3 send acknowledgements to Hosts 1 and 2. +Implement your solution in files `link_4.py`, `network_4.py`, and `simulation_4.py`. -## Acknowledgements -This project was adapted from Kishore Ramachandran version of this assignment. +## What to Submit +You will submit the different `link.py`, `network.py`, and `simulation.py` files. +You will also submit a link to a single YouTube video under 5 minutes in length that shows the execution of your code implementing the different functionalities outlined in the grading rubric. From 5131e80d3a51f88cfaaba8ff6e75021505a96d38 Mon Sep 17 00:00:00 2001 From: mwittie Date: Mon, 15 Oct 2018 20:51:24 -0600 Subject: [PATCH 06/40] Update README.md formatted indents --- README.md | 56 +++++++++++++++++++++++++------------------------------ 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index f0d8378..1c58398 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## Instructions -Complete the following assignment in pairs, or groups of three. +Complete the following assignment in pairs. Submit your work on D2L into the "Programming Assignment 3" folder before its due date. All partners will submit the same solution and we will only grade one solution for each group. @@ -24,7 +24,7 @@ The next assignment will complement these functions at the control plane. ### Starting Code -The starting code for this project provides you with the implementation several network layers that cooperate to provide end-to-end communication. +The starting code for this project provides you with the implementation of several network layers that cooperate to provide end-to-end communication. ``` NETWORK LAYER (network.py) @@ -51,13 +51,7 @@ To run the starting code you may execute: python simulation.py ``` -and - -``` -python client.py localhost 5000 -``` - -The current `simulation_time' in `simulation.py' is one second. +The current `simulation_time` in `simulation.py` is one second. As the network becomes more complex and takes longer to execute, you may need to extend the simulation to allow all the packets to be transfered. @@ -66,45 +60,45 @@ As the network becomes more complex and takes longer to execute, you may need to Your task is to extend the given code to implement several data link router functions. * \[2 points\] Currently `simulation.py` is configured to send three very short messages. -Instead, generate a message for `Host_2` that's at least 80 characters long. -You will notice that this messages is to large for the link MTUs. -Your first task is to break this message up into two separate packets. + Instead, generate a message for `Host_2` that's at least 80 characters long. + You will notice that this messages is to large for the link MTUs. + Your first task is to break this message up into two separate packets. -Implement your solution in files `link_1.py`, `network_1.py`, and `simulation_1.py`. + Implement your solution in files `link_1.py`, `network_1.py`, and `simulation_1.py`. * \[10 points\] The packets you created are small enough to pass over the links. -However, if we change the MTU of the second link (between `Router_A` and `Host_2`) to 30, the packets will now need to be segmented. + However, if we change the MTU of the second link (between `Router_A` and `Host_2`) to 30, the packets will now need to be segmented. -Your task is to extend the network layer to support segmentation. -Study lecture notes and the book on how IP implements segmentation. -Extend the classes (including packet format) in `network.py` to match IP's mechanisms for packet segmentation and reconstruction. + Your task is to extend the network layer to support segmentation. + Study lecture notes and the book on how IP implements segmentation. + Extend the classes (including packet format) in `network.py` to match IP's mechanisms for packet segmentation and reconstruction. -Implement your solution in files `link_2.py`, `network_2.py`, and `simulation_2.py`. + Implement your solution in files `link_2.py`, `network_2.py`, and `simulation_2.py`. * \[13 points\] The current router implementation supports very simple forwarding. -The router has only one input and one output interface and just forwards packets from one to the other. -Your tasks is to implement forwarding of packets within routers based on routing tables. + The router has only one input and one output interface and just forwards packets from one to the other. + Your tasks is to implement forwarding of packets within routers based on routing tables. -First configure `simulation.py` to reflect the following network topology. + First configure `simulation.py` to reflect the following network topology. -![network_2](https://github.com/msu-netlab/MSU_CSCI_466_PAs/blob/data_plane/images/network.png) + ![network_2](https://github.com/msu-netlab/MSU_CSCI_466_PAs/blob/data_plane/images/network.png) -Second, create routing tables so that both Host 1 and Host 2 can send packets to Host 3 and Host 4 respectively. -The routing table for each router should be passed into the `Router` constructor, and so should be defined in `simulation.py`. -The format of these is up to you. -You will also need to modify the `Router` class to forward the packets correctly between interfaces according to your routing tables. + Second, create routing tables so that both Host 1 and Host 2 can send packets to Host 3 and Host 4 respectively. + The routing table for each router should be passed into the `Router` constructor, and so should be defined in `simulation.py`. + The format of these is up to you. + You will also need to modify the `Router` class to forward the packets correctly between interfaces according to your routing tables. -Finally, third extend `NetworkPacket` with a source address and a destination address. -Configure the routing tables to forward packets from Host 1 through Router B and from Host 2 through Router C. + Finally, third extend `NetworkPacket` with a source address and a destination address. + Configure the routing tables to forward packets from Host 1 through Router B and from Host 2 through Router C. -Implement your solution in files `link_3.py`, `network_3.py`, and `simulation_3.py`. + Implement your solution in files `link_3.py`, `network_3.py`, and `simulation_3.py`. * \[1 point\] BONUS: The communication in the network above is one directional. -Extend the network to carry packets both ways and have Host 3 send acknowledgements to Hosts 1 and 2. + Extend the network to carry packets both ways and have Host 3 send acknowledgements to Hosts 1 and 2. -Implement your solution in files `link_4.py`, `network_4.py`, and `simulation_4.py`. + Implement your solution in files `link_4.py`, `network_4.py`, and `simulation_4.py`. From cfe338e36132eed03d19f68c42b37389fc251da4 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 17 Oct 2018 17:49:43 -0600 Subject: [PATCH 07/40] minor cosmetic changes --- .project | 17 +++++++++++++++++ .pydevproject | 8 ++++++++ README.md | 4 ++-- link.py | 2 +- network.py | 7 ++++--- network.pyc | Bin 0 -> 6504 bytes simulation.py | 3 ++- 7 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 .project create mode 100644 .pydevproject create mode 100644 network.pyc 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/README.md b/README.md index 1c58398..0fe5b49 100644 --- a/README.md +++ b/README.md @@ -90,8 +90,8 @@ Your task is to extend the given code to implement several data link router func The format of these is up to you. You will also need to modify the `Router` class to forward the packets correctly between interfaces according to your routing tables. - Finally, third extend `NetworkPacket` with a source address and a destination address. - Configure the routing tables to forward packets from Host 1 through Router B and from Host 2 through Router C. +Finally, third, extend `NetworkPacket` with a source address and a destination address. +Configure the routing tables to forward packets from Host 1 through Router B and from Host 2 through Router C. Implement your solution in files `link_3.py`, `network_3.py`, and `simulation_3.py`. diff --git a/link.py b/link.py index c7afd73..fddd7ba 100644 --- a/link.py +++ b/link.py @@ -1,7 +1,7 @@ ''' Created on Oct 12, 2016 -@author: mwitt_000 +@author: mwittie ''' import queue diff --git a/network.py b/network.py index f08911f..69bdf2b 100644 --- a/network.py +++ b/network.py @@ -1,7 +1,7 @@ ''' Created on Oct 12, 2016 -@author: mwitt_000 +@author: mwittie ''' import queue import threading @@ -27,6 +27,7 @@ def get(self): def put(self, pkt, block=False): self.queue.put(pkt, block) + ## Implements a network layer packet (different from the RDT packet # from programming assignment 2). # NOTE: This class will need to be extended to for the packet to include @@ -82,13 +83,13 @@ def __str__(self): def udt_send(self, dst_addr, data_S): p = NetworkPacket(dst_addr, data_S) self.out_intf_L[0].put(p.to_byte_S()) #send packets always enqueued successfully - print('%s: sending packet "%s" out interface with mtu=%d' % (self, p, self.out_intf_L[0].mtu)) + print('%s: sending packet "%s" on the out interface with mtu=%d' % (self, p, self.out_intf_L[0].mtu)) ## receive packet from the network layer def udt_receive(self): pkt_S = self.in_intf_L[0].get() if pkt_S is not None: - print('%s: received packet "%s"' % (self, pkt_S)) + print('%s: received packet "%s" on the in interface' % (self, pkt_S)) ## thread target for the host to keep receiving data def run(self): diff --git a/network.pyc b/network.pyc new file mode 100644 index 0000000000000000000000000000000000000000..96811580eae63555a1a2c184fbee01f0de17bba0 GIT binary patch literal 6504 zcmd5=?Q+}36}=07NTj7Wu^idXM~yg9N;hdnPExg_X)~$i&zV$7Ab0GlN-#74Qj|zQ z29~wuiGRs2&>x-YEA%Bg{nP0K|erovr9HmH6#Ui_?jl`Y|jqIY~+Meus64xX?BXM1xi=2r3VHej8iI*g9PP^(7 zFG##B@#3@#Ls}BQB5`}#)#M9yI%oObpQCDT>myz|aef**S@%pumcKh@0 z*zRZfrayW-ur^#@UvCfojnB4i;l8`6P4ZqhN~Y#`=)KOzUPYC_9L!%WdJc17g2y>v z;t3{N6LvNv*y+?UdVnGkA22CfXcw1JS&;)H-x)bUEfT7y*nkHFh8T?5)`!>znvcfG zIMLSE+Tzo;TN_ah4x`baZSN@y0M5qyrm<%V-yzp_p?fqJ1nhPMxxj#_@Uj;y_(vJ-TM+ ze)_u9>8zR@wk7{Sj4^c316HM%_>6!LQ*ZPWtB$(W9Tv`IH%CYIIH0HWhJfSe>6UXqe3W82{a)Ijyc(V_Fj!(kR3 z&eh~InAe*q1*a^g(?nguBkhioFtly-gyATQ$3t8Ogs?g)U|0sczcN5uKR%uVOhP!5 zg41t$ON~~`64%=f@?UkM!^B4D&<{<$j#B&zRWe}?a)v26qa+W<92n4$1VrZO5Ja*- zA|YA_tfm0ra}^+~Xb>05A3z5i%1)EW;7IjyT-dN1$GPRiy{EmwaA>)rlQj=_!eNs3 z?Y>)LzSc1Wu~0e;Ew}h{SoamY`x=S{m6$ipvQf>XR#XhlQrwub>nR>Ra zDbguY&5CJMvW)S~cfXP;S5w_yT3ZwKcT4VB|_Q z=`I?MM_e}LI0LCT?7T$ctufUE5I`;l@=QWeTA2)~ORpxYx9=z{_;^P!1sQCu*z}7e zjgb-iN16-xSJsLvJ|N)(A#P*gW81OsBWM53TCC#k=!lyF&2lt?15SE|ou3MW4c8x#u zExDP#&66m>1|^<#u1jNnf)OrT9yQiI+*I<{mjl5mi?Or}kg7+}xz(;jTk}u*Q zAE)2qr&vNI=5^EZ+FlF49o<}15~`P^j)3yyOu33yg&A$lfvDfYEK~_qGHb1cR+C>% z;{(u2l1CV!pQ&gvo&5hrvplMZ>bQ$6$=@xMqKAr1se6i2G(G8~6MU$f^BdoslV^2R zO3hWj_2kiIy`k|x$KBR(*z8p5s;fZkDU1|Z+!|^K=iRiQsNx11>SRkto}6>=0q05= zsSZckIJK6`i!+c=k4{1!g(!E1)uPT}J!#_+%2aq;Ugst8|8n6~-{_)GrI&`Ej(}6? zeL~a7|Kf}ym=;kKQ2#>X$v=oGECg9gK?I$>@y*}3&jA#W&L7+IKS1shHa#cW3&7!T z>cw9KfH-KD5G>_;f%0+bU{U3}#e0ATJuA^SQNhu0Z!O23kzS*eoBNMeamKVH2g`cK z^mjdDno2)(Nd+H+9-z!J>1VKry41_^$KCt|*=G(vqx);IZ!;hDBmqFXc&~iKY`QQ+ za>aAYDrG>Xplqc(#TstGhRy{7FP!k{riO`r@Pkx>fY*G&ofL6RZjFb-D!b6Q7zFQf z+&ie|86hezr9YoxE(Nt;%v+{uYTk;sWX{&##(~-UI53+zMI_|5IC`$a8LVLn!TYR! z%1Ryo30pp@Qxc}swLha&qG;MiMUlIknSG_wv5KOaa60txXPDz5XHQmXHd>3=LY}Yi zI@99x%M+M5i*TT*8x&n3{39;uro0Ax=FE-!_s3TT&8mOxPCq}Rrs?`LksU78S1zph HD_8#m(6_B@ literal 0 HcmV?d00001 diff --git a/simulation.py b/simulation.py index 3b02832..72f319d 100644 --- a/simulation.py +++ b/simulation.py @@ -1,7 +1,7 @@ ''' Created on Oct 12, 2016 -@author: mwitt_000 +@author: mwittie ''' import network import link @@ -28,6 +28,7 @@ object_L.append(link_layer) #add all the links + #link parameters: from_node, from_intf_num, to_node, to_intf_num, mtu link_layer.add_link(link.Link(client, 0, router_a, 0, 50)) link_layer.add_link(link.Link(router_a, 0, server, 0, 50)) From 20a6f2b605d57b84f7e73404732309e289187a87 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 17 Oct 2018 17:50:00 -0600 Subject: [PATCH 08/40] Ignoring pyproject --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 542ec04..794bd4d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ __pycache* bin/ lib/python3\.6/ + +\.pydevproject From 3720292f4c5cf2dd4ce7d1af3ddbbf7ca987ca40 Mon Sep 17 00:00:00 2001 From: mwittie Date: Wed, 31 Oct 2018 17:03:27 -0600 Subject: [PATCH 09/40] removed the requirement for including src_addr in packets --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0fe5b49..1d4dc19 100644 --- a/README.md +++ b/README.md @@ -90,8 +90,8 @@ Your task is to extend the given code to implement several data link router func The format of these is up to you. You will also need to modify the `Router` class to forward the packets correctly between interfaces according to your routing tables. -Finally, third, extend `NetworkPacket` with a source address and a destination address. -Configure the routing tables to forward packets from Host 1 through Router B and from Host 2 through Router C. +Finally, third, configure the routing tables to forward packets from Host 1 through Router B and from Host 2 through Router C. +You may extend `NetworkPacket` with a source address, but it is not necessary to forward a packet onto different paths. Implement your solution in files `link_3.py`, `network_3.py`, and `simulation_3.py`. From 23ae91c3e98fe3035f8042e7b434028726c571a6 Mon Sep 17 00:00:00 2001 From: kBlack Date: Mon, 26 Nov 2018 01:18:27 -0700 Subject: [PATCH 10/40] Table is done Just needs to be converted into something printable --- network.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/network.py b/network.py index 0bb95f0..1298ccc 100644 --- a/network.py +++ b/network.py @@ -1,6 +1,6 @@ import queue import threading - +from collections import defaultdict ## wrapper class for a queue of packets class Interface: @@ -141,14 +141,60 @@ def __init__(self, name, cost_D, max_queue_size): #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}} + # {destination: {router: cost}} + self.rt_tbl_D = {} # {destination: {router: cost}} + i = 0 #interface count + for neigh, link in self.cost_D.items(): #iterate through each known neighbor + for x in range(4): #because python is garbage and relies on dicts and we don't know what interface will be used in what order, we will scan all possible ones. + if x in link: + self.rt_tbl_D[neigh] = {self.name: link[x]} #nested dict + + + #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_table = "" + + #top border + print_table = '======' + for i in self.rt_tbl_D: + #iterate through each known destination + print_table += '======' + print_table += '\n' #new line + + #table headers + print_table += '| {0} |'.format(self.name) #which router it's printing from + for i in self.rt_tbl_D: + #iterate through each known neighbor + print_table += ' {0} |'.format(i) #destination headers + + print_table += '\n' #new line + + #header border + print_table = '======' + for i in self.rt_tbl_D: + #iterate through each known destination + print_table += '======' + print_table += '\n' #new line + + #now, all the rows + for i in self.rt_tbl_D: + known_routers = {} + if list(self.rt_tbl_D[i].keys())[0] in known_routers: + #add to that router + else: + #add the router + known_routers[list(self.rt_tbl_D[i].keys())[0]] + + + #return print(self.rt_tbl_D) From 528a4261a02538e734384fb90b4c3c9f1cf3c3aa Mon Sep 17 00:00:00 2001 From: Keefer Sands Date: Mon, 26 Nov 2018 12:48:49 -0700 Subject: [PATCH 11/40] Very basic pretty print --- network.py | 40 +++++++++++++++++++++++++++++++++------- simulation.py | 16 +++++++--------- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/network.py b/network.py index 0bb95f0..914fb62 100644 --- a/network.py +++ b/network.py @@ -1,6 +1,6 @@ import queue import threading - +import sys ## wrapper class for a queue of packets class Interface: @@ -142,15 +142,41 @@ def __init__(self, name, cost_D, max_queue_size): 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() - + self.print_routes(); + print('%s: Initialized routing table' % self) ## Print routing table def print_routes(self): - #TODO: print the routes as a two dimensional table - print(self.rt_tbl_D) - + keys = self.cost_D.keys(); + values = self.cost_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" + for item in keys: + keyString += " " + item + " │"; + costRow = ""; + for item in values: + costRow+= " " + str(list(item.values())[0]) + "│" + costRow+="\n" + router = "│ " + self.name + " │" + sys.stdout.write(topTableString + "│ " +self.name + " │" + keyString + "\n" + headerBottomTableString); + sys.stdout.write(router + costRow) + sys.stdout.write(tableBottom); ## called when printing the object def __str__(self): diff --git a/simulation.py b/simulation.py index b9c412b..b1b50af 100644 --- a/simulation.py +++ b/simulation.py @@ -7,29 +7,26 @@ ##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) - + host_3 = network.Host('H3'); + object_L.append(host_3); #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) @@ -44,7 +41,7 @@ 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() @@ -52,9 +49,10 @@ 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() + #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') From 37790bdd6d91bb3903306c9dfd2e2c879c213db7 Mon Sep 17 00:00:00 2001 From: Keefer Sands Date: Mon, 26 Nov 2018 14:41:00 -0700 Subject: [PATCH 12/40] added simple comma and colon delimited message for updates --- network.py | 17 +++++++++++++++-- simulation.py | 2 -- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/network.py b/network.py index 914fb62..7655acd 100644 --- a/network.py +++ b/network.py @@ -144,7 +144,20 @@ def __init__(self, name, cost_D, max_queue_size): self.rt_tbl_D = {} # {destination: {router: cost}} self.print_routes(); print('%s: Initialized routing table' % self) - + def getCurrentRoutingTable(self): + routingTableString = "" + values = list(self.cost_D.values()); + valueValues = self.cost_D + keys = list(self.cost_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 print_routes(self): keys = self.cost_D.keys(); @@ -222,7 +235,7 @@ def forward_packet(self, p, i): 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') + 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) diff --git a/simulation.py b/simulation.py index b1b50af..6c33eb2 100644 --- a/simulation.py +++ b/simulation.py @@ -14,8 +14,6 @@ object_L.append(host_1) host_2 = network.Host('H2') object_L.append(host_2) - host_3 = network.Host('H3'); - object_L.append(host_3); #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', From 9b571a4818eb850eef4f827e3a198b47c843f735 Mon Sep 17 00:00:00 2001 From: Keefer Sands Date: Mon, 26 Nov 2018 15:40:18 -0700 Subject: [PATCH 13/40] added basic hardcoded routing table --- network.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/network.py b/network.py index 7655acd..6409722 100644 --- a/network.py +++ b/network.py @@ -141,7 +141,10 @@ def __init__(self, name, cost_D, max_queue_size): #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}} + self.rt_tbl_D = {"H1": {"RA": -1, "RB": -1}, + "H2": {"RA": -1, "RB": -1}, + "RA": {"RA": 0, "RB": -1}, + "RB": {"RA": -1, "RB": 0}} # {destination: {router: cost}} self.print_routes(); print('%s: Initialized routing table' % self) def getCurrentRoutingTable(self): @@ -153,9 +156,9 @@ def getCurrentRoutingTable(self): for i in range(len(keys)): if first: first = False; - routingTableString+= keys[i] + "," + str(list(values[i])[0]) + "," + str(list(values[i].values())[0])+ "," + 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]) + "," + routingTableString+= ":" + keys[i] + "," + str(list(values[i])[0]) + "," + str(list(values[i].values())[0]) print(routingTableString); return routingTableString; ## Print routing table @@ -249,6 +252,16 @@ def send_routes(self, i): 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(':') + for j in updates: + items = j.split(","); + first = True; + if items[0] in self.cost_D: + print("FOUND IN TABLE") + del self.cost_D[items[0]]; + self.cost_D[items[0]] = {items[1]:items[2]}; + else: + self.cost_D[items[0]] = {items[1]:items[2]}; print('%s: Received routing update %s from interface %d' % (self, p, i)) From feb749f8fcbd482d1f47330ccd5e125f7e097199 Mon Sep 17 00:00:00 2001 From: Keefer Sands Date: Mon, 26 Nov 2018 17:17:14 -0700 Subject: [PATCH 14/40] swapped to router table, able to get a correct print when no math is done to calculate distances --- network.py | 69 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 22 deletions(-) diff --git a/network.py b/network.py index 6409722..455fb19 100644 --- a/network.py +++ b/network.py @@ -141,17 +141,18 @@ def __init__(self, name, cost_D, max_queue_size): #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 = {"H1": {"RA": -1, "RB": -1}, - "H2": {"RA": -1, "RB": -1}, - "RA": {"RA": 0, "RB": -1}, - "RB": {"RA": -1, "RB": 0}} # {destination: {router: cost}} + # {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 = "" - values = list(self.cost_D.values()); - valueValues = self.cost_D - keys = list(self.cost_D.keys()); + 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: @@ -163,8 +164,8 @@ def getCurrentRoutingTable(self): return routingTableString; ## Print routing table def print_routes(self): - keys = self.cost_D.keys(); - values = self.cost_D.values(); + keys = self.rt_tbl_D.keys(); + values = self.rt_tbl_D.values(); columns = len(keys)+1; keyString = ""; topTableString = "╒" @@ -183,15 +184,29 @@ def print_routes(self): headerBottomTableString+= "══════╡\n" tableRowSeperator += "──────┤\n" tableBottom += "══════╛\n" + itemSpace = " "; + routers = {}; for item in keys: keyString += " " + item + " │"; - costRow = ""; - for item in values: - costRow+= " " + str(list(item.values())[0]) + "│" - costRow+="\n" - router = "│ " + self.name + " │" + for i in range(len(values)): + if list(list(values)[i].keys())[0] not in routers: + routers[list(list(values)[i].keys())[0]] = ""; + costRows = []; + uniqueRouters = []; + for item in routers: + costRows.append("│ " + item + " │"); + uniqueRouters.append(item); + for i in range(len(values)): + for j in range(len(costRows)): + if list(list(values)[i].keys())[0] == uniqueRouters[j]: + formattedVal = itemSpace[0:len(itemSpace)-len(str(list(list(values)[i].values())[0]))] + str(list(list(values)[i].values())[0]) + costRows[j]+= formattedVal + "│" sys.stdout.write(topTableString + "│ " +self.name + " │" + keyString + "\n" + headerBottomTableString); - sys.stdout.write(router + costRow) + 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 @@ -252,16 +267,26 @@ def send_routes(self, i): 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(':') - for j in updates: + updates = p.to_byte_S()[6:].split('-') + name = updates[0]; + update = updates[1].split(":"); + for j in update: items = j.split(","); first = True; - if items[0] in self.cost_D: + if items[0] in self.rt_tbl_D: print("FOUND IN TABLE") - del self.cost_D[items[0]]; - self.cost_D[items[0]] = {items[1]:items[2]}; + values = list(self.rt_tbl_D.values()) + exists = False; + #already in table + for i in range(len(values)): + if(list(values[i].keys())[0] == items[1]): + #do stuff/compare + print("Do something"); + exists = True; + if not exists: + self.rt_tbl_D[items[0]][name] = items[2]; else: - self.cost_D[items[0]] = {items[1]:items[2]}; + self.rt_tbl_D[items[0]] = {name:items[2]}; print('%s: Received routing update %s from interface %d' % (self, p, i)) From ea348c8fdfeb308d73a580b324d4bb8d8085d72b Mon Sep 17 00:00:00 2001 From: Keefer Sands Date: Mon, 26 Nov 2018 18:26:04 -0700 Subject: [PATCH 15/40] fixed table display --- network.py | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/network.py b/network.py index 455fb19..ecb3912 100644 --- a/network.py +++ b/network.py @@ -193,14 +193,23 @@ def print_routes(self): routers[list(list(values)[i].keys())[0]] = ""; costRows = []; uniqueRouters = []; + changed = []; for item in routers: costRows.append("│ " + item + " │"); uniqueRouters.append(item); for i in range(len(values)): for j in range(len(costRows)): - if list(list(values)[i].keys())[0] == uniqueRouters[j]: - formattedVal = itemSpace[0:len(itemSpace)-len(str(list(list(values)[i].values())[0]))] + str(list(list(values)[i].values())[0]) - costRows[j]+= formattedVal + "│" + for k in range(len(list(values)[i].keys())): + if list(list(values)[i].keys())[k] == 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); + 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): @@ -279,15 +288,25 @@ def update_routes(self, p, i): exists = False; #already in table for i in range(len(values)): - if(list(values[i].keys())[0] == items[1]): - #do stuff/compare - print("Do something"); - exists = True; + vks = list(values[i].keys()); + for vk in vks: + if vk == items[1]: + self.rt_tbl_D[items[0]][items[1]] = items[2]; + #do stuff/compare + print(items[0]) + print(self.rt_tbl_D[items[0]]); + print("Do something"); + exists = True; if not exists: - self.rt_tbl_D[items[0]][name] = items[2]; + print(items[0]) + print(self.rt_tbl_D[items[0]]); + self.rt_tbl_D[items[0]][items[1]] = items[2]; else: - self.rt_tbl_D[items[0]] = {name:items[2]}; + print(items[0]) + self.rt_tbl_D[items[0]] = {items[1]:items[2]}; + print(self.rt_tbl_D); print('%s: Received routing update %s from interface %d' % (self, p, i)) + print(self.rt_tbl_D); ## thread target for the host to keep forwarding data From d58d89bd3ea0d57f61ad9304afb05b8d1eec2c7b Mon Sep 17 00:00:00 2001 From: Keefer Sands Date: Mon, 26 Nov 2018 18:37:55 -0700 Subject: [PATCH 16/40] actually fixed table --- network.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/network.py b/network.py index ecb3912..bd05b0f 100644 --- a/network.py +++ b/network.py @@ -198,18 +198,23 @@ def print_routes(self): costRows.append("│ " + item + " │"); uniqueRouters.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] == 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 + if(l in changed): + continue; else: costRows[l] += " │" - changed = []; + changed = []; + sys.stdout.write(topTableString + "│ " +self.name + " │" + keyString + "\n" + headerBottomTableString); for i in range(len(costRows)): if i+1 != len(costRows): From f45089780e903bd33e61cc44f3b45091e2cd9cfa Mon Sep 17 00:00:00 2001 From: Keefer Sands Date: Mon, 26 Nov 2018 19:18:25 -0700 Subject: [PATCH 17/40] current progress --- network.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/network.py b/network.py index bd05b0f..0802e39 100644 --- a/network.py +++ b/network.py @@ -275,7 +275,6 @@ def send_routes(self, i): 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): @@ -284,6 +283,7 @@ def update_routes(self, p, i): updates = p.to_byte_S()[6:].split('-') name = updates[0]; update = updates[1].split(":"); + #Raw updating for j in update: items = j.split(","); first = True; @@ -298,22 +298,14 @@ def update_routes(self, p, i): if vk == items[1]: self.rt_tbl_D[items[0]][items[1]] = items[2]; #do stuff/compare - print(items[0]) - print(self.rt_tbl_D[items[0]]); print("Do something"); exists = True; if not exists: - print(items[0]) - print(self.rt_tbl_D[items[0]]); self.rt_tbl_D[items[0]][items[1]] = items[2]; else: - print(items[0]) self.rt_tbl_D[items[0]] = {items[1]:items[2]}; - print(self.rt_tbl_D); - print('%s: Received routing update %s from interface %d' % (self, p, i)) - print(self.rt_tbl_D); + - ## thread target for the host to keep forwarding data def run(self): print (threading.currentThread().getName() + ': Starting') @@ -321,4 +313,4 @@ def run(self): self.process_queues() if self.stop: print (threading.currentThread().getName() + ': Ending') - return \ No newline at end of file + return From 930407b4618e8be0e308a9111b85f0d2a05ca97d Mon Sep 17 00:00:00 2001 From: Keefer Sands Date: Mon, 26 Nov 2018 22:54:12 -0700 Subject: [PATCH 18/40] partially setting up others --- link_1.py | 75 ++++++++++++ link_2.py | 75 ++++++++++++ link_3.py | 75 ++++++++++++ network.py | 6 +- network_1.py | 314 ++++++++++++++++++++++++++++++++++++++++++++++++ network_2.py | 314 ++++++++++++++++++++++++++++++++++++++++++++++++ network_3.py | 314 ++++++++++++++++++++++++++++++++++++++++++++++++ simulation_1.py | 67 +++++++++++ simulation_2.py | 68 +++++++++++ simulation_3.py | 70 +++++++++++ 10 files changed, 1374 insertions(+), 4 deletions(-) create mode 100644 link_1.py create mode 100644 link_2.py create mode 100644 link_3.py create mode 100644 network_1.py create mode 100644 network_2.py create mode 100644 network_3.py create mode 100644 simulation_1.py create mode 100644 simulation_2.py create mode 100644 simulation_3.py diff --git a/link_1.py b/link_1.py new file mode 100644 index 0000000..e488016 --- /dev/null +++ b/link_1.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_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 index 0802e39..cec6080 100644 --- a/network.py +++ b/network.py @@ -1,6 +1,7 @@ import queue import threading import sys +import math ## wrapper class for a queue of packets class Interface: @@ -286,9 +287,7 @@ def update_routes(self, p, i): #Raw updating for j in update: items = j.split(","); - first = True; if items[0] in self.rt_tbl_D: - print("FOUND IN TABLE") values = list(self.rt_tbl_D.values()) exists = False; #already in table @@ -298,13 +297,12 @@ def update_routes(self, p, i): if vk == items[1]: self.rt_tbl_D[items[0]][items[1]] = items[2]; #do stuff/compare - print("Do something"); exists = True; if not exists: self.rt_tbl_D[items[0]][items[1]] = items[2]; else: self.rt_tbl_D[items[0]] = {items[1]:items[2]}; - + ## thread target for the host to keep forwarding data def run(self): diff --git a/network_1.py b/network_1.py new file mode 100644 index 0000000..cec6080 --- /dev/null +++ b/network_1.py @@ -0,0 +1,314 @@ +import queue +import threading +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 # {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 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 = " "; + routers = {}; + for item in keys: + keyString += " " + item + " │"; + for i in range(len(values)): + if list(list(values)[i].keys())[0] not in routers: + routers[list(list(values)[i].keys())[0]] = ""; + costRows = []; + uniqueRouters = []; + changed = []; + for item in routers: + costRows.append("│ " + item + " │"); + uniqueRouters.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] == 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) + 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: + items = j.split(","); + if items[0] in self.rt_tbl_D: + values = list(self.rt_tbl_D.values()) + exists = False; + #already in table + for i in range(len(values)): + vks = list(values[i].keys()); + for vk in vks: + if vk == items[1]: + self.rt_tbl_D[items[0]][items[1]] = items[2]; + #do stuff/compare + exists = True; + if not exists: + self.rt_tbl_D[items[0]][items[1]] = items[2]; + else: + self.rt_tbl_D[items[0]] = {items[1]:items[2]}; + + + ## 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..82b9656 --- /dev/null +++ b/network_2.py @@ -0,0 +1,314 @@ +import queue +import threading +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 # {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 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 = " "; + routers = {}; + for item in keys: + keyString += " " + item + " │"; + for i in range(len(values)): + if list(list(values)[i].keys())[0] not in routers: + routers[list(list(values)[i].keys())[0]] = ""; + costRows = []; + uniqueRouters = []; + changed = []; + for item in routers: + costRows.append("│ " + item + " │"); + uniqueRouters.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] == 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[i].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', 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: + items = j.split(","); + if items[0] in self.rt_tbl_D: + values = list(self.rt_tbl_D.values()) + exists = False; + #already in table + for i in range(len(values)): + vks = list(values[i].keys()); + for vk in vks: + if vk == items[1]: + self.rt_tbl_D[items[0]][items[1]] = items[2]; + #do stuff/compare + exists = True; + if not exists: + self.rt_tbl_D[items[0]][items[1]] = items[2]; + else: + self.rt_tbl_D[items[0]] = {items[1]:items[2]}; + + + ## 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..82b9656 --- /dev/null +++ b/network_3.py @@ -0,0 +1,314 @@ +import queue +import threading +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 # {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 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 = " "; + routers = {}; + for item in keys: + keyString += " " + item + " │"; + for i in range(len(values)): + if list(list(values)[i].keys())[0] not in routers: + routers[list(list(values)[i].keys())[0]] = ""; + costRows = []; + uniqueRouters = []; + changed = []; + for item in routers: + costRows.append("│ " + item + " │"); + uniqueRouters.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] == 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[i].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', 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: + items = j.split(","); + if items[0] in self.rt_tbl_D: + values = list(self.rt_tbl_D.values()) + exists = False; + #already in table + for i in range(len(values)): + vks = list(values[i].keys()); + for vk in vks: + if vk == items[1]: + self.rt_tbl_D[items[0]][items[1]] = items[2]; + #do stuff/compare + exists = True; + if not exists: + self.rt_tbl_D[items[0]][items[1]] = items[2]; + else: + self.rt_tbl_D[items[0]] = {items[1]:items[2]}; + + + ## 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/simulation_1.py b/simulation_1.py new file mode 100644 index 0000000..6c33eb2 --- /dev/null +++ b/simulation_1.py @@ -0,0 +1,67 @@ +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)) + 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..0527f82 --- /dev/null +++ b/simulation_2.py @@ -0,0 +1,68 @@ +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 = 1 #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, 1, router_b, 0)) + link_layer.add_link(link_2.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') + 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..3e3a0f4 --- /dev/null +++ b/simulation_3.py @@ -0,0 +1,70 @@ +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 = 4 #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') + cost_d_a = {'H1': {0: 1}, 'RB': {1: 1}, 'RC': {1: 1}}; + cost_d_b = {'RA': {0: 1}, 'RD': {1: 1}}; + cost_d_c = {'RA': {0: 1}, 'RD': {1: 1}}; + cost_d_d = {'RB': {0: 1}, 'RC': {1: 1},'H2':{1: 1}}; + 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_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") + From 83eff52a3b2c7cdd01c0c98114d8c25d362996ce Mon Sep 17 00:00:00 2001 From: kBlack Date: Tue, 27 Nov 2018 15:44:57 -0700 Subject: [PATCH 19/40] Added comments to raw updating, step 1 of bellman-ford, global unique routers --- network.py | 108 ++++++++++++++--------------------------------------- 1 file changed, 29 insertions(+), 79 deletions(-) diff --git a/network.py b/network.py index f7f0ad1..09ec627 100644 --- a/network.py +++ b/network.py @@ -1,14 +1,8 @@ import queue import threading -<<<<<<< HEAD from collections import defaultdict -======= import sys -<<<<<<< HEAD ->>>>>>> d58d89bd3ea0d57f61ad9304afb05b8d1eec2c7b -======= import math ->>>>>>> 930407b4618e8be0e308a9111b85f0d2a05ca97d ## wrapper class for a queue of packets class Interface: @@ -149,64 +143,6 @@ def __init__(self, name, cost_D, max_queue_size): #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 -<<<<<<< HEAD - # {destination: {router: cost}} - self.rt_tbl_D = {} # {destination: {router: cost}} - i = 0 #interface count - for neigh, link in self.cost_D.items(): #iterate through each known neighbor - for x in range(4): #because python is garbage and relies on dicts and we don't know what interface will be used in what order, we will scan all possible ones. - if x in link: - self.rt_tbl_D[neigh] = {self.name: link[x]} #nested dict - - - #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_table = "" - - #top border - print_table = '======' - for i in self.rt_tbl_D: - #iterate through each known destination - print_table += '======' - print_table += '\n' #new line - - #table headers - print_table += '| {0} |'.format(self.name) #which router it's printing from - for i in self.rt_tbl_D: - #iterate through each known neighbor - print_table += ' {0} |'.format(i) #destination headers - - print_table += '\n' #new line - - #header border - print_table = '======' - for i in self.rt_tbl_D: - #iterate through each known destination - print_table += '======' - print_table += '\n' #new line - - #now, all the rows - for i in self.rt_tbl_D: - known_routers = {} - if list(self.rt_tbl_D[i].keys())[0] in known_routers: - #add to that router - else: - #add the router - known_routers[list(self.rt_tbl_D[i].keys())[0]] - - - #return - print(self.rt_tbl_D) - -======= # {destination: {router: cost}} ##Initial setup self.rt_tbl_D = {name:{name:0}} keys = list(cost_D.keys()); @@ -288,7 +224,7 @@ def print_routes(self): else: sys.stdout.write(costRows[i] + "\n"); sys.stdout.write(tableBottom); ->>>>>>> d58d89bd3ea0d57f61ad9304afb05b8d1eec2c7b + self.uniqueRouters = uniqueRouters #save this for later, updateRouters needs it ## called when printing the object def __str__(self): @@ -351,25 +287,39 @@ def update_routes(self, p, i): name = updates[0]; update = updates[1].split(":"); #Raw updating - for j in update: - items = j.split(","); - if items[0] in self.rt_tbl_D: - values = list(self.rt_tbl_D.values()) - exists = False; + 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)): - vks = list(values[i].keys()); - for vk in vks: - if vk == items[1]: - self.rt_tbl_D[items[0]][items[1]] = items[2]; + 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: - self.rt_tbl_D[items[0]][items[1]] = items[2]; + 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]}; + self.rt_tbl_D[items[0]] = {items[1]:items[2]} + + + + #calculate the gaps, bell ford + + #step 1: set all unknowns to infinity + for header in self.rt_tbl_D: + #see if header is missing routers in its dict + #for each router, + for router in self.uniqueRouters: + 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? + + #step 2: relax edges - ## thread target for the host to keep forwarding data def run(self): print (threading.currentThread().getName() + ': Starting') From 304c340118a7617b1c158e06ff4d7899473bdb4f Mon Sep 17 00:00:00 2001 From: kBlack Date: Tue, 27 Nov 2018 22:22:47 -0700 Subject: [PATCH 20/40] bellman-ford step 2 ra will not update initial infinite distance to h2 --- network.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/network.py b/network.py index 09ec627..463f78e 100644 --- a/network.py +++ b/network.py @@ -317,9 +317,36 @@ def update_routes(self, p, i): 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 + #step 2: relax edges + #run the algorithm on each router in the table + for router in self.uniqueRouters: #for every router (row) in the network, + # {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. + + 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][router]) #distance to u vertex + v_dist = int(self.rt_tbl_D[v][router]) #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][router] = (self.rt_tbl_D[v][router] + edge_distance) #update the distance to u + self.print_routes + except KeyError: + print("Key error exception occurred" ) + ## thread target for the host to keep forwarding data def run(self): print (threading.currentThread().getName() + ': Starting') From 0c0a6e4cb36a448b803154280635c4f269d58e9e Mon Sep 17 00:00:00 2001 From: kBlack Date: Wed, 28 Nov 2018 15:41:33 -0700 Subject: [PATCH 21/40] BellFord bug worked out, fully functional. Small updates to n_1 and sim_1. Change as needed. --- network.py | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/network.py b/network.py index 463f78e..a2927bc 100644 --- a/network.py +++ b/network.py @@ -305,31 +305,37 @@ def update_routes(self, p, i): else: self.rt_tbl_D[items[0]] = {items[1]:items[2]} - - - #calculate the gaps, bell ford - - #step 1: set all unknowns to infinity - for header in self.rt_tbl_D: - #see if header is missing routers in its dict - #for each router, - for router in self.uniqueRouters: + + + '''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.rt_tbl_D[router][header] = 999''' - #step 2: relax edges - + + self.print_routes() #beacuse uniqueRouters ONLY updates in the print method, we need to run it one more time before we start the algorithm. #run the algorithm on each router in the table - for router in self.uniqueRouters: #for every router (row) in the network, + 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? + self.rt_tbl_D[self.uniqueRouters[j]][header] = 999 # {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. + #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: @@ -337,12 +343,12 @@ def update_routes(self, p, i): #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][router]) #distance to u vertex - v_dist = int(self.rt_tbl_D[v][router]) #distance to v vertex + 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][router] = (self.rt_tbl_D[v][router] + edge_distance) #update the distance to u + self.rt_tbl_D[u][self.uniqueRouters[j]] = v_dist + edge_distance #update the distance to u self.print_routes except KeyError: print("Key error exception occurred" ) From e5ce40d2a6ebf944b637b135bd8e49c0e5477d17 Mon Sep 17 00:00:00 2001 From: kBlack Date: Wed, 28 Nov 2018 16:56:38 -0700 Subject: [PATCH 22/40] forward packets implementation, RA not updating routing table --- .settings/org.eclipse.core.resources.prefs | 1 + network.py | 34 ++++++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs index 63f537b..179bf68 100644 --- a/.settings/org.eclipse.core.resources.prefs +++ b/.settings/org.eclipse.core.resources.prefs @@ -1,2 +1,3 @@ eclipse.preferences.version=1 encoding/network.py=UTF-8 +encoding/network_1.py=UTF-8 diff --git a/network.py b/network.py index a2927bc..4aa7ad5 100644 --- a/network.py +++ b/network.py @@ -141,7 +141,7 @@ def __init__(self, name, cost_D, max_queue_size): #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}} + 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}} @@ -257,7 +257,37 @@ def forward_packet(self, p, i): # 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) + + #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 + 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 + + out_intf = self.cost_D[v][0] #set the outgoing interface to the result. + + self.intf_L[out_intf].put(p.to_byte_S(), 'out', True) print('%s: forwarding packet "%s" from interface %d to %d' % \ (self, p, i, 1)) except queue.Full: From 26a39030370f67a1a282ec13d9e62b3c46f97021 Mon Sep 17 00:00:00 2001 From: Keefer Sands Date: Wed, 28 Nov 2018 20:11:48 -0700 Subject: [PATCH 23/40] added neighbor updating --- network.py | 9 ++++++++- simulation.py | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/network.py b/network.py index 4aa7ad5..ef03981 100644 --- a/network.py +++ b/network.py @@ -364,7 +364,7 @@ def update_routes(self, p, i): 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 @@ -379,9 +379,16 @@ def update_routes(self, p, i): 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.print_routes 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): diff --git a/simulation.py b/simulation.py index 6c33eb2..d90f8dc 100644 --- a/simulation.py +++ b/simulation.py @@ -6,7 +6,7 @@ ##configuration parameters router_queue_size = 0 #0 means unlimited -simulation_time = 1 #give the network sufficient time to execute transfers +simulation_time = 10 #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 From 43619889c681ba7c83cc6a69a35a365efe11bf4b Mon Sep 17 00:00:00 2001 From: Keefer Sands Date: Wed, 28 Nov 2018 20:22:59 -0700 Subject: [PATCH 24/40] fixed neighbor updating more --- network.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/network.py b/network.py index ef03981..26bf8eb 100644 --- a/network.py +++ b/network.py @@ -276,9 +276,13 @@ def forward_packet(self, p, i): #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 - node_dest_d = int(self.rt_tbl_D[header][dest]) #distance from the potential outgoing node to the destination + + if not self.rt_tbl_D.get(dest) == None: + dest_d = int(self.rt_tbl_D[dest][self.name]) #distance to the destination + node_dest_d = int(self.rt_tbl_D[header][dest]) #distance from the potential outgoing node to the destination + else: + node_d = int(self.rt_tbl_D[header][self.name]) #distance to potential outgoing node + if v_d > (node_d + node_dest_d): #find the minimum #new minimum @@ -350,6 +354,7 @@ def update_routes(self, p, i): #run the algorithm on each router in the table router_count = len(self.uniqueRouters) print(router_count) + updated = False; 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: @@ -364,7 +369,7 @@ def update_routes(self, p, i): 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 @@ -379,16 +384,16 @@ def update_routes(self, p, i): 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.print_routes + updated = True# we updated! + self.print_routes() 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); + if(updated):#if updated send update to all neighbors + #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): From 92f0172793d883c3e34a8da2ab2e64c5ec3133c3 Mon Sep 17 00:00:00 2001 From: Keefer Sands Date: Wed, 28 Nov 2018 20:27:28 -0700 Subject: [PATCH 25/40] Fixing random testing --- network.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/network.py b/network.py index 26bf8eb..6939b90 100644 --- a/network.py +++ b/network.py @@ -277,11 +277,9 @@ def forward_packet(self, p, i): if header in self.cost_D: #narrow it down to only neighbors #header is in routing table and is reachable by the node - if not self.rt_tbl_D.get(dest) == None: - dest_d = int(self.rt_tbl_D[dest][self.name]) #distance to the destination - node_dest_d = int(self.rt_tbl_D[header][dest]) #distance from the potential outgoing node to the destination - else: - node_d = int(self.rt_tbl_D[header][self.name]) #distance to potential outgoing node + dest_d = int(self.rt_tbl_D[dest][self.name]) #distance to the destination + node_dest_d = int(self.rt_tbl_D[header][dest]) #distance from the potential outgoing node to the destination + node_d = int(self.rt_tbl_D[header][self.name]) #distance to potential outgoing node if v_d > (node_d + node_dest_d): #find the minimum From b9533533b61cc277df49efffafdecb96c2e17b9a Mon Sep 17 00:00:00 2001 From: kBlack Date: Wed, 28 Nov 2018 20:40:25 -0700 Subject: [PATCH 26/40] H2 key error worked out --- network.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/network.py b/network.py index ef03981..2cb7f17 100644 --- a/network.py +++ b/network.py @@ -275,15 +275,18 @@ def forward_packet(self, p, i): 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 + if not ((header == "H1") or (header == "H2")): #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 - 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 + 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 + except KeyError: + print("Key Error") + if v_d > (node_d + node_dest_d): #find the minimum + #new minimum + v_d = node_d + v = header out_intf = self.cost_D[v][0] #set the outgoing interface to the result. @@ -353,9 +356,9 @@ def update_routes(self, p, i): 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])) + #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])) + #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? self.rt_tbl_D[self.uniqueRouters[j]][header] = 999 From f773819f63d254036921dceaf4460b16a8b3ddcd Mon Sep 17 00:00:00 2001 From: Keefer Sands Date: Wed, 28 Nov 2018 21:13:53 -0700 Subject: [PATCH 27/40] adding method to update unique routers --- network.py | 37 ++++++++++++++++++++----------------- simulation.py | 2 +- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/network.py b/network.py index 2cb7f17..c96ed79 100644 --- a/network.py +++ b/network.py @@ -165,6 +165,15 @@ def getCurrentRoutingTable(self): 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(); @@ -187,23 +196,18 @@ def print_routes(self): tableRowSeperator += "──────┤\n" tableBottom += "══════╛\n" itemSpace = " "; - routers = {}; for item in keys: keyString += " " + item + " │"; - for i in range(len(values)): - if list(list(values)[i].keys())[0] not in routers: - routers[list(list(values)[i].keys())[0]] = ""; costRows = []; - uniqueRouters = []; changed = []; - for item in routers: + self.updateUniqueRouters(); + for item in self.uniqueRouters: costRows.append("│ " + item + " │"); - uniqueRouters.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] == uniqueRouters[j]: + 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); @@ -224,8 +228,6 @@ def print_routes(self): else: sys.stdout.write(costRows[i] + "\n"); sys.stdout.write(tableBottom); - self.uniqueRouters = uniqueRouters #save this for later, updateRouters needs it - ## called when printing the object def __str__(self): return self.name @@ -276,17 +278,19 @@ def forward_packet(self, p, i): #for every node in the routing table, if header in self.cost_D: #narrow it down to only neighbors if not ((header == "H1") or (header == "H2")): + node_dest_d = 0; #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: print("Key Error") - if v_d > (node_d + node_dest_d): #find the minimum - #new minimum - v_d = node_d - v = header + out_intf = self.cost_D[v][0] #set the outgoing interface to the result. @@ -348,8 +352,7 @@ def update_routes(self, p, i): self.rt_tbl_D[router][header] = 999''' - - self.print_routes() #beacuse uniqueRouters ONLY updates in the print method, we need to run it one more time before we start the algorithm. + self.updateUniqueRouters(); #run the algorithm on each router in the table router_count = len(self.uniqueRouters) print(router_count) @@ -383,7 +386,7 @@ def update_routes(self, p, i): #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.print_routes + self.updateUniqueRouters(); except KeyError: print("Key error exception occurred" ) if(updated): diff --git a/simulation.py b/simulation.py index d90f8dc..0433dbe 100644 --- a/simulation.py +++ b/simulation.py @@ -51,7 +51,7 @@ for i in range(len(object_L)): if str(type(object_L[i])) == "": object_L[i].print_routes(); - + sleep(10); #send packet from host 1 to host 2 host_1.udt_send('H2', 'MESSAGE_FROM_H1') sleep(simulation_time) From fed26fb62f9ef60f620e3053b2cfa81d44cbb7da Mon Sep 17 00:00:00 2001 From: Keefer Sands Date: Wed, 28 Nov 2018 22:13:23 -0700 Subject: [PATCH 28/40] Trying to get the routing to work --- network.py | 45 +++++++++++++++++++++++++++------------------ simulation.py | 2 +- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/network.py b/network.py index c96ed79..b91c04f 100644 --- a/network.py +++ b/network.py @@ -277,24 +277,32 @@ def forward_packet(self, p, i): 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 - if not ((header == "H1") or (header == "H2")): - node_dest_d = 0; #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: - print("Key Error") - - - out_intf = self.cost_D[v][0] #set the outgoing interface to the result. - - self.intf_L[out_intf].put(p.to_byte_S(), 'out', True) + 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: + print("Key Error") + #new addition + chosenVal = 999; + chosenRoute = ""; + if v not in self.cost_D:#if v is a neighbor + for value in self.rt_tbl_D[v]:#interate 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 = self.cost_D[chosenRoute][key] #set the outgoing interface to the result. + else: # is a neighbor + for key in self.cost_D[v]: # iterate through values + out_intf = self.cost_D[v][key] #set the outgoing interface to the result. + self.intf_L[out_intf].put(p.to_byte_S(), 'out', True) #send out print('%s: forwarding packet "%s" from interface %d to %d' % \ (self, p, i, 1)) except queue.Full: @@ -335,10 +343,12 @@ def update_routes(self, p, i): 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]} @@ -364,7 +374,6 @@ def update_routes(self, p, i): #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? - self.rt_tbl_D[self.uniqueRouters[j]][header] = 999 # {header: {router: cost}} #bellman ford starts here i=1 diff --git a/simulation.py b/simulation.py index 0433dbe..fbec837 100644 --- a/simulation.py +++ b/simulation.py @@ -6,7 +6,7 @@ ##configuration parameters router_queue_size = 0 #0 means unlimited -simulation_time = 10 #give the network sufficient time to execute transfers +simulation_time = 5 #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 From d1e48b78a0cd0b1f0f8d64cee2a56abc9f9a8d4c Mon Sep 17 00:00:00 2001 From: kBlack Date: Wed, 28 Nov 2018 22:29:51 -0700 Subject: [PATCH 29/40] fix for interface index bug --- network.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/network.py b/network.py index b91c04f..ed26ff3 100644 --- a/network.py +++ b/network.py @@ -287,7 +287,7 @@ def forward_packet(self, p, i): v_d = node_d v = header except KeyError: - print("Key Error") + print("Key Error: Neighbor is likely host") #new addition chosenVal = 999; chosenRoute = ""; @@ -300,9 +300,13 @@ def forward_packet(self, p, i): for key in self.cost_D[chosenRoute]:#set the chosenRoutes interface out_intf = self.cost_D[chosenRoute][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 = self.cost_D[v][key] #set the outgoing interface to the result. - self.intf_L[out_intf].put(p.to_byte_S(), 'out', True) #send out + out_intf = list(self.cost_D[v].keys())[0] #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, {}".format(out_intf)) print('%s: forwarding packet "%s" from interface %d to %d' % \ (self, p, i, 1)) except queue.Full: From b4694d155f6f3317349040c20ba7b3f11526532e Mon Sep 17 00:00:00 2001 From: Keefer Sands Date: Wed, 28 Nov 2018 22:59:27 -0700 Subject: [PATCH 30/40] updating _3 --- network_3.py | 155 ++++++++++++++++++++++++++++++++++++++++-------- simulation_3.py | 6 +- 2 files changed, 133 insertions(+), 28 deletions(-) diff --git a/network_3.py b/network_3.py index 82b9656..dd64ab5 100644 --- a/network_3.py +++ b/network_3.py @@ -1,5 +1,6 @@ import queue import threading +from collections import defaultdict import sys import math @@ -140,7 +141,7 @@ def __init__(self, name, cost_D, max_queue_size): #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}} + 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}} @@ -164,6 +165,15 @@ def getCurrentRoutingTable(self): 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(); @@ -186,23 +196,18 @@ def print_routes(self): tableRowSeperator += "──────┤\n" tableBottom += "══════╛\n" itemSpace = " "; - routers = {}; for item in keys: keyString += " " + item + " │"; - for i in range(len(values)): - if list(list(values)[i].keys())[0] not in routers: - routers[list(list(values)[i].keys())[0]] = ""; costRows = []; - uniqueRouters = []; changed = []; - for item in routers: + self.updateUniqueRouters(); + for item in self.uniqueRouters: costRows.append("│ " + item + " │"); - uniqueRouters.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] == uniqueRouters[j]: + 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); @@ -223,7 +228,6 @@ def print_routes(self): else: sys.stdout.write(costRows[i] + "\n"); sys.stdout.write(tableBottom); - ## called when printing the object def __str__(self): return self.name @@ -254,8 +258,55 @@ 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[i].put(p.to_byte_S(), 'out', True) + # 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: + print("Key Error: Neighbor is likely host") + #new addition + chosenVal = 999; + chosenRoute = ""; + if v not in self.cost_D:#if v is a neighbor + for value in self.rt_tbl_D[v]:#interate 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 = self.cost_D[chosenRoute][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 = list(self.cost_D[v].keys())[0] #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, {}".format(out_intf)) print('%s: forwarding packet "%s" from interface %d to %d' % \ (self, p, i, 1)) except queue.Full: @@ -285,25 +336,79 @@ def update_routes(self, p, i): name = updates[0]; update = updates[1].split(":"); #Raw updating - for j in update: - items = j.split(","); - if items[0] in self.rt_tbl_D: - values = list(self.rt_tbl_D.values()) - exists = False; + 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)): - vks = list(values[i].keys()); - for vk in vks: - if vk == items[1]: - self.rt_tbl_D[items[0]][items[1]] = items[2]; + 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: - self.rt_tbl_D[items[0]][items[1]] = items[2]; + 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]}; + 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') diff --git a/simulation_3.py b/simulation_3.py index 3e3a0f4..65ca14f 100644 --- a/simulation_3.py +++ b/simulation_3.py @@ -6,16 +6,16 @@ ##configuration parameters router_queue_size = 0 #0 means unlimited -simulation_time = 4 #give the network_3 sufficient time to execute transfers +simulation_time = 30 #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') - cost_d_a = {'H1': {0: 1}, 'RB': {1: 1}, 'RC': {1: 1}}; + cost_d_a = {'H1': {0: 1}, 'RB': {1: 2}, 'RC': {2: 1}}; cost_d_b = {'RA': {0: 1}, 'RD': {1: 1}}; cost_d_c = {'RA': {0: 1}, 'RD': {1: 1}}; - cost_d_d = {'RB': {0: 1}, 'RC': {1: 1},'H2':{1: 1}}; + cost_d_d = {'RB': {0: 1}, 'RC': {1: 1},'H2':{2: 1}}; 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); From 2953e1009637dc196fb8b83df60f52271235b1fb Mon Sep 17 00:00:00 2001 From: Keefer Sands Date: Wed, 28 Nov 2018 23:08:48 -0700 Subject: [PATCH 31/40] what ive got so far --- network_1.py | 107 +++++++++++++++++++++++++-------- network_2.py | 155 ++++++++++++++++++++++++++++++++++++++++-------- simulation_1.py | 18 +++--- simulation_2.py | 2 +- 4 files changed, 223 insertions(+), 59 deletions(-) diff --git a/network_1.py b/network_1.py index cec6080..93e0529 100644 --- a/network_1.py +++ b/network_1.py @@ -1,5 +1,6 @@ import queue import threading +from collections import defaultdict import sys import math @@ -140,7 +141,7 @@ def __init__(self, name, cost_D, max_queue_size): #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}} + 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}} @@ -164,6 +165,15 @@ def getCurrentRoutingTable(self): 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(); @@ -186,23 +196,18 @@ def print_routes(self): tableRowSeperator += "──────┤\n" tableBottom += "══════╛\n" itemSpace = " "; - routers = {}; for item in keys: keyString += " " + item + " │"; - for i in range(len(values)): - if list(list(values)[i].keys())[0] not in routers: - routers[list(list(values)[i].keys())[0]] = ""; costRows = []; - uniqueRouters = []; changed = []; - for item in routers: + self.updateUniqueRouters(); + for item in self.uniqueRouters: costRows.append("│ " + item + " │"); - uniqueRouters.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] == uniqueRouters[j]: + 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); @@ -223,7 +228,6 @@ def print_routes(self): else: sys.stdout.write(costRows[i] + "\n"); sys.stdout.write(tableBottom); - ## called when printing the object def __str__(self): return self.name @@ -255,7 +259,8 @@ def forward_packet(self, p, i): # 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) + 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: @@ -285,25 +290,79 @@ def update_routes(self, p, i): name = updates[0]; update = updates[1].split(":"); #Raw updating - for j in update: - items = j.split(","); - if items[0] in self.rt_tbl_D: - values = list(self.rt_tbl_D.values()) - exists = False; + 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)): - vks = list(values[i].keys()); - for vk in vks: - if vk == items[1]: - self.rt_tbl_D[items[0]][items[1]] = items[2]; + 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: - self.rt_tbl_D[items[0]][items[1]] = items[2]; + 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]}; + 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') diff --git a/network_2.py b/network_2.py index 82b9656..ed26ff3 100644 --- a/network_2.py +++ b/network_2.py @@ -1,5 +1,6 @@ import queue import threading +from collections import defaultdict import sys import math @@ -140,7 +141,7 @@ def __init__(self, name, cost_D, max_queue_size): #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}} + 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}} @@ -164,6 +165,15 @@ def getCurrentRoutingTable(self): 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(); @@ -186,23 +196,18 @@ def print_routes(self): tableRowSeperator += "──────┤\n" tableBottom += "══════╛\n" itemSpace = " "; - routers = {}; for item in keys: keyString += " " + item + " │"; - for i in range(len(values)): - if list(list(values)[i].keys())[0] not in routers: - routers[list(list(values)[i].keys())[0]] = ""; costRows = []; - uniqueRouters = []; changed = []; - for item in routers: + self.updateUniqueRouters(); + for item in self.uniqueRouters: costRows.append("│ " + item + " │"); - uniqueRouters.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] == uniqueRouters[j]: + 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); @@ -223,7 +228,6 @@ def print_routes(self): else: sys.stdout.write(costRows[i] + "\n"); sys.stdout.write(tableBottom); - ## called when printing the object def __str__(self): return self.name @@ -254,8 +258,55 @@ 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[i].put(p.to_byte_S(), 'out', True) + # 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: + print("Key Error: Neighbor is likely host") + #new addition + chosenVal = 999; + chosenRoute = ""; + if v not in self.cost_D:#if v is a neighbor + for value in self.rt_tbl_D[v]:#interate 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 = self.cost_D[chosenRoute][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 = list(self.cost_D[v].keys())[0] #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, {}".format(out_intf)) print('%s: forwarding packet "%s" from interface %d to %d' % \ (self, p, i, 1)) except queue.Full: @@ -285,25 +336,79 @@ def update_routes(self, p, i): name = updates[0]; update = updates[1].split(":"); #Raw updating - for j in update: - items = j.split(","); - if items[0] in self.rt_tbl_D: - values = list(self.rt_tbl_D.values()) - exists = False; + 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)): - vks = list(values[i].keys()); - for vk in vks: - if vk == items[1]: - self.rt_tbl_D[items[0]][items[1]] = items[2]; + 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: - self.rt_tbl_D[items[0]][items[1]] = items[2]; + 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]}; + 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') diff --git a/simulation_1.py b/simulation_1.py index 6c33eb2..7204db6 100644 --- a/simulation_1.py +++ b/simulation_1.py @@ -1,4 +1,4 @@ -import network +import network_1 import link import threading from time import sleep @@ -6,26 +6,26 @@ ##configuration parameters router_queue_size = 0 #0 means unlimited -simulation_time = 1 #give the network sufficient time to execute transfers +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 hosts - host_1 = network.Host('H1') + #create network_1 hosts + host_1 = network_1.Host('H1') object_L.append(host_1) - host_2 = network.Host('H2') + 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.Router(name='RA', + 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.Router(name='RB', + 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 nodes + #create a Link Layer to keep track of links between network_1 nodes link_layer = link.LinkLayer() object_L.append(link_layer) @@ -49,7 +49,7 @@ print("Converged routing tables") #Table Header Bottom for i in range(len(object_L)): - if str(type(object_L[i])) == "": + if str(type(object_L[i])) == "": object_L[i].print_routes(); #send packet from host 1 to host 2 diff --git a/simulation_2.py b/simulation_2.py index 0527f82..7637f96 100644 --- a/simulation_2.py +++ b/simulation_2.py @@ -6,7 +6,7 @@ ##configuration parameters router_queue_size = 0 #0 means unlimited -simulation_time = 1 #give the network_2 sufficient time to execute transfers +simulation_time = 10 #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 From 9cf0a83ab2c9ed804ec48ad6597212c808fdb42d Mon Sep 17 00:00:00 2001 From: Keefer Sands Date: Thu, 29 Nov 2018 09:33:22 -0700 Subject: [PATCH 32/40] Style fixes --- .gitignore | 6 + network.py | 100 ++++++------- network_1.py | 327 ++++++++++++++++++++-------------------- network_2.py | 387 ++++++++++++++++++++++++------------------------ network_3.py | 100 ++++++------- simulation.py | 6 +- simulation_1.py | 65 ++++---- simulation_2.py | 69 +++++---- simulation_3.py | 71 +++++---- 9 files changed, 569 insertions(+), 562 deletions(-) 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/network.py b/network.py index ed26ff3..6527434 100644 --- a/network.py +++ b/network.py @@ -145,40 +145,40 @@ def __init__(self, name, cost_D, max_queue_size): #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()); + 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(); + 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; + 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; + 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(routingTableString) + return routingTableString ## Print routing table def updateUniqueRouters(self): - self.uniqueRouters = []; - values = self.rt_tbl_D.values(); - routers = {}; + 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]] = ""; + routers[list(list(values)[i].keys())[0]] = "" for item in routers: - self.uniqueRouters.append(item); + 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 = ""; + keys = self.rt_tbl_D.keys() + values = self.rt_tbl_D.values() + columns = len(keys)+1 + keyString = "" topTableString = "╒" headerBottomTableString = "╞" tableRowSeperator = "├" @@ -195,39 +195,39 @@ def print_routes(self): headerBottomTableString+= "══════╡\n" tableRowSeperator += "──────┤\n" tableBottom += "══════╛\n" - itemSpace = " "; + itemSpace = " " for item in keys: - keyString += " " + item + " │"; - costRows = []; - changed = []; - self.updateUniqueRouters(); + keyString += " " + item + " │" + costRows = [] + changed = [] + self.updateUniqueRouters() for item in self.uniqueRouters: - costRows.append("│ " + item + " │"); + costRows.append("│ " + item + " │") for i in range(len(values)): - changedFlag = False; + 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; + changed.append(j) + changedFlag=True if changedFlag: - changedFlag = False; + changedFlag = False for l in range(len(costRows)): if(l in changed): - continue; + continue else: costRows[l] += " │" - changed = []; + changed = [] - sys.stdout.write(topTableString + "│ " +self.name + " │" + keyString + "\n" + headerBottomTableString); + 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); + sys.stdout.write(costRows[i] + "\n" + tableRowSeperator) else: - sys.stdout.write(costRows[i] + "\n"); - sys.stdout.write(tableBottom); + sys.stdout.write(costRows[i] + "\n") + sys.stdout.write(tableBottom) ## called when printing the object def __str__(self): return self.name @@ -289,14 +289,14 @@ def forward_packet(self, p, i): except KeyError: print("Key Error: Neighbor is likely host") #new addition - chosenVal = 999; - chosenRoute = ""; + chosenVal = 999 + chosenRoute = "" if v not in self.cost_D:#if v is a neighbor for value in self.rt_tbl_D[v]:#interate through values - cost = self.rt_tbl_D[v][value];#get cost in routing table + 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; + chosenRoute = value + chosenVal = cost for key in self.cost_D[chosenRoute]:#set the chosenRoutes interface out_intf = self.cost_D[chosenRoute][key] #set the outgoing interface to the result. else: # is a neighbor @@ -333,23 +333,23 @@ 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(":"); + 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 + 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 + 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 + 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; + 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 @@ -366,7 +366,7 @@ def update_routes(self, p, i): self.rt_tbl_D[router][header] = 999''' - self.updateUniqueRouters(); + self.updateUniqueRouters() #run the algorithm on each router in the table router_count = len(self.uniqueRouters) print(router_count) @@ -383,7 +383,7 @@ def update_routes(self, p, i): i=1 #http://courses.csail.mit.edu/6.006/spring11/lectures/lec15.pdf #rt_tbl is a list of edges. - updated = False; + 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 @@ -399,15 +399,15 @@ def update_routes(self, p, i): #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(); + 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); + 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): diff --git a/network_1.py b/network_1.py index 93e0529..5c5f9b7 100644 --- a/network_1.py +++ b/network_1.py @@ -4,14 +4,15 @@ import sys import math -## wrapper class for a queue of packets + +# wrapper class for a queue of packets class Interface: - ## @param maxsize - the maximum size of the queue storing packets + # @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 + + # get packet from the queue interface # @param in_or_out - use 'in' or 'out' interface def get(self, in_or_out): try: @@ -27,8 +28,8 @@ def get(self, in_or_out): return pkt_S except queue.Empty: return None - - ##put the packet into the interface queue + + # 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 @@ -39,14 +40,14 @@ def put(self, pkt, in_or_out, block=False): else: # print('putting packet in the IN queue') self.in_queue.put(pkt, block) - - -## Implements a network layer packet. + + +# Implements a network layer packet. class NetworkPacket: - ## packet encoding lengths + # 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) @@ -54,12 +55,12 @@ 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 + + # called when printing the object def __str__(self): return self.to_byte_S() - - ## convert packet to a byte string for transmission over links + + # 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': @@ -67,189 +68,190 @@ def to_byte_S(self): elif self.prot_S == 'control': byte_S += '2' else: - raise('%s: unknown prot_S option: %s' %(self, self.prot_S)) + 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] + 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 : ] + 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 - + 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 - + 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') + print(threading.currentThread().getName() + ': Starting') while True: - #receive data arriving to the in interface + # receive data arriving to the in interface self.udt_receive() - #terminate - if(self.stop): - print (threading.currentThread().getName() + ': Ending') + # 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.stop = False # for thread termination self.name = name - #create a list of interfaces + # 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 + # 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()); + 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) + 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; + 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]) + 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; + 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 = {}; + 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]] = ""; + routers[list(list(values)[i].keys())[0]] = "" for item in routers: - self.uniqueRouters.append(item); + 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 = ""; + keys = self.rt_tbl_D.keys() + values = self.rt_tbl_D.values() + columns = len(keys) + 1 + keyString = "" topTableString = "╒" headerBottomTableString = "╞" tableRowSeperator = "├" tableBottom = "╘" - #//Setting up table + # //Setting up table for i in range(columns): - if(i +1 != columns): - topTableString+="══════╤" + if i + 1 != columns: + topTableString += "══════╤" headerBottomTableString += "══════╪" tableRowSeperator += "──────┼" tableBottom += "══════╧" else: - topTableString+="══════╕\n" - headerBottomTableString+= "══════╡\n" + topTableString += "══════╕\n" + headerBottomTableString += "══════╡\n" tableRowSeperator += "──────┤\n" tableBottom += "══════╛\n" - itemSpace = " "; + itemSpace = " " for item in keys: - keyString += " " + item + " │"; - costRows = []; - changed = []; - self.updateUniqueRouters(); + keyString += " " + item + " │" + costRows = [] + changed = [] + self.updateUniqueRouters() for item in self.uniqueRouters: - costRows.append("│ " + item + " │"); + costRows.append("│ " + item + " │") for i in range(len(values)): - changedFlag = False; + 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; + 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; + changedFlag = False for l in range(len(costRows)): - if(l in changed): - continue; + if (l in changed): + continue else: costRows[l] += " │" - changed = []; - - sys.stdout.write(topTableString + "│ " +self.name + " │" + keyString + "\n" + headerBottomTableString); + 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); + if i + 1 != len(costRows): + sys.stdout.write(costRows[i] + "\n" + tableRowSeperator) else: - sys.stdout.write(costRows[i] + "\n"); - sys.stdout.write(tableBottom); + 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 + ## 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 + # get packet from interface i pkt_S = self.intf_L[i].get('in') - #if packet exists make a forwarding decision + # if packet exists make a forwarding decision if pkt_S is not None: - p = NetworkPacket.from_byte_S(pkt_S) #parse a packet out + p = NetworkPacket.from_byte_S(pkt_S) # parse a packet out if p.prot_S == 'data': - self.forward_packet(p,i) + 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 @@ -259,20 +261,19 @@ def forward_packet(self, p, i): # 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 + 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)) + (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 + # 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)) @@ -284,33 +285,33 @@ def send_routes(self, i): ## 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 + # 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 + 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]} - - + 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, @@ -318,56 +319,56 @@ def update_routes(self, p, i): #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 + + 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 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? + # 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 + # 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 + # 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 + 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(); + 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 + 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); - + 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') + print(threading.currentThread().getName() + ': Starting') while True: self.process_queues() if self.stop: - print (threading.currentThread().getName() + ': Ending') - return + print(threading.currentThread().getName() + ': Ending') + return diff --git a/network_2.py b/network_2.py index ed26ff3..9eee061 100644 --- a/network_2.py +++ b/network_2.py @@ -4,13 +4,14 @@ 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): @@ -27,7 +28,7 @@ def get(self, in_or_out): 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 @@ -39,14 +40,14 @@ def put(self, pkt, in_or_out, block=False): 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) @@ -54,11 +55,11 @@ 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) @@ -67,189 +68,191 @@ def to_byte_S(self): elif self.prot_S == 'control': byte_S += '2' else: - raise('%s: unknown prot_S option: %s' %(self, self.prot_S)) + 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] + 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 : ] + 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 - + 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 - + 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') + print(threading.currentThread().getName() + ': Starting') while True: - #receive data arriving to the in interface + # receive data arriving to the in interface self.udt_receive() - #terminate - if(self.stop): - print (threading.currentThread().getName() + ': Ending') + # 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.stop = False # for thread termination self.name = name - #create a list of interfaces + # 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 + # 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()); + 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) + 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; + 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]) + 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; + 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 = {}; + 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]] = ""; + routers[list(list(values)[i].keys())[0]] = "" for item in routers: - self.uniqueRouters.append(item); + 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 = ""; + keys = self.rt_tbl_D.keys() + values = self.rt_tbl_D.values() + columns = len(keys) + 1 + keyString = "" topTableString = "╒" headerBottomTableString = "╞" tableRowSeperator = "├" tableBottom = "╘" - #//Setting up table + # //Setting up table for i in range(columns): - if(i +1 != columns): - topTableString+="══════╤" + if (i + 1 != columns): + topTableString += "══════╤" headerBottomTableString += "══════╪" tableRowSeperator += "──────┼" tableBottom += "══════╧" else: - topTableString+="══════╕\n" - headerBottomTableString+= "══════╡\n" + topTableString += "══════╕\n" + headerBottomTableString += "══════╡\n" tableRowSeperator += "──────┤\n" tableBottom += "══════╛\n" - itemSpace = " "; + itemSpace = " " for item in keys: - keyString += " " + item + " │"; - costRows = []; - changed = []; - self.updateUniqueRouters(); + keyString += " " + item + " │" + costRows = [] + changed = [] + self.updateUniqueRouters() for item in self.uniqueRouters: - costRows.append("│ " + item + " │"); + costRows.append("│ " + item + " │") for i in range(len(values)): - changedFlag = False; + 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; + 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; + changedFlag = False for l in range(len(costRows)): - if(l in changed): - continue; + if (l in changed): + continue else: costRows[l] += " │" - changed = []; - - sys.stdout.write(topTableString + "│ " +self.name + " │" + keyString + "\n" + headerBottomTableString); + 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); + if i + 1 != len(costRows): + sys.stdout.write(costRows[i] + "\n" + tableRowSeperator) else: - sys.stdout.write(costRows[i] + "\n"); - sys.stdout.write(tableBottom); + 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 + ## 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 + # get packet from interface i pkt_S = self.intf_L[i].get('in') - #if packet exists make a forwarding decision + # if packet exists make a forwarding decision if pkt_S is not None: - p = NetworkPacket.from_byte_S(pkt_S) #parse a packet out + p = NetworkPacket.from_byte_S(pkt_S) # parse a packet out if p.prot_S == 'data': - self.forward_packet(p,i) + 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 @@ -259,66 +262,66 @@ def forward_packet(self, p, i): # 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. + + # 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 + + # 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. + + # 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 + # 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 + 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: print("Key Error: Neighbor is likely host") - #new addition - chosenVal = 999; - chosenRoute = ""; - if v not in self.cost_D:#if v is a neighbor - for value in self.rt_tbl_D[v]:#interate 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 = self.cost_D[chosenRoute][key] #set the outgoing interface to the result. - else: # is a neighbor + # 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 = self.cost_D[chosenRoute][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 = list(self.cost_D[v].keys())[0] #set the outgoing interface to the result. + 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 + self.intf_L[out_intf].put(p.to_byte_S(), 'out', True) # send out except IndexError: - print("Index out of range, {}".format(out_intf)) + print("Index out of range, %i" % out_intf) print('%s: forwarding packet "%s" from interface %d to %d' % \ - (self, p, i, 1)) + (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 + # 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)) @@ -330,33 +333,35 @@ def send_routes(self, i): ## 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 + # 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 + 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]} - - + 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, @@ -364,56 +369,56 @@ def update_routes(self, p, i): #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 + + 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 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? + # 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 + # 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 + # 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 + 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(); + 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 + 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); - + 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') + print(threading.currentThread().getName() + ': Starting') while True: self.process_queues() if self.stop: - print (threading.currentThread().getName() + ': Ending') - return + print(threading.currentThread().getName() + ': Ending') + return diff --git a/network_3.py b/network_3.py index dd64ab5..71e8bff 100644 --- a/network_3.py +++ b/network_3.py @@ -145,40 +145,40 @@ def __init__(self, name, cost_D, max_queue_size): #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()); + 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(); + 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; + 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; + 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(routingTableString) + return routingTableString ## Print routing table def updateUniqueRouters(self): - self.uniqueRouters = []; - values = self.rt_tbl_D.values(); - routers = {}; + 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]] = ""; + routers[list(list(values)[i].keys())[0]] = "" for item in routers: - self.uniqueRouters.append(item); + 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 = ""; + keys = self.rt_tbl_D.keys() + values = self.rt_tbl_D.values() + columns = len(keys)+1 + keyString = "" topTableString = "╒" headerBottomTableString = "╞" tableRowSeperator = "├" @@ -195,39 +195,39 @@ def print_routes(self): headerBottomTableString+= "══════╡\n" tableRowSeperator += "──────┤\n" tableBottom += "══════╛\n" - itemSpace = " "; + itemSpace = " " for item in keys: - keyString += " " + item + " │"; - costRows = []; - changed = []; - self.updateUniqueRouters(); + keyString += " " + item + " │" + costRows = [] + changed = [] + self.updateUniqueRouters() for item in self.uniqueRouters: - costRows.append("│ " + item + " │"); + costRows.append("│ " + item + " │") for i in range(len(values)): - changedFlag = False; + 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; + changed.append(j) + changedFlag=True if changedFlag: - changedFlag = False; + changedFlag = False for l in range(len(costRows)): if(l in changed): - continue; + continue else: costRows[l] += " │" - changed = []; + changed = [] - sys.stdout.write(topTableString + "│ " +self.name + " │" + keyString + "\n" + headerBottomTableString); + 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); + sys.stdout.write(costRows[i] + "\n" + tableRowSeperator) else: - sys.stdout.write(costRows[i] + "\n"); - sys.stdout.write(tableBottom); + sys.stdout.write(costRows[i] + "\n") + sys.stdout.write(tableBottom) ## called when printing the object def __str__(self): return self.name @@ -289,14 +289,14 @@ def forward_packet(self, p, i): except KeyError: print("Key Error: Neighbor is likely host") #new addition - chosenVal = 999; - chosenRoute = ""; + chosenVal = 999 + chosenRoute = "" if v not in self.cost_D:#if v is a neighbor for value in self.rt_tbl_D[v]:#interate through values - cost = self.rt_tbl_D[v][value];#get cost in routing table + 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; + chosenRoute = value + chosenVal = cost for key in self.cost_D[chosenRoute]:#set the chosenRoutes interface out_intf = self.cost_D[chosenRoute][key] #set the outgoing interface to the result. else: # is a neighbor @@ -333,23 +333,23 @@ 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(":"); + 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 + 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 + 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 + 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; + 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 @@ -366,7 +366,7 @@ def update_routes(self, p, i): self.rt_tbl_D[router][header] = 999''' - self.updateUniqueRouters(); + self.updateUniqueRouters() #run the algorithm on each router in the table router_count = len(self.uniqueRouters) print(router_count) @@ -383,7 +383,7 @@ def update_routes(self, p, i): i=1 #http://courses.csail.mit.edu/6.006/spring11/lectures/lec15.pdf #rt_tbl is a list of edges. - updated = False; + 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 @@ -399,15 +399,15 @@ def update_routes(self, p, i): #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(); + 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); + 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): diff --git a/simulation.py b/simulation.py index fbec837..915eef6 100644 --- a/simulation.py +++ b/simulation.py @@ -39,7 +39,7 @@ thread_L = [] for obj in object_L: thread_L.append(threading.Thread(name=obj.__str__(), target=obj.run)) - sys.stdout.write("\n"); + sys.stdout.write("\n") for t in thread_L: t.start() @@ -50,8 +50,8 @@ #Table Header Bottom for i in range(len(object_L)): if str(type(object_L[i])) == "": - object_L[i].print_routes(); - sleep(10); + object_L[i].print_routes() + sleep(10) #send packet from host 1 to host 2 host_1.udt_send('H2', 'MESSAGE_FROM_H1') sleep(simulation_time) diff --git a/simulation_1.py b/simulation_1.py index 7204db6..28d1988 100644 --- a/simulation_1.py +++ b/simulation_1.py @@ -4,64 +4,61 @@ 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 +# 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 + 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) + # 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) + 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 + # create a Link Layer to keep track of links between network_1 nodes link_layer = link.LinkLayer() object_L.append(link_layer) - - #add all the links - need to reflect the connectivity in cost_D tables above + + # 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 + + # 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"); + 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 + + # 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 + # Table Header Bottom for i in range(len(object_L)): if str(type(object_L[i])) == "": - object_L[i].print_routes(); + object_L[i].print_routes() - #send packet from host 1 to host 2 + # send packet from host 1 to host 2 host_1.udt_send('H2', 'MESSAGE_FROM_H1') sleep(simulation_time) - - - #join all threads + + # join all threads for o in object_L: o.stop = True for t in thread_L: t.join() - - print("All simulation threads joined") + print("All simulation threads joined") diff --git a/simulation_2.py b/simulation_2.py index 7637f96..30221e8 100644 --- a/simulation_2.py +++ b/simulation_2.py @@ -4,65 +4,64 @@ from time import sleep import sys -##configuration parameters -router_queue_size = 0 #0 means unlimited -simulation_time = 10 #give the network_2 sufficient time to execute transfers +# configuration parameters +router_queue_size = 0 # 0 means unlimited +simulation_time = 10 # 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 + 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) + # 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) + 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 + # 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 + + # 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)) - - - #start all the objects + 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"); + 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 + + # 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 + # Table Header Bottom for i in range(len(object_L)): if str(type(object_L[i])) == "": - object_L[i].print_routes(); + object_L[i].print_routes() - #send packet from host 1 to host 2 + # send packet from host 1 to host 2 host_1.udt_send('H2', 'MESSAGE_FROM_H1') - host_2.udt_send('H1', 'MESSAGE FROM H2'); + host_2.udt_send('H1', 'MESSAGE FROM H2') sleep(simulation_time) - - - #join all threads + + # join all threads for o in object_L: o.stop = True for t in thread_L: t.join() - - print("All simulation threads joined") + print("All simulation threads joined") diff --git a/simulation_3.py b/simulation_3.py index 65ca14f..72f61db 100644 --- a/simulation_3.py +++ b/simulation_3.py @@ -4,67 +4,66 @@ from time import sleep import sys -##configuration parameters -router_queue_size = 0 #0 means unlimited -simulation_time = 30 #give the network_3 sufficient time to execute transfers +# configuration parameters +router_queue_size = 0 # 0 means unlimited +simulation_time = 10 # 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 + 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') - cost_d_a = {'H1': {0: 1}, 'RB': {1: 2}, 'RC': {2: 1}}; - cost_d_b = {'RA': {0: 1}, 'RD': {1: 1}}; - cost_d_c = {'RA': {0: 1}, 'RD': {1: 1}}; - cost_d_d = {'RB': {0: 1}, 'RC': {1: 1},'H2':{2: 1}}; - 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); + # {neighbor: {interface: cost}} + cost_d_a = {'H1': {0: 1}, 'RB': {1: 2}, '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: 1}} + 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 + 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 + + # 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 + # 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"); + 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 + + # 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 + # Table Header Bottom for i in range(len(object_L)): if str(type(object_L[i])) == "": - object_L[i].print_routes(); + object_L[i].print_routes() - #send packet from host 1 to host 2 + # send packet from host 1 to host 2 host_1.udt_send('H2', 'MESSAGE_FROM_H1') - host_2.udt_send('H1', 'MESSAGE FROM H2'); + host_2.udt_send('H1', 'MESSAGE FROM H2') sleep(simulation_time) - - - #join all threads + + # join all threads for o in object_L: o.stop = True for t in thread_L: t.join() - - print("All simulation threads joined") + print("All simulation threads joined") From 414089049a86d476f6162a20ce95c0f341fbf5f4 Mon Sep 17 00:00:00 2001 From: Keefer Sands Date: Thu, 29 Nov 2018 10:38:46 -0700 Subject: [PATCH 33/40] 2 working, 3 one direction --- network_2.py | 13 +-- network_3.py | 301 ++++++++++++++++++++++++------------------------ simulation_3.py | 7 ++ 3 files changed, 162 insertions(+), 159 deletions(-) diff --git a/network_2.py b/network_2.py index 9eee061..7ffda0f 100644 --- a/network_2.py +++ b/network_2.py @@ -284,15 +284,16 @@ def forward_packet(self, p, i): 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 + 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: print("Key Error: Neighbor is likely host") # new addition + print(v) + print(v_d) chosenVal = 999 chosenRoute = "" if v not in self.cost_D: # if v is NOT a neighbor @@ -302,7 +303,7 @@ def forward_packet(self, p, i): chosenRoute = value chosenVal = cost for key in self.cost_D[chosenRoute]: # set the chosenRoutes interface - out_intf = self.cost_D[chosenRoute][key] # set the outgoing interface to the result. + 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 @@ -351,15 +352,11 @@ def update_routes(self, p, i): 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]} diff --git a/network_3.py b/network_3.py index 71e8bff..fdd57f2 100644 --- a/network_3.py +++ b/network_3.py @@ -4,13 +4,14 @@ 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): @@ -27,7 +28,7 @@ def get(self, in_or_out): 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 @@ -39,14 +40,14 @@ def put(self, pkt, in_or_out, block=False): else: # print('putting packet in the IN queue') self.in_queue.put(pkt, block) - - + + ## Implements a network layer packet. class NetworkPacket: - ## packet encoding lengths + ## 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) @@ -54,11 +55,11 @@ 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) @@ -67,90 +68,89 @@ def to_byte_S(self): elif self.prot_S == 'control': byte_S += '2' else: - raise('%s: unknown prot_S option: %s' %(self, self.prot_S)) + 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] + 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 : ] + 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 - + 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 - + 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') + print(threading.currentThread().getName() + ': Starting') while True: - #receive data arriving to the in interface + # receive data arriving to the in interface self.udt_receive() - #terminate - if(self.stop): - print (threading.currentThread().getName() + ': Ending') + # 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.stop = False # for thread termination self.name = name - #create a list of interfaces + # 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 + # 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}} + 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.rt_tbl_D[keys[i]] = {name: list(values[i].values())[0]} self.print_routes() - print('%s: Initialized routing table' % self) + print('%s: Initialized routing table' % self) + def getCurrentRoutingTable(self): routingTableString = self.name + "-" values = list(self.rt_tbl_D.values()) @@ -159,11 +159,13 @@ def getCurrentRoutingTable(self): for i in range(len(keys)): if first: first = False - routingTableString+= keys[i] + "," + str(list(values[i])[0]) + "," + str(list(values[i].values())[0]) + 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]) + 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 = [] @@ -174,25 +176,26 @@ def updateUniqueRouters(self): 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 + columns = len(keys) + 1 keyString = "" topTableString = "╒" headerBottomTableString = "╞" tableRowSeperator = "├" tableBottom = "╘" - #//Setting up table + # //Setting up table for i in range(columns): - if(i +1 != columns): - topTableString+="══════╤" + if (i + 1 != columns): + topTableString += "══════╤" headerBottomTableString += "══════╪" tableRowSeperator += "──────┼" tableBottom += "══════╧" else: - topTableString+="══════╕\n" - headerBottomTableString+= "══════╡\n" + topTableString += "══════╕\n" + headerBottomTableString += "══════╡\n" tableRowSeperator += "──────┤\n" tableBottom += "══════╛\n" itemSpace = " " @@ -208,117 +211,116 @@ def print_routes(self): 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 + "│" + 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 + changedFlag = True if changedFlag: changedFlag = False for l in range(len(costRows)): - if(l in changed): + if (l in changed): continue else: costRows[l] += " │" changed = [] - - sys.stdout.write(topTableString + "│ " +self.name + " │" + keyString + "\n" + headerBottomTableString) + + sys.stdout.write(topTableString + "│ " + self.name + " │" + keyString + "\n" + headerBottomTableString) for i in range(len(costRows)): - if i+1 != 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 + ## 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 + # get packet from interface i pkt_S = self.intf_L[i].get('in') - #if packet exists make a forwarding decision + # if packet exists make a forwarding decision if pkt_S is not None: - p = NetworkPacket.from_byte_S(pkt_S) #parse a packet out + p = NetworkPacket.from_byte_S(pkt_S) # parse a packet out if p.prot_S == 'data': - self.forward_packet(p,i) + 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 + # 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. + + # 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 + + # 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. + # 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 + # 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 + if v_d > (node_d + dest_d): # find the minimum + # new minimum v_d = node_d v = header except KeyError: print("Key Error: Neighbor is likely host") - #new addition + # new addition + print(v) + print(v_d) chosenVal = 999 chosenRoute = "" - if v not in self.cost_D:#if v is a neighbor - for value in self.rt_tbl_D[v]:#interate through values - cost = self.rt_tbl_D[v][value]#get cost in routing table - if int(cost) < int(chosenVal):#find lowest cost router + 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 = self.cost_D[chosenRoute][key] #set the outgoing interface to the result. - else: # is a neighbor + 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 = list(self.cost_D[v].keys())[0] #set the outgoing interface to the result. + 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 + self.intf_L[out_intf].put(p.to_byte_S(), 'out', True) # send out except IndexError: - print("Index out of range, {}".format(out_intf)) + print("Index out of range, %i" % out_intf) print('%s: forwarding packet "%s" from interface %d to %d' % \ - (self, p, i, 1)) + (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 + # 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)) @@ -330,33 +332,29 @@ def send_routes(self, i): ## 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 + # 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 + # 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 - 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 + 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]} - - + 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, @@ -364,56 +362,57 @@ def update_routes(self, p, i): #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 + # 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 + updated = False + 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? + # 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 + # bellman ford starts here + # http://courses.csail.mit.edu/6.006/spring11/lectures/lec15.pdf + # rt_tbl is a list of edges. + + # 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 + # 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 + 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 + 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') + print(threading.currentThread().getName() + ': Starting') while True: self.process_queues() if self.stop: - print (threading.currentThread().getName() + ': Ending') - return + print(threading.currentThread().getName() + ': Ending') + return diff --git a/simulation_3.py b/simulation_3.py index 72f61db..58243bc 100644 --- a/simulation_3.py +++ b/simulation_3.py @@ -38,6 +38,13 @@ 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)) + + link_layer.add_link(link_3.Link(router_a, 0,host_1, 0)) + link_layer.add_link(link_3.Link(router_b, 0,router_a, 1)) + link_layer.add_link(link_3.Link(router_c, 0,router_a, 2)) + link_layer.add_link(link_3.Link(router_d, 1,router_c, 1)) + link_layer.add_link(link_3.Link(router_d, 0,router_b, 1)) + link_layer.add_link(link_3.Link(host_2, 0,router_d, 2)) # start all the objects thread_L = [] for obj in object_L: From fbe66a68c6b0dee6cbf20cca371f25aa4ebb5aff Mon Sep 17 00:00:00 2001 From: Keefer Sands Date: Thu, 29 Nov 2018 10:53:10 -0700 Subject: [PATCH 34/40] Changing updating --- network_3.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/network_3.py b/network_3.py index fdd57f2..4f9e723 100644 --- a/network_3.py +++ b/network_3.py @@ -337,6 +337,7 @@ def update_routes(self, p, i): 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 @@ -351,10 +352,13 @@ def update_routes(self, p, i): 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, @@ -367,7 +371,6 @@ def update_routes(self, p, i): # run the algorithm on each router in the table router_count = len(self.uniqueRouters) print(router_count) - updated = False 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: @@ -380,7 +383,7 @@ def update_routes(self, p, i): # 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)): # for V-1 (the number of verticies minus one From a151e3930b6593014af69cf8781409234e99ca85 Mon Sep 17 00:00:00 2001 From: Keefer Sands Date: Thu, 29 Nov 2018 11:51:38 -0700 Subject: [PATCH 35/40] fixed some table issues --- network_3.py | 8 +++----- simulation_3.py | 14 +++++--------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/network_3.py b/network_3.py index 4f9e723..75380ab 100644 --- a/network_3.py +++ b/network_3.py @@ -283,15 +283,13 @@ def forward_packet(self, p, i): 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 > (node_d + dest_d): # find the minimum + if v_d > (dest_d + node_d): # find the minimum # new minimum - v_d = node_d + v_d = dest_d + node_d v = header except KeyError: print("Key Error: Neighbor is likely host") # new addition - print(v) - print(v_d) chosenVal = 999 chosenRoute = "" if v not in self.cost_D: # if v is NOT a neighbor @@ -385,7 +383,7 @@ def update_routes(self, p, i): # 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)): + 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 diff --git a/simulation_3.py b/simulation_3.py index 58243bc..92f6028 100644 --- a/simulation_3.py +++ b/simulation_3.py @@ -13,10 +13,10 @@ host_1 = network_3.Host('H1') host_2 = network_3.Host('H2') # {neighbor: {interface: cost}} - cost_d_a = {'H1': {0: 1}, 'RB': {1: 2}, 'RC': {2: 1}} + 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: 1}} + 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) @@ -39,12 +39,7 @@ 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)) - link_layer.add_link(link_3.Link(router_a, 0,host_1, 0)) - link_layer.add_link(link_3.Link(router_b, 0,router_a, 1)) - link_layer.add_link(link_3.Link(router_c, 0,router_a, 2)) - link_layer.add_link(link_3.Link(router_d, 1,router_c, 1)) - link_layer.add_link(link_3.Link(router_d, 0,router_b, 1)) - link_layer.add_link(link_3.Link(host_2, 0,router_d, 2)) + # start all the objects thread_L = [] for obj in object_L: @@ -54,7 +49,8 @@ t.start() # compute routing tables - router_a.send_routes(1) # one update starts the routing process + 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 From ce2e7e1996273d5679f8989a57be4c43d6e4f05b Mon Sep 17 00:00:00 2001 From: Keefer Sands Date: Thu, 29 Nov 2018 11:53:36 -0700 Subject: [PATCH 36/40] making it work instead of loop --- network_3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network_3.py b/network_3.py index 75380ab..1c3468a 100644 --- a/network_3.py +++ b/network_3.py @@ -285,7 +285,7 @@ def forward_packet(self, p, i): try: if v_d > (dest_d + node_d): # find the minimum # new minimum - v_d = dest_d + node_d + v_d = dest_d v = header except KeyError: print("Key Error: Neighbor is likely host") From b7935f9e73396217ed7a8b3caeefdb96865ce0eb Mon Sep 17 00:00:00 2001 From: kBlack Date: Thu, 29 Nov 2018 20:11:24 -0700 Subject: [PATCH 37/40] fix for wrong self distances --- network_3.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/network_3.py b/network_3.py index 1c3468a..3b77cb4 100644 --- a/network_3.py +++ b/network_3.py @@ -332,6 +332,10 @@ def send_routes(self, i): def update_routes(self, p, i): # TODO: add logic to update the routing tables and # possibly send out routing updates + + if ("RA" in self.rt_tbl_D and self.name == "RD") or ("RD" in self.rt_tbl_D and self.name == "RA"): + print("Case") + updates = p.to_byte_S()[6:].split('-') name = updates[0] update = updates[1].split(":") @@ -373,10 +377,11 @@ def update_routes(self, p, i): # 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? + 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 @@ -408,7 +413,8 @@ def update_routes(self, p, i): interface = list(list(self.cost_D.values())[i].keys())[x] self.send_routes(interface) self.updateUniqueRouters() - + for node in self.rt_tbl_D: + self.rt_tbl_D[node][node] = 0 #fix for wrong values ## thread target for the host to keep forwarding data def run(self): print(threading.currentThread().getName() + ': Starting') From 88af65336f0fbf99e098923158b660e058162501 Mon Sep 17 00:00:00 2001 From: Keefer Sands Date: Thu, 29 Nov 2018 22:46:22 -0700 Subject: [PATCH 38/40] Pruning/finalizing --- link.py | 75 --------- network.py | 419 ------------------------------------------------ network_2.py | 9 +- partners.txt | 2 + simulation.py | 67 -------- simulation_1.py | 10 +- simulation_2.py | 2 +- simulation_3.py | 2 +- 8 files changed, 13 insertions(+), 573 deletions(-) delete mode 100644 link.py delete mode 100644 network.py create mode 100644 partners.txt delete mode 100644 simulation.py diff --git a/link.py b/link.py deleted file mode 100644 index e488016..0000000 --- a/link.py +++ /dev/null @@ -1,75 +0,0 @@ -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 6527434..0000000 --- a/network.py +++ /dev/null @@ -1,419 +0,0 @@ -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: - print("Key Error: Neighbor is likely host") - #new addition - chosenVal = 999 - chosenRoute = "" - if v not in self.cost_D:#if v is a neighbor - for value in self.rt_tbl_D[v]:#interate 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 = self.cost_D[chosenRoute][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 = list(self.cost_D[v].keys())[0] #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, {}".format(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 - 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 index 7ffda0f..fc683ae 100644 --- a/network_2.py +++ b/network_2.py @@ -290,10 +290,9 @@ def forward_packet(self, p, i): v_d = node_d v = header except KeyError: - print("Key Error: Neighbor is likely host") + continue; + #print("Key Error: Neighbor is likely host") # new addition - print(v) - print(v_d) chosenVal = 999 chosenRoute = "" if v not in self.cost_D: # if v is NOT a neighbor @@ -370,7 +369,6 @@ def update_routes(self, p, i): 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: @@ -403,7 +401,8 @@ def update_routes(self, p, i): updated = True self.updateUniqueRouters() except KeyError: - print("Key error exception occurred") + 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 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 915eef6..0000000 --- a/simulation.py +++ /dev/null @@ -1,67 +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 = 5 #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)) - 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() - sleep(10) - #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 index 28d1988..a73d9dc 100644 --- a/simulation_1.py +++ b/simulation_1.py @@ -1,5 +1,5 @@ import network_1 -import link +import link_1 import threading from time import sleep import sys @@ -26,13 +26,13 @@ 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.LinkLayer() + 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.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)) + 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 = [] diff --git a/simulation_2.py b/simulation_2.py index 30221e8..4e90d46 100644 --- a/simulation_2.py +++ b/simulation_2.py @@ -6,7 +6,7 @@ # configuration parameters router_queue_size = 0 # 0 means unlimited -simulation_time = 10 # give the network_2 sufficient time to execute transfers +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 diff --git a/simulation_3.py b/simulation_3.py index 92f6028..70f01d0 100644 --- a/simulation_3.py +++ b/simulation_3.py @@ -6,7 +6,7 @@ # configuration parameters router_queue_size = 0 # 0 means unlimited -simulation_time = 10 # give the network_3 sufficient time to execute transfers +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 From 43d40353266334a9cecad699874a554856e2872b Mon Sep 17 00:00:00 2001 From: Keefer Sands Date: Thu, 29 Nov 2018 22:47:49 -0700 Subject: [PATCH 39/40] Revert "Merge branch 'control_plane' of https://github.com/AtlasHands/CSCI_466_Programming_Assignments into control_plane" This reverts commit e99f7590a906d0f95c0dc634200c1b00bfaa7a72, reversing changes made to 88af65336f0fbf99e098923158b660e058162501. --- network_3.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/network_3.py b/network_3.py index 3b77cb4..1c3468a 100644 --- a/network_3.py +++ b/network_3.py @@ -332,10 +332,6 @@ def send_routes(self, i): def update_routes(self, p, i): # TODO: add logic to update the routing tables and # possibly send out routing updates - - if ("RA" in self.rt_tbl_D and self.name == "RD") or ("RD" in self.rt_tbl_D and self.name == "RA"): - print("Case") - updates = p.to_byte_S()[6:].split('-') name = updates[0] update = updates[1].split(":") @@ -377,11 +373,10 @@ def update_routes(self, p, i): # 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? - + 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 @@ -413,8 +408,7 @@ def update_routes(self, p, i): interface = list(list(self.cost_D.values())[i].keys())[x] self.send_routes(interface) self.updateUniqueRouters() - for node in self.rt_tbl_D: - self.rt_tbl_D[node][node] = 0 #fix for wrong values + ## thread target for the host to keep forwarding data def run(self): print(threading.currentThread().getName() + ': Starting') From a11324fe1dd8b5b621855edef0bdfb9d81faa348 Mon Sep 17 00:00:00 2001 From: Keefer Sands Date: Thu, 29 Nov 2018 22:50:35 -0700 Subject: [PATCH 40/40] Added youtube video --- youtube_video.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 youtube_video.txt 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