diff --git a/README.md b/README.md index 76041e1..e09af3f 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,12 @@ To set up using Docker Compose, run the following command: docker compose up -d --build ``` -This will start an example environment, including the entry node, exit node, and a backend service. +This will start an example environment, including: +* Entry node +* Exit node +* Exit node with https reverse proxy +* [Cashu Nutshell](https://github.com/cashubtc/nutshell) (backend service) +* [nostr-relay](https://github.com/scsibug/nostr-rs-relay) You can run the following commands to receive your nprofiles: @@ -65,9 +70,9 @@ When using https, the entry node can be used as a service, since the operator wi ## Build from source -The exit node must be set up to make the services reachable via Nostr. +The exit node must be set up to make your services reachable via Nostr. -### Configuration +### Exit node Configuration Configuration should be completed using environment variables. Alternatively, you can create a `.env` file in the current working directory with the following content: @@ -77,7 +82,7 @@ NOSTR_PRIVATE_KEY = "EXITPRIVATEHEX" BACKEND_HOST = 'localhost:3338' ``` -- `NOSTR_RELAYS`: A list of nostr relays to publish events to. Will only be used if there was no nprofile in the +- `NOSTR_RELAYS`: A list of nostr relays to publish events to. Will only be used if there was no relay data in the request. - `NOSTR_PRIVATE_KEY`: The private key to sign the events - `BACKEND_HOST`: The host of the backend to forward requests to @@ -92,18 +97,18 @@ If your backend services support TLS, your service can now start using TLS encry --- +### Entry node Configuration + To run an entry node for accessing NWS services behind exit nodes, use the following command: ``` go run cmd/entry/main.go ``` - -#### Entry node Configuration - -If you used environment variables, no further configuration is needed. -For `.env` file configurations, do so in the current working directory with the following content: +If you don't want to use the `PUBLIC_ADDRESS` feature, no further configuration is needed. ``` -NOSTR_RELAYS = 'ws://localhost:6666;wss://relay.com' +PUBLIC_ADDRESS = ':' ``` -Here, NOSTR_RELAYS is a list of nostr relays to publish events to and will only be used if there was no nprofile in the request. +- `PUBLIC_ADDRESS`: This can be set if the entry node is publicly available. When set, the entry node will additionally bind to this address. Exit node discovery will still be done using Nostr. Once a connection is established, this public address will be used to transmit further data. +- `NOSTR_RELAYS`: A list of nostr relays to publish events to. Will only be used if there was no relay data in the + request. diff --git a/cmd/entry/proxy.go b/cmd/entry/proxy.go index 6915f63..29bd6ec 100644 --- a/cmd/entry/proxy.go +++ b/cmd/entry/proxy.go @@ -9,7 +9,7 @@ import ( func main() { // load the configuration // from the environment - cfg, err := config.LoadConfig[config.ProxyConfig]() + cfg, err := config.LoadConfig[config.EntryConfig]() if err != nil { panic(err) } diff --git a/config/config.go b/config/config.go index 161c76e..d9caf3d 100644 --- a/config/config.go +++ b/config/config.go @@ -9,8 +9,9 @@ import ( "github.com/joho/godotenv" ) -type ProxyConfig struct { - NostrRelays []string `env:"NOSTR_RELAYS" envSeparator:";"` +type EntryConfig struct { + NostrRelays []string `env:"NOSTR_RELAYS" envSeparator:";"` + PublicAddress string `env:"PUBLIC_ADDRESS"` } type ExitConfig struct { diff --git a/exit/exit.go b/exit/exit.go index a0e4c50..62d9b84 100644 --- a/exit/exit.go +++ b/exit/exit.go @@ -183,6 +183,8 @@ func (e *Exit) processMessage(ctx context.Context, msg nostr.IncomingEvent) { switch protocolMessage.Type { case protocol.MessageConnect: e.handleConnect(ctx, msg, protocolMessage, false) + case protocol.MessageConnectReverse: + e.handleConnectReverse(ctx, protocolMessage, false) case protocol.MessageTypeSocks5: e.handleSocks5ProxyMessage(msg, protocolMessage) } @@ -226,6 +228,33 @@ func (e *Exit) handleConnect(ctx context.Context, msg nostr.IncomingEvent, proto go socks5.Proxy(connection, dst, nil) } +func (e *Exit) handleConnectReverse(ctx context.Context, protocolMessage *protocol.Message, isTLS bool) { + e.mutexMap.Lock(protocolMessage.Key.String()) + defer e.mutexMap.Unlock(protocolMessage.Key.String()) + connection, err := net.Dial("tcp", protocolMessage.Destination) + if err != nil { + return + } + var dst net.Conn + if isTLS { + conf := tls.Config{InsecureSkipVerify: true} + dst, err = tls.Dial("tcp", e.config.BackendHost, &conf) + } else { + dst, err = net.Dial("tcp", e.config.BackendHost) + } + if err != nil { + slog.Error("could not connect to backend", "error", err) + return + } + + _, err = connection.Write([]byte(protocolMessage.Key.String())) + if err != nil { + return + } + go socks5.Proxy(dst, connection, nil) + go socks5.Proxy(connection, dst, nil) +} + // handleSocks5ProxyMessage handles the SOCKS5 proxy message by writing it to the destination connection. // If the destination connection does not exist, the function returns without doing anything. // diff --git a/go.mod b/go.mod index cc628cf..7896e92 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/samber/lo v1.45.0 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 - golang.org/x/net v0.23.0 + golang.org/x/net v0.27.0 ) require ( @@ -26,6 +26,8 @@ require ( github.com/gobwas/ws v1.2.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/kr/pretty v0.1.0 // indirect + github.com/kr/text v0.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect @@ -35,5 +37,6 @@ require ( golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 // indirect golang.org/x/sys v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index b9fae80..a1f34df 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,7 @@ github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46f github.com/caarlos0/env/v11 v11.0.0 h1:ZIlkOjuL3xoZS0kmUJlF74j2Qj8GMOq3CDLX/Viak8Q= github.com/caarlos0/env/v11 v11.0.0/go.mod h1:2RC3HQu8BQqtEK3V4iHPxj0jOdWdbPpWJ6pOueeU1xM= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -69,6 +70,12 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/nbd-wtf/go-nostr v0.30.2 h1:dG/2X52/XDg+7phZH+BClcvA5D+S6dXvxJKkBaySEzI= @@ -114,8 +121,8 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -143,8 +150,9 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/netstr/dial.go b/netstr/dial.go index b7ffd58..103b2c1 100644 --- a/netstr/dial.go +++ b/netstr/dial.go @@ -11,22 +11,28 @@ import ( "strings" ) +type DialOptions struct { + Pool *nostr.SimplePool + PublicAddress string + ConnectionID uuid.UUID + MessageType protocol.MessageType +} + // DialSocks connects to a destination using the provided SimplePool and returns a Dialer function. // It creates a new Connection using the specified context, private key, destination address, subscription flag, and connectionID. // It parses the destination address to get the public key and relays. // It creates a signed event using the private key, public key, and destination address. // It ensures that the relays are available in the pool and publishes the signed event to each relay. // Finally, it returns the Connection and nil error. If there are any errors, nil connection and the error are returned. -func DialSocks(pool *nostr.SimplePool) func(ctx context.Context, net_, addr string) (net.Conn, error) { +func DialSocks(options DialOptions) func(ctx context.Context, net_, addr string) (net.Conn, error) { return func(ctx context.Context, net_, addr string) (net.Conn, error) { addr = strings.ReplaceAll(addr, ".", "") - connectionID := uuid.New() key := nostr.GeneratePrivateKey() connection := NewConnection(ctx, WithPrivateKey(key), WithDst(addr), WithSub(), - WithUUID(connectionID)) + WithUUID(options.ConnectionID)) publicKey, relays, err := ParseDestination(addr) if err != nil { @@ -39,16 +45,20 @@ func DialSocks(pool *nostr.SimplePool) func(ctx context.Context, net_, addr stri return nil, err } opts := []protocol.MessageOption{ - protocol.WithType(protocol.MessageConnect), - protocol.WithUUID(connectionID), - protocol.WithDestination(addr), + protocol.WithType(options.MessageType), + protocol.WithUUID(options.ConnectionID), + } + if options.PublicAddress != "" { + opts = append(opts, protocol.WithDestination(options.PublicAddress)) + } else { + opts = append(opts, protocol.WithDestination(addr)) // todo -- use public key instead } ev, err := signer.CreateSignedEvent(publicKey, protocol.KindEphemeralEvent, nostr.Tags{nostr.Tag{"p", publicKey}}, opts...) for _, relayUrl := range relays { - relay, err := pool.EnsureRelay(relayUrl) + relay, err := options.Pool.EnsureRelay(relayUrl) if err != nil { slog.Error("error creating relay", err) continue diff --git a/protocol/message.go b/protocol/message.go index ddacdf5..7ecf161 100644 --- a/protocol/message.go +++ b/protocol/message.go @@ -8,8 +8,9 @@ import ( type MessageType string var ( - MessageTypeSocks5 = MessageType("SOCKS5") - MessageConnect = MessageType("CONNECT") + MessageTypeSocks5 = MessageType("SOCKS5") + MessageConnect = MessageType("CONNECT") + MessageConnectReverse = MessageType("CONNECTR") ) type Message struct { diff --git a/protocol/signer.go b/protocol/signer.go index f696b97..68050a3 100644 --- a/protocol/signer.go +++ b/protocol/signer.go @@ -11,8 +11,7 @@ const KindEphemeralEvent int = 38333 // EventSigner represents a signer that can create and sign events. // -// EventSigner provides methods for creating unsigned events, creating signed events, -// and decrypting and writing events received from an exit node. +// EventSigner provides methods for creating unsigned events, creating signed events type EventSigner struct { PublicKey string privateKey string diff --git a/proxy/proxy.go b/proxy/proxy.go index e1695d8..00b4a51 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -11,14 +11,14 @@ import ( ) type Proxy struct { - config *config.ProxyConfig // the configuration for the gateway + config *config.EntryConfig // the configuration for the gateway // a list of nostr relays to publish events to relays []*nostr.Relay // deprecated -- should be used for default relay configuration pool *nostr.SimplePool socksServer *socks5.Server } -func New(ctx context.Context, config *config.ProxyConfig) *Proxy { +func New(ctx context.Context, config *config.EntryConfig) *Proxy { s := &Proxy{ config: config, pool: nostr.NewSimplePool(ctx), @@ -32,7 +32,7 @@ func New(ctx context.Context, config *config.ProxyConfig) *Proxy { BindIP: net.IP{0, 0, 0, 0}, Logger: nil, Dial: nil, - }, s.pool) + }, s.pool, config) if err != nil { panic(err) } diff --git a/socks5/auth_test.go b/socks5/auth_test.go index 962afbe..bf526e9 100644 --- a/socks5/auth_test.go +++ b/socks5/auth_test.go @@ -2,6 +2,7 @@ package socks5 import ( "bytes" + "github.com/asmogo/nws/config" "github.com/nbd-wtf/go-nostr" "testing" ) @@ -11,7 +12,7 @@ func TestNoAuth(t *testing.T) { req.Write([]byte{1, NoAuth}) var resp bytes.Buffer - s, _ := New(&Config{}, &nostr.SimplePool{}) + s, _ := New(&Config{}, &nostr.SimplePool{}, &config.EntryConfig{}) ctx, err := s.authenticate(&resp, req) if err != nil { t.Fatalf("err: %v", err) @@ -39,7 +40,7 @@ func TestPasswordAuth_Valid(t *testing.T) { cator := UserPassAuthenticator{Credentials: cred} - s, _ := New(&Config{AuthMethods: []Authenticator{cator}}, &nostr.SimplePool{}) + s, _ := New(&Config{AuthMethods: []Authenticator{cator}}, &nostr.SimplePool{}, &config.EntryConfig{}) ctx, err := s.authenticate(&resp, req) if err != nil { @@ -75,7 +76,7 @@ func TestPasswordAuth_Invalid(t *testing.T) { "foo": "bar", } cator := UserPassAuthenticator{Credentials: cred} - s, _ := New(&Config{AuthMethods: []Authenticator{cator}}, &nostr.SimplePool{}) + s, _ := New(&Config{AuthMethods: []Authenticator{cator}}, &nostr.SimplePool{}, &config.EntryConfig{}) ctx, err := s.authenticate(&resp, req) if err != UserAuthFailed { @@ -102,7 +103,7 @@ func TestNoSupportedAuth(t *testing.T) { } cator := UserPassAuthenticator{Credentials: cred} - s, _ := New(&Config{AuthMethods: []Authenticator{cator}}, &nostr.SimplePool{}) + s, _ := New(&Config{AuthMethods: []Authenticator{cator}}, &nostr.SimplePool{}, &config.EntryConfig{}) ctx, err := s.authenticate(&resp, req) if err != NoSupportedAuth { diff --git a/socks5/request.go b/socks5/request.go index b18dafc..194115f 100644 --- a/socks5/request.go +++ b/socks5/request.go @@ -4,6 +4,8 @@ import ( "context" "fmt" "github.com/asmogo/nws/netstr" + "github.com/asmogo/nws/protocol" + "github.com/google/uuid" "io" "net" "strconv" @@ -164,11 +166,23 @@ func (s *Server) handleConnect(ctx context.Context, conn net.Conn, req *Request) } else { ctx = ctx_ } - + ch := make(chan net.Conn) // Attempt to connect + connectionID := uuid.New() + options := netstr.DialOptions{ + Pool: s.pool, + PublicAddress: s.config.entryConfig.PublicAddress, + ConnectionID: connectionID, + } dial := s.config.Dial if dial == nil { - dial = netstr.DialSocks(s.pool) + if s.tcpListener != nil { + s.tcpListener.AddConnectChannel(connectionID, ch) + options.MessageType = protocol.MessageConnectReverse + } else { + options.MessageType = protocol.MessageConnect + } + dial = netstr.DialSocks(options) } target, err := dial(ctx, "tcp", req.realDestAddr.FQDN) if err != nil { @@ -192,7 +206,13 @@ func (s *Server) handleConnect(ctx context.Context, conn net.Conn, req *Request) if err := SendReply(conn, successReply, &bind); err != nil { return fmt.Errorf("failed to send reply: %v", err) } - + // read + if options.MessageType == protocol.MessageConnectReverse { + // wait for the connection + // in this case, our target needs to be the reversed tcp connection + target = <-ch + defer target.Close() + } // Start proxying errCh := make(chan error, 2) go Proxy(target, conn, errCh) diff --git a/socks5/socks5.go b/socks5/socks5.go index 1bbb4f4..dca76a0 100644 --- a/socks5/socks5.go +++ b/socks5/socks5.go @@ -3,6 +3,7 @@ package socks5 import ( "bufio" "fmt" + "github.com/asmogo/nws/config" "github.com/nbd-wtf/go-nostr" "log" "net" @@ -49,6 +50,8 @@ type Config struct { // Optional function for dialing out Dial func(ctx context.Context, network, addr string) (net.Conn, error) + + entryConfig *config.EntryConfig } var ErrorNoServerAvailable = fmt.Errorf("no socks server available") @@ -59,10 +62,11 @@ type Server struct { config *Config authMethods map[uint8]Authenticator pool *nostr.SimplePool + tcpListener *TCPListener } // New creates a new Server and potentially returns an error -func New(conf *Config, pool *nostr.SimplePool) (*Server, error) { +func New(conf *Config, pool *nostr.SimplePool, config *config.EntryConfig) (*Server, error) { // Ensure we have at least one authentication method enabled if len(conf.AuthMethods) == 0 { if conf.Credentials != nil { @@ -86,12 +90,22 @@ func New(conf *Config, pool *nostr.SimplePool) (*Server, error) { if conf.Logger == nil { conf.Logger = log.New(os.Stdout, "", log.LstdFlags) } + if conf.entryConfig == nil { + conf.entryConfig = config + } server := &Server{ config: conf, pool: pool, } - + if conf.entryConfig.PublicAddress != "" { + listener, err := NewTCPListener(conf.entryConfig.PublicAddress) + if err != nil { + return nil, err + } + go listener.Start() + server.tcpListener = listener + } server.authMethods = make(map[uint8]Authenticator) for _, a := range conf.AuthMethods { diff --git a/socks5/tcp.go b/socks5/tcp.go new file mode 100644 index 0000000..813dfe8 --- /dev/null +++ b/socks5/tcp.go @@ -0,0 +1,63 @@ +package socks5 + +import ( + "github.com/google/uuid" + "github.com/puzpuzpuz/xsync/v3" + "log/slog" + "net" +) + +type TCPListener struct { + listener net.Listener + connectChannels *xsync.MapOf[string, chan net.Conn] // todo -- use [16]byte for uuid instead of string +} + +func NewTCPListener(address string) (*TCPListener, error) { + l, err := net.Listen("tcp", address) + if err != nil { + return nil, err + } + return &TCPListener{ + listener: l, + connectChannels: xsync.NewMapOf[string, chan net.Conn](), + }, nil +} + +func (l *TCPListener) AddConnectChannel(uuid uuid.UUID, ch chan net.Conn) { + l.connectChannels.Store(uuid.String(), ch) +} + +// Start starts the listener +func (l *TCPListener) Start() { + for { + conn, err := l.listener.Accept() + if err != nil { + return + } + go l.handleConnection(conn) + } +} + +// handleConnection handles the connection +func (l *TCPListener) handleConnection(conn net.Conn) { + //defer conn.Close() + for { + // read uuid from the connection + readbuffer := make([]byte, 36) + + _, err := conn.Read(readbuffer) + if err != nil { + return + } + + // check if uuid is in the map + ch, ok := l.connectChannels.Load(string(readbuffer)) + if !ok { + slog.Error("uuid not found in map") + return + } + // send the connection to the channel + ch <- conn + return + } +}