aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Doan <daviddoan@Davids-MacBook-Pro-70.local>2023-10-23 18:00:09 -0400
committerDavid Doan <daviddoan@Davids-MacBook-Pro-70.local>2023-10-23 18:00:09 -0400
commit8410dde94f746c254d62461947abbe68d25009b2 (patch)
tree27f4f3fef199d67c8b86ed82a0b3c24fd8db76a2
parentaaa3e0e5279c534bb07856bbce867e4cde2ed255 (diff)
comments and refactoring
-rw-r--r--README.md92
-rw-r--r--cmd/vhost/main.go7
-rw-r--r--cmd/vrouter/main.go5
-rw-r--r--pkg/ipstack/ipstack.go91
-rwxr-xr-xvhostbin0 -> 3110277 bytes
-rwxr-xr-xvrouterbin0 -> 3110285 bytes
6 files changed, 138 insertions, 57 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6984dee
--- /dev/null
+++ b/README.md
@@ -0,0 +1,92 @@
+## Introduction
+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.
+
+
+## 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:
+
+li: List interfaces
+lr: List routes
+ln: List available neighbors
+up: Enable an interface
+down: Disable an interface
+send: Send test packet
+
+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.
+
+## IPStack Design
+We build our abstractions for the IP layer and interfaces with the following structs:
+
+type Interface struct {
+ Name string
+ IpPrefix netip.Prefix
+ UdpAddr netip.AddrPort
+
+ RecvSocket net.UDPConn
+ SocketChannel chan bool
+ State bool
+}
+
+type Neighbor struct {
+ Name string
+ VipAddr netip.Addr
+ UdpAddr netip.AddrPort
+}
+
+type RIPHeader struct {
+ command uint16
+ numEntries uint16
+}
+
+type RIPEntry struct {
+ prefix netip.Prefix
+ cost uint32
+}
+
+type Hop struct {
+ Cost uint32
+ Type string
+
+ Interface *Interface
+ VIP netip.Addr
+}
+
+With these structs, we are able to maintain the information necessary for the IPStack to function and for the vhost and vrouter to interact with its interfaces, neighbors, and routes when applicable.
+
+# Initialization
+
+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.
+
+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.
+
+# Interface Up/Down
+
+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.
+
+# Send Test Packet
+
+When a test packet is sent, the packet is sent to the destination address. The goroutine that is listening on the UDP socket will check if the packet is for itself, and if it is, it will handle the packet per the protocol. If the packet is not for itself, the goroutine will check its neighbors and routing table and forward the packet accordingly. If the packet is not in the routing table, the packet will be dropped.
+
+# RIP Threads
+
+# Periodic Updates
+
+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
+
+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.
+
+# 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.
+
+
+## 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.
+
+## 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. \ No newline at end of file
diff --git a/cmd/vhost/main.go b/cmd/vhost/main.go
index 26280ef..a72dc98 100644
--- a/cmd/vhost/main.go
+++ b/cmd/vhost/main.go
@@ -56,8 +56,9 @@ func main() {
// combine message into one string
messageToSend := strings.Join(message, " ")
messageToSendBytes := []byte(messageToSend)
-
- hop, err := ipstack.LongestPrefix(netip.MustParseAddr(ipAddr))
+
+ address, _ := netip.ParseAddr(ipAddr)
+ hop, err := ipstack.LongestPrefix(address)
if err != nil {
fmt.Println(err)
continue
@@ -65,7 +66,7 @@ func main() {
myAddr := hop.Interface.IpPrefix.Addr()
for _, neighbor := range ipstack.GetNeighbors()[hop.Interface.Name] {
// TODO: fix multiple send bug here on static route
- if neighbor.VipAddr == netip.MustParseAddr(ipAddr) ||
+ if neighbor.VipAddr == address ||
neighbor.VipAddr == hop.VIP && hop.Type == "S" {
bytesWritten, err := ipstack.SendIP(&myAddr, neighbor, ipstack.TEST_PROTOCOL, messageToSendBytes, ipAddr, nil)
if err != nil {
diff --git a/cmd/vrouter/main.go b/cmd/vrouter/main.go
index 02104c5..0bc4d98 100644
--- a/cmd/vrouter/main.go
+++ b/cmd/vrouter/main.go
@@ -76,7 +76,8 @@ func main() {
messageToSendBytes := []byte(messageToSend)
// get the longest prefix match for the destination
- hop, err := ipstack.LongestPrefix(netip.MustParseAddr(ipAddr))
+ address, _ := netip.ParseAddr(ipAddr)
+ hop, err := ipstack.LongestPrefix(address)
if err != nil {
fmt.Println(err)
continue
@@ -85,7 +86,7 @@ func main() {
myAddr := hop.Interface.IpPrefix.Addr()
// attempt to send the message to the destination
for _, neighbor := range ipstack.GetNeighbors()[hop.Interface.Name] {
- if neighbor.VipAddr == netip.MustParseAddr(ipAddr) ||
+ if neighbor.VipAddr == address ||
neighbor.VipAddr == hop.VIP {
// send the message to the neighbor
bytesWritten, err := ipstack.SendIP(&myAddr, neighbor, ipstack.TEST_PROTOCOL, messageToSendBytes, ipAddr, nil)
diff --git a/pkg/ipstack/ipstack.go b/pkg/ipstack/ipstack.go
index e3d9ca2..d8db8d2 100644
--- a/pkg/ipstack/ipstack.go
+++ b/pkg/ipstack/ipstack.go
@@ -24,6 +24,7 @@ const (
RIP_PROTOCOL = 200
TEST_PROTOCOL = 0
SIZE_OF_RIP_HEADER = 4
+ MAX_TIMEOUT = 12
)
// STRUCTS ---------------------------------------------------------------------
@@ -74,6 +75,9 @@ var protocolHandlers = make(map[int]HandlerFunc)
var routingTable = make(map[netip.Prefix]Hop)
+var mu sync.Mutex
+var timeoutTable = make(map[netip.Prefix]int)
+
// ************************************** INIT FUNCTIONS **********************************************************
// reference: https://github.com/brown-csci1680/lecture-examples/blob/main/ip-demo/cmd/udp-ip-recv/main.go
@@ -195,8 +199,6 @@ func InterfaceListenerRoutine(i *Interface) {
//if !isUp { // don't call the listeners if interface is down
// continue
//}
- // TODO: remove these "training wheels"
- // time.Sleep(1 * time.Millisecond)
err := RecvIP(i, &isUp)
if err != nil {
continue
@@ -355,6 +357,13 @@ func SprintRoutingTable() string {
return tmp
}
+// prints the test packet as per the spec
+func handleTestPackets(src *Interface, dest *Neighbor, message []byte, hdr *ipv4header.IPv4Header) error {
+ fmt.Printf("Received test packet: Src: %s, Dst: %s, TTL: %d, Data: %s\n",
+ hdr.Src.String(), hdr.Dst.String(), hdr.TTL, string(message))
+ return nil
+}
+
// ************************************** BASIC FUNCTIONS **********************************************************
// cleans up the data structures and closes the UDP sockets
@@ -453,6 +462,7 @@ func SendIP(src *netip.Addr, dest *Neighbor, protocolNum int, message []byte, de
}
// TODO: make this faster by removing call
+ // send the packet
bytesWritten, err := iface.RecvSocket.WriteToUDP(bytesToSend, sendAddr)
if err != nil {
fmt.Println("Error writing to UDP socket")
@@ -594,7 +604,8 @@ func makeRipMessage(command uint16, entries []RIPEntry) []byte {
binary.BigEndian.PutUint16(buf[2:4], uint16(0))
return buf
}
- // else, command == 2, response message
+
+ // command == 2, response message
// create the buffer
bufLen := SIZE_OF_RIP_HEADER + // sizeof uint16 is 2, we have two of them
@@ -621,23 +632,24 @@ func makeRipMessage(command uint16, entries []RIPEntry) []byte {
return buf
}
-
+// sends updates to neighbors every 5 seconds
func periodicUpdateRoutine() {
for {
// for each periodic update, we want to send our nodes in the table
for _, iface := range myInterfaces {
for _, n := range myNeighbors[iface.Name] {
_, in := myRIPNeighbors[n.VipAddr.String()]
+ // if the neighbor is not a RIP neighbor, skip it
if !in {
continue
}
// TODO: consider making this multithreaded and loops above more efficient
- // if we're here, we are sending this to a rip neighbor
+ // Sending to a rip neighbor
+ // create the entries
entries := make([]RIPEntry, 0)
for prefix, hop := range routingTable {
// implement split horizon + poison reverse at entry level
- // fmt.Println("prefix: ", prefix)
var cost uint32
if hop.VIP == n.VipAddr {
cost = INFINITY
@@ -651,6 +663,7 @@ func periodicUpdateRoutine() {
})
}
+ // make the message and send it
message := makeRipMessage(2, entries)
addr := iface.IpPrefix.Addr()
_, err := SendIP(&addr, n, RIP_PROTOCOL, message, n.VipAddr.String(), nil)
@@ -661,23 +674,22 @@ func periodicUpdateRoutine() {
}
}
- // wait 5 sec
+ // wait 5 sec and repeat
time.Sleep(5 * time.Second)
}
}
-var mu sync.Mutex
-var timeoutTable = make(map[netip.Prefix]int)
-var MAX_TIMEOUT = 12
-
+// when triggered, sends updates to neighbors
func sendTriggeredUpdates(newEntries []RIPEntry) {
for _, iface := range myInterfaces {
for _, n := range myNeighbors[iface.Name] {
+ // only send to RIP neighbors, else skip
_, in := myRIPNeighbors[n.VipAddr.String()]
if !in {
continue
}
+ // send the made entries to the neighbor
message := makeRipMessage(2, newEntries)
addr := iface.IpPrefix.Addr()
_, err := SendIP(&addr, n, RIP_PROTOCOL, message, n.VipAddr.String(), nil)
@@ -689,60 +701,41 @@ func sendTriggeredUpdates(newEntries []RIPEntry) {
}
}
-func timeoutKey(prefix netip.Prefix, addr netip.Addr) string {
- return prefix.String() + "-" + addr.String()
-}
-
+// manages the timeout table go routine
func manageTimeoutsRoutine() {
for {
time.Sleep(time.Second)
- // note: waitgroup causes deadlock then crashing
- //wg := &sync.WaitGroup{}
- //wg.Add(len(timeoutTable))
- //mu.Lock()
- //for prefix, _ := range timeoutTable {
- // go func(p netip.Prefix) {
- // timeoutTable[p]++
- // if timeoutTable[p] == MAX_TIMEOUT {
- // delete(routingTable, p)
- // delete(timeoutTable, p)
- // // TODO: send triggered update
- // }
- //
- // wg.Done()
- // }(prefix)
- //}
- //wg.Wait()
- //mu.Unlock()
-
mu.Lock()
+ // check if any timeouts have occurred
for key, _ := range timeoutTable {
timeoutTable[key]++
+ // if the timeout is MAX_TIMEOUT, delete the entry
if timeoutTable[key] == MAX_TIMEOUT {
delete(timeoutTable, key)
newEntries := make([]RIPEntry, 0)
delete(routingTable, key)
newEntries = append(newEntries, RIPEntry{key, INFINITY})
+
// send triggered update on timeout
if len(newEntries) > 0 {
sendTriggeredUpdates(newEntries)
}
}
}
- // fmt.Println("timeout table: ", timeoutTable)
mu.Unlock()
-
//fmt.Println("Timeout table: ", timeoutTable)
}
}
+// go routine to send rip requests to neighbors
func startRipRoutines() {
// send a request to every neighbor
go func() {
for _, iface := range myInterfaces {
for _, neighbor := range myNeighbors[iface.Name] {
+ // only send to RIP neighbors, else skip
_, in := myRIPNeighbors[neighbor.VipAddr.String()]
if !in {
continue
@@ -758,16 +751,16 @@ func startRipRoutines() {
}
}()
+ // start a routine that sends updates every 5 seconds
go periodicUpdateRoutine()
// make a "timeout" table, for each response we add to the table via rip
go manageTimeoutsRoutine()
-
- // start a routine that sends updates every 10 seconds
}
// ************************************** Protocol Handlers *******************************************************
+// registers a protocol handler
func RegisterProtocolHandler(protocolNum int) bool {
if protocolNum == RIP_PROTOCOL {
protocolHandlers[protocolNum] = handleRIP
@@ -783,24 +776,22 @@ func RegisterProtocolHandler(protocolNum int) bool {
func handleRIP(src *Interface, dest *Neighbor, message []byte, hdr *ipv4header.IPv4Header) error {
// parse the RIP message
- // SIZE_OF_RIP_HEADER := 2 * 2
command := int(binary.BigEndian.Uint16(message[0:2]))
switch command {
+ // request message
case 1:
//fmt.Println("Received RIP command for specific info")
+
// only send if the person asking is a RIP neighbor
neighbor, in := myRIPNeighbors[hdr.Src.String()]
if !in {
break
}
- // fmt.Println("he is my rip neighbor ", hdr.Src.String())
-
// build the entries
entries := make([]RIPEntry, 0)
for prefix, hop := range routingTable {
// implement split horizon + poison reverse at entry level
- // fmt.Println("prefix: ", prefix)
var cost uint32
if hop.VIP == hdr.Src {
cost = INFINITY
@@ -813,15 +804,17 @@ func handleRIP(src *Interface, dest *Neighbor, message []byte, hdr *ipv4header.I
cost: cost,
})
}
+ // send the entries
res := makeRipMessage(2, entries)
_, err := SendIP(&hdr.Dst, neighbor, RIP_PROTOCOL, res, hdr.Src.String(), nil)
if err != nil {
return err
}
break
+ // response message
case 2:
- numEntries := int(binary.BigEndian.Uint16(message[2:4]))
// fmt.Println("Received RIP response with", numEntries, "entries")
+ numEntries := int(binary.BigEndian.Uint16(message[2:4]))
// parse the entries
entries := make([]RIPEntry, 0)
@@ -929,14 +922,8 @@ func handleRIP(src *Interface, dest *Neighbor, message []byte, hdr *ipv4header.I
return nil
}
-func handleTestPackets(src *Interface, dest *Neighbor, message []byte, hdr *ipv4header.IPv4Header) error {
- fmt.Printf("Received test packet: Src: %s, Dst: %s, TTL: %d, Data: %s\n",
- hdr.Src.String(), hdr.Dst.String(), hdr.TTL, string(message))
- return nil
-}
-
// ************************************** CHECKSUM FUNCTIONS ******************************************************
-
+// reference: https://github.com/brown-csci1680/lecture-examples/blob/main/ip-demo/cmd/udp-ip-recv/main.go
func ComputeChecksum(b []byte) uint16 {
checksum := header.Checksum(b, 0)
checksumInv := checksum ^ 0xffff
@@ -950,9 +937,9 @@ func ValidateChecksum(b []byte, fromHeader uint16) uint16 {
return checksum
}
-// ************************************** RIP FUNCTIONS **********************************************************
+// ******************************************* RIP HELPERS **********************************************************
-// TODO @ MICHAEL: LONGEST PREFIX MATCHING
+// returns the longest prefix match for a given ip
func LongestPrefix(src netip.Addr) (Hop, error) {
possibleBits := [2]int{32, 24}
for _, bits := range possibleBits {
diff --git a/vhost b/vhost
new file mode 100755
index 0000000..727a450
--- /dev/null
+++ b/vhost
Binary files differ
diff --git a/vrouter b/vrouter
new file mode 100755
index 0000000..b193fa9
--- /dev/null
+++ b/vrouter
Binary files differ