feature: Adds more commands and adds an example.

This commit is contained in:
greysoh 2024-12-01 22:07:10 -05:00
parent 0d0f16174b
commit 3cb9526716
Signed by: imterah
GPG key ID: 8FA7DD57BA6CEA37
9 changed files with 352 additions and 41 deletions

7
.gitignore vendored
View file

@ -1,3 +1,8 @@
# Go artifacts
api/gosrc/sshbackend/sshbackend
api/gosrc/dummybackend/dummybackend
api/gosrc/externalbackendlauncher/externalbackendlauncher
# LOM # LOM
lom/keys lom/keys
@ -135,4 +140,4 @@ dist
.yarn/install-state.gz .yarn/install-state.gz
.pnp.* .pnp.*
.tmp .tmp

View file

@ -0,0 +1,154 @@
package backendutil
import (
"fmt"
"net"
"os"
"git.greysoh.dev/imterah/nextnet/commonbackend"
"github.com/charmbracelet/log"
)
type BackendApplicationHelper struct {
Backend BackendInterface
SocketPath string
socket net.Conn
}
func (helper *BackendApplicationHelper) Start() error {
var err error
helper.socket, err = net.Dial("unix", helper.SocketPath)
if err != nil {
return err
}
for {
commandType, commandRaw, err := commonbackend.Unmarshal(helper.socket)
if err != nil {
return err
}
switch commandType {
case "start":
// TODO: implement response logic
command, ok := commandRaw.(*commonbackend.StartCommand)
if !ok {
return fmt.Errorf("failed to typecast")
}
// ok, err :=
_, _ = helper.Backend.StartBackend(command.Arguments)
case "stop":
// TODO: implement response logic
_, ok := commandRaw.(*commonbackend.StopCommand)
if !ok {
return fmt.Errorf("failed to typecast")
}
_, _ = helper.Backend.StopBackend()
case "addConnection":
// TODO: implement response logic
command, ok := commandRaw.(*commonbackend.AddConnectionCommand)
if !ok {
return fmt.Errorf("failed to typecast")
}
_, _ = helper.Backend.AddConnection(command)
case "removeConnection":
// TODO: implement response logic
command, ok := commandRaw.(*commonbackend.RemoveConnectionCommand)
if !ok {
return fmt.Errorf("failed to typecast")
}
_, _ = helper.Backend.RemoveConnection(command)
case "getAllConnections":
_, ok := commandRaw.(*commonbackend.AddConnectionCommand)
if !ok {
return fmt.Errorf("failed to typecast")
}
connections := helper.Backend.GetAllConnections()
serverParams := &commonbackend.ConnectionsResponse{
Type: "connectionsResponse",
Connections: connections,
}
byteData, err := commonbackend.Marshal(serverParams.Type, serverParams)
if err != nil {
return err
}
if _, err = helper.socket.Write(byteData); err != nil {
return err
}
case "checkClientParameters":
// TODO: implement response logic
command, ok := commandRaw.(*commonbackend.CheckClientParameters)
if !ok {
return fmt.Errorf("failed to typecast")
}
resp := helper.Backend.CheckParametersForConnections(command)
resp.Type = "checkParametersResponse"
resp.InResponseTo = "checkClientParameters"
byteData, err := commonbackend.Marshal(resp.Type, resp)
if err != nil {
return err
}
if _, err = helper.socket.Write(byteData); err != nil {
return err
}
case "checkServerParameters":
// TODO: implement response logic
command, ok := commandRaw.(*commonbackend.CheckServerParameters)
if !ok {
return fmt.Errorf("failed to typecast")
}
resp := helper.Backend.CheckParametersForBackend(command.Arguments)
resp.Type = "checkParametersResponse"
resp.InResponseTo = "checkServerParameters"
byteData, err := commonbackend.Marshal(resp.Type, resp)
if err != nil {
return err
}
if _, err = helper.socket.Write(byteData); err != nil {
return err
}
}
}
}
func NewHelper(backend BackendInterface) *BackendApplicationHelper {
socketPath, ok := os.LookupEnv("NEXTNET_API_SOCK")
if !ok {
log.Warn("NEXTNET_API_SOCK is not defined! This will cause an issue unless the backend manually overwrites it")
}
helper := &BackendApplicationHelper{
Backend: backend,
SocketPath: socketPath,
}
return helper
}

View file

@ -3,7 +3,7 @@ package backendutil
import "git.greysoh.dev/imterah/nextnet/commonbackend" import "git.greysoh.dev/imterah/nextnet/commonbackend"
type BackendInterface interface { type BackendInterface interface {
StartBackend() (bool, error) StartBackend(arguments []byte) (bool, error)
StopBackend() (bool, error) StopBackend() (bool, error)
AddConnection(command *commonbackend.AddConnectionCommand) (bool, error) AddConnection(command *commonbackend.AddConnectionCommand) (bool, error)
RemoveConnection(command *commonbackend.RemoveConnectionCommand) (bool, error) RemoveConnection(command *commonbackend.RemoveConnectionCommand) (bool, error)

View file

@ -1,5 +1,17 @@
package commonbackend package commonbackend
// Not all of these structs are implemented commands.
// Currently unimplemented commands:
// GetAllConnectionsRequest
// BackendStatusResponse
// BackendStatusRequest
// ProxyStatusRequest
// ProxyStatusResponse
// GetAllConnectionsRequest
// TODO (imterah): Rename AddConnectionCommand/RemoveConnectionCommand to AddProxyCommand/RemoveProxyCommand
// and their associated function calls
type StartCommand struct { type StartCommand struct {
Type string // Will be 'start' always Type string // Will be 'start' always
Arguments []byte Arguments []byte
@ -25,6 +37,51 @@ type RemoveConnectionCommand struct {
Protocol string // Will be either 'tcp' or 'udp' Protocol string // Will be either 'tcp' or 'udp'
} }
type GetProxyStatus struct {
Type string // Will be 'getProxyStatus' always
SourceIP string
SourcePort uint16
DestPort uint16
Protocol string // Will be either 'tcp' or 'udp'
}
type ProxyStatusResponse struct {
Type string // Will be 'proxyStatusResponse' always
SourceIP string
SourcePort uint16
DestPort uint16
Protocol string // Will be either 'tcp' or 'udp'
IsActive bool
}
type ProxyConnection struct {
SourceIP string
SourcePort uint16
DestPort uint16
Protocol string // Will be either 'tcp' or 'udp'
}
type ProxyConnectionResponse struct {
Type string // Will be 'proxyConnectionResponse' always
Connections []*ProxyConnection // List of connections
}
type BackendStatusResponse struct {
Type string // Will be 'backendStatusResponse' always
InResponseTo string // Can be either for 'start' or 'stop'
StatusCode int // Either the 'Success' or 'Failure' constant
Message string // String message from the client (ex. failed to dial TCP)
}
type BackendStatusRequest struct {
Type string // Will be 'backendStatusRequest' always
ForProperty string // Can be either for 'start' or 'stop'
}
type GetAllConnectionsRequest struct {
Type string // Will be 'getAllConnectionsRequest' always
}
type ClientConnection struct { type ClientConnection struct {
SourceIP string SourceIP string
SourcePort uint16 SourcePort uint16
@ -33,8 +90,8 @@ type ClientConnection struct {
ClientPort uint16 ClientPort uint16
} }
type GetAllConnections struct { type ConnectionsResponse struct {
Type string // Will be 'getAllConnections' always Type string // Will be 'connectionsResponse' always
Connections []*ClientConnection // List of connections Connections []*ClientConnection // List of connections
} }
@ -53,10 +110,10 @@ type CheckServerParameters struct {
// Sent as a response to either CheckClientParameters or CheckBackendParameters // Sent as a response to either CheckClientParameters or CheckBackendParameters
type CheckParametersResponse struct { type CheckParametersResponse struct {
Type string // Will be 'checkParametersResponse' always Type string // Will be 'checkParametersResponse' always
InReplyTo string // Will be either 'checkClientParameters' or 'checkServerParameters' InResponseTo string // Will be either 'checkClientParameters' or 'checkServerParameters'
IsValid bool // If true, valid, and if false, invalid 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) Message string // String message from the client (ex. failed to unmarshal JSON: x is not defined)
} }
const ( const (
@ -74,10 +131,19 @@ const (
const ( const (
TCP = iota TCP = iota
UDP UDP
)
const (
StatusSuccess = iota
StatusFailure
)
const (
// IP versions
IPv4 = 4 IPv4 = 4
IPv6 = 6 IPv6 = 6
// TODO: net has these constants defined already. We should switch to these
IPv4Size = 4 IPv4Size = 4
IPv6Size = 16 IPv6Size = 16
) )

View file

@ -156,8 +156,8 @@ func Marshal(commandType string, command interface{}) ([]byte, error) {
removeConnectionBytes[6+len(ipBytes)] = protocol removeConnectionBytes[6+len(ipBytes)] = protocol
return removeConnectionBytes, nil return removeConnectionBytes, nil
case "getAllConnections": case "connectionsResponse":
allConnectionsCommand, ok := command.(*GetAllConnections) allConnectionsCommand, ok := command.(*ConnectionsResponse)
if !ok { if !ok {
return nil, fmt.Errorf("failed to typecast") return nil, fmt.Errorf("failed to typecast")
@ -247,9 +247,9 @@ func Marshal(commandType string, command interface{}) ([]byte, error) {
var checkMethod uint8 var checkMethod uint8
if checkParametersCommand.InReplyTo == "checkClientParameters" { if checkParametersCommand.InResponseTo == "checkClientParameters" {
checkMethod = CheckClientParametersID checkMethod = CheckClientParametersID
} else if checkParametersCommand.InReplyTo == "checkServerParameters" { } else if checkParametersCommand.InResponseTo == "checkServerParameters" {
checkMethod = CheckServerParametersID checkMethod = CheckServerParametersID
} else { } else {
return nil, fmt.Errorf("invalid mode recieved (must be either checkClientParameters or checkServerParameters)") return nil, fmt.Errorf("invalid mode recieved (must be either checkClientParameters or checkServerParameters)")

View file

@ -219,8 +219,8 @@ func TestRemoveConnectionCommandMarshalSupport(t *testing.T) {
} }
func TestGetAllConnectionsCommandMarshalSupport(t *testing.T) { func TestGetAllConnectionsCommandMarshalSupport(t *testing.T) {
commandInput := &GetAllConnections{ commandInput := &ConnectionsResponse{
Type: "getAllConnections", Type: "connectionsResponse",
Connections: []*ClientConnection{ Connections: []*ClientConnection{
{ {
SourceIP: "127.0.0.1", SourceIP: "127.0.0.1",
@ -268,7 +268,7 @@ func TestGetAllConnectionsCommandMarshalSupport(t *testing.T) {
log.Print("command type does not match up!") log.Print("command type does not match up!")
} }
commandUnmarshalled, ok := commandUnmarshalledRaw.(*GetAllConnections) commandUnmarshalled, ok := commandUnmarshalledRaw.(*ConnectionsResponse)
if !ok { if !ok {
t.Fatal("failed typecast") t.Fatal("failed typecast")
@ -418,10 +418,10 @@ func TestCheckServerParametersMarshalSupport(t *testing.T) {
func TestCheckParametersResponseMarshalSupport(t *testing.T) { func TestCheckParametersResponseMarshalSupport(t *testing.T) {
commandInput := &CheckParametersResponse{ commandInput := &CheckParametersResponse{
Type: "checkParametersResponse", Type: "checkParametersResponse",
InReplyTo: "checkClientParameters", InResponseTo: "checkClientParameters",
IsValid: true, IsValid: true,
Message: "Hello from automated testing", Message: "Hello from automated testing",
} }
commandMarshalled, err := Marshal(commandInput.Type, commandInput) commandMarshalled, err := Marshal(commandInput.Type, commandInput)
@ -457,9 +457,9 @@ func TestCheckParametersResponseMarshalSupport(t *testing.T) {
log.Printf("Types are not equal (orig: %s, unmsh: %s)", commandInput.Type, commandUnmarshalled.Type) log.Printf("Types are not equal (orig: %s, unmsh: %s)", commandInput.Type, commandUnmarshalled.Type)
} }
if commandInput.InReplyTo != commandUnmarshalled.InReplyTo { if commandInput.InResponseTo != commandUnmarshalled.InResponseTo {
t.Fail() t.Fail()
log.Printf("InReplyTo's are not equal (orig: %s, unmsh: %s)", commandInput.InReplyTo, commandUnmarshalled.InReplyTo) log.Printf("InResponseTo's are not equal (orig: %s, unmsh: %s)", commandInput.InResponseTo, commandUnmarshalled.InResponseTo)
} }
if commandInput.IsValid != commandUnmarshalled.IsValid { if commandInput.IsValid != commandUnmarshalled.IsValid {

View file

@ -254,8 +254,8 @@ func Unmarshal(conn io.Reader) (string, interface{}, error) {
} }
} }
return "getAllConnections", &GetAllConnections{ return "connectionsResponse", &ConnectionsResponse{
Type: "getAllConnections", Type: "connectionsResponse",
Connections: connections, Connections: connections,
}, errorReturn }, errorReturn
case CheckClientParametersID: case CheckClientParametersID:
@ -376,10 +376,10 @@ func Unmarshal(conn io.Reader) (string, interface{}, error) {
} }
return "checkParametersResponse", &CheckParametersResponse{ return "checkParametersResponse", &CheckParametersResponse{
Type: "checkParametersResponse", Type: "checkParametersResponse",
InReplyTo: checkMethod, InResponseTo: checkMethod,
IsValid: isValid[0] == 1, IsValid: isValid[0] == 1,
Message: message, Message: message,
}, nil }, nil
} }

View file

@ -0,0 +1,83 @@
package main
import (
"os"
"git.greysoh.dev/imterah/nextnet/backendutil"
"git.greysoh.dev/imterah/nextnet/commonbackend"
"github.com/charmbracelet/log"
)
type DummyBackend struct {
}
func (backend *DummyBackend) StartBackend(byte []byte) (bool, error) {
return true, nil
}
func (backend *DummyBackend) StopBackend() (bool, error) {
return true, nil
}
func (backend *DummyBackend) AddConnection(command *commonbackend.AddConnectionCommand) (bool, error) {
return true, nil
}
func (backend *DummyBackend) RemoveConnection(command *commonbackend.RemoveConnectionCommand) (bool, error) {
return true, nil
}
func (backend *DummyBackend) GetAllConnections() []*commonbackend.ClientConnection {
return []*commonbackend.ClientConnection{}
}
func (backend *DummyBackend) CheckParametersForConnections(clientParameters *commonbackend.CheckClientParameters) *commonbackend.CheckParametersResponse {
// You don't have to specify Type and InReplyTo. Those will be handled for you.
// Message is optional.
return &commonbackend.CheckParametersResponse{
IsValid: true,
Message: "Valid!",
}
}
func (backend *DummyBackend) CheckParametersForBackend(arguments []byte) *commonbackend.CheckParametersResponse {
// You don't have to specify Type and InReplyTo. Those will be handled for you.
// Message is optional.
return &commonbackend.CheckParametersResponse{
IsValid: true,
Message: "Valid!",
}
}
func main() {
// When using logging, you should use charmbracelet/log, because that's what everything else uses in this ecosystem of a project. - imterah
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)
}
}
backend := &DummyBackend{}
application := backendutil.NewHelper(backend)
err := application.Start()
if err != nil {
log.Fatalf("failed execution in application: %s", err.Error())
}
}

View file

@ -33,23 +33,25 @@ func main() {
tempDir, err := os.MkdirTemp("", "nextnet-sockets-") tempDir, err := os.MkdirTemp("", "nextnet-sockets-")
logLevel := os.Getenv("NEXTNET_LOG_LEVEL") logLevel := os.Getenv("NEXTNET_LOG_LEVEL")
if logLevel != "" { if logLevel == "" {
switch logLevel { logLevel = "fatal"
case "debug": }
log.SetLevel(log.DebugLevel)
case "info": switch logLevel {
log.SetLevel(log.InfoLevel) case "debug":
log.SetLevel(log.DebugLevel)
case "warn": case "info":
log.SetLevel(log.WarnLevel) log.SetLevel(log.InfoLevel)
case "error": case "warn":
log.SetLevel(log.ErrorLevel) log.SetLevel(log.WarnLevel)
case "fatal": case "error":
log.SetLevel(log.FatalLevel) log.SetLevel(log.ErrorLevel)
}
case "fatal":
log.SetLevel(log.FatalLevel)
} }
if len(os.Args) != 3 { if len(os.Args) != 3 {
@ -109,8 +111,9 @@ func main() {
for { for {
log.Info("starting process...") log.Info("starting process...")
// TODO: can we reuse cmd? // TODO: can we reuse cmd?
cmd := exec.Command(executablePath) cmd := exec.Command(executablePath)
cmd.Env = append(cmd.Env, fmt.Sprintf("NEXTNET_API_SOCK=%s", sockPath)) cmd.Env = append(cmd.Env, fmt.Sprintf("NEXTNET_API_SOCK=%s", sockPath), fmt.Sprintf("NEXTNET_LOG_LEVEL=%s", logLevel))
cmd.Stdout = stdout cmd.Stdout = stdout
cmd.Stderr = stderr cmd.Stderr = stderr