diff --git a/api/gosrc/backendlauncher/backendlauncher.go b/api/gosrc/backendlauncher/backendlauncher.go new file mode 100644 index 0000000..3543014 --- /dev/null +++ b/api/gosrc/backendlauncher/backendlauncher.go @@ -0,0 +1,18 @@ +package backendlauncher + +import ( + "fmt" + "math/rand/v2" + "net" +) + +func GetUnixSocket(folder string) (string, net.Listener, error) { + socketPath := fmt.Sprintf("%s/sock-%d.sock", folder, rand.Uint()) + listener, err := net.Listen("unix", socketPath) + + if err != nil { + return "", nil, err + } + + return socketPath, listener, nil +} diff --git a/api/gosrc/backendparser/backendparser.go b/api/gosrc/backendparser/backendparser.go new file mode 100644 index 0000000..31ad2d7 --- /dev/null +++ b/api/gosrc/backendparser/backendparser.go @@ -0,0 +1 @@ +package backendparser diff --git a/api/gosrc/commonbackend/constants.go b/api/gosrc/commonbackend/constants.go new file mode 100644 index 0000000..5b6f29d --- /dev/null +++ b/api/gosrc/commonbackend/constants.go @@ -0,0 +1,83 @@ +package commonbackend + +type StartCommand struct { + Type string // Will be 'start' always + Arguments []byte +} + +type StopCommand struct { + Type string // Will be 'stop' always +} + +type AddConnectionCommand struct { + Type string // Will be 'addConnection' always + SourceIP string + SourcePort uint16 + DestPort uint16 + Protocol string // Will be either 'tcp' or 'udp' +} + +type RemoveConnectionCommand struct { + Type string // Will be 'removeConnection' always + SourceIP string + SourcePort uint16 + DestPort uint16 + Protocol string // Will be either 'tcp' or 'udp' +} + +type ClientConnection struct { + SourceIP string + SourcePort uint16 + DestPort uint16 + ClientIP string + ClientPort uint16 +} + +type GetAllConnections struct { + Type string // Will be 'getAllConnections' always + Connections []*ClientConnection // List of connections +} + +type CheckClientParameters struct { + Type string // Will be 'checkClientParameters' always + SourceIP string + SourcePort uint16 + DestPort uint16 + Protocol string // Will be either 'tcp' or 'udp' +} + +type CheckServerParameters struct { + Type string // Will be 'checkServerParameters' always + Arguments []byte +} + +// Sent as a response to either CheckClientParameters or CheckBackendParameters +type CheckParametersResponse struct { + Type string // Will be 'checkParametersResponse' always + InReplyTo string // Will be either 'checkClientParameters' or 'checkServerParameters' + IsValid bool // If true, valid, and if false, invalid + Message string // String message from the client (ex. failed to unmarshal JSON: x is not defined) +} + +const ( + StartCommandID = iota + StopCommandID + AddConnectionCommandID + RemoveConnectionCommandID + ClientConnectionID + GetAllConnectionsID + CheckClientParametersID + CheckServerParametersID + CheckParametersResponseID +) + +const ( + TCP = iota + UDP + + IPv4 = 4 + IPv6 = 6 + + IPv4Size = 4 + IPv6Size = 16 +) diff --git a/api/gosrc/commonbackend/marshal.go b/api/gosrc/commonbackend/marshal.go new file mode 100644 index 0000000..fa215e3 --- /dev/null +++ b/api/gosrc/commonbackend/marshal.go @@ -0,0 +1,279 @@ +package commonbackend + +import ( + "encoding/binary" + "fmt" + "net" +) + +func marshalIndividualConnectionStruct(conn *ClientConnection) []byte { + sourceIPOriginal := net.ParseIP(conn.SourceIP) + clientIPOriginal := net.ParseIP(conn.ClientIP) + + var serverIPVer uint8 + var sourceIP []byte + + if sourceIPOriginal.To4() == nil { + serverIPVer = IPv6 + sourceIP = sourceIPOriginal.To16() + } else { + serverIPVer = IPv4 + sourceIP = sourceIPOriginal.To4() + } + + var clientIPVer uint8 + var clientIP []byte + + if clientIPOriginal.To4() == nil { + clientIPVer = IPv6 + clientIP = clientIPOriginal.To16() + } else { + clientIPVer = IPv4 + clientIP = clientIPOriginal.To4() + } + + connectionBlock := make([]byte, 8+len(sourceIP)+len(clientIP)) + + connectionBlock[0] = serverIPVer + copy(connectionBlock[1:len(sourceIP)+1], sourceIP) + binary.BigEndian.PutUint16(connectionBlock[1+len(sourceIP):3+len(sourceIP)], conn.SourcePort) + binary.BigEndian.PutUint16(connectionBlock[3+len(sourceIP):5+len(sourceIP)], conn.DestPort) + + connectionBlock[5+len(sourceIP)] = clientIPVer + copy(connectionBlock[6+len(sourceIP):6+len(sourceIP)+len(clientIP)], clientIP) + binary.BigEndian.PutUint16(connectionBlock[6+len(sourceIP)+len(clientIP):8+len(sourceIP)+len(clientIP)], conn.ClientPort) + + return connectionBlock +} + +func Marshal(commandType string, command interface{}) ([]byte, error) { + switch commandType { + case "start": + startCommand, ok := command.(*StartCommand) + + if !ok { + return nil, fmt.Errorf("failed to typecast") + } + + startCommandBytes := make([]byte, 1+2+len(startCommand.Arguments)) + startCommandBytes[0] = StartCommandID + binary.BigEndian.PutUint16(startCommandBytes[1:3], uint16(len(startCommand.Arguments))) + copy(startCommandBytes[3:], startCommand.Arguments) + + return startCommandBytes, nil + case "stop": + _, ok := command.(*StopCommand) + + if !ok { + return nil, fmt.Errorf("failed to typecast") + } + + stopCommandBytes := make([]byte, 1) + stopCommandBytes[0] = StopCommandID + + return stopCommandBytes, nil + case "addConnection": + addConnectionCommand, ok := command.(*AddConnectionCommand) + + if !ok { + return nil, fmt.Errorf("failed to typecast") + } + + sourceIP := net.ParseIP(addConnectionCommand.SourceIP) + + var ipVer uint8 + var ipBytes []byte + + if sourceIP.To4() == nil { + ipBytes = sourceIP.To16() + ipVer = IPv6 + } else { + ipBytes = sourceIP.To4() + ipVer = IPv4 + } + + addConnectionBytes := make([]byte, 1+1+len(ipBytes)+2+2+1) + + addConnectionBytes[0] = AddConnectionCommandID + addConnectionBytes[1] = ipVer + + copy(addConnectionBytes[2:2+len(ipBytes)], ipBytes) + + binary.BigEndian.PutUint16(addConnectionBytes[2+len(ipBytes):4+len(ipBytes)], addConnectionCommand.SourcePort) + binary.BigEndian.PutUint16(addConnectionBytes[4+len(ipBytes):6+len(ipBytes)], addConnectionCommand.DestPort) + + var protocol uint8 + + if addConnectionCommand.Protocol == "tcp" { + protocol = TCP + } else if addConnectionCommand.Protocol == "udp" { + protocol = UDP + } else { + return nil, fmt.Errorf("invalid protocol") + } + + addConnectionBytes[6+len(ipBytes)] = protocol + + return addConnectionBytes, nil + case "removeConnection": + removeConnectionCommand, ok := command.(*RemoveConnectionCommand) + + if !ok { + return nil, fmt.Errorf("failed to typecast") + } + + sourceIP := net.ParseIP(removeConnectionCommand.SourceIP) + + var ipVer uint8 + var ipBytes []byte + + if sourceIP.To4() == nil { + ipBytes = sourceIP.To16() + ipVer = IPv6 + } else { + ipBytes = sourceIP.To4() + ipVer = IPv4 + } + + removeConnectionBytes := make([]byte, 1+1+len(ipBytes)+2+2+1) + + removeConnectionBytes[0] = RemoveConnectionCommandID + removeConnectionBytes[1] = ipVer + copy(removeConnectionBytes[2:2+len(ipBytes)], ipBytes) + binary.BigEndian.PutUint16(removeConnectionBytes[2+len(ipBytes):4+len(ipBytes)], removeConnectionCommand.SourcePort) + binary.BigEndian.PutUint16(removeConnectionBytes[4+len(ipBytes):6+len(ipBytes)], removeConnectionCommand.DestPort) + + var protocol uint8 + + if removeConnectionCommand.Protocol == "tcp" { + protocol = TCP + } else if removeConnectionCommand.Protocol == "udp" { + protocol = UDP + } else { + return nil, fmt.Errorf("invalid protocol") + } + + removeConnectionBytes[6+len(ipBytes)] = protocol + + return removeConnectionBytes, nil + case "getAllConnections": + allConnectionsCommand, ok := command.(*GetAllConnections) + + if !ok { + return nil, fmt.Errorf("failed to typecast") + } + + connectionsArray := make([][]byte, len(allConnectionsCommand.Connections)) + totalSize := 0 + + for connIndex, conn := range allConnectionsCommand.Connections { + connectionsArray[connIndex] = marshalIndividualConnectionStruct(conn) + totalSize += len(connectionsArray[connIndex]) + 1 + } + + connectionCommandArray := make([]byte, totalSize+1) + connectionCommandArray[0] = GetAllConnectionsID + + currentPosition := 1 + + for _, connection := range connectionsArray { + copy(connectionCommandArray[currentPosition:currentPosition+len(connection)], connection) + connectionCommandArray[currentPosition+len(connection)] = '\r' + currentPosition += len(connection) + 1 + } + + connectionCommandArray[totalSize] = '\n' + return connectionCommandArray, nil + case "checkClientParameters": + checkClientCommand, ok := command.(*CheckClientParameters) + + if !ok { + return nil, fmt.Errorf("failed to typecast") + } + + sourceIP := net.ParseIP(checkClientCommand.SourceIP) + + var ipVer uint8 + var ipBytes []byte + + if sourceIP.To4() == nil { + ipBytes = sourceIP.To16() + ipVer = IPv6 + } else { + ipBytes = sourceIP.To4() + ipVer = IPv4 + } + + checkClientBytes := make([]byte, 1+1+len(ipBytes)+2+2+1) + + checkClientBytes[0] = CheckClientParametersID + checkClientBytes[1] = ipVer + copy(checkClientBytes[2:2+len(ipBytes)], ipBytes) + binary.BigEndian.PutUint16(checkClientBytes[2+len(ipBytes):4+len(ipBytes)], checkClientCommand.SourcePort) + binary.BigEndian.PutUint16(checkClientBytes[4+len(ipBytes):6+len(ipBytes)], checkClientCommand.DestPort) + + var protocol uint8 + + if checkClientCommand.Protocol == "tcp" { + protocol = TCP + } else if checkClientCommand.Protocol == "udp" { + protocol = UDP + } else { + return nil, fmt.Errorf("invalid protocol") + } + + checkClientBytes[6+len(ipBytes)] = protocol + + return checkClientBytes, nil + case "checkServerParameters": + checkServerCommand, ok := command.(*CheckServerParameters) + + if !ok { + return nil, fmt.Errorf("failed to typecast") + } + + serverCommandBytes := make([]byte, 1+2+len(checkServerCommand.Arguments)) + serverCommandBytes[0] = CheckServerParametersID + binary.BigEndian.PutUint16(serverCommandBytes[1:3], uint16(len(checkServerCommand.Arguments))) + copy(serverCommandBytes[3:], checkServerCommand.Arguments) + + return serverCommandBytes, nil + case "checkParametersResponse": + checkParametersCommand, ok := command.(*CheckParametersResponse) + + if !ok { + return nil, fmt.Errorf("failed to typecast") + } + + var checkMethod uint8 + + if checkParametersCommand.InReplyTo == "checkClientParameters" { + checkMethod = CheckClientParametersID + } else if checkParametersCommand.InReplyTo == "checkServerParameters" { + checkMethod = CheckServerParametersID + } else { + return nil, fmt.Errorf("invalid mode recieved (must be either checkClientParameters or checkServerParameters)") + } + + var isValid uint8 + + if checkParametersCommand.IsValid { + isValid = 1 + } + + checkResponseBytes := make([]byte, 3+2+len(checkParametersCommand.Message)) + checkResponseBytes[0] = CheckParametersResponseID + checkResponseBytes[1] = checkMethod + checkResponseBytes[2] = isValid + + binary.BigEndian.PutUint16(checkResponseBytes[3:5], uint16(len(checkParametersCommand.Message))) + + if len(checkParametersCommand.Message) != 0 { + copy(checkResponseBytes[5:], []byte(checkParametersCommand.Message)) + } + + return checkResponseBytes, nil + } + + return nil, fmt.Errorf("couldn't match command") +} diff --git a/api/gosrc/commonbackend/marshalling_test.go b/api/gosrc/commonbackend/marshalling_test.go new file mode 100644 index 0000000..edf8c8f --- /dev/null +++ b/api/gosrc/commonbackend/marshalling_test.go @@ -0,0 +1,474 @@ +package commonbackend + +import ( + "bytes" + "log" + "os" + "testing" +) + +var logLevel = os.Getenv("NEXTNET_LOG_LEVEL") + +func TestStartCommandMarshalSupport(t *testing.T) { + commandInput := &StartCommand{ + Type: "start", + Arguments: []byte("Hello from automated testing"), + } + + commandMarshalled, err := Marshal(commandInput.Type, commandInput) + + if logLevel == "debug" { + log.Printf("Generated array contents: %v", commandMarshalled) + } + + if err != nil { + t.Fatalf(err.Error()) + } + + buf := bytes.NewBuffer(commandMarshalled) + commandType, commandUnmarshalledRaw, err := Unmarshal(buf) + + if err != nil { + t.Fatal(err.Error()) + } + + if commandType != commandInput.Type { + t.Fail() + log.Print("command type does not match up!") + } + + commandUnmarshalled, ok := commandUnmarshalledRaw.(*StartCommand) + + if !ok { + t.Fatal("failed typecast") + } + + if commandInput.Type != commandUnmarshalled.Type { + t.Fail() + log.Printf("Types are not equal (orig: %s, unmsh: %s)", commandInput.Type, commandUnmarshalled.Type) + } + + if !bytes.Equal(commandInput.Arguments, commandUnmarshalled.Arguments) { + log.Fatalf("Arguments are not equal (orig: '%s', unmsh: '%s')", string(commandInput.Arguments), string(commandUnmarshalled.Arguments)) + } +} + +func TestStopCommandMarshalSupport(t *testing.T) { + commandInput := &StopCommand{ + Type: "stop", + } + + commandMarshalled, err := Marshal(commandInput.Type, commandInput) + + if logLevel == "debug" { + log.Printf("Generated array contents: %v", commandMarshalled) + } + + if err != nil { + t.Fatalf(err.Error()) + } + + buf := bytes.NewBuffer(commandMarshalled) + commandType, commandUnmarshalledRaw, err := Unmarshal(buf) + + if err != nil { + t.Fatal(err.Error()) + } + + if commandType != commandInput.Type { + t.Fail() + log.Print("command type does not match up!") + } + + commandUnmarshalled, ok := commandUnmarshalledRaw.(*StopCommand) + + if !ok { + t.Fatal("failed typecast") + } + + if commandInput.Type != commandUnmarshalled.Type { + t.Fail() + log.Printf("Types are not equal (orig: %s, unmsh: %s)", commandInput.Type, commandUnmarshalled.Type) + } +} + +func TestAddConnectionCommandMarshalSupport(t *testing.T) { + commandInput := &AddConnectionCommand{ + Type: "addConnection", + SourceIP: "192.168.0.139", + SourcePort: 19132, + DestPort: 19132, + Protocol: "tcp", + } + + commandMarshalled, err := Marshal(commandInput.Type, commandInput) + + if logLevel == "debug" { + log.Printf("Generated array contents: %v", commandMarshalled) + } + + if err != nil { + t.Fatalf(err.Error()) + } + + buf := bytes.NewBuffer(commandMarshalled) + commandType, commandUnmarshalledRaw, err := Unmarshal(buf) + + if err != nil { + t.Fatal(err.Error()) + } + + if commandType != commandInput.Type { + t.Fail() + log.Print("command type does not match up!") + } + + commandUnmarshalled, ok := commandUnmarshalledRaw.(*AddConnectionCommand) + + if !ok { + t.Fatal("failed typecast") + } + + if commandInput.Type != commandUnmarshalled.Type { + t.Fail() + log.Printf("Types are not equal (orig: %s, unmsh: %s)", commandInput.Type, commandUnmarshalled.Type) + } + + if commandInput.SourceIP != commandUnmarshalled.SourceIP { + t.Fail() + log.Printf("SourceIP's are not equal (orig: %s, unmsh: %s)", commandInput.SourceIP, commandUnmarshalled.SourceIP) + } + + if commandInput.SourcePort != commandUnmarshalled.SourcePort { + t.Fail() + log.Printf("SourcePort's are not equal (orig: %d, unmsh: %d)", commandInput.SourcePort, commandUnmarshalled.SourcePort) + } + + if commandInput.DestPort != commandUnmarshalled.DestPort { + t.Fail() + log.Printf("DestPort's are not equal (orig: %d, unmsh: %d)", commandInput.DestPort, commandUnmarshalled.DestPort) + } + + if commandInput.Protocol != commandUnmarshalled.Protocol { + t.Fail() + log.Printf("Protocols are not equal (orig: %s, unmsh: %s)", commandInput.Protocol, commandUnmarshalled.Protocol) + } +} + +func TestRemoveConnectionCommandMarshalSupport(t *testing.T) { + commandInput := &RemoveConnectionCommand{ + Type: "removeConnection", + SourceIP: "192.168.0.139", + SourcePort: 19132, + DestPort: 19132, + Protocol: "tcp", + } + + commandMarshalled, err := Marshal(commandInput.Type, commandInput) + + if err != nil { + t.Fatalf(err.Error()) + } + + if logLevel == "debug" { + log.Printf("Generated array contents: %v", commandMarshalled) + } + + buf := bytes.NewBuffer(commandMarshalled) + commandType, commandUnmarshalledRaw, err := Unmarshal(buf) + + if err != nil { + t.Fatal(err.Error()) + } + + if commandType != commandInput.Type { + t.Fail() + log.Print("command type does not match up!") + } + + commandUnmarshalled, ok := commandUnmarshalledRaw.(*RemoveConnectionCommand) + + if !ok { + t.Fatal("failed typecast") + } + + if commandInput.Type != commandUnmarshalled.Type { + t.Fail() + log.Printf("Types are not equal (orig: %s, unmsh: %s)", commandInput.Type, commandUnmarshalled.Type) + } + + if commandInput.SourceIP != commandUnmarshalled.SourceIP { + t.Fail() + log.Printf("SourceIP's are not equal (orig: %s, unmsh: %s)", commandInput.SourceIP, commandUnmarshalled.SourceIP) + } + + if commandInput.SourcePort != commandUnmarshalled.SourcePort { + t.Fail() + log.Printf("SourcePort's are not equal (orig: %d, unmsh: %d)", commandInput.SourcePort, commandUnmarshalled.SourcePort) + } + + if commandInput.DestPort != commandUnmarshalled.DestPort { + t.Fail() + log.Printf("DestPort's are not equal (orig: %d, unmsh: %d)", commandInput.DestPort, commandUnmarshalled.DestPort) + } + + if commandInput.Protocol != commandUnmarshalled.Protocol { + t.Fail() + log.Printf("Protocols are not equal (orig: %s, unmsh: %s)", commandInput.Protocol, commandUnmarshalled.Protocol) + } +} + +func TestGetAllConnectionsCommandMarshalSupport(t *testing.T) { + commandInput := &GetAllConnections{ + Type: "getAllConnections", + Connections: []*ClientConnection{ + { + SourceIP: "127.0.0.1", + SourcePort: 19132, + DestPort: 19132, + ClientIP: "127.0.0.1", + ClientPort: 12321, + }, + { + SourceIP: "127.0.0.1", + SourcePort: 19132, + DestPort: 19132, + ClientIP: "192.168.0.168", + ClientPort: 23457, + }, + { + SourceIP: "127.0.0.1", + SourcePort: 19132, + DestPort: 19132, + ClientIP: "68.42.203.47", + ClientPort: 38721, + }, + }, + } + + commandMarshalled, err := Marshal(commandInput.Type, commandInput) + + if err != nil { + t.Fatalf(err.Error()) + } + + if logLevel == "debug" { + log.Printf("Generated array contents: %v", commandMarshalled) + } + + buf := bytes.NewBuffer(commandMarshalled) + commandType, commandUnmarshalledRaw, err := Unmarshal(buf) + + if err != nil { + t.Fatal(err.Error()) + } + + if commandType != commandInput.Type { + t.Fail() + log.Print("command type does not match up!") + } + + commandUnmarshalled, ok := commandUnmarshalledRaw.(*GetAllConnections) + + if !ok { + t.Fatal("failed typecast") + } + + if commandInput.Type != commandUnmarshalled.Type { + t.Fail() + log.Printf("Types are not equal (orig: %s, unmsh: %s)", commandInput.Type, commandUnmarshalled.Type) + } + + for commandIndex, originalConnection := range commandInput.Connections { + remoteConnection := commandUnmarshalled.Connections[commandIndex] + + if originalConnection.SourceIP != remoteConnection.SourceIP { + t.Fail() + log.Printf("(in #%d) SourceIP's are not equal (orig: %s, unmsh: %s)", commandIndex, originalConnection.SourceIP, remoteConnection.SourceIP) + } + + if originalConnection.SourcePort != remoteConnection.SourcePort { + t.Fail() + log.Printf("(in #%d) SourcePort's are not equal (orig: %d, unmsh: %d)", commandIndex, originalConnection.SourcePort, remoteConnection.SourcePort) + } + + if originalConnection.SourcePort != remoteConnection.SourcePort { + t.Fail() + log.Printf("(in #%d) DestPort's are not equal (orig: %d, unmsh: %d)", commandIndex, originalConnection.DestPort, remoteConnection.DestPort) + } + + if originalConnection.SourcePort != remoteConnection.SourcePort { + t.Fail() + log.Printf("(in #%d) ClientIP's are not equal (orig: %s, unmsh: %s)", commandIndex, originalConnection.ClientIP, remoteConnection.ClientIP) + } + + if originalConnection.SourcePort != remoteConnection.SourcePort { + t.Fail() + log.Printf("(in #%d) ClientPort's are not equal (orig: %d, unmsh: %d)", commandIndex, originalConnection.ClientPort, remoteConnection.ClientPort) + } + } +} + +func TestCheckClientParametersMarshalSupport(t *testing.T) { + commandInput := &CheckClientParameters{ + Type: "checkClientParameters", + SourceIP: "192.168.0.139", + SourcePort: 19132, + DestPort: 19132, + Protocol: "tcp", + } + + commandMarshalled, err := Marshal(commandInput.Type, commandInput) + + if err != nil { + t.Fatalf(err.Error()) + } + + if logLevel == "debug" { + log.Printf("Generated array contents: %v", commandMarshalled) + } + + buf := bytes.NewBuffer(commandMarshalled) + commandType, commandUnmarshalledRaw, err := Unmarshal(buf) + + if err != nil { + t.Fatal(err.Error()) + } + + if commandType != commandInput.Type { + t.Fail() + log.Printf("command type does not match up! (orig: %s, unmsh: %s)", commandType, commandInput.Type) + } + + commandUnmarshalled, ok := commandUnmarshalledRaw.(*CheckClientParameters) + + if !ok { + t.Fatal("failed typecast") + } + + if commandInput.Type != commandUnmarshalled.Type { + t.Fail() + log.Printf("Types are not equal (orig: %s, unmsh: %s)", commandInput.Type, commandUnmarshalled.Type) + } + + if commandInput.SourceIP != commandUnmarshalled.SourceIP { + t.Fail() + log.Printf("SourceIP's are not equal (orig: %s, unmsh: %s)", commandInput.SourceIP, commandUnmarshalled.SourceIP) + } + + if commandInput.SourcePort != commandUnmarshalled.SourcePort { + t.Fail() + log.Printf("SourcePort's are not equal (orig: %d, unmsh: %d)", commandInput.SourcePort, commandUnmarshalled.SourcePort) + } + + if commandInput.DestPort != commandUnmarshalled.DestPort { + t.Fail() + log.Printf("DestPort's are not equal (orig: %d, unmsh: %d)", commandInput.DestPort, commandUnmarshalled.DestPort) + } + + if commandInput.Protocol != commandUnmarshalled.Protocol { + t.Fail() + log.Printf("Protocols are not equal (orig: %s, unmsh: %s)", commandInput.Protocol, commandUnmarshalled.Protocol) + } +} + +func TestCheckServerParametersMarshalSupport(t *testing.T) { + commandInput := &CheckServerParameters{ + Type: "checkServerParameters", + Arguments: []byte("Hello from automated testing"), + } + + commandMarshalled, err := Marshal(commandInput.Type, commandInput) + + if logLevel == "debug" { + log.Printf("Generated array contents: %v", commandMarshalled) + } + + if err != nil { + t.Fatalf(err.Error()) + } + + buf := bytes.NewBuffer(commandMarshalled) + commandType, commandUnmarshalledRaw, err := Unmarshal(buf) + + if err != nil { + t.Fatal(err.Error()) + } + + if commandType != commandInput.Type { + t.Fail() + log.Print("command type does not match up!") + } + + commandUnmarshalled, ok := commandUnmarshalledRaw.(*CheckServerParameters) + + if !ok { + t.Fatal("failed typecast") + } + + if commandInput.Type != commandUnmarshalled.Type { + t.Fail() + log.Printf("Types are not equal (orig: %s, unmsh: %s)", commandInput.Type, commandUnmarshalled.Type) + } + + if !bytes.Equal(commandInput.Arguments, commandUnmarshalled.Arguments) { + log.Fatalf("Arguments are not equal (orig: '%s', unmsh: '%s')", string(commandInput.Arguments), string(commandUnmarshalled.Arguments)) + } +} + +func TestCheckParametersResponseMarshalSupport(t *testing.T) { + commandInput := &CheckParametersResponse{ + Type: "checkParametersResponse", + InReplyTo: "checkClientParameters", + IsValid: true, + Message: "Hello from automated testing", + } + + commandMarshalled, err := Marshal(commandInput.Type, commandInput) + + if err != nil { + t.Fatalf(err.Error()) + } + + if logLevel == "debug" { + log.Printf("Generated array contents: %v", commandMarshalled) + } + + buf := bytes.NewBuffer(commandMarshalled) + commandType, commandUnmarshalledRaw, err := Unmarshal(buf) + + if err != nil { + t.Fatal(err.Error()) + } + + if commandType != commandInput.Type { + t.Fail() + log.Printf("command type does not match up! (orig: %s, unmsh: %s)", commandType, commandInput.Type) + } + + commandUnmarshalled, ok := commandUnmarshalledRaw.(*CheckParametersResponse) + + if !ok { + t.Fatal("failed typecast") + } + + if commandInput.Type != commandUnmarshalled.Type { + t.Fail() + log.Printf("Types are not equal (orig: %s, unmsh: %s)", commandInput.Type, commandUnmarshalled.Type) + } + + if commandInput.InReplyTo != commandUnmarshalled.InReplyTo { + t.Fail() + log.Printf("InReplyTo's are not equal (orig: %s, unmsh: %s)", commandInput.InReplyTo, commandUnmarshalled.InReplyTo) + } + + if commandInput.IsValid != commandUnmarshalled.IsValid { + t.Fail() + log.Printf("IsValid's are not equal (orig: %t, unmsh: %t)", commandInput.IsValid, commandUnmarshalled.IsValid) + } + + if commandInput.Message != commandUnmarshalled.Message { + t.Fail() + log.Printf("Messages are not equal (orig: %s, unmsh: %s)", commandInput.Message, commandUnmarshalled.Message) + } +} diff --git a/api/gosrc/commonbackend/unmarshal.go b/api/gosrc/commonbackend/unmarshal.go new file mode 100644 index 0000000..c14c762 --- /dev/null +++ b/api/gosrc/commonbackend/unmarshal.go @@ -0,0 +1,387 @@ +package commonbackend + +import ( + "encoding/binary" + "fmt" + "io" + "net" +) + +func unmarshalIndividualConnectionStruct(conn io.Reader) (*ClientConnection, error) { + serverIPVersion := make([]byte, 1) + + if _, err := conn.Read(serverIPVersion); err != nil { + return nil, fmt.Errorf("couldn't read server IP version") + } + + var serverIPSize uint8 + + if serverIPVersion[0] == 4 { + serverIPSize = IPv4Size + } else if serverIPVersion[0] == 6 { + serverIPSize = IPv6Size + } else { + return nil, fmt.Errorf("invalid server IP version recieved") + } + + serverIP := make(net.IP, serverIPSize) + + if _, err := conn.Read(serverIP); err != nil { + return nil, fmt.Errorf("couldn't read server IP") + } + + sourcePort := make([]byte, 2) + + if _, err := conn.Read(sourcePort); err != nil { + return nil, fmt.Errorf("couldn't read source port") + } + + destinationPort := make([]byte, 2) + + if _, err := conn.Read(destinationPort); err != nil { + return nil, fmt.Errorf("couldn't read source port") + } + + clientIPVersion := make([]byte, 1) + + if _, err := conn.Read(clientIPVersion); err != nil { + return nil, fmt.Errorf("couldn't read server IP version") + } + + var clientIPSize uint8 + + if clientIPVersion[0] == 4 { + clientIPSize = IPv4Size + } else if clientIPVersion[0] == 6 { + clientIPSize = IPv6Size + } else { + return nil, fmt.Errorf("invalid server IP version recieved") + } + + clientIP := make(net.IP, clientIPSize) + + if _, err := conn.Read(clientIP); err != nil { + return nil, fmt.Errorf("couldn't read server IP") + } + + clientPort := make([]byte, 2) + + if _, err := conn.Read(clientPort); err != nil { + return nil, fmt.Errorf("couldn't read source port") + } + + return &ClientConnection{ + SourceIP: serverIP.String(), + SourcePort: binary.BigEndian.Uint16(sourcePort), + DestPort: binary.BigEndian.Uint16(destinationPort), + ClientIP: clientIP.String(), + ClientPort: binary.BigEndian.Uint16(clientPort), + }, nil +} + +func Unmarshal(conn io.Reader) (string, interface{}, error) { + commandType := make([]byte, 1) + + if _, err := conn.Read(commandType); err != nil { + return "", nil, fmt.Errorf("couldn't read command") + } + + switch commandType[0] { + case StartCommandID: + argumentsLength := make([]byte, 2) + + if _, err := conn.Read(argumentsLength); err != nil { + return "", nil, fmt.Errorf("couldn't read argument length") + } + + arguments := make([]byte, binary.BigEndian.Uint16(argumentsLength)) + + if _, err := conn.Read(arguments); err != nil { + return "", nil, fmt.Errorf("couldn't read arguments") + } + + return "start", &StartCommand{ + Type: "start", + Arguments: arguments, + }, nil + case StopCommandID: + return "stop", &StopCommand{ + Type: "stop", + }, nil + case AddConnectionCommandID: + ipVersion := make([]byte, 1) + + if _, err := conn.Read(ipVersion); err != nil { + return "", nil, fmt.Errorf("couldn't read ip version") + } + + var ipSize uint8 + + if ipVersion[0] == 4 { + ipSize = IPv4Size + } else if ipVersion[0] == 6 { + ipSize = IPv6Size + } else { + return "", nil, fmt.Errorf("invalid IP version recieved") + } + + ip := make(net.IP, ipSize) + + if _, err := conn.Read(ip); err != nil { + return "", nil, fmt.Errorf("couldn't read source IP") + } + + sourcePort := make([]byte, 2) + + if _, err := conn.Read(sourcePort); err != nil { + return "", nil, fmt.Errorf("couldn't read source port") + } + + destPort := make([]byte, 2) + + if _, err := conn.Read(destPort); err != nil { + return "", nil, fmt.Errorf("couldn't read destination port") + } + + protocolBytes := make([]byte, 1) + + if _, err := conn.Read(protocolBytes); err != nil { + return "", nil, fmt.Errorf("couldn't read protocol") + } + + var protocol string + + if protocolBytes[0] == TCP { + protocol = "tcp" + } else if protocolBytes[1] == UDP { + protocol = "udp" + } else { + return "", nil, fmt.Errorf("invalid protocol") + } + + return "addConnection", &AddConnectionCommand{ + Type: "addConnection", + SourceIP: ip.String(), + SourcePort: binary.BigEndian.Uint16(sourcePort), + DestPort: binary.BigEndian.Uint16(destPort), + Protocol: protocol, + }, nil + case RemoveConnectionCommandID: + ipVersion := make([]byte, 1) + + if _, err := conn.Read(ipVersion); err != nil { + return "", nil, fmt.Errorf("couldn't read ip version") + } + + var ipSize uint8 + + if ipVersion[0] == 4 { + ipSize = IPv4Size + } else if ipVersion[0] == 6 { + ipSize = IPv6Size + } else { + return "", nil, fmt.Errorf("invalid IP version recieved") + } + + ip := make(net.IP, ipSize) + + if _, err := conn.Read(ip); err != nil { + return "", nil, fmt.Errorf("couldn't read source IP") + } + + sourcePort := make([]byte, 2) + + if _, err := conn.Read(sourcePort); err != nil { + return "", nil, fmt.Errorf("couldn't read source port") + } + + destPort := make([]byte, 2) + + if _, err := conn.Read(destPort); err != nil { + return "", nil, fmt.Errorf("couldn't read destination port") + } + + protocolBytes := make([]byte, 1) + + if _, err := conn.Read(protocolBytes); err != nil { + return "", nil, fmt.Errorf("couldn't read protocol") + } + + var protocol string + + if protocolBytes[0] == TCP { + protocol = "tcp" + } else if protocolBytes[1] == UDP { + protocol = "udp" + } else { + return "", nil, fmt.Errorf("invalid protocol") + } + + return "removeConnection", &RemoveConnectionCommand{ + Type: "removeConnection", + SourceIP: ip.String(), + SourcePort: binary.BigEndian.Uint16(sourcePort), + DestPort: binary.BigEndian.Uint16(destPort), + Protocol: protocol, + }, nil + case GetAllConnectionsID: + connections := []*ClientConnection{} + delimiter := make([]byte, 1) + var errorReturn error + + // Infinite loop because we don't know the length + for { + connection, err := unmarshalIndividualConnectionStruct(conn) + + if err != nil { + return "", nil, err + } + + connections = append(connections, connection) + + if _, err := conn.Read(delimiter); err != nil { + return "", nil, fmt.Errorf("couldn't read delimiter") + } + + if delimiter[0] == '\r' { + continue + } else if delimiter[0] == '\n' { + break + } else { + // WTF? This shouldn't happen. Break out and return, but give an error + errorReturn = fmt.Errorf("invalid delimiter recieved while processing stream") + break + } + } + + return "getAllConnections", &GetAllConnections{ + Type: "getAllConnections", + Connections: connections, + }, errorReturn + case CheckClientParametersID: + ipVersion := make([]byte, 1) + + if _, err := conn.Read(ipVersion); err != nil { + return "", nil, fmt.Errorf("couldn't read ip version") + } + + var ipSize uint8 + + if ipVersion[0] == 4 { + ipSize = IPv4Size + } else if ipVersion[0] == 6 { + ipSize = IPv6Size + } else { + return "", nil, fmt.Errorf("invalid IP version recieved") + } + + ip := make(net.IP, ipSize) + + if _, err := conn.Read(ip); err != nil { + return "", nil, fmt.Errorf("couldn't read source IP") + } + + sourcePort := make([]byte, 2) + + if _, err := conn.Read(sourcePort); err != nil { + return "", nil, fmt.Errorf("couldn't read source port") + } + + destPort := make([]byte, 2) + + if _, err := conn.Read(destPort); err != nil { + return "", nil, fmt.Errorf("couldn't read destination port") + } + + protocolBytes := make([]byte, 1) + + if _, err := conn.Read(protocolBytes); err != nil { + return "", nil, fmt.Errorf("couldn't read protocol") + } + + var protocol string + + if protocolBytes[0] == TCP { + protocol = "tcp" + } else if protocolBytes[1] == UDP { + protocol = "udp" + } else { + return "", nil, fmt.Errorf("invalid protocol") + } + + return "checkClientParameters", &CheckClientParameters{ + Type: "checkClientParameters", + SourceIP: ip.String(), + SourcePort: binary.BigEndian.Uint16(sourcePort), + DestPort: binary.BigEndian.Uint16(destPort), + Protocol: protocol, + }, nil + case CheckServerParametersID: + argumentsLength := make([]byte, 2) + + if _, err := conn.Read(argumentsLength); err != nil { + return "", nil, fmt.Errorf("couldn't read argument length") + } + + arguments := make([]byte, binary.BigEndian.Uint16(argumentsLength)) + + if _, err := conn.Read(arguments); err != nil { + return "", nil, fmt.Errorf("couldn't read arguments") + } + + return "checkServerParameters", &CheckServerParameters{ + Type: "checkServerParameters", + Arguments: arguments, + }, nil + case CheckParametersResponseID: + checkMethodByte := make([]byte, 1) + + if _, err := conn.Read(checkMethodByte); err != nil { + return "", nil, fmt.Errorf("couldn't read check method byte") + } + + var checkMethod string + + if checkMethodByte[0] == CheckClientParametersID { + checkMethod = "checkClientParameters" + } else if checkMethodByte[1] == CheckServerParametersID { + checkMethod = "checkServerParameters" + } else { + return "", nil, fmt.Errorf("invalid check method recieved") + } + + isValid := make([]byte, 1) + + if _, err := conn.Read(isValid); err != nil { + return "", nil, fmt.Errorf("couldn't read isValid byte") + } + + messageLengthBytes := make([]byte, 2) + + if _, err := conn.Read(messageLengthBytes); err != nil { + return "", nil, fmt.Errorf("couldn't read message length") + } + + messageLength := binary.BigEndian.Uint16(messageLengthBytes) + var message string + + if messageLength != 0 { + messageBytes := make([]byte, messageLength) + + if _, err := conn.Read(messageBytes); err != nil { + return "", nil, fmt.Errorf("couldn't read message") + } + + message = string(messageBytes) + } + + return "checkParametersResponse", &CheckParametersResponse{ + Type: "checkParametersResponse", + InReplyTo: checkMethod, + IsValid: isValid[0] == 1, + Message: message, + }, nil + } + + return "", nil, fmt.Errorf("couldn't match command") +} diff --git a/api/gosrc/externalbackendlauncher/main.go b/api/gosrc/externalbackendlauncher/main.go new file mode 100644 index 0000000..b5d1544 --- /dev/null +++ b/api/gosrc/externalbackendlauncher/main.go @@ -0,0 +1,133 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "strings" + "time" + + "git.greysoh.dev/imterah/nextnet/backendlauncher" + "github.com/charmbracelet/log" +) + +type WriteLogger struct { + UseError bool +} + +func (writer WriteLogger) Write(p []byte) (n int, err error) { + logSplit := strings.Split(string(p), "\n") + + for _, line := range logSplit { + if writer.UseError { + log.Errorf("application: %s", line) + } else { + log.Infof("application: %s", line) + } + } + + return len(p), err +} + +func main() { + tempDir, err := os.MkdirTemp("", "nextnet-sockets-") + logLevel := os.Getenv("NEXTNET_LOG_LEVEL") + + if logLevel != "" { + switch logLevel { + case "debug": + log.SetLevel(log.DebugLevel) + + case "info": + log.SetLevel(log.InfoLevel) + + case "warn": + log.SetLevel(log.WarnLevel) + + case "error": + log.SetLevel(log.ErrorLevel) + + case "fatal": + log.SetLevel(log.FatalLevel) + } + } + + if len(os.Args) != 3 { + log.Fatalf("missing arguments! example: ./externalbackendlauncher ") + } + + executablePath := os.Args[1] + executableParamsPath := os.Args[2] + + _, err = os.ReadFile(executableParamsPath) + + if err != nil { + log.Fatalf("could not read backend parameters: %s", err.Error()) + } + + _, err = os.Stat(executablePath) + + if err != nil { + log.Fatalf("failed backend checks: %s", err.Error()) + } + + log.Debug("running socket acquisition") + + sockPath, sockListener, err := backendlauncher.GetUnixSocket(tempDir) + + if err != nil { + log.Fatalf("failed to acquire unix socket: %s", err.Error()) + } + + log.Debugf("acquisition was successful: %s", sockPath) + + go func() { + log.Debug("entering execution loop (in auxiliary goroutine)...") + + for { + log.Info("waiting for Unix socket connections...") + sock, err := sockListener.Accept() + + if err != nil { + log.Warnf("failed to accept socket connection: %s", err.Error()) + } + + defer sock.Close() + } + }() + + log.Debug("entering execution loop (in main goroutine)...") + + stdout := WriteLogger{ + UseError: false, + } + + stderr := WriteLogger{ + UseError: true, + } + + for { + log.Info("starting process...") + // TODO: can we reuse cmd? + cmd := exec.Command(executablePath) + cmd.Env = append(cmd.Env, fmt.Sprintf("NEXTNET_API_SOCK=%s", sockPath)) + + cmd.Stdout = stdout + cmd.Stderr = stderr + + err := cmd.Run() + + if err != nil { + if err, ok := err.(*exec.ExitError); ok { + log.Warnf("backend died with exit code '%d' and with error '%s'", err.ExitCode(), err.Error()) + } else { + log.Warnf("backend died with error: %s", err.Error()) + } + } else { + log.Info("process exited gracefully.") + } + + log.Info("sleeping 5 seconds, and then restarting process") + time.Sleep(5 * time.Millisecond) + } +} diff --git a/api/gosrc/go.mod b/api/gosrc/go.mod new file mode 100644 index 0000000..8f40ef1 --- /dev/null +++ b/api/gosrc/go.mod @@ -0,0 +1,18 @@ +module git.greysoh.dev/imterah/nextnet + +go 1.23.3 + +require ( + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/charmbracelet/lipgloss v0.10.0 // indirect + github.com/charmbracelet/log v0.4.0 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.15.2 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/sys v0.13.0 // indirect +) diff --git a/api/gosrc/go.sum b/api/gosrc/go.sum new file mode 100644 index 0000000..8cc0806 --- /dev/null +++ b/api/gosrc/go.sum @@ -0,0 +1,28 @@ +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s= +github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= +github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM= +github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=