Compare commits
2 commits
432d457ad7
...
f8a4fe00a0
Author | SHA1 | Date | |
---|---|---|---|
f8a4fe00a0 | |||
15176831e6 |
7 changed files with 803 additions and 177 deletions
|
@ -132,12 +132,12 @@ func (helper *BackendApplicationHelper) Start() error {
|
|||
ok, err := helper.Backend.StartProxy(command)
|
||||
var hasAnyFailed bool
|
||||
|
||||
if !ok {
|
||||
log.Warnf("failed to add proxy (%s:%d -> remote:%d): StartProxy returned into failure state", command.SourceIP, command.SourcePort, command.DestPort)
|
||||
hasAnyFailed = true
|
||||
} else if err != nil {
|
||||
if err != nil {
|
||||
log.Warnf("failed to add proxy (%s:%d -> remote:%d): %s", command.SourceIP, command.SourcePort, command.DestPort, err.Error())
|
||||
hasAnyFailed = true
|
||||
} else if !ok {
|
||||
log.Warnf("failed to add proxy (%s:%d -> remote:%d): StartProxy returned into failure state", command.SourceIP, command.SourcePort, command.DestPort)
|
||||
hasAnyFailed = true
|
||||
}
|
||||
|
||||
response := &commonbackend.ProxyStatusResponse{
|
||||
|
@ -160,12 +160,12 @@ func (helper *BackendApplicationHelper) Start() error {
|
|||
ok, err := helper.Backend.StopProxy(command)
|
||||
var hasAnyFailed bool
|
||||
|
||||
if !ok {
|
||||
log.Warnf("failed to remove proxy (%s:%d -> remote:%d): RemoveProxy returned into failure state", command.SourceIP, command.SourcePort, command.DestPort)
|
||||
hasAnyFailed = true
|
||||
} else if err != nil {
|
||||
if err != nil {
|
||||
log.Warnf("failed to remove proxy (%s:%d -> remote:%d): %s", command.SourceIP, command.SourcePort, command.DestPort, err.Error())
|
||||
hasAnyFailed = true
|
||||
} else if !ok {
|
||||
log.Warnf("failed to remove proxy (%s:%d -> remote:%d): RemoveProxy returned into failure state", command.SourceIP, command.SourcePort, command.DestPort)
|
||||
hasAnyFailed = true
|
||||
}
|
||||
|
||||
response := &commonbackend.ProxyStatusResponse{
|
||||
|
|
|
@ -213,7 +213,7 @@ func Unmarshal(conn io.Reader) (interface{}, error) {
|
|||
|
||||
if protocolBytes[0] == TCP {
|
||||
protocol = "tcp"
|
||||
} else if protocolBytes[1] == UDP {
|
||||
} else if protocolBytes[0] == UDP {
|
||||
protocol = "udp"
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid protocol")
|
||||
|
@ -270,7 +270,7 @@ func Unmarshal(conn io.Reader) (interface{}, error) {
|
|||
|
||||
if protocolBytes[0] == TCP {
|
||||
protocol = "tcp"
|
||||
} else if protocolBytes[1] == UDP {
|
||||
} else if protocolBytes[0] == UDP {
|
||||
protocol = "udp"
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid protocol")
|
||||
|
@ -364,7 +364,7 @@ func Unmarshal(conn io.Reader) (interface{}, error) {
|
|||
|
||||
if protocolBytes[0] == TCP {
|
||||
protocol = "tcp"
|
||||
} else if protocolBytes[1] == UDP {
|
||||
} else if protocolBytes[0] == UDP {
|
||||
protocol = "udp"
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid protocol")
|
||||
|
@ -523,7 +523,7 @@ func Unmarshal(conn io.Reader) (interface{}, error) {
|
|||
|
||||
if protocolBytes[0] == TCP {
|
||||
protocol = "tcp"
|
||||
} else if protocolBytes[1] == UDP {
|
||||
} else if protocolBytes[0] == UDP {
|
||||
protocol = "udp"
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid protocol")
|
||||
|
@ -580,7 +580,7 @@ func Unmarshal(conn io.Reader) (interface{}, error) {
|
|||
|
||||
if protocolBytes[0] == TCP {
|
||||
protocol = "tcp"
|
||||
} else if protocolBytes[1] == UDP {
|
||||
} else if protocolBytes[0] == UDP {
|
||||
protocol = "udp"
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid protocol")
|
||||
|
|
30
backend/sshappbackend/gaslighter/gaslighter.go
Normal file
30
backend/sshappbackend/gaslighter/gaslighter.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package gaslighter
|
||||
|
||||
import "io"
|
||||
|
||||
type Gaslighter struct {
|
||||
Byte byte
|
||||
HasGaslit bool
|
||||
ProxiedReader io.Reader
|
||||
}
|
||||
|
||||
func (gaslighter *Gaslighter) Read(p []byte) (n int, err error) {
|
||||
if gaslighter.HasGaslit {
|
||||
return gaslighter.ProxiedReader.Read(p)
|
||||
}
|
||||
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
p[0] = gaslighter.Byte
|
||||
gaslighter.HasGaslit = true
|
||||
|
||||
if len(p) > 1 {
|
||||
n, err := gaslighter.ProxiedReader.Read(p[1:])
|
||||
|
||||
return n + 1, err
|
||||
} else {
|
||||
return 1, nil
|
||||
}
|
||||
}
|
|
@ -5,19 +5,37 @@ import (
|
|||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand/v2"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.terah.dev/imterah/hermes/backend/backendutil"
|
||||
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
||||
"git.terah.dev/imterah/hermes/backend/sshappbackend/datacommands"
|
||||
"git.terah.dev/imterah/hermes/backend/sshappbackend/gaslighter"
|
||||
"git.terah.dev/imterah/hermes/backend/sshappbackend/local-code/porttranslation"
|
||||
"github.com/charmbracelet/log"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/pkg/sftp"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type TCPProxy struct {
|
||||
proxyInformation *commonbackend.AddProxy
|
||||
connections map[uint16]net.Conn
|
||||
}
|
||||
|
||||
type UDPProxy struct {
|
||||
proxyInformation *commonbackend.AddProxy
|
||||
portTranslation *porttranslation.PortTranslation
|
||||
}
|
||||
|
||||
type SSHAppBackendData struct {
|
||||
IP string `json:"ip" validate:"required"`
|
||||
Port uint16 `json:"port" validate:"required"`
|
||||
|
@ -27,14 +45,27 @@ type SSHAppBackendData struct {
|
|||
}
|
||||
|
||||
type SSHAppBackend struct {
|
||||
config *SSHAppBackendData
|
||||
conn *ssh.Client
|
||||
clients []*commonbackend.ProxyClientConnection
|
||||
arrayPropMutex sync.Mutex
|
||||
config *SSHAppBackendData
|
||||
conn *ssh.Client
|
||||
listener net.Listener
|
||||
currentSock net.Conn
|
||||
|
||||
tcpProxies map[uint16]*TCPProxy
|
||||
udpProxies map[uint16]*UDPProxy
|
||||
|
||||
// globalNonCriticalMessageLock: Locks all messages that don't need low-latency transmissions & high
|
||||
// speed behind a lock. This ensures safety when it comes to handling messages correctly.
|
||||
globalNonCriticalMessageLock sync.Mutex
|
||||
// globalNonCriticalMessageChan: Channel for handling messages that need a reply / aren't critical.
|
||||
globalNonCriticalMessageChan chan interface{}
|
||||
}
|
||||
|
||||
func (backend *SSHAppBackend) StartBackend(configBytes []byte) (bool, error) {
|
||||
log.Info("SSHAppBackend is initializing...")
|
||||
backend.globalNonCriticalMessageChan = make(chan interface{})
|
||||
backend.tcpProxies = map[uint16]*TCPProxy{}
|
||||
backend.udpProxies = map[uint16]*UDPProxy{}
|
||||
|
||||
var backendData SSHAppBackendData
|
||||
|
||||
if err := json.Unmarshal(configBytes, &backendData); err != nil {
|
||||
|
@ -77,8 +108,8 @@ func (backend *SSHAppBackend) StartBackend(configBytes []byte) (bool, error) {
|
|||
|
||||
backend.conn = conn
|
||||
|
||||
log.Info("SSHAppBackend has connected successfully.")
|
||||
log.Info("Getting CPU architecture...")
|
||||
log.Debug("SSHAppBackend has connected successfully.")
|
||||
log.Debug("Getting CPU architecture...")
|
||||
|
||||
session, err := backend.conn.NewSession()
|
||||
|
||||
|
@ -125,7 +156,7 @@ func (backend *SSHAppBackend) StartBackend(configBytes []byte) (bool, error) {
|
|||
return false, fmt.Errorf("CPU architecture not compiled/supported currently")
|
||||
}
|
||||
|
||||
log.Info("Checking if we need to copy the application...")
|
||||
log.Debug("Checking if we need to copy the application...")
|
||||
|
||||
var binary []byte
|
||||
needsToCopyBinary := true
|
||||
|
@ -187,7 +218,8 @@ func (backend *SSHAppBackend) StartBackend(configBytes []byte) (bool, error) {
|
|||
}
|
||||
|
||||
if needsToCopyBinary {
|
||||
log.Info("Copying binary...")
|
||||
log.Debug("Copying binary...")
|
||||
|
||||
sftpInstance, err := sftp.NewClient(conn)
|
||||
|
||||
if err != nil {
|
||||
|
@ -213,13 +245,13 @@ func (backend *SSHAppBackend) StartBackend(configBytes []byte) (bool, error) {
|
|||
var file *sftp.File
|
||||
|
||||
if fileExists {
|
||||
file, err = sftpInstance.Create("/tmp/sshappbackend.runtime")
|
||||
} else {
|
||||
file, err = sftpInstance.OpenFile("/tmp/sshappbackend.runtime", os.O_WRONLY)
|
||||
} else {
|
||||
file, err = sftpInstance.Create("/tmp/sshappbackend.runtime")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Failed to create file: %s", err.Error())
|
||||
log.Warnf("Failed to create (or open) file: %s", err.Error())
|
||||
conn.Close()
|
||||
backend.conn = nil
|
||||
return false, err
|
||||
|
@ -234,7 +266,7 @@ func (backend *SSHAppBackend) StartBackend(configBytes []byte) (bool, error) {
|
|||
return false, err
|
||||
}
|
||||
|
||||
err = file.Chmod(775)
|
||||
err = file.Chmod(0755)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Failed to change permissions on file: %s", err.Error())
|
||||
|
@ -243,12 +275,25 @@ func (backend *SSHAppBackend) StartBackend(configBytes []byte) (bool, error) {
|
|||
return false, err
|
||||
}
|
||||
|
||||
log.Info("Done copying file.")
|
||||
log.Debug("Done copying file.")
|
||||
sftpInstance.Close()
|
||||
} else {
|
||||
log.Info("Skipping copying as there's a copy on disk already.")
|
||||
log.Debug("Skipping copying as there's a copy on disk already.")
|
||||
}
|
||||
|
||||
log.Info("Starting process...")
|
||||
log.Debug("Initializing Unix socket...")
|
||||
|
||||
socketPath := fmt.Sprintf("/tmp/sock-%d.sock", rand.Uint())
|
||||
listener, err := conn.ListenUnix(socketPath)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Failed to listen on socket: %s", err.Error())
|
||||
conn.Close()
|
||||
backend.conn = nil
|
||||
return false, err
|
||||
}
|
||||
|
||||
log.Debug("Starting process...")
|
||||
|
||||
session, err = backend.conn.NewSession()
|
||||
|
||||
|
@ -259,10 +304,56 @@ func (backend *SSHAppBackend) StartBackend(configBytes []byte) (bool, error) {
|
|||
return false, err
|
||||
}
|
||||
|
||||
backend.listener = listener
|
||||
|
||||
session.Stdout = WriteLogger{}
|
||||
session.Stderr = WriteLogger{}
|
||||
|
||||
go session.Run("/tmp/sshappbackend.runtime")
|
||||
go func() {
|
||||
for {
|
||||
err := session.Run(fmt.Sprintf("HERMES_LOG_LEVEL=\"%s\" HERMES_API_SOCK=\"%s\" /tmp/sshappbackend.runtime", os.Getenv("HERMES_LOG_LEVEL"), socketPath))
|
||||
|
||||
if err != nil && !errors.Is(err, &ssh.ExitError{}) && !errors.Is(err, &ssh.ExitMissingError{}) {
|
||||
log.Errorf("Critically failed during execution of remote code: %s", err.Error())
|
||||
return
|
||||
} else {
|
||||
log.Warn("Remote code failed for an unknown reason. Restarting...")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go backend.sockServerHandler()
|
||||
|
||||
log.Debug("Started process. Waiting for Unix socket connection...")
|
||||
|
||||
for backend.currentSock == nil {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
log.Debug("Detected connection. Sending initialization command...")
|
||||
|
||||
proxyStatusRaw, err := backend.SendNonCriticalMessage(&commonbackend.Start{
|
||||
Arguments: []byte{},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
proxyStatus, ok := proxyStatusRaw.(*commonbackend.BackendStatusResponse)
|
||||
|
||||
if !ok {
|
||||
return false, fmt.Errorf("recieved invalid response type: %T", proxyStatusRaw)
|
||||
}
|
||||
|
||||
if proxyStatus.StatusCode == commonbackend.StatusFailure {
|
||||
if proxyStatus.Message == "" {
|
||||
return false, fmt.Errorf("failed to initialize backend in remote code")
|
||||
} else {
|
||||
return false, fmt.Errorf("failed to initialize backend in remote code: %s", proxyStatus.Message)
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("SSHAppBackend has initialized successfully.")
|
||||
|
||||
return true, nil
|
||||
|
@ -283,17 +374,171 @@ func (backend *SSHAppBackend) GetBackendStatus() (bool, error) {
|
|||
}
|
||||
|
||||
func (backend *SSHAppBackend) StartProxy(command *commonbackend.AddProxy) (bool, error) {
|
||||
proxyStatusRaw, err := backend.SendNonCriticalMessage(command)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
proxyStatus, ok := proxyStatusRaw.(*datacommands.ProxyStatusResponse)
|
||||
|
||||
if !ok {
|
||||
return false, fmt.Errorf("recieved invalid response type: %T", proxyStatusRaw)
|
||||
}
|
||||
|
||||
if !proxyStatus.IsActive {
|
||||
return false, fmt.Errorf("failed to initialize proxy in remote code")
|
||||
}
|
||||
|
||||
if command.Protocol == "tcp" {
|
||||
backend.tcpProxies[proxyStatus.ProxyID] = &TCPProxy{
|
||||
proxyInformation: command,
|
||||
}
|
||||
|
||||
backend.tcpProxies[proxyStatus.ProxyID].connections = map[uint16]net.Conn{}
|
||||
} else if command.Protocol == "udp" {
|
||||
backend.udpProxies[proxyStatus.ProxyID] = &UDPProxy{
|
||||
proxyInformation: command,
|
||||
portTranslation: &porttranslation.PortTranslation{},
|
||||
}
|
||||
|
||||
backend.udpProxies[proxyStatus.ProxyID].portTranslation.UDPAddr = &net.UDPAddr{
|
||||
IP: net.ParseIP(command.SourceIP),
|
||||
Port: int(command.SourcePort),
|
||||
}
|
||||
|
||||
udpMessageCommand := &datacommands.UDPProxyData{}
|
||||
udpMessageCommand.ProxyID = proxyStatus.ProxyID
|
||||
|
||||
backend.udpProxies[proxyStatus.ProxyID].portTranslation.WriteFrom = func(ip string, port uint16, data []byte) {
|
||||
udpMessageCommand.ClientIP = ip
|
||||
udpMessageCommand.ClientPort = port
|
||||
udpMessageCommand.DataLength = uint16(len(data))
|
||||
|
||||
marshalledCommand, err := datacommands.Marshal(udpMessageCommand)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Failed to marshal UDP message header")
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := backend.currentSock.Write(marshalledCommand); err != nil {
|
||||
log.Warnf("Failed to write UDP message header")
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := backend.currentSock.Write(data); err != nil {
|
||||
log.Warnf("Failed to write UDP message")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(3 * time.Minute)
|
||||
|
||||
// Checks if the proxy still exists before continuing
|
||||
_, ok := backend.udpProxies[proxyStatus.ProxyID]
|
||||
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Then attempt to run cleanup tasks
|
||||
log.Debug("Running UDP proxy cleanup tasks (invoking CleanupPorts() on portTranslation)")
|
||||
backend.udpProxies[proxyStatus.ProxyID].portTranslation.CleanupPorts()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (backend *SSHAppBackend) StopProxy(command *commonbackend.RemoveProxy) (bool, error) {
|
||||
if command.Protocol == "tcp" {
|
||||
for proxyIndex, proxy := range backend.tcpProxies {
|
||||
if proxy.proxyInformation.DestPort != command.DestPort {
|
||||
continue
|
||||
}
|
||||
|
||||
onDisconnect := &datacommands.TCPConnectionClosed{
|
||||
ProxyID: proxyIndex,
|
||||
}
|
||||
|
||||
for connectionIndex, connection := range proxy.connections {
|
||||
connection.Close()
|
||||
delete(proxy.connections, connectionIndex)
|
||||
|
||||
onDisconnect.ConnectionID = connectionIndex
|
||||
disconnectionCommandMarshalled, err := datacommands.Marshal(onDisconnect)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("failed to marshal disconnection message: %s", err.Error())
|
||||
}
|
||||
|
||||
backend.currentSock.Write(disconnectionCommandMarshalled)
|
||||
}
|
||||
|
||||
proxyStatusRaw, err := backend.SendNonCriticalMessage(&datacommands.RemoveProxy{
|
||||
ProxyID: proxyIndex,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
proxyStatus, ok := proxyStatusRaw.(*datacommands.ProxyStatusResponse)
|
||||
|
||||
if !ok {
|
||||
log.Warn("Failed to stop proxy: typecast failed")
|
||||
return true, fmt.Errorf("failed to stop proxy: typecast failed")
|
||||
}
|
||||
|
||||
if proxyStatus.IsActive {
|
||||
log.Warn("Failed to stop proxy: still running")
|
||||
return true, fmt.Errorf("failed to stop proxy: still running")
|
||||
}
|
||||
}
|
||||
} else if command.Protocol == "udp" {
|
||||
for proxyIndex, proxy := range backend.udpProxies {
|
||||
if proxy.proxyInformation.DestPort != command.DestPort {
|
||||
continue
|
||||
}
|
||||
|
||||
proxyStatusRaw, err := backend.SendNonCriticalMessage(&datacommands.RemoveProxy{
|
||||
ProxyID: proxyIndex,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
proxyStatus, ok := proxyStatusRaw.(*datacommands.ProxyStatusResponse)
|
||||
|
||||
if !ok {
|
||||
log.Warn("Failed to stop proxy: typecast failed")
|
||||
return true, fmt.Errorf("failed to stop proxy: typecast failed")
|
||||
}
|
||||
|
||||
if proxyStatus.IsActive {
|
||||
log.Warn("Failed to stop proxy: still running")
|
||||
return true, fmt.Errorf("failed to stop proxy: still running")
|
||||
}
|
||||
|
||||
proxy.portTranslation.StopAllPorts()
|
||||
delete(backend.udpProxies, proxyIndex)
|
||||
}
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("could not find the proxy")
|
||||
}
|
||||
|
||||
// TODO: implement!
|
||||
func (backend *SSHAppBackend) GetAllClientConnections() []*commonbackend.ProxyClientConnection {
|
||||
return backend.clients
|
||||
return []*commonbackend.ProxyClientConnection{}
|
||||
}
|
||||
|
||||
// We don't have any parameter limitations, so we should be good.
|
||||
func (backend *SSHAppBackend) CheckParametersForConnections(clientParameters *commonbackend.CheckClientParameters) *commonbackend.CheckParametersResponse {
|
||||
return &commonbackend.CheckParametersResponse{
|
||||
IsValid: true,
|
||||
|
@ -322,6 +567,226 @@ func (backend *SSHAppBackend) CheckParametersForBackend(arguments []byte) *commo
|
|||
}
|
||||
}
|
||||
|
||||
func (backend *SSHAppBackend) OnTCPConnectionOpened(proxyID, connectionID uint16) {
|
||||
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", backend.tcpProxies[proxyID].proxyInformation.SourceIP, backend.tcpProxies[proxyID].proxyInformation.SourcePort))
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("failed to dial sock: %s", err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
dataBuf := make([]byte, 65535)
|
||||
|
||||
tcpData := &datacommands.TCPProxyData{
|
||||
ProxyID: proxyID,
|
||||
ConnectionID: connectionID,
|
||||
}
|
||||
|
||||
for {
|
||||
len, err := conn.Read(dataBuf)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
return
|
||||
} else if err.Error() != "EOF" {
|
||||
log.Warnf("failed to read from sock: %s", err.Error())
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
break
|
||||
}
|
||||
|
||||
tcpData.DataLength = uint16(len)
|
||||
marshalledMessageCommand, err := datacommands.Marshal(tcpData)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("failed to marshal message data: %s", err.Error())
|
||||
|
||||
conn.Close()
|
||||
break
|
||||
}
|
||||
|
||||
if _, err := backend.currentSock.Write(marshalledMessageCommand); err != nil {
|
||||
log.Warnf("failed to send marshalled message data: %s", err.Error())
|
||||
|
||||
conn.Close()
|
||||
break
|
||||
}
|
||||
|
||||
if _, err := backend.currentSock.Write(dataBuf[:len]); err != nil {
|
||||
log.Warnf("failed to send raw message data: %s", err.Error())
|
||||
|
||||
conn.Close()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
onDisconnect := &datacommands.TCPConnectionClosed{
|
||||
ProxyID: proxyID,
|
||||
ConnectionID: connectionID,
|
||||
}
|
||||
|
||||
disconnectionCommandMarshalled, err := datacommands.Marshal(onDisconnect)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("failed to marshal disconnection message: %s", err.Error())
|
||||
}
|
||||
|
||||
backend.currentSock.Write(disconnectionCommandMarshalled)
|
||||
}()
|
||||
|
||||
backend.tcpProxies[proxyID].connections[connectionID] = conn
|
||||
}
|
||||
|
||||
func (backend *SSHAppBackend) OnTCPConnectionClosed(proxyID, connectionID uint16) {
|
||||
proxy, ok := backend.tcpProxies[proxyID]
|
||||
|
||||
if !ok {
|
||||
log.Warn("Could not find TCP proxy")
|
||||
}
|
||||
|
||||
connection, ok := proxy.connections[connectionID]
|
||||
|
||||
if !ok {
|
||||
log.Warn("Could not find connection in TCP proxy")
|
||||
}
|
||||
|
||||
connection.Close()
|
||||
delete(proxy.connections, connectionID)
|
||||
}
|
||||
|
||||
func (backend *SSHAppBackend) HandleTCPMessage(message *datacommands.TCPProxyData, data []byte) {
|
||||
proxy, ok := backend.tcpProxies[message.ProxyID]
|
||||
|
||||
if !ok {
|
||||
log.Warn("Could not find TCP proxy")
|
||||
}
|
||||
|
||||
connection, ok := proxy.connections[message.ConnectionID]
|
||||
|
||||
if !ok {
|
||||
log.Warn("Could not find connection in TCP proxy")
|
||||
}
|
||||
|
||||
connection.Write(data)
|
||||
}
|
||||
|
||||
func (backend *SSHAppBackend) HandleUDPMessage(message *datacommands.UDPProxyData, data []byte) {
|
||||
proxy, ok := backend.udpProxies[message.ProxyID]
|
||||
|
||||
if !ok {
|
||||
log.Warn("Could not find UDP proxy")
|
||||
}
|
||||
|
||||
if _, err := proxy.portTranslation.WriteTo(message.ClientIP, message.ClientPort, data); err != nil {
|
||||
log.Warnf("Failed to write to UDP: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (backend *SSHAppBackend) SendNonCriticalMessage(iface interface{}) (interface{}, error) {
|
||||
if backend.currentSock == nil {
|
||||
return nil, fmt.Errorf("socket connection not initialized yet")
|
||||
}
|
||||
|
||||
bytes, err := datacommands.Marshal(iface)
|
||||
|
||||
if err != nil && err.Error() == "unsupported command type" {
|
||||
bytes, err = commonbackend.Marshal(iface)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
backend.globalNonCriticalMessageLock.Lock()
|
||||
|
||||
if _, err := backend.currentSock.Write(bytes); err != nil {
|
||||
backend.globalNonCriticalMessageLock.Unlock()
|
||||
return nil, fmt.Errorf("failed to write message: %s", err.Error())
|
||||
}
|
||||
|
||||
reply, ok := <-backend.globalNonCriticalMessageChan
|
||||
|
||||
if !ok {
|
||||
backend.globalNonCriticalMessageLock.Unlock()
|
||||
return nil, fmt.Errorf("failed to get reply back: chan not OK")
|
||||
}
|
||||
|
||||
backend.globalNonCriticalMessageLock.Unlock()
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
func (backend *SSHAppBackend) sockServerHandler() {
|
||||
for {
|
||||
conn, err := backend.listener.Accept()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Failed to accept remote connection: %s", err.Error())
|
||||
}
|
||||
|
||||
log.Debug("Successfully connected.")
|
||||
|
||||
backend.currentSock = conn
|
||||
|
||||
commandID := make([]byte, 1)
|
||||
|
||||
gaslighter := &gaslighter.Gaslighter{}
|
||||
gaslighter.ProxiedReader = conn
|
||||
|
||||
dataBuffer := make([]byte, 65535)
|
||||
|
||||
var commandRaw interface{}
|
||||
|
||||
for {
|
||||
if _, err := conn.Read(commandID); err != nil {
|
||||
log.Warnf("Failed to read command ID: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
gaslighter.Byte = commandID[0]
|
||||
gaslighter.HasGaslit = false
|
||||
|
||||
if gaslighter.Byte > 100 {
|
||||
commandRaw, err = datacommands.Unmarshal(gaslighter)
|
||||
} else {
|
||||
commandRaw, err = commonbackend.Unmarshal(gaslighter)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Failed to parse command: %s", err.Error())
|
||||
}
|
||||
|
||||
switch command := commandRaw.(type) {
|
||||
case *datacommands.TCPConnectionOpened:
|
||||
backend.OnTCPConnectionOpened(command.ProxyID, command.ConnectionID)
|
||||
case *datacommands.TCPConnectionClosed:
|
||||
backend.OnTCPConnectionClosed(command.ProxyID, command.ConnectionID)
|
||||
case *datacommands.TCPProxyData:
|
||||
if _, err := io.ReadFull(conn, dataBuffer[:command.DataLength]); err != nil {
|
||||
log.Warnf("Failed to read entire data buffer: %s", err.Error())
|
||||
break
|
||||
}
|
||||
|
||||
backend.HandleTCPMessage(command, dataBuffer[:command.DataLength])
|
||||
case *datacommands.UDPProxyData:
|
||||
if _, err := io.ReadFull(conn, dataBuffer[:command.DataLength]); err != nil {
|
||||
log.Warnf("Failed to read entire data buffer: %s", err.Error())
|
||||
break
|
||||
}
|
||||
|
||||
backend.HandleUDPMessage(command, dataBuffer[:command.DataLength])
|
||||
default:
|
||||
select {
|
||||
case backend.globalNonCriticalMessageChan <- command:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
logLevel := os.Getenv("HERMES_LOG_LEVEL")
|
||||
|
||||
|
|
112
backend/sshappbackend/local-code/porttranslation/translation.go
Normal file
112
backend/sshappbackend/local-code/porttranslation/translation.go
Normal file
|
@ -0,0 +1,112 @@
|
|||
package porttranslation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type connectionData struct {
|
||||
udpConn *net.UDPConn
|
||||
buf []byte
|
||||
hasBeenAliveFor time.Time
|
||||
}
|
||||
|
||||
type PortTranslation struct {
|
||||
UDPAddr *net.UDPAddr
|
||||
WriteFrom func(ip string, port uint16, data []byte)
|
||||
|
||||
newConnectionLock sync.Mutex
|
||||
connections map[string]map[uint16]*connectionData
|
||||
}
|
||||
|
||||
func (translation *PortTranslation) CleanupPorts() {
|
||||
if translation.connections == nil {
|
||||
translation.connections = map[string]map[uint16]*connectionData{}
|
||||
return
|
||||
}
|
||||
|
||||
for connectionIPIndex, connectionPorts := range translation.connections {
|
||||
anyAreAlive := false
|
||||
|
||||
for connectionPortIndex, connectionData := range connectionPorts {
|
||||
if time.Now().Before(connectionData.hasBeenAliveFor.Add(3 * time.Minute)) {
|
||||
anyAreAlive = true
|
||||
continue
|
||||
}
|
||||
|
||||
connectionData.udpConn.Close()
|
||||
delete(connectionPorts, connectionPortIndex)
|
||||
}
|
||||
|
||||
if !anyAreAlive {
|
||||
delete(translation.connections, connectionIPIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (translation *PortTranslation) StopAllPorts() {
|
||||
if translation.connections == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for connectionIPIndex, connectionPorts := range translation.connections {
|
||||
for connectionPortIndex, connectionData := range connectionPorts {
|
||||
connectionData.udpConn.Close()
|
||||
delete(connectionPorts, connectionPortIndex)
|
||||
}
|
||||
|
||||
delete(translation.connections, connectionIPIndex)
|
||||
}
|
||||
|
||||
translation.connections = nil
|
||||
}
|
||||
|
||||
func (translation *PortTranslation) WriteTo(ip string, port uint16, data []byte) (int, error) {
|
||||
if translation.connections == nil {
|
||||
translation.connections = map[string]map[uint16]*connectionData{}
|
||||
}
|
||||
|
||||
connectionPortData, ok := translation.connections[ip]
|
||||
|
||||
if !ok {
|
||||
translation.connections[ip] = map[uint16]*connectionData{}
|
||||
connectionPortData = translation.connections[ip]
|
||||
}
|
||||
|
||||
connectionStruct, ok := connectionPortData[port]
|
||||
|
||||
if !ok {
|
||||
connectionPortData[port] = &connectionData{}
|
||||
connectionStruct = connectionPortData[port]
|
||||
|
||||
udpConn, err := net.DialUDP("udp", nil, translation.UDPAddr)
|
||||
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to initialize UDP socket: %s", err.Error())
|
||||
}
|
||||
|
||||
connectionStruct.udpConn = udpConn
|
||||
connectionStruct.buf = make([]byte, 65535)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
n, err := udpConn.Read(connectionStruct.buf)
|
||||
|
||||
if err != nil {
|
||||
udpConn.Close()
|
||||
delete(connectionPortData, port)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
connectionStruct.hasBeenAliveFor = time.Now()
|
||||
translation.WriteFrom(ip, port, connectionStruct.buf[:n])
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
connectionStruct.hasBeenAliveFor = time.Now()
|
||||
return connectionStruct.udpConn.Write(data)
|
||||
}
|
|
@ -8,6 +8,7 @@ import (
|
|||
"git.terah.dev/imterah/hermes/backend/backendutil"
|
||||
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
||||
"git.terah.dev/imterah/hermes/backend/sshappbackend/datacommands"
|
||||
"git.terah.dev/imterah/hermes/backend/sshappbackend/gaslighter"
|
||||
"github.com/charmbracelet/log"
|
||||
)
|
||||
|
||||
|
@ -38,10 +39,28 @@ func (helper *BackendApplicationHelper) Start() error {
|
|||
|
||||
log.Debug("Sucessfully connected")
|
||||
|
||||
for {
|
||||
commandRaw, err := datacommands.Unmarshal(helper.socket)
|
||||
gaslighter := &gaslighter.Gaslighter{}
|
||||
gaslighter.ProxiedReader = helper.socket
|
||||
|
||||
if err != nil && err.Error() != "couldn't match command ID" {
|
||||
commandID := make([]byte, 1)
|
||||
|
||||
for {
|
||||
if _, err := helper.socket.Read(commandID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gaslighter.Byte = commandID[0]
|
||||
gaslighter.HasGaslit = false
|
||||
|
||||
var commandRaw interface{}
|
||||
|
||||
if gaslighter.Byte > 100 {
|
||||
commandRaw, err = datacommands.Unmarshal(gaslighter)
|
||||
} else {
|
||||
commandRaw, err = commonbackend.Unmarshal(gaslighter)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -127,155 +146,146 @@ func (helper *BackendApplicationHelper) Start() error {
|
|||
}
|
||||
|
||||
helper.Backend.HandleUDPMessage(command, bytes)
|
||||
default:
|
||||
commandRaw, err := commonbackend.Unmarshal(helper.socket)
|
||||
case *commonbackend.Start:
|
||||
ok, err := helper.Backend.StartBackend(command.Arguments)
|
||||
|
||||
var (
|
||||
message string
|
||||
statusCode int
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
message = err.Error()
|
||||
statusCode = commonbackend.StatusFailure
|
||||
} else {
|
||||
statusCode = commonbackend.StatusSuccess
|
||||
}
|
||||
|
||||
response := &commonbackend.BackendStatusResponse{
|
||||
IsRunning: ok,
|
||||
StatusCode: statusCode,
|
||||
Message: message,
|
||||
}
|
||||
|
||||
responseMarshalled, err := commonbackend.Marshal(response)
|
||||
|
||||
if err != nil {
|
||||
log.Error("failed to marshal response: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
helper.socket.Write(responseMarshalled)
|
||||
case *commonbackend.Stop:
|
||||
ok, err := helper.Backend.StopBackend()
|
||||
|
||||
var (
|
||||
message string
|
||||
statusCode int
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
message = err.Error()
|
||||
statusCode = commonbackend.StatusFailure
|
||||
} else {
|
||||
statusCode = commonbackend.StatusSuccess
|
||||
}
|
||||
|
||||
response := &commonbackend.BackendStatusResponse{
|
||||
IsRunning: !ok,
|
||||
StatusCode: statusCode,
|
||||
Message: message,
|
||||
}
|
||||
|
||||
responseMarshalled, err := commonbackend.Marshal(response)
|
||||
|
||||
if err != nil {
|
||||
log.Error("failed to marshal response: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
helper.socket.Write(responseMarshalled)
|
||||
case *commonbackend.BackendStatusRequest:
|
||||
ok, err := helper.Backend.GetBackendStatus()
|
||||
|
||||
var (
|
||||
message string
|
||||
statusCode int
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
message = err.Error()
|
||||
statusCode = commonbackend.StatusFailure
|
||||
} else {
|
||||
statusCode = commonbackend.StatusSuccess
|
||||
}
|
||||
|
||||
response := &commonbackend.BackendStatusResponse{
|
||||
IsRunning: ok,
|
||||
StatusCode: statusCode,
|
||||
Message: message,
|
||||
}
|
||||
|
||||
responseMarshalled, err := commonbackend.Marshal(response)
|
||||
|
||||
if err != nil {
|
||||
log.Error("failed to marshal response: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
helper.socket.Write(responseMarshalled)
|
||||
case *commonbackend.AddProxy:
|
||||
id, ok, err := helper.Backend.StartProxy(command)
|
||||
var hasAnyFailed bool
|
||||
|
||||
if !ok {
|
||||
log.Warnf("failed to add proxy (%s:%d -> remote:%d): StartProxy returned into failure state", command.SourceIP, command.SourcePort, command.DestPort)
|
||||
hasAnyFailed = true
|
||||
} else if err != nil {
|
||||
log.Warnf("failed to add proxy (%s:%d -> remote:%d): %s", command.SourceIP, command.SourcePort, command.DestPort, err.Error())
|
||||
hasAnyFailed = true
|
||||
}
|
||||
|
||||
response := &datacommands.ProxyStatusResponse{
|
||||
ProxyID: id,
|
||||
IsActive: !hasAnyFailed,
|
||||
}
|
||||
|
||||
responseMarshalled, err := datacommands.Marshal(response)
|
||||
|
||||
if err != nil {
|
||||
log.Error("failed to marshal response: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
helper.socket.Write(responseMarshalled)
|
||||
case *commonbackend.CheckClientParameters:
|
||||
resp := helper.Backend.CheckParametersForConnections(command)
|
||||
resp.InResponseTo = "checkClientParameters"
|
||||
|
||||
byteData, err := commonbackend.Marshal(resp)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch command := commandRaw.(type) {
|
||||
case *commonbackend.Start:
|
||||
ok, err := helper.Backend.StartBackend(command.Arguments)
|
||||
|
||||
var (
|
||||
message string
|
||||
statusCode int
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
message = err.Error()
|
||||
statusCode = commonbackend.StatusFailure
|
||||
} else {
|
||||
statusCode = commonbackend.StatusSuccess
|
||||
}
|
||||
|
||||
response := &commonbackend.BackendStatusResponse{
|
||||
IsRunning: ok,
|
||||
StatusCode: statusCode,
|
||||
Message: message,
|
||||
}
|
||||
|
||||
responseMarshalled, err := commonbackend.Marshal(response)
|
||||
|
||||
if err != nil {
|
||||
log.Error("failed to marshal response: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
helper.socket.Write(responseMarshalled)
|
||||
case *commonbackend.Stop:
|
||||
ok, err := helper.Backend.StopBackend()
|
||||
|
||||
var (
|
||||
message string
|
||||
statusCode int
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
message = err.Error()
|
||||
statusCode = commonbackend.StatusFailure
|
||||
} else {
|
||||
statusCode = commonbackend.StatusSuccess
|
||||
}
|
||||
|
||||
response := &commonbackend.BackendStatusResponse{
|
||||
IsRunning: !ok,
|
||||
StatusCode: statusCode,
|
||||
Message: message,
|
||||
}
|
||||
|
||||
responseMarshalled, err := commonbackend.Marshal(response)
|
||||
|
||||
if err != nil {
|
||||
log.Error("failed to marshal response: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
helper.socket.Write(responseMarshalled)
|
||||
case *commonbackend.BackendStatusRequest:
|
||||
ok, err := helper.Backend.GetBackendStatus()
|
||||
|
||||
var (
|
||||
message string
|
||||
statusCode int
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
message = err.Error()
|
||||
statusCode = commonbackend.StatusFailure
|
||||
} else {
|
||||
statusCode = commonbackend.StatusSuccess
|
||||
}
|
||||
|
||||
response := &commonbackend.BackendStatusResponse{
|
||||
IsRunning: ok,
|
||||
StatusCode: statusCode,
|
||||
Message: message,
|
||||
}
|
||||
|
||||
responseMarshalled, err := commonbackend.Marshal(response)
|
||||
|
||||
if err != nil {
|
||||
log.Error("failed to marshal response: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
helper.socket.Write(responseMarshalled)
|
||||
case *commonbackend.AddProxy:
|
||||
id, ok, err := helper.Backend.StartProxy(command)
|
||||
var hasAnyFailed bool
|
||||
|
||||
if !ok {
|
||||
log.Warnf("failed to add proxy (%s:%d -> remote:%d): StartProxy returned into failure state", command.SourceIP, command.SourcePort, command.DestPort)
|
||||
hasAnyFailed = true
|
||||
} else if err != nil {
|
||||
log.Warnf("failed to add proxy (%s:%d -> remote:%d): %s", command.SourceIP, command.SourcePort, command.DestPort, err.Error())
|
||||
hasAnyFailed = true
|
||||
}
|
||||
|
||||
response := &datacommands.ProxyStatusResponse{
|
||||
ProxyID: id,
|
||||
IsActive: !hasAnyFailed,
|
||||
}
|
||||
|
||||
responseMarshalled, err := datacommands.Marshal(response)
|
||||
|
||||
if err != nil {
|
||||
log.Error("failed to marshal response: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
helper.socket.Write(responseMarshalled)
|
||||
case *commonbackend.CheckClientParameters:
|
||||
resp := helper.Backend.CheckParametersForConnections(command)
|
||||
resp.InResponseTo = "checkClientParameters"
|
||||
|
||||
byteData, err := commonbackend.Marshal(resp)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = helper.socket.Write(byteData); err != nil {
|
||||
return err
|
||||
}
|
||||
case *commonbackend.CheckServerParameters:
|
||||
resp := helper.Backend.CheckParametersForBackend(command.Arguments)
|
||||
resp.InResponseTo = "checkServerParameters"
|
||||
|
||||
byteData, err := commonbackend.Marshal(resp)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = helper.socket.Write(byteData); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
log.Warnf("Unsupported command recieved: %T", command)
|
||||
if _, err = helper.socket.Write(byteData); err != nil {
|
||||
return err
|
||||
}
|
||||
case *commonbackend.CheckServerParameters:
|
||||
resp := helper.Backend.CheckParametersForBackend(command.Arguments)
|
||||
resp.InResponseTo = "checkServerParameters"
|
||||
|
||||
byteData, err := commonbackend.Marshal(resp)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = helper.socket.Write(byteData); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
log.Warnf("Unsupported command recieved: %T", command)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,8 @@ type SSHRemoteAppBackend struct {
|
|||
tcpProxies map[uint16]*TCPProxy
|
||||
udpProxies map[uint16]*UDPProxy
|
||||
|
||||
isRunning bool
|
||||
|
||||
sock net.Conn
|
||||
}
|
||||
|
||||
|
@ -43,6 +45,8 @@ func (backend *SSHRemoteAppBackend) StartBackend(byte []byte) (bool, error) {
|
|||
backend.tcpProxies = map[uint16]*TCPProxy{}
|
||||
backend.udpProxies = map[uint16]*UDPProxy{}
|
||||
|
||||
backend.isRunning = true
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
|
@ -61,11 +65,12 @@ func (backend *SSHRemoteAppBackend) StopBackend() (bool, error) {
|
|||
delete(backend.udpProxies, udpProxyIndex)
|
||||
}
|
||||
|
||||
backend.isRunning = false
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (backend *SSHRemoteAppBackend) GetBackendStatus() (bool, error) {
|
||||
return true, nil
|
||||
return backend.isRunning, nil
|
||||
}
|
||||
|
||||
func (backend *SSHRemoteAppBackend) StartProxy(command *commonbackend.AddProxy) (uint16, bool, error) {
|
||||
|
@ -104,6 +109,8 @@ func (backend *SSHRemoteAppBackend) StartProxy(command *commonbackend.AddProxy)
|
|||
backend.tcpProxies[proxyID].connectionIDIndex++
|
||||
backend.tcpProxies[proxyID].connectionIDLock.Unlock()
|
||||
|
||||
backend.tcpProxies[proxyID].connections[connectionID] = conn
|
||||
|
||||
dataBuf := make([]byte, 65535)
|
||||
|
||||
onConnection := &datacommands.TCPConnectionOpened{
|
||||
|
@ -372,12 +379,14 @@ func (backend *SSHRemoteAppBackend) HandleTCPMessage(message *datacommands.TCPPr
|
|||
tcpProxy, ok := backend.tcpProxies[message.ProxyID]
|
||||
|
||||
if !ok {
|
||||
log.Warnf("could not find tcp proxy (ID %d)", message.ProxyID)
|
||||
return
|
||||
}
|
||||
|
||||
connection, ok := tcpProxy.connections[message.ConnectionID]
|
||||
|
||||
if !ok {
|
||||
log.Warnf("could not find tcp proxy (ID %d) with connection ID (%d)", message.ProxyID, message.ConnectionID)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue