aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Doan <daviddoan@Davids-MacBook-Pro-70.local>2023-10-11 16:39:56 -0400
committerDavid Doan <daviddoan@Davids-MacBook-Pro-70.local>2023-10-11 16:39:56 -0400
commitdb0a9f0a4605d85ba4e535ba0ab590776cc4ba0a (patch)
tree4cfc83ca904f33b7d052ce5a6784d5996e827c90
parentded5a362b43715497a6f887354dd1a20bc9a621b (diff)
parentce42396f99e1d99d7e3b3016acabd9380627a297 (diff)
merge
-rw-r--r--.idea/.gitignore8
-rw-r--r--.idea/iptcp-jailpt2.iml9
-rw-r--r--.idea/modules.xml8
-rw-r--r--.idea/vcs.xml6
-rw-r--r--cmd/example/main.go31
-rw-r--r--doc-example/binaries.example.json17
-rw-r--r--doc-example/nodes.json7
-rw-r--r--go.mod2
-rw-r--r--pkg/ipstack/ipstack.go413
-rw-r--r--pkg/ipstack/ipstack_test.go253
-rw-r--r--pkg/lnxconfig/lnxconfig.go (renamed from pkg/lnxconfig.go)14
-rw-r--r--pkg/protocol.go300
-rw-r--r--pkg/routingTable.go71
-rw-r--r--pkg/routingtable/routingtable.go90
14 files changed, 845 insertions, 384 deletions
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/iptcp-jailpt2.iml b/.idea/iptcp-jailpt2.iml
new file mode 100644
index 0000000..5e764c4
--- /dev/null
+++ b/.idea/iptcp-jailpt2.iml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+ <component name="Go" enabled="true" />
+ <component name="NewModuleRootManager">
+ <content url="file://$MODULE_DIR$" />
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module> \ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..6ae4065
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/.idea/iptcp-jailpt2.iml" filepath="$PROJECT_DIR$/.idea/iptcp-jailpt2.iml" />
+ </modules>
+ </component>
+</project> \ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="" vcs="Git" />
+ </component>
+</project> \ No newline at end of file
diff --git a/cmd/example/main.go b/cmd/example/main.go
new file mode 100644
index 0000000..383e490
--- /dev/null
+++ b/cmd/example/main.go
@@ -0,0 +1,31 @@
+package main
+
+import (
+ "fmt"
+ "iptcp/pkg/lnxconfig"
+ "net/netip"
+ "os"
+)
+
+func main() {
+ if len(os.Args) != 2 {
+ fmt.Printf("Usage: %s <configFile>\n", os.Args[0])
+ os.Exit(1)
+ }
+ fileName := os.Args[1]
+
+ // Parse the file
+ lnxConfig, err := lnxconfig.ParseConfig(fileName)
+ if err != nil {
+ panic(err)
+ }
+
+ // Demo: print out the IP for each interface in this config
+ for _, iface := range lnxConfig.Interfaces {
+ prefixForm := netip.PrefixFrom(iface.AssignedIP, iface.AssignedPrefix.Bits())
+ fmt.Printf("%s has IP %s\n", iface.Name, prefixForm.String())
+
+ fmt.Printf(iface.UDPAddr.String() + "\n")
+ fmt.Printf(iface.AssignedIP.String() + "\n")
+ }
+}
diff --git a/doc-example/binaries.example.json b/doc-example/binaries.example.json
new file mode 100644
index 0000000..b6ff6d9
--- /dev/null
+++ b/doc-example/binaries.example.json
@@ -0,0 +1,17 @@
+{
+ "h1": {
+ "binary_path": "./vhost"
+ },
+ "h2": {
+ "binary_path": "./vhost"
+ },
+ "h3": {
+ "binary_path": "./vhost"
+ },
+ "r1": {
+ "binary_path": "./vrouter"
+ },
+ "r2": {
+ "binary_path": "./vrouter"
+ }
+} \ No newline at end of file
diff --git a/doc-example/nodes.json b/doc-example/nodes.json
new file mode 100644
index 0000000..7b91355
--- /dev/null
+++ b/doc-example/nodes.json
@@ -0,0 +1,7 @@
+{
+ "h1": "host",
+ "h2": "host",
+ "h3": "host",
+ "r1": "router",
+ "r2": "router"
+} \ No newline at end of file
diff --git a/go.mod b/go.mod
index 9654e0e..b7e6cbe 100644
--- a/go.mod
+++ b/go.mod
@@ -1,4 +1,4 @@
-module golang-sockets
+module iptcp
go 1.20
diff --git a/pkg/ipstack/ipstack.go b/pkg/ipstack/ipstack.go
new file mode 100644
index 0000000..be8bc1e
--- /dev/null
+++ b/pkg/ipstack/ipstack.go
@@ -0,0 +1,413 @@
+package ipstack
+
+import (
+ "fmt"
+ ipv4header "github.com/brown-csci1680/iptcp-headers"
+ "github.com/google/netstack/tcpip/header"
+ "github.com/pkg/errors"
+ "iptcp/pkg/lnxconfig"
+ "log"
+ "net"
+ "net/netip"
+ "time"
+)
+
+const (
+ MAX_IP_PACKET_SIZE = 1400
+ LOCAL_COST uint32 = 0
+ STATIC_COST uint32 = 4294967295 // 2^32 - 1
+)
+
+// STRUCTS ---------------------------------------------------------------------
+type Interface struct {
+ Name string
+ IpPrefix netip.Prefix
+ UdpAddr netip.AddrPort
+
+ RecvSocket net.UDPConn
+ SocketChannel chan bool
+ State bool
+}
+
+type Neighbor struct {
+ VipAddr netip.Addr
+ UdpAddr netip.AddrPort
+
+ SendSocket net.UDPConn
+ SocketChannel chan bool
+}
+
+type RIPMessage struct {
+ command uint8
+ numEntries uint8
+ entries []RIPEntry
+}
+
+type RIPEntry struct {
+ addr netip.Addr
+ cost uint32
+ mask netip.Prefix
+}
+
+type Hop struct {
+ Cost uint32
+ VipAsStr string
+}
+
+// GLOBAL VARIABLES (data structures) ------------------------------------------
+var myVIP netip.Addr
+var myInterfaces []*Interface
+var myNeighbors = make(map[string][]*Neighbor)
+
+// var myRIPNeighbors = make(map[string]Neighbor)
+type HandlerFunc func(int, string, *[]byte) error
+
+var protocolHandlers = make(map[uint16]HandlerFunc)
+
+// var routingTable = routingtable.New()
+var routingTable = make(map[netip.Prefix]Hop)
+
+// reference: https://github.com/brown-csci1680/lecture-examples/blob/main/ip-demo/cmd/udp-ip-recv/main.go
+func createUDPConn(UdpAddr netip.AddrPort, conn *net.UDPConn) error {
+ listenString := UdpAddr.String()
+ listenAddr, err := net.ResolveUDPAddr("udp4", listenString)
+ if err != nil {
+ return errors.WithMessage(err, "Error resolving address->\t"+listenString)
+ }
+ tmpConn, err := net.ListenUDP("udp4", listenAddr)
+ if err != nil {
+ return errors.WithMessage(err, "Could not bind to UDP port->\t"+listenString)
+ }
+ *conn = *tmpConn
+
+ return nil
+}
+
+func Initialize(lnxFilePath string) error {
+ //if len(os.Args) != 2 {
+ // fmt.Printf("Usage: %s <configFile>\n", os.Args[0])
+ // os.Exit(1)
+ //}
+ //lnxFilePath := os.Args[1]
+
+ // Parse the file
+ lnxConfig, err := lnxconfig.ParseConfig(lnxFilePath)
+ if err != nil {
+ return errors.WithMessage(err, "Error parsing config file->\t"+lnxFilePath)
+ }
+
+ // 1) initialize the interfaces on this node here and into the routing table
+ static := false
+ for _, iface := range lnxConfig.Interfaces {
+ prefix := netip.PrefixFrom(iface.AssignedIP, iface.AssignedPrefix.Bits())
+ i := &Interface{
+ Name: iface.Name,
+ IpPrefix: prefix,
+ UdpAddr: iface.UDPAddr,
+ RecvSocket: net.UDPConn{},
+ SocketChannel: make(chan bool),
+ State: false,
+ }
+
+ err := createUDPConn(iface.UDPAddr, &i.RecvSocket)
+ if err != nil {
+ return errors.WithMessage(err, "Error creating UDP socket for interface->\t"+iface.Name)
+ }
+ go InterfaceListenerRoutine(i.RecvSocket, i.SocketChannel)
+ myInterfaces = append(myInterfaces, i)
+
+ // TODO: (FOR HOSTS ONLY)
+ // add STATIC to routing table
+ if !static {
+ ifacePrefix := netip.MustParsePrefix("0.0.0.0/0")
+ routingTable[ifacePrefix] = Hop{STATIC_COST, iface.Name}
+ static = true
+ }
+ }
+
+ // 2) initialize the neighbors connected to the node and into the routing table
+ for _, neighbor := range lnxConfig.Neighbors {
+ n := &Neighbor{
+ VipAddr: neighbor.DestAddr,
+ UdpAddr: neighbor.UDPAddr,
+ SendSocket: net.UDPConn{},
+ SocketChannel: make(chan bool),
+ }
+
+ err := createUDPConn(neighbor.UDPAddr, &n.SendSocket)
+ if err != nil {
+ return errors.WithMessage(err, "Error creating UDP socket for neighbor->\t"+neighbor.DestAddr.String())
+ }
+
+ myNeighbors[neighbor.InterfaceName] = append(myNeighbors[neighbor.InterfaceName], n)
+
+ // add to routing table
+ // TODO: REVISIT AND SEE IF "24" IS CORRECT
+ neighborPrefix := netip.PrefixFrom(neighbor.DestAddr, 24)
+ routingTable[neighborPrefix] = Hop{LOCAL_COST, neighbor.InterfaceName}
+ }
+
+ return nil
+}
+
+func InterfaceListenerRoutine(socket net.UDPConn, signal <-chan bool) {
+ isUp := false
+ closed := false
+
+ // go routine that hangs on the recv
+ fmt.Println("MAKING GO ROUTINE TO LISTEN:\t", socket.LocalAddr().String())
+ go func() {
+ defer func() { // on close, set isUp to false
+ fmt.Println("exiting go routine that listens on ", socket.LocalAddr().String())
+ }()
+
+ for {
+ if closed { // stop this go routine if channel is closed
+ return
+ }
+ if !isUp { // don't call the listeners if interface is down
+ continue
+ }
+ // TODO: remove these "training wheels"
+ time.Sleep(1 * time.Millisecond)
+ err := RecvIP(socket, &isUp)
+ if err != nil {
+ fmt.Println("Error receiving IP packet", err)
+ return
+ }
+ }
+ }()
+
+ for {
+ select {
+ case sig, ok := <-signal:
+ if !ok {
+ fmt.Println("channel closed, exiting")
+ closed = true
+ return
+ }
+ fmt.Println("received isUP SIGNAL with value", sig)
+ isUp = sig
+ default:
+ continue
+ }
+ }
+}
+
+func InterfaceUp(iface *Interface) {
+ iface.State = true
+ iface.SocketChannel <- true
+}
+
+func InterfaceDown(iface *Interface) {
+ iface.SocketChannel <- false
+ iface.State = false
+}
+
+func GetInterfaceByName(ifaceName string) (*Interface, error) {
+ for _, iface := range myInterfaces {
+ if iface.Name == ifaceName {
+ return iface, nil
+ }
+ }
+
+ return nil, errors.Errorf("No interface with name %s", ifaceName)
+}
+
+func GetNeighborsToInterface(ifaceName string) ([]*Neighbor, error) {
+ if neighbors, ok := myNeighbors[ifaceName]; ok {
+ return neighbors, nil
+ }
+
+ return nil, errors.Errorf("No interface with name %s", ifaceName)
+}
+
+func SprintInterfaces() string {
+ buf := ""
+ for _, iface := range myInterfaces {
+ buf += fmt.Sprintf("%s\t%s\t%t\n", iface.Name, iface.IpPrefix.String(), iface.State)
+ }
+ return buf
+}
+
+func SprintNeighbors() string {
+ buf := ""
+ for ifaceName, neighbor := range myNeighbors {
+ for _, n := range neighbor {
+ buf += fmt.Sprintf("%s\t%s\t%s\n", ifaceName, n.UdpAddr.String(), n.VipAddr.String())
+ }
+ }
+ return buf
+}
+
+func SprintRoutingTable() string {
+ buf := ""
+ for prefix, hop := range routingTable {
+ buf += fmt.Sprintf("%s\t%s\t%d\n", prefix.String(), hop.VipAsStr, hop.Cost)
+ }
+ return buf
+}
+
+func DebugNeighbors() {
+ for ifaceName, neighbor := range myNeighbors {
+ for _, n := range neighbor {
+ fmt.Printf("%s\t%s\t%s\n", ifaceName, n.UdpAddr.String(), n.VipAddr.String())
+ }
+ }
+}
+
+func CleanUp() {
+ fmt.Print("Cleaning up...\n")
+ // go through the interfaces, pop thread & close the UDP FDs
+ for _, iface := range myInterfaces {
+ if iface.SocketChannel != nil {
+ close(iface.SocketChannel)
+ }
+ err := iface.RecvSocket.Close()
+ if err != nil {
+ continue
+ }
+ }
+
+ // go through the neighbors, pop thread & close the UDP FDs
+ for _, neighbor := range myNeighbors {
+ for _, n := range neighbor {
+ if n.SocketChannel != nil {
+ close(n.SocketChannel)
+ }
+ err := n.SendSocket.Close()
+ if err != nil {
+ continue
+ }
+ }
+ }
+
+ // delete all the neighbors
+ myNeighbors = make(map[string][]*Neighbor)
+ // delete all the interfaces
+ myInterfaces = nil
+ // delete the routing table
+ routingTable = make(map[netip.Prefix]Hop)
+
+ time.Sleep(5 * time.Millisecond)
+}
+
+// TODO: have it take TTL so we can decrement it when forwarding
+func SendIP(src Interface, dest Neighbor, protocolNum int, message []byte) error {
+ hdr := ipv4header.IPv4Header{
+ Version: 4,
+ Len: 20, // Header length is always 20 when no IP options
+ TOS: 0,
+ TotalLen: ipv4header.HeaderLen + len(message),
+ ID: 0,
+ Flags: 0,
+ FragOff: 0,
+ TTL: 32,
+ Protocol: protocolNum,
+ Checksum: 0, // Should be 0 until checksum is computed
+ Src: src.IpPrefix.Addr(),
+ Dst: dest.VipAddr,
+ Options: []byte{},
+ }
+
+ // Assemble the header into a byte array
+ headerBytes, err := hdr.Marshal()
+ if err != nil {
+ return err
+ }
+
+ // Compute the checksum (see below)
+ // Cast back to an int, which is what the Header structure expects
+ hdr.Checksum = int(ComputeChecksum(headerBytes))
+
+ headerBytes, err = hdr.Marshal()
+ if err != nil {
+ log.Fatalln("Error marshalling header: ", err)
+ }
+
+ bytesToSend := make([]byte, 0, len(headerBytes)+len(message))
+ bytesToSend = append(bytesToSend, headerBytes...)
+ bytesToSend = append(bytesToSend, []byte(message)...)
+
+ // Send the message to the "link-layer" addr:port on UDP
+ listenAddr, err := net.ResolveUDPAddr("udp4", dest.UdpAddr.String())
+ if err != nil {
+ return err
+ }
+ bytesWritten, err := dest.SendSocket.WriteToUDP(bytesToSend, listenAddr)
+ if err != nil {
+ return err
+ }
+ fmt.Printf("Sent %d bytes to %s\n", bytesWritten, listenAddr.String())
+
+ return nil
+}
+
+func RecvIP(conn net.UDPConn, isOpen *bool) error {
+ buffer := make([]byte, MAX_IP_PACKET_SIZE) // TODO: fix wordking
+
+ // Read on the UDP port
+ fmt.Println("wating to read from UDP socket")
+ _, sourceAddr, err := conn.ReadFromUDP(buffer)
+ if err != nil {
+ return err
+ }
+
+ if !*isOpen {
+ return errors.New("interface is down")
+ }
+
+ // Marshal the received byte array into a UDP header
+ // NOTE: This does not validate the checksum or check any fields
+ // (You'll need to do this part yourself)
+ hdr, err := ipv4header.ParseHeader(buffer)
+ if err != nil {
+ // What should you if the message fails to parse?
+ // Your node should not crash or exit when you get a bad message.
+ // Instead, simply drop the packet and return to processing.
+ fmt.Println("Error parsing header", err)
+ return err
+ }
+
+ headerSize := hdr.Len
+ headerBytes := buffer[:headerSize]
+ checksumFromHeader := uint16(hdr.Checksum)
+ computedChecksum := ValidateChecksum(headerBytes, checksumFromHeader)
+
+ var checksumState string
+ if computedChecksum == checksumFromHeader {
+ checksumState = "OK"
+ } else {
+ checksumState = "FAIL"
+ }
+
+ // Next, get the message, which starts after the header
+ message := buffer[headerSize:]
+
+ // Finally, print everything out
+ fmt.Printf("Received IP packet from %s\nHeader: %v\nChecksum: %s\nMessage: %s\n",
+ sourceAddr.String(), hdr, checksumState, string(message))
+
+ // TODO: handle the message
+ // 1) check if the TTL & checksum is valid
+ // 2) check if the message is for me, if so, sendUP (aka call the correct handler)
+ // if not, need to forward the packer to a neighbor or check the table
+ // after decrementing TTL and updating checksum
+ // 3) check if message is for a neighbor, if so, sendIP there
+ // 4) check if message is for a neighbor, if so, forward to the neighbor with that VIP
+
+ return nil
+}
+
+func ComputeChecksum(b []byte) uint16 {
+ checksum := header.Checksum(b, 0)
+ checksumInv := checksum ^ 0xffff
+
+ return checksumInv
+}
+
+func ValidateChecksum(b []byte, fromHeader uint16) uint16 {
+ checksum := header.Checksum(b, fromHeader)
+
+ return checksum
+}
diff --git a/pkg/ipstack/ipstack_test.go b/pkg/ipstack/ipstack_test.go
new file mode 100644
index 0000000..941c4e9
--- /dev/null
+++ b/pkg/ipstack/ipstack_test.go
@@ -0,0 +1,253 @@
+package ipstack
+
+import (
+ "fmt"
+ ipv4header "github.com/brown-csci1680/iptcp-headers"
+ "net"
+ "net/netip"
+ "testing"
+ "time"
+)
+
+func TestInitialize(t *testing.T) {
+ lnxFilePath := "../../doc-example/r2.lnx"
+ err := Initialize(lnxFilePath)
+ if err != nil {
+ t.Error(err)
+ }
+ fmt.Printf("Interfaces:\n%s\n\n", SprintInterfaces())
+ fmt.Printf("Neighbors:\n%s\n", SprintNeighbors())
+ fmt.Printf("RoutingTable:\n%s\n", SprintRoutingTable())
+
+ fmt.Println("TestInitialize successful")
+ t.Cleanup(func() { CleanUp() })
+}
+
+func TestInterfaceUpThenDown(t *testing.T) {
+ lnxFilePath := "../../doc-example/r2.lnx"
+ err := Initialize(lnxFilePath)
+ if err != nil {
+ t.Error(err)
+ }
+
+ iface, err := GetInterfaceByName("if0")
+ if err != nil {
+ t.Error(err)
+ }
+
+ InterfaceUp(iface)
+ if iface.State == false {
+ t.Error("iface state should be true")
+ }
+
+ fmt.Printf("Interfaces:\n%s\n", SprintInterfaces())
+
+ time.Sleep(5 * time.Millisecond) // allow time to print
+
+ InterfaceDown(iface)
+ if iface.State == true {
+ t.Error("iface state should be false")
+ }
+
+ time.Sleep(5 * time.Millisecond) // allow time to print
+
+ fmt.Printf("Interfaces:\n%s\n", SprintInterfaces())
+
+ fmt.Println("TestInterfaceUpThenDown successful")
+ t.Cleanup(func() { CleanUp() })
+}
+
+func TestInterfaceUpThenDownTwice(t *testing.T) {
+ lnxFilePath := "../../doc-example/r2.lnx"
+ err := Initialize(lnxFilePath)
+ if err != nil {
+ t.Error(err)
+ }
+
+ iface, err := GetInterfaceByName("if0")
+ if err != nil {
+ t.Error(err)
+ }
+
+ InterfaceUp(iface)
+ if iface.State == false {
+ t.Error("iface state should be true")
+ }
+
+ fmt.Printf("Interfaces:\n%s\n", SprintInterfaces())
+
+ time.Sleep(5 * time.Millisecond) // allow time to print
+
+ fmt.Println("putting interface down")
+ InterfaceDown(iface)
+ if iface.State == true {
+ t.Error("iface state should be false")
+ }
+
+ time.Sleep(3 * time.Millisecond)
+
+ fmt.Println("putting interface back up for 3 iterations")
+ InterfaceUp(iface)
+ if iface.State == false {
+ t.Error("iface state should be true")
+ }
+ time.Sleep(3 * time.Millisecond) // allow time to print
+
+ fmt.Println("putting interface down")
+ InterfaceDown(iface)
+ if iface.State == true {
+ t.Error("iface state should be false")
+ }
+
+ time.Sleep(5 * time.Millisecond) // allow time to print
+
+ fmt.Printf("Interfaces:\n%s\n", SprintInterfaces())
+
+ fmt.Println("TestInterfaceUpThenDownTwice successful")
+ t.Cleanup(func() { CleanUp() })
+}
+
+func TestSendIPToNeighbor(t *testing.T) {
+ lnxFilePath := "../../doc-example/r2.lnx"
+ err := Initialize(lnxFilePath)
+ if err != nil {
+ t.Error(err)
+ }
+
+ // get the first neighbor of this interface
+ iface, err := GetInterfaceByName("if0")
+ if err != nil {
+ t.Error(err)
+ }
+ neighbors, err := GetNeighborsToInterface("if0")
+ if err != nil {
+ t.Error(err)
+ }
+
+ // setup a neighbor listener socket
+ testNeighbor := neighbors[0]
+ // close the socket so we can listen on it
+ err = testNeighbor.SendSocket.Close()
+ if err != nil {
+ t.Error(err)
+ }
+
+ fmt.Printf("Interfaces:\n%s\n", SprintInterfaces())
+ fmt.Printf("Neighbors:\n%s\n", SprintNeighbors())
+
+ listenString := testNeighbor.UdpAddr.String()
+ fmt.Println("listening on " + listenString)
+ listenAddr, err := net.ResolveUDPAddr("udp4", listenString)
+ if err != nil {
+ t.Error(err)
+ }
+ recvSocket, err := net.ListenUDP("udp4", listenAddr)
+ if err != nil {
+ t.Error(err)
+ }
+ testNeighbor.SendSocket = *recvSocket
+
+ sent := false
+ go func() {
+ buffer := make([]byte, MAX_IP_PACKET_SIZE)
+ fmt.Println("wating to read from UDP socket")
+ _, sourceAddr, err := recvSocket.ReadFromUDP(buffer)
+ if err != nil {
+ t.Error(err)
+ }
+ fmt.Println("read from UDP socket")
+ hdr, err := ipv4header.ParseHeader(buffer)
+ if err != nil {
+ t.Error(err)
+ }
+ headerSize := hdr.Len
+ headerBytes := buffer[:headerSize]
+ checksumFromHeader := uint16(hdr.Checksum)
+ computedChecksum := ValidateChecksum(headerBytes, checksumFromHeader)
+
+ var checksumState string
+ if computedChecksum == checksumFromHeader {
+ checksumState = "OK"
+ } else {
+ checksumState = "FAIL"
+ }
+ message := buffer[headerSize:]
+ fmt.Printf("Received IP packet from %s\nHeader: %v\nChecksum: %s\nMessage: %s\n",
+ sourceAddr.String(), hdr, checksumState, string(message))
+ if err != nil {
+ t.Error(err)
+ }
+
+ sent = true
+ }()
+
+ time.Sleep(10 * time.Millisecond)
+
+ // send a message to the neighbor
+ fmt.Printf("sending message to neighbor\t%t\n", sent)
+ err = SendIP(*iface, *testNeighbor, 0, []byte("You are my firest neighbor!"))
+ if err != nil {
+ t.Error(err)
+ }
+
+ fmt.Printf("SENT message to neighbor\t%t\n", sent)
+ // give a little time for the message to be sent
+ time.Sleep(1000 * time.Millisecond)
+ if !sent {
+ t.Error("Message not sent")
+ t.Fail()
+ }
+
+ fmt.Println("TestSendIPToNeighbor successful")
+ t.Cleanup(func() { CleanUp() })
+}
+
+func TestRecvIP(t *testing.T) {
+ lnxFilePath := "../../doc-example/r2.lnx"
+ err := Initialize(lnxFilePath)
+ if err != nil {
+ t.Error(err)
+ }
+
+ // get the first neighbor of this interface to RecvIP from
+ iface, err := GetInterfaceByName("if0")
+ if err != nil {
+ t.Error(err)
+ }
+ InterfaceUp(iface)
+
+ // setup a random socket to send an ip packet from
+ listenAddr, err := net.ResolveUDPAddr("udp4", "127.0.0.1:6969")
+ sendSocket, err := net.ListenUDP("udp4", listenAddr)
+
+ // send a message to the neighbor
+ ifaceAsNeighbor := Neighbor{
+ VipAddr: iface.IpPrefix.Addr(),
+ UdpAddr: iface.UdpAddr,
+ SendSocket: iface.RecvSocket,
+ SocketChannel: iface.SocketChannel,
+ }
+ fakeIface := Interface{
+ Name: "if69",
+ IpPrefix: netip.MustParsePrefix("10.69.0.1/24"),
+ UdpAddr: netip.MustParseAddrPort("127.0.0.1:6969"),
+ RecvSocket: net.UDPConn{},
+ SocketChannel: nil,
+ State: true,
+ }
+ err = SendIP(fakeIface, ifaceAsNeighbor, 0, []byte("hello"))
+ if err != nil {
+ return
+ }
+
+ time.Sleep(10 * time.Millisecond)
+
+ // TODO: potenially make this a channel, so it actually checks values.
+ // For now, you must read the message from the console.
+
+ err = sendSocket.Close()
+ if err != nil {
+ t.Error(err)
+ }
+ t.Cleanup(func() { CleanUp() })
+}
diff --git a/pkg/lnxconfig.go b/pkg/lnxconfig/lnxconfig.go
index 36b1b56..8e43613 100644
--- a/pkg/lnxconfig.go
+++ b/pkg/lnxconfig/lnxconfig.go
@@ -3,6 +3,7 @@ package lnxconfig
import (
"bufio"
"fmt"
+ "github.com/pkg/errors"
"net/netip"
"os"
"strings"
@@ -16,17 +17,6 @@ const (
RoutingTypeRIP RoutingMode = 2
)
-/*
- * NOTE: These data structures only represent structure of a
- * configuration file. In your implementation, you will still need to
- * build your own data structures that store relevant information
- * about your links, interfaces, etc. at runtime.
- *
- * These structs only represent the things in the config file--you
- * will probably only parse these at startup in order to set up your own
- * data structures.
- *
- */
type IPConfig struct {
Interfaces []InterfaceConfig
Neighbors []NeighborConfig
@@ -362,4 +352,4 @@ func ParseConfig(configFile string) (*IPConfig, error) {
}
return config, nil
-} \ No newline at end of file
+}
diff --git a/pkg/protocol.go b/pkg/protocol.go
deleted file mode 100644
index 63951d6..0000000
--- a/pkg/protocol.go
+++ /dev/null
@@ -1,300 +0,0 @@
-package protocol
-
-import (
- "net"
- "net/netip"
- "fmt"
- "os"
- "bufio"
- "time"
- "github.com/pkg/errors"
- ipv4header "github.com/brown-csci1680/iptcp-headers"
- "github.com/google/netstack/tcpip/header"
- "github.com/brown-csci1680/ipstack-utils"
-)
-
-const (
- MAX_IP_PACKET_SIZE = 1400
-)
-
-type Interface struct {
- Name string
- AssignedIP netip.Addr
- AssignedPrefix netip.Prefix
-
- UDPAddr netip.AddrPort
- State uint8_t
- neighbors map[netip.AddrPort]netip.AddrPort
-}
-
-// type Host struct {
-// Interface Interface
-// Neighbors []Neighbor
-// }
-
-// type Router struct {
-// Interfaces []Interface
-// Neighbors []Neighbor
-// RIPNeighbors []Neighbor
-// }
-
-
-type Neighbor struct{
- DestAddr netip.Addr
- UDPAddr netip.AddrPort
-
- InterfaceName string
-}
-
-type RIPMessage struct {
- command uint8_t;
- numEntries uint8_t;
- entries []RIPEntry;
-}
-
-type RIPEntry struct {
- addr netip.Addr;
- cost uint16_t;
- mask netip.Prefix;
-}
-
-type RoutingTable interface {
- Initialize(config IpConfig) (error)
- AddRoute(dest Address, cost uint16_t, mask netip.Prefix) (error)
- RemoveRoute(dest Address) (error)
- GetRoute(dest Address) (Routing, error)
-}
-
-
-myInterfaces := make([]Interface);
-myNeighbors := make(map[string]Neighbor)
-myRIPNeighbors := make(map[string]Neighbor)
-protocolHandlers := make(map[uint16]HandlerFunc)
-routingTable := RoutingTable{}
-// routingTable := make(map[Address]Routing)
-
-func Initialize(config IpConfig) (error) {
- if len(os.Args) != 2 {
- fmt.Printf("Usage: %s <configFile>\n", os.Args[0])
- os.Exit(1)
- }
- fileName := os.Args[1]
-
- // Parse the file
- lnxConfig, err := lnxconfig.ParseConfig(fileName)
- if err != nil {
- panic(err)
- }
-
- // populate routing table???
- for _, iface := range lnxConfig.Interfaces {
- myInterfaces = append(myInterfaces, Interface{iface.Name, iface.AssignedIP, iface.AssignedPrefix, iface.UDPAddr, 0})
- }
-
- for _, neighbor := range lnxConfig.Neighbors {
- myNeighbors[neighbor.DestAddr.String()] = Neighbor{neighbor.DestAddr, neighbor.UDPAddr, neighbor.InterfaceName}
- }
-
- // add RIP neighbors
- for _, neighbor := range lnxConfig.RipNeighbors {
- myRIPNeighbors[neighbor.DestAddr.String()] = Neighbor{neighbor.DestAddr, neighbor.UDPAddr, neighbor.InterfaceName}
- }
- // call routingTable.Initialize(config)
- // create new routing table
- routingTable.Initialize(config)
-
-}
-
-func ListerToInterfaces() {
- for _, iface := range myInterfaces {
- go RecvIp(iface)
- }
-}
-
-func RecvIp(iface Interface) (error) {
- for {
- buffer := make([]byte, MAX_IP_PACKET_SIZE)
- _, sourceAddr, err := iface.udp.ReadFrom(buffer)
- if err != nil {
- log.Panicln("Error reading from UDP socket ", err)
- }
-
- hdr, err := ipv4header.ParseHeader(buffer)
-
- if err != nil {
- fmt.Println("Error parsing header", err)
- continue
- }
-
- headerSize := hdr.Len
- headerBytes := buffer[:headerSize]
-
- checksumFromHeader := uint16(hdr.Checksum)
- computedChecksum := ValidateChecksum(headerBytes, checksumFromHeader)
-
- var checksumState string
- if computedChecksum == checksumFromHeader {
- checksumState = "OK"
- } else {
- checksumState = "FAIL"
- continue
- }
-
- // check ttl
- ttl := data[8]
- if ttl == 0 {
- fmt.Println("TTL is 0")
- continue
- }
-
-
- destAddr := netip.AddrFrom(data[16:20])
- protocolNum := data[9]
-
- if destAddr == iface.addr {
- // send to handler
- protocolHandlers[protocolNum](data)
- // message := buffer[headerSize:]
-
- // fmt.Printf("Received IP packet from %s\nHeader: %v\nChecksum: %s\nMessage: %s\n",
- // sourceAddr.String(), hdr, checksumState, string(message))
- } else {
- // decrement ttl and update checksum
- data[8] = ttl - 1
- data[10] = 0
- data[11] = 0
- newChecksum := int(ComputeChecksum(data[:headerSize]))
- data[10] = newChecksum >> 8
- data[11] = newChecksum & 0xff
-
- // check neighbors
- for _, neighbor := range iface.neighbors {
- if neighbor == destAddr {
- // send to neighbor
- // SendIp(destAddr, protocolNum, data)
- }
- }
-
- // check forwarding table
-
- }
-
- }
-}
-
-func ValidateChecksum(b []byte, fromHeader uint16) uint16 {
- checksum := header.Checksum(b, fromHeader)
-
- return checksum
-}
-
-func SendIp(dst netip.Addr, port uint16, protocolNum uint16, data []byte, iface Interface) (error) {
- bindLocalAddr, err := net.ResolveUDPAddr("udp4", iface.UDPAddr.String())
- if err != nil {
- log.Panicln("Error resolving address: ", err)
- }
-
- addrString := fmt.Sprintf("%s:%s", dst, port)
- remoteAddr, err := net.ResolveUDPAddr("udp4", addrString)
- if err != nil {
- log.Panicln("Error resolving address: ", err)
- }
-
- fmt.Printf("Sending to %s:%d\n",
- remoteAddr.IP.String(), remoteAddr.Port)
-
- // Bind on the local UDP port: this sets the source port
- // and creates a conn
- conn, err := net.ListenUDP("udp4", bindLocalAddr)
- if err != nil {
- log.Panicln("Dial: ", err)
- }
-
- message := data[20:]
- hdr := ipv4header.IPv4Header{
- Version: data[0] >> 4,
- Len: 20, // Header length is always 20 when no IP options
- TOS: data[1],
- TotalLen: ipv4header.HeaderLen + len(message),
- ID: data[4],
- Flags: data[6] >> 5,
- FragOff: data[6] & 0x1f,
- TTL: data[8],
- Protocol: data[9],
- Checksum: 0, // Should be 0 until checksum is computed
- Src: netip.MustParseAddr(iface.addr.String()),
- Dst: netip.MustParseAddr(dst.String()),
- Options: []byte{},
- }
-
- headerBytes, err := hdr.Marshal()
- if err != nil {
- log.Fatalln("Error marshalling header: ", err)
- }
-
- hdr.Checksum = int(ComputeChecksum(headerBytes))
-
- headerBytes, err = hdr.Marshal()
- if err != nil {
- log.Fatalln("Error marshalling header: ", err)
- }
-
- bytesToSend := make([]byte, 0, len(headerBytes)+len(message))
- bytesToSend = append(bytesToSend, headerBytes...)
- bytesToSend = append(bytesToSend, []byte(message)...)
-
- // Send the message to the "link-layer" addr:port on UDP
- bytesWritten, err := conn.WriteToUDP(bytesToSend, remoteAddr)
- if err != nil {
- log.Panicln("Error writing to socket: ", err)
- }
- fmt.Printf("Sent %d bytes\n", bytesWritten)
-}
-
-func ComputeChecksum(b []byte) uint16 {
- checksum := header.Checksum(b, 0)
- checksumInv := checksum ^ 0xffff
-
- return checksumInv
-}
-
-func ForwardIP(data []byte) (error) {
-}
-
-type HandlerFunc = func help(*Packet, []interface{}) (error) {
-
- // do smth with packet
-}
-
-func AddRecvHandler(protocolNum uint8, callbackFunc HandlerFunc) (error) {
- if protocolHandlers[protocolNum] != nil {
- fmt.Printf("Warning: Handler for protocol %d already exists", protocolNum)
- }
- protocolHandlers[protocolNum] = callbackFunc
- return nil
-}
-
-func RemoveRecvHandler(protocolNum uint8) (error) {
- // consider error
- if protocolHandlers[protocolNum] == nil {
- return errors.Errorf("No handler for protocol %d", protocolNum)
- }
- delete(protocolHandlers, protocolNum)
- return nil
-}
-
-func PrintNeighbors() {
- for _, iface := range myNeighbors {
- fmt.Printf("%s\n", iface.addr.String())
- }
-}
-
-func PrintInterfaces() {
- for _, iface := range myInterfaces {
- fmt.Printf("%s\n", iface.addr.String())
- }
-}
-func GetNeighbors() ([]netip.Addr) {
- return myNeighbors
-}
-
diff --git a/pkg/routingTable.go b/pkg/routingTable.go
deleted file mode 100644
index bda4524..0000000
--- a/pkg/routingTable.go
+++ /dev/null
@@ -1,71 +0,0 @@
-package routingTable
-
-import (
- "fmt"
- "net"
- "net/netip"
- "os"
- "bufio"
-)
-
-type Address struct {
- addr netip.Addr;
- prefix netip.Prefix;
-}
-
-type Routing struct {
- dest Address;
- cost uint16_t;
- mask netip.Prefix;
-}
-
-routingTable := make(map[Address]Routing)
-
-func Initialize(config IpConfig) (error) {
- if len(os.Args) != 2 {
- fmt.Printf("Usage: %s <configFile>\n", os.Args[0])
- os.Exit(1)
- }
- fileName := os.Args[1]
-
- lnxConfig, err := lnxconfig.ParseConfig(fileName)
- if err != nil {
- panic(err)
- }
-
- // populate routing table
- for _, iface := range lnxConfig.Interfaces {
- routingTable[Address{iface.AssignedIP, iface.AssignedPrefix}] = Routing{Address{iface.AssignedIP, iface.AssignedPrefix}, 0, iface.AssignedPrefix}
- }
-
-}
-
-func AddRoute(dest Address, cost uint16_t, mask netip.Prefix) (error) {
- if _, ok := routingTable[dest]; ok {
- return newErrString(ln, "Route already exists")
- }
- routingTable[dest] = Routing{dest, cost, mask}
-}
-
-func RemoveRoute(dest Address) (error) {
- if _, ok := routingTable[dest]; !ok {
- return newErrString(ln, "Route does not exist")
- }
- delete(routingTable, dest)
-}
-
-func GetRoute(dest Address) (Routing, error) {
- // get the most specific route
- for key, value := range routingTable {
- if key.prefix.Contains(dest.addr) {
- return value, nil
- }
- }
- return nil, newErrString(ln, "Route does not exist")
-}
-
-func PrintRoutingTable() {
- for key, value := range routingTable {
- fmt.Printf("%s/%d\t%d\n", key.addr, key.prefix.Bits(), value.cost)
- }
-} \ No newline at end of file
diff --git a/pkg/routingtable/routingtable.go b/pkg/routingtable/routingtable.go
new file mode 100644
index 0000000..90b64ae
--- /dev/null
+++ b/pkg/routingtable/routingtable.go
@@ -0,0 +1,90 @@
+package routingtable
+
+import (
+ "fmt"
+ "github.com/pkg/errors"
+ "net/netip"
+)
+
+type Address struct {
+ Addr netip.Addr
+ Prefix netip.Prefix
+}
+
+type Hop struct {
+ Cost uint32
+ VipAsStr string
+}
+
+type RoutingTable map[Address]Hop
+
+const (
+ STATIC_COST uint32 = 4294967295 // 2^32 - 1
+)
+
+// TODO: consider making this take in arguments, such as a config file
+func New() *RoutingTable {
+ var table = make(RoutingTable)
+ return &table
+}
+
+//func Initialize(config lnxconfig.IPConfig) error {
+// if len(os.Args) != 2 {
+// fmt.Printf("Usage: %s <configFile>\n", os.Args[0])
+// os.Exit(1)
+// }
+// fileName := os.Args[1]
+//
+// lnxConfig, err := lnxconfig.ParseConfig(fileName)
+// if err != nil {
+// panic(err)
+// }
+//
+// // make and populate routing table
+// table = make(map[Address]Route)
+// for _, iface := range lnxConfig.Interfaces {
+// var address = Address{iface.AssignedIP, iface.AssignedPrefix}
+// var route = Route{Address{iface.AssignedIP, iface.AssignedPrefix}, 0, iface.AssignedPrefix}
+// table[address] = route
+// }
+//
+//
+//}
+
+func AddRoute(srcAddr Address, cost uint32, addrAsStr string, tableReference *RoutingTable) error {
+ if _, ok := (*tableReference)[srcAddr]; ok {
+ return errors.New("Route already exists")
+ }
+
+ (*tableReference)[srcAddr] = Hop{cost, addrAsStr}
+ return nil
+}
+
+func RemoveRoute(dest Address, table *RoutingTable) error {
+ if _, ok := (*table)[dest]; !ok {
+ return errors.New("Route doesn't exist")
+ }
+
+ delete(*table, dest)
+ return nil
+}
+
+// TODO: implement this with most specific prefix matching
+func Route(dest Address, table *RoutingTable) (Hop, error) {
+ // get the most specific route
+ for address, route := range *table {
+ if address.Prefix.Contains(dest.Addr) {
+ return route, nil
+ }
+ }
+ return Hop{}, errors.New("Route doesn't exist")
+}
+
+func SprintRoutingTable(table *RoutingTable) string {
+ message := ""
+ for address, route := range *table {
+ message += fmt.Sprintf("%s/%d\t%d\n", address.Addr, address.Prefix, route.Cost)
+ }
+
+ return message
+}