diff options
-rw-r--r-- | .vscode/settings.json | 5 | ||||
-rw-r--r-- | cmd/pkg/protocol.go | 66 | ||||
-rw-r--r-- | cmd/vrouter/main.go | 28 | ||||
-rw-r--r-- | go.mod | 4 | ||||
-rw-r--r-- | go.sum | 2 | ||||
-rw-r--r-- | pkg/lnxconfig.go | 365 | ||||
-rw-r--r-- | pkg/protocol.go | 183 | ||||
-rw-r--r-- | pkg/routingTable.go | 71 |
8 files changed, 657 insertions, 67 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0d84b99 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "lnxconfig" + ] +}
\ No newline at end of file diff --git a/cmd/pkg/protocol.go b/cmd/pkg/protocol.go deleted file mode 100644 index 84444c3..0000000 --- a/cmd/pkg/protocol.go +++ /dev/null @@ -1,66 +0,0 @@ -package pkg - -import ( - "net" - // "netip" -) - -const ( - MAX_IP_PACKET_SIZE = 1400 -) - -func Initialize(config IpConfig) (error) { - // ip config from go parser - - // initialize ip table - - // error check - - // different for router and host?? - // host - // create node interfaces? -} - -func ipRecv(data []byte) (error) { - // parse ip header - - // check ip checksum - - // check ip version - - // check ip length - - // check ip ttl - - // check ip protocol - - // check ip destination - - // check ip source - - // check forwarding table -} - -func ipForwarding(dst netip.Addr, protocolNum uint16, data []byte) (error) { - // send test packest to dst - - // lookup forwarding table - - // locally - - // not locally -} - -type HandlerFunc = func(*Packet, []interface{}) (error) { - - // do smth with packet -} - -func RegisterRecvHandler(protocolNum uint8, callbackFunc HandlerFunc) (error) { -} - -func routeRip() (error) { - // communicate with other routers - - // update forwarding table -} diff --git a/cmd/vrouter/main.go b/cmd/vrouter/main.go index 82b8195..158016a 100644 --- a/cmd/vrouter/main.go +++ b/cmd/vrouter/main.go @@ -4,9 +4,37 @@ import ( "bufio" "fmt" "os" + "time" ) +func SendUpdates() { + for _, iface := range myInterfaces { + // send RIP updates to all neighbors + for _, neighbor := range myNeighbors { + iface.udp.Write(neighbor, data) + // wait for response for 12 seconds + response := make([]byte, 512) + iface.udp.Read(response) + time.Sleep(12 * time.Second) + if len(response) == 0 { + RemoveNeighbor(neighbor) + } + } + } + time.Sleep(5 * time.Second) +} + func main() { + if len(os.Args) != 2 { + fmt.Printf("Usage: %s <configFile>\n", os.Args[0]) + os.Exit(1) + } + + fileName := os.Args[1] + + initialize(fileName) + go SendUpdates() + scanner := bufio.NewScanner(os.Stdin) @@ -1,3 +1,5 @@ module golang-sockets -go 1.18
\ No newline at end of file +go 1.20 + +require github.com/pkg/errors v0.9.1 // indirect
\ No newline at end of file @@ -0,0 +1,2 @@ +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/pkg/lnxconfig.go b/pkg/lnxconfig.go new file mode 100644 index 0000000..36b1b56 --- /dev/null +++ b/pkg/lnxconfig.go @@ -0,0 +1,365 @@ +package lnxconfig + +import ( + "bufio" + "fmt" + "net/netip" + "os" + "strings" +) + +type RoutingMode int + +const ( + RoutingTypeNone RoutingMode = 0 + RoutingTypeStatic RoutingMode = 1 + 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 + + OriginatingPrefixes []netip.Prefix // Unused in F23, ignore. + + RoutingMode RoutingMode + + // ROUTERS ONLY: Neighbors to send RIP packets + RipNeighbors []netip.Addr + + // Manually-added routes ("route" directive, usually just for default on hosts) + StaticRoutes map[netip.Prefix]netip.Addr +} + +type InterfaceConfig struct { + Name string + AssignedIP netip.Addr + AssignedPrefix netip.Prefix + + UDPAddr netip.AddrPort +} + +type NeighborConfig struct { + DestAddr netip.Addr + UDPAddr netip.AddrPort + + InterfaceName string +} + +// Static config for testing +var LnxConfig = IPConfig{ + Interfaces: []InterfaceConfig{ + { + Name: "if0", + AssignedIP: netip.MustParseAddr("10.1.0.1"), + AssignedPrefix: netip.MustParsePrefix("10.1.0.1/24"), + UDPAddr: netip.MustParseAddrPort("127.0.0.1:5000"), + }, + { + Name: "if1", + AssignedIP: netip.MustParseAddr("10.10.1.1"), + AssignedPrefix: netip.MustParsePrefix("10.10.1.1/24"), + UDPAddr: netip.MustParseAddrPort("127.0.0.1:5001"), + }, + }, + + Neighbors: []NeighborConfig{ + { + DestAddr: netip.MustParseAddr("10.1.0.10"), + UDPAddr: netip.MustParseAddrPort("127.0.0.1:6001"), + InterfaceName: "if0", + }, + { + DestAddr: netip.MustParseAddr("10.10.1.2"), + UDPAddr: netip.MustParseAddrPort("127.0.0.1:5100"), + InterfaceName: "if1", + }, + }, + + OriginatingPrefixes: []netip.Prefix{ + netip.MustParsePrefix("10.1.0.1/24"), + }, + + RoutingMode: RoutingTypeStatic, + + RipNeighbors: []netip.Addr{ + netip.MustParseAddr("10.10.1.2"), + }, +} + +// ******************** END PUBLIC INTERFACE ********************************************* +// (You shouldn't need to worry about what's below, unless you want to modify the parser.) + +type ParseFunc func(int, string, *IPConfig) error + +var parseCommands = map[string]ParseFunc{ + "interface": parseInterface, + "neighbor": parseNeighbor, + "routing": parseRouting, + "route": parseRoute, + "rip": parseRip, +} + +func parseRip(ln int, line string, config *IPConfig) error { + tokens := strings.Split(line, " ") + + if len(tokens) < 2 { + return newErrString(ln, "Usage: rip [cmd] ...") + } + cmd := tokens[1] + ripTokens := tokens[2:] + + switch cmd { + case "originate": + if len(ripTokens) < 2 && ripTokens[0] != "prefix" { + return newErrString(ln, "Usage: rip originate prefix <prefix>") + } + ripPrefix, err := netip.ParsePrefix(ripTokens[1]) + if err != nil { + return newErr(ln, err) + } + ripPrefix = ripPrefix.Masked() + + // Check if prefix is in config + err = addOriginatingPrefix(config, ripPrefix) + if err != nil { + return err + } + case "advertise-to": + if len(ripTokens) < 1 { + return newErrString(ln, "Usage: rip advertise-to <neighbor IP>") + } + addr, err := netip.ParseAddr(ripTokens[0]) + if err != nil { + return newErr(ln, err) + } + err = addRipNeighbor(config, addr) + if err != nil { + return err + } + default: + return newErrString(ln, "Unrecognized RIP command %s", cmd) + } + + return nil +} + +func addOriginatingPrefix(config *IPConfig, prefix netip.Prefix) error { + for _, iface := range config.Interfaces { + if iface.AssignedPrefix == prefix { + config.OriginatingPrefixes = append(config.OriginatingPrefixes, prefix) + return nil + } + } + + return errors.Errorf("No matching prefix %s in config", prefix.String()) +} + +func addRipNeighbor(config *IPConfig, neighbor netip.Addr) error { + for _, iface := range config.Neighbors { + if iface.DestAddr == neighbor { + config.RipNeighbors = append(config.RipNeighbors, neighbor) + return nil + } + } + + return errors.Errorf("RIP neighbor %s is not a neighbor IP", neighbor.String()) +} + +func parseRouting(ln int, line string, config *IPConfig) error { + tokens := strings.Split(line, " ") + + if len(tokens) < 2 { + return newErrString(ln, "routing directive must have format: routing <type>") + } + rt := tokens[1] + + switch rt { + case "static": + config.RoutingMode = RoutingTypeStatic + case "rip": + config.RoutingMode = RoutingTypeRIP + default: + return newErrString(ln, "Invalid routing type: %s", rt) + } + + return nil +} + +func parseRoute(ln int, line string, config *IPConfig) error { + var sPrefix, sAddr string + + format := "route <prefix> via <addr>" + r := strings.NewReader(line) + n, err := fmt.Fscanf(r, "route %s via %s", &sPrefix, &sAddr) + + if err != nil { + return err + } + + if n != 2 { + return newErrString(ln, "route directive must have format %s", format) + } + + prefix, err := netip.ParsePrefix(sPrefix) + if err != nil { + return err + } + + addr, err := netip.ParseAddr(sAddr) + if err != nil { + return err + } + + config.StaticRoutes[prefix] = addr + return nil +} + +func parseInterface(ln int, line string, config *IPConfig) error { + var sName, sPrefix, sBindAddr string + + format := "interface <name> <prefix> <bindAddr>" + + r := strings.NewReader(line) + n, err := fmt.Fscanf(r, "interface %s %s %s", + &sName, &sPrefix, &sBindAddr) + + if err != nil { + return err + } + + if n != 3 { + return newErrString(ln, "interface directive must have format: %s", format) + } + + // Check prefix format first + prefix, err := netip.ParsePrefix(sPrefix) + if err != nil { + return err + } + + addr := prefix.Addr() // Get addr part + prefix = prefix.Masked() // Clear add bits for prefix + + addrPort, err := netip.ParseAddrPort(sBindAddr) + if err != nil { + return err + } + + iface := InterfaceConfig{ + Name: sName, + AssignedIP: addr, + AssignedPrefix: prefix, + UDPAddr: addrPort, + } + + config.Interfaces = append(config.Interfaces, iface) + return nil +} + +func parseNeighbor(ln int, line string, config *IPConfig) error { + var sDestAddr, sUDPAddr, sIfName string + + format := "neighbor <vip> at <bindAddr> via <interface name>" + + r := strings.NewReader(line) + n, err := fmt.Fscanf(r, "neighbor %s at %s via %s", + &sDestAddr, &sUDPAddr, &sIfName) + + if err != nil { + return err + } + + if n != 3 { + newErrString(ln, "neighbor directive must have format: %s", format) + } + + destAddr, err := netip.ParseAddr(sDestAddr) + if err != nil { + return err + } + + udpAddr, err := netip.ParseAddrPort(sUDPAddr) + if err != nil { + return err + } + + neighbor := NeighborConfig{ + DestAddr: destAddr, + UDPAddr: udpAddr, + InterfaceName: sIfName, + } + + config.Neighbors = append(config.Neighbors, neighbor) + + return nil +} + +func newErrString(line int, msg string, args ...any) error { + _msg := fmt.Sprintf(msg, args...) + return errors.New(fmt.Sprintf("Parse error on line %d: %s", line, _msg)) +} + +func newErr(line int, err error) error { + return errors.New(fmt.Sprintf("Parse error on line %d: %s", line, err.Error())) + +} + +// Parse a configuration file +func ParseConfig(configFile string) (*IPConfig, error) { + fd, err := os.Open(configFile) + if err != nil { + return nil, errors.New("Unable to open file") + } + defer fd.Close() + + config := &IPConfig{ + Interfaces: make([]InterfaceConfig, 0, 1), + Neighbors: make([]NeighborConfig, 0, 1), + OriginatingPrefixes: make([]netip.Prefix, 0, 1), + + RipNeighbors: make([]netip.Addr, 0), + StaticRoutes: make(map[netip.Prefix]netip.Addr, 0), + } + + scanner := bufio.NewScanner(fd) + ln := 0 + for scanner.Scan() { + ln++ + + line := scanner.Text() + tokens := strings.Split(line, " ") + + if len(tokens) == 0 { + continue + } + + // Skip comments + head := tokens[0] + if len(head) == 0 || head == "#" || head[0] == '#' { + continue + } + + pf, found := parseCommands[head] + if !found { + return nil, newErrString(ln, "Unrecognized token %s", head) + } + err = pf(ln, line, config) + if err != nil { + return nil, newErr(ln, err) + } + } + + return config, nil +}
\ No newline at end of file diff --git a/pkg/protocol.go b/pkg/protocol.go new file mode 100644 index 0000000..10cf058 --- /dev/null +++ b/pkg/protocol.go @@ -0,0 +1,183 @@ +package protocol + +import ( + "net" + "net/netip" + "fmt" + "os" + "bufio" + "time" + "github.com/brown-cs1680-f23/iptcp-jailpt2/pkg/lnxconfig" +) + +const ( + MAX_IP_PACKET_SIZE = 1400 +) + +type Interface struct { + Name string + AssignedIP netip.Addr + AssignedPrefix netip.Prefix + + UDPAddr netip.AddrPort + State uint8_t +} + + +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; +} + +myInterfaces := make([]Interface); +myNeighbors := make(map[string]Neighbor) +myRIPNeighbors := make(map[string]Neighbor) +protocolHandlers := make(map[uint16]HandlerFunc) +// 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} + } +} + +func RecvIp(data []byte) (error) { + // deconstruct packet + + // check ip checksum + checksum := netip.Checksum(data) + if checksum != 0 { + return errors.Errorf("Checksum failed: %d", checksum) + } + + // check ttl + ttl := data[8] + if ttl == 0 { + return errors.Errorf("TTL is 0") + } + + destAddr := netip.AddrFrom(data[16:20]) + protocolNum := data[9] + + // decrement ttl and update checksum + data[8] = ttl - 1 + data[10] = 0 + data[11] = 0 + newChecksum := netip.Checksum(data) + data[10] = newChecksum >> 8 + data[11] = newChecksum & 0xff + + SendIp(destAddr, protocolNum, data) +} + +func SendIp(dst netip.Addr, protocolNum uint16, data []byte) (error) { + // check if dst is local + islocal := false + for _, iface := range myNeighbors { + if iface.addr == dst { + islocal = true + break + } + } + + if islocal { + // send to local handler + protocolHandlers[protocolNum](data) + } else { + // send to forwarding table + // lookup forwarding table + // toHop, err := lookupForwardingTable(dst) + // if err != nil { + // routeRip() + // } + + // // send to toHop interface + // for _, iface := range myInterfaces { + // if iface.addr == toHop { + // iface.udp.Write(data) + // break + // } + // } +} + +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 routeRip(data []byte) (error) { + // deconstruct packet + newRIPMessage := RIPMessage{} + newRIPMessage.command = data[0] + newRIPMessage.numEntries = data[1] + newRIPMessage.entries = make([]RIPEntry, newRIPMessage.numEntries) +} + +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 new file mode 100644 index 0000000..bda4524 --- /dev/null +++ b/pkg/routingTable.go @@ -0,0 +1,71 @@ +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 |