aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsotech117 <michael_foiani@brown.edu>2023-10-23 20:50:53 -0400
committersotech117 <michael_foiani@brown.edu>2023-10-23 20:50:53 -0400
commitf639761a78f672d02bc616f6062f77e95f461d03 (patch)
tree9eb13871f132db648f70b915eb7d4309b2abc24e
parentbb4eefb4fc928b1e681966167c60a4bc79c5b55b (diff)
reassemble the executables
-rw-r--r--README.md173
-rw-r--r--pkg/ipstack/ipstack.go1
-rwxr-xr-xvhostbin3110473 -> 3105198 bytes
-rwxr-xr-xvrouterbin3110473 -> 3105238 bytes
4 files changed, 135 insertions, 39 deletions
diff --git a/README.md b/README.md
index 76f4ca4..053ba6a 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,83 @@
-## Introduction
+# Introduction to JailPT2's IP Project!
This project is an implementation of IP pipelines in Go. The project is split into two parts, the first being the IPStack, which is a library that implements the IP pipeline, and the second being the vhost and vrouter, which are the two nodes that are used to test the IPStack.
+Because the vhost and vrouter are so similar, we maintain most of the logic within the IPStack in which the vrouter and vhost call when necessary.
+
+# Abstractions
+
+## IP Layer and Interface abstraction
+Using these two structs and two data structures below, we abstract for the interfaces into the IP layer:
+
+```
+// structs
+type Interface struct {
+ Name string
+ IpPrefix netip.Prefix
+ UdpAddr netip.AddrPort
+
+ Socket net.UDPConn
+ SocketChannel chan bool
+ State bool
+}
+
+type Neighbor struct {
+ Name string
+ VipAddr netip.Addr
+ UdpAddr netip.AddrPort
+}
+
+// data structures
+var myInterfaces []*Interface
+var myNeighbors = make(map[string][]*Neighbor)
+```
+
+The `interface` struct holds the UDP socket used for sending and receiving data over a particular interface, abstracting its low-level Link Layer into a `Socket`.
+The IP stack then populates the `myInterfaces` slice with every interface defined for the node to store and access its interfaces.
+
+The `Neighbor` struct represents "endpoints" of the Link Layer from a particular interface, where, in our case, the "endpoint" is the `UDPAddr` of the neighbor.
+The `myNeighbors` maps each interface to a slice of neighbors, abstracting one end of an interface's Link Layer to all possible endpoints (Neighbors) on the other side of the switch.
+
+In this way, if the IPStack needs to send a packet to a particular neighbor, it must iterate over every interface and check if the neighbor is in the slice of neighbors for that interface.
+But, more often than not, we know the interface to receive or send on, so finding the neighbor to communicate with doesn't require an iteration over all interfaces.
-## Vrouter and Vhost Design
-Vrouter and Vhost follow similar designs, with a check to ensure than an lnxconfig file is passed into, to initialize the node with the parsed information from the lnxfiles. For both nodes we register the test protocol handler, and the rip protocol handler specifically for routers. We then listen for command line interfaces, while threads were initialized in the initialization. We follow the specifications of the handout having the following functions and functionality:
+## vrouter/vhost and ipstack abstraction
+The ipstack offers an API to nodes to abstract the large amounts of similar code that different types of nodes share.
+The API is as follows:
```
+// functions
+func Initialize(lnxFilePath string) error
+func RegisterProtocolHandler(protocolNumber uint8) bool
+func SendIP(src *netip.Addr, dest *Neighbor, protocolNum int,
+ message []byte, destIP string, hdr *ipv4header.IPv4Header) (int, error)
+func InterfaceUp(iface *Interface)
+func InterfaceDown(iface *Interface)
+func Route(src netip.Addr) Hop
+func SprintRoutingTable() string
+func SprintNeighbors() string
+func SprintInterfaces() string
+// ... other getters not shown
+
+// constants
+RIP_PROTOCOL
+TEST_PROTOCOL
+```
+
+In general, to correctly use the API, the node must:
+1) First, call initialize to setup the IPStack data structures.
+2) Then, call `RegisterProtocolHandler(X_PROTOCOL)` to subscribe to a protocol.
+3) After that, depending on the node's needs, use other API functions to interact with the IPStack.
+
+The only protocols that are currently supported are the TEST_PROTOCOL and RIP_PROTOCOL, as given by the constants.
+To add more, like TCP_PROTOCOL, the current best way is to implement them in the IPStack.
+
+In our case, vhost will subscribe to ONLY the TEST_PROTOCOL, while vrouter will subscribe to BOTH the TEST_PROTOCOL and RIP_PROTOCOL.
+
+The other API functions allow for more specific actions within vrouter and vhost, such as `Route`, `SprintNeighbors`, `SendIP`, (and all getters).
+The REPL inside a node is expected to use these functions to implement its REPL commands.
+
+For vhost and vrouter, we use the API's functions to implement the following REPL:
+```
li: List interfaces
lr: List routes
ln: List available neighbors
@@ -15,66 +87,89 @@ send: Send test packet
q/exit: Quit program
```
+Note: we originally planned to also incorporate the REPL into either the IPStack or separate API, but due to time constraints, we have the REPL in the VRouter and VHost.
+For TCP, we will consider abstracting the REPL in this way.
-Because the vhost and vrouter are so similar, we maintain most of the logic within the IPStack in which the vrouter and vhost call when necessary.
+# Thread Design
-## Abstractions
+Main routines are clearly distinguished by the function name, ending in Routine (e.g. `InterfaceListenerRoutine`, `ManageTimeoutRoutine`, etc.).
+These main routines are defined as having "forever" loops, repeatedly executing the same task in succession.
+There are cases where we can spawn wait-free threads doing parallelize actions for every element in a loop, but it's not worth to have this our implementation.
+We simply use threads to avoid blocking conditions or update something consecutively.
-Using these two struct below, we abstract for the IP layer and interfaces with the following structs:
+The main routines are discussed below.
-type Interface struct {
+## Listener Routine
- Name string
- IpPrefix netip.Prefix
- UdpAddr netip.AddrPort
+Each interface needs a listener routine to ensure that the node acts when it receives a packet on any interface.
+Hence, this routine should be setup for each interface. It hangs on an interface's UDP socket, then calls `RecvIP` upon receiving a packet.
+`RecvIP` beings the pipeline of corresponding function calls, based on the packet.
- Socket net.UDPConn
- SocketChannel chan bool
- State bool
+There is some added complexity with the listener thread interacting with the state of the interface (up, down) through a channel, managed by a subroutine inside of this listener routine.
+For more detail on this, see the implementation of `InterfaceListenerRoutine()` in `ipstack.go`.
-}
+## RIP Routines
+Following `StartRipRoutines()`, we use two main routines to maintain the RIP protocol: `PeriodicUpdateRoutine()` and `ManageTimeoutRoutine()`.
-type Neighbor struct {
+### Periodic Update Routine
- Name string
- VipAddr netip.Addr
- UdpAddr netip.AddrPort
+This routine is responsible for sending periodic updates to all neighbors every 5 seconds.
-}
+### Manage Timeout Routine
-With these structs,
+This routine is responsible for managing the timeout table.
+Every second it increments the timeout of every entry in the table by 1.
+Once the entry reaches MAX_TIMEOUT == 12 (if it wasn't reset to 0 by an update), this thread removes the entry from the routing table (and timeout table) then sends a triggered update to all neighbors.
+A mutex is used on the timeout table to ensure safe interaction from RIP updates resetting timeouts (in a separate thread).
-# Initialization/Main Thread
-First we, parse the lnxfile and populate our the data structures such as myInterfaces, myNeighbors and routingTable based on the specification in the lnx files. For each not we create a UDP listener conn and utilize a go routines to listen on the UDP socket.
+# Processing IP packets
-This goroutine is responsible for listening on the UDP socket and handling the packets that are received. The goroutine will hang on the recv and wait for a packet to be received. Once a packet is received, the goroutine will check if the interface is up and handle the packet per the protocol. If the interface is down, the goroutine will drop the packet, and it the channel is closed the thread will close.
+## Processing Incoming Packets
+Upon receiving a packet at the Link Layer, a node should call `RecvIP` to process the IP packet.
-## Interface Up/Down
+`RecvIP` then does the following:
+1) Check if the interface is up/down. If down, drop the packet (i.e. return).
+2) Parse the header. Check if the packet is valid by validating checksum and seeing TTL > 0. If not, drop the packet.
+3) Check if the packet is for me (any of my interface). If so, SENDUP to handler (if protocol is register) with the parsed message. End function.
+4) Check the routing table for the next hop. If not found, drop the packet. If found, determine if the hop is a neighbor or not, then:
+ 1) If the hop is a neighbor, send directly to that neighbor.
+ 2) If the hop is not neighbor, forward the packet to the appropriate neighbor (i.e. the neighbor matches the packet's destination header).
+8) If failed to find a route in the table, print an error & drop the packet (note: vhost will always succeed due it's static route).
-When an interface is brought up or down, the state of the interface is changed and the channel is signaled. If the interface is brought up, the channel is signaled with true, and if the interface is brought down, the channel is signaled with false. The goroutine that is listening on the UDP socket will check the state of the interface and handle the packet accordingly. If the interface is down, the packet will be dropped, and if the interface is up, the packet will be handled per the protocol.
+This allows for the ip stack to completely handle the processing of packets, given that even the listener thread that calls `RecvIP` is managed by the IPStack.
-# RIP Threads
+## Processing Outgoing Packets
+To sending a packet at the Link Layer, a node should call `SendIP` to process the IP packet, which consumes the source address, neighbor to send to, protocol number, and message content.
+For forwarding, you can resuse the same header in SendIP's final argument.
-## Periodic Updates
+`SendIP` then does the following:
+1) Check if the interface is up/down. If down, don't send the packet (i.e. return).
+2) Build the header, then update its checksum.
+3) Build the message buffer.
+4) Send the packet to the desired neighbor's socket.
-Every 5 seconds, a goroutine will send periodic updates to all of its neighbors. The goroutine will iterate through the nodes interfaces and neighbors and send the periodic updates to all of its RIP neighbors by creating and sending the entries to the corroding neighbor.
-## Manage Timeouts
+# Miscellaneous
-Every second, a goroutine will check the timeout table and increment the timeout for each entry. If the timeout is equal to the MAX_TIMEOUT, the entry will be deleted from the timeout table and the routing table. The goroutine will then send a triggered update to all of its neighbors.
+## Notable Design Decisions
+Our design is mostly straighforward. However, we did improve our design from the basic design in two following ways.
-## Send Rip Requests
-There is a goroutine in RegisterProtocolHandler that will send a RIP request to all of its RIP neighbors. That goroutine will iterate through the nodes interfaces and neighbors and send the RIP request to all of its RIP neighbors by creating and sending the entries to the corroding neighbor. Then the main goroutine will create the periodic update goroutine and the manage timeout goroutine.
+### More RIP requests and triggered updates
+When an interface is set to down or up from the REPL, the router instantly knows new information. Hence, it makes sense to act on this new information to propagate information quicker.
+- If an interface goes down, then send a triggered update to all neighbors of the new INFINITY cost to the neighbors affected by that LL loss.
+- If an interface goes up, then send a RIP request to all neighbors to instantly get the new, expanded routes, versus waiting 5 seconds for the next periodic update.
+See `interfaceUp()` & `interfaceDown()` for specific info.
-# Processing IP packets
-We process IP packets in the following way:
-We first check if the interface is up, and if it is, we validate the checksum and TTL of the header. If either of these conditions fail, we drop the packet. Then we check who the package is for. If it is for me, I look at the protocol number to determine how to handle the packet. If the packet is not for me, I check my neighbors and routing table and forward the packet accordingly with SendIP. If the packet is not in the routing table, the packet will be dropped.
+### Specific Garbage Collection for Expired Entries
+If a route is expired, as told by receiving a cost of INFINITY for a route, we let that route exist for 12 seconds with value INFINITY before deleting it.
+The textbook states this is the preferred method.
+
+This is different from the reference, which would delete these routes instantly.
-# Other Design Decisions
-We have getter function, printing functions, checksum validation and other functions without our IPStack that either act as helper functions for bigger functions or to provide REPL functionality for the vrouter and vhost.
+## Known Bugs
+No known bugs :)
-# Known Bugs
-No known bugs. \ No newline at end of file
+However, the reference often crashed on startup when spawning the loop network with tmux. \ No newline at end of file
diff --git a/pkg/ipstack/ipstack.go b/pkg/ipstack/ipstack.go
index 6bce6e8..d3178b8 100644
--- a/pkg/ipstack/ipstack.go
+++ b/pkg/ipstack/ipstack.go
@@ -680,6 +680,7 @@ func StartRipRoutines() {
// ************************************** PROTOCOL HANDLERS *******************************************************
// RegisterProtocolHandler registers a protocol handler for a given protocol number
+// Returns true if the protocol number is valid, false otherwise
func RegisterProtocolHandler(protocolNum int) bool {
if protocolNum == RIP_PROTOCOL {
protocolHandlers[protocolNum] = HandleRIP
diff --git a/vhost b/vhost
index 8d82e3e..8ecce6f 100755
--- a/vhost
+++ b/vhost
Binary files differ
diff --git a/vrouter b/vrouter
index 3f5c0b2..948abfc 100755
--- a/vrouter
+++ b/vrouter
Binary files differ