mirror of
https://github.com/asmogo/nws.git
synced 2024-12-13 18:56:21 +00:00
Merge pull request #22 from asmogo/reverse_connect
Implement reverse connection handling in SOCKS5 server
This commit is contained in:
commit
c957f9144c
27
README.md
27
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 = '<public_ip>:<port>'
|
||||
```
|
||||
|
||||
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.
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
29
exit/exit.go
29
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.
|
||||
//
|
||||
|
5
go.mod
5
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
|
||||
)
|
||||
|
14
go.sum
14
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=
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
63
socks5/tcp.go
Normal file
63
socks5/tcp.go
Normal file
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user