diff options
-rw-r--r-- | .idea/.gitignore | 8 | ||||
-rw-r--r-- | .idea/iptcp-jailpt2.iml | 9 | ||||
-rw-r--r-- | .idea/modules.xml | 8 | ||||
-rw-r--r-- | .idea/vcs.xml | 6 | ||||
-rw-r--r-- | cmd/example/main.go | 31 | ||||
-rw-r--r-- | doc-example/binaries.example.json | 17 | ||||
-rw-r--r-- | doc-example/nodes.json | 7 | ||||
-rw-r--r-- | go.mod | 2 | ||||
-rw-r--r-- | pkg/ipstack/ipstack.go | 413 | ||||
-rw-r--r-- | pkg/ipstack/ipstack_test.go | 253 | ||||
-rw-r--r-- | pkg/lnxconfig/lnxconfig.go (renamed from pkg/lnxconfig.go) | 14 | ||||
-rw-r--r-- | pkg/protocol.go | 300 | ||||
-rw-r--r-- | pkg/routingTable.go | 71 | ||||
-rw-r--r-- | pkg/routingtable/routingtable.go | 90 |
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 @@ -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 +} |