~emersion/soju-dev

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch

[RFC PATCH] Add upstream connections over Tor

Details
Message ID
<20240415234454.11226-1-egor@opensrc.club>
DKIM signature
missing
Download raw message
Patch: +32 -7
where "Tor" means SOCKS5 with a hardcoded address
---
This is good enough for my purposes, but I understand it's probably
better to implement the ability to specify any proxy address.

So, should I make a more generic SOCKS5 patch? And if so, what would be
a good URL format for it? I'm thinking of using a query parameter to
specify the proxy host, like so:
ircs://example.com/?socks5=127.0.0.1:9050

 doc/soju.1.scd |  4 ++++
 go.mod         |  1 +
 go.sum         |  2 ++
 service.go     |  4 ++--
 upstream.go    | 26 ++++++++++++++++++++++----
 user.go        |  2 +-
 6 files changed, 32 insertions(+), 7 deletions(-)

diff --git a/doc/soju.1.scd b/doc/soju.1.scd
index f06d5f8..c818a15 100644
--- a/doc/soju.1.scd
+++ b/doc/soju.1.scd
@@ -262,6 +262,10 @@ character.
	- _[ircs://]<host>[:port]_ connects with TLS over TCP
	- _irc+insecure://<host>[:port]_ connects with plain-text TCP
	- _irc+unix:///<path>_ connects to a Unix socket
	- _ircs+tor://<host>[:port]_ connects with TLS over SOCKS5 proxy at
	  127.0.0.1:9050
	- _irc+tor://<host>[:port]_ connects with plain-text over SOCKS5 proxy at
	  127.0.0.1:9050

	For example, to connect to Libera Chat:

diff --git a/go.mod b/go.mod
index d6aa0cb..44bc574 100644
--- a/go.mod
+++ b/go.mod
@@ -14,6 +14,7 @@ require (
	github.com/pires/go-proxyproto v0.7.0
	github.com/prometheus/client_golang v1.18.0
	golang.org/x/crypto v0.21.0
	golang.org/x/net v0.21.0
	golang.org/x/time v0.5.0
	gopkg.in/irc.v4 v4.0.0
	modernc.org/sqlite v1.29.5
diff --git a/go.sum b/go.sum
index aff0fcc..117d641 100644
--- a/go.sum
+++ b/go.sum
@@ -71,6 +71,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
diff --git a/service.go b/service.go
index a39b276..d699366 100644
--- a/service.go
+++ b/service.go
@@ -500,9 +500,9 @@ func (fs *networkFlagSet) update(network *database.Network) error {
		if addrParts := strings.SplitN(*fs.Addr, "://", 2); len(addrParts) == 2 {
			scheme := addrParts[0]
			switch scheme {
			case "ircs", "irc+insecure", "unix":
			case "ircs", "irc+insecure", "unix", "ircs+tor", "irc+tor":
			default:
				return fmt.Errorf("unknown scheme %q (supported schemes: ircs, irc+insecure, unix)", scheme)
				return fmt.Errorf("unknown scheme %q (supported schemes: ircs, irc+insecure, unix, ircs+tor, irc+tor)", scheme)
			}
		}
		network.Addr = *fs.Addr
diff --git a/upstream.go b/upstream.go
index ca17ea8..8ac95f8 100644
--- a/upstream.go
+++ b/upstream.go
@@ -18,6 +18,8 @@ import (
	"strings"
	"time"

	"golang.org/x/net/proxy"

	"github.com/emersion/go-sasl"
	"gopkg.in/irc.v4"

@@ -250,7 +252,7 @@ func connectToUpstream(ctx context.Context, network *network) (*upstreamConn, er

	var netConn net.Conn
	switch u.Scheme {
	case "ircs":
	case "ircs", "ircs+tor":
		addr := u.Host
		host, _, err := net.SplitHostPort(u.Host)
		if err != nil {
@@ -314,7 +316,11 @@ func connectToUpstream(ctx context.Context, network *network) (*upstreamConn, er
		}

		logger.Printf("connecting to TLS server at address %q", addr)
		netConn, err = dialTCP(ctx, network.user, addr)
		if u.Scheme == "ircs+tor" {
			netConn, err = dialTor(ctx, addr)
		} else {
			netConn, err = dialTCP(ctx, network.user, addr)
		}
		if err != nil {
			return nil, err
		}
@@ -323,14 +329,18 @@ func connectToUpstream(ctx context.Context, network *network) (*upstreamConn, er
		// the new connection with identd ASAP. See:
		// https://todo.sr.ht/~emersion/soju/69#event-41859
		netConn = tls.Client(netConn, tlsConfig)
	case "irc+insecure":
	case "irc+insecure", "irc+tor":
		addr := u.Host
		if _, _, err := net.SplitHostPort(addr); err != nil {
			addr = u.Host + ":6667"
		}

		logger.Printf("connecting to plain-text server at address %q", addr)
		netConn, err = dialTCP(ctx, network.user, addr)
		if u.Scheme == "irc+tor" {
			netConn, err = dialTor(ctx, addr)
		} else {
			netConn, err = dialTCP(ctx, network.user, addr)
		}
		if err != nil {
			return nil, err
		}
@@ -373,6 +383,14 @@ func connectToUpstream(ctx context.Context, network *network) (*upstreamConn, er
	return uc, nil
}

func dialTor(ctx context.Context, addr string) (net.Conn, error) {
	dialer, err := proxy.SOCKS5("tcp", "127.0.0.1:9050", nil, proxy.Direct)
	if err != nil {
		return nil, err
	}
	return dialer.(proxy.ContextDialer).DialContext(ctx, "tcp", addr)
}

func dialTCP(ctx context.Context, user *user, addr string) (net.Conn, error) {
	var dialer net.Dialer
	upstreamUserIPs := user.srv.Config().UpstreamUserIPs
diff --git a/user.go b/user.go
index 8c0fd6e..d02da00 100644
--- a/user.go
+++ b/user.go
@@ -964,7 +964,7 @@ func (u *user) checkNetwork(record *database.Network) error {
		return fmt.Errorf("%v:// URL must not have a fragment", url.Scheme)
	}
	switch url.Scheme {
	case "ircs", "irc+insecure":
	case "ircs", "irc+insecure", "ircs+tor", "irc+tor":
		if url.Host == "" {
			return fmt.Errorf("%v:// URL must have a host", url.Scheme)
		}
-- 
2.39.2
Reply to thread Export thread (mbox)