Skip to content

Commit

Permalink
socks5: implement proxy through remote peer
Browse files Browse the repository at this point in the history
  • Loading branch information
pymq committed Aug 17, 2024
1 parent 3c44446 commit beb2814
Show file tree
Hide file tree
Showing 16 changed files with 621 additions and 10 deletions.
10 changes: 10 additions & 0 deletions application.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ type Application struct {
Api *api.Handler
AuthStatus *service.AuthStatus
Tunnel *service.Tunnel
SOCKS5 *service.SOCKS5
Dns *DNSService
}

Expand Down Expand Up @@ -116,10 +117,15 @@ func (a *Application) Init(ctx context.Context, tunDevice tun.Device) error {
a.Dns = NewDNSService(a.Conf, a.Eventbus, a.ctx, a.logger)
a.AuthStatus = service.NewAuthStatus(a.P2p, a.Conf, a.Eventbus)
a.Tunnel = service.NewTunnel(a.P2p, vpnDevice, a.Conf)
a.SOCKS5, err = service.NewSOCKS5(a.P2p, a.Conf)
if err != nil {
return fmt.Errorf("failed to init socks5: %v", err)
}

p2pHost.SetStreamHandler(protocol.GetStatusMethod, a.AuthStatus.StatusStreamHandler)
p2pHost.SetStreamHandler(protocol.AuthMethod, a.AuthStatus.AuthStreamHandler)
p2pHost.SetStreamHandler(protocol.TunnelPacketMethod, a.Tunnel.StreamHandler)
p2pHost.SetStreamHandler(protocol.Socks5PacketMethod, a.SOCKS5.ProxyStreamHandler)

awlevent.WrapSubscriptionToCallback(a.ctx, func(_ interface{}) {
a.Tunnel.RefreshPeersList()
Expand All @@ -135,6 +141,7 @@ func (a *Application) Init(ctx context.Context, tunDevice tun.Device) error {
go a.P2p.MaintainBackgroundConnections(a.ctx, a.Conf.P2pNode.ReconnectionIntervalSec*time.Second, a.Conf.KnownPeersIds)
go a.AuthStatus.BackgroundRetryAuthRequests(a.ctx)
go a.AuthStatus.BackgroundExchangeStatusInfo(a.ctx)
go a.SOCKS5.ServeConns(a.ctx)

if useAwldns {
interfaceName, err := a.vpnDevice.InterfaceName()
Expand Down Expand Up @@ -231,6 +238,9 @@ func (a *Application) Close() {
if a.Tunnel != nil {
a.Tunnel.Close()
}
if a.SOCKS5 != nil {
a.SOCKS5.Close()
}
if a.vpnDevice != nil {
err := a.vpnDevice.Close()
if err != nil {
Expand Down
78 changes: 78 additions & 0 deletions application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import (
"encoding/hex"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"runtime"
"sync"
Expand All @@ -27,6 +31,7 @@ import (
"github.com/quic-go/quic-go/integrationtests/tools/israce"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zapcore"
"golang.org/x/net/proxy"
"golang.zx2c4.com/wireguard/tun"

"github.com/anywherelan/awl/api"
Expand Down Expand Up @@ -211,6 +216,8 @@ func TestUpdateUseAsExitNodeConfig(t *testing.T) {
return peer2Config.AllowedUsingAsExitNode
}, 15*time.Second, 100*time.Millisecond)

ts.Equal(peer2.PeerID(), peer1.app.Conf.SOCKS5.UsingPeerID)

// allow from peer1, check that peer2 got our config
err = peer1.api.UpdatePeerSettings(entity.UpdatePeerSettingsRequest{
PeerID: peer2.PeerID(),
Expand All @@ -227,6 +234,8 @@ func TestUpdateUseAsExitNodeConfig(t *testing.T) {
return peer1Config.AllowedUsingAsExitNode && peer1Config.WeAllowUsingAsExitNode
}, 15*time.Second, 100*time.Millisecond)

ts.Equal(peer1.PeerID(), peer2.app.Conf.SOCKS5.UsingPeerID)

// disallow from peer2, check that peer1 got our new config
err = peer2.api.UpdatePeerSettings(entity.UpdatePeerSettingsRequest{
PeerID: peer1.PeerID(),
Expand All @@ -242,6 +251,59 @@ func TestUpdateUseAsExitNodeConfig(t *testing.T) {

return !peer2Config.AllowedUsingAsExitNode && peer2Config.WeAllowUsingAsExitNode
}, 15*time.Second, 100*time.Millisecond)

ts.Equal("", peer1.app.Conf.SOCKS5.UsingPeerID)
testProxy(ts, peer1.app.Conf.SOCKS5.ListenAddress, true)

testProxy(ts, peer2.app.Conf.SOCKS5.ListenAddress, false)
}

func testProxy(ts *TestSuite, proxyAddr string, expectSocksErr bool) {
// setup mock server
addr := pickFreeAddr(ts.t)
mux := http.NewServeMux()
mux.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprintf(w, "test text")
})
httpServer := &http.Server{Addr: addr, Handler: mux}
go func() {
_ = httpServer.ListenAndServe()
}()
defer func() {
httpServer.Shutdown(context.Background())
}()

// client
dialer, err := proxy.SOCKS5("tcp", proxyAddr, nil, nil)
ts.NoError(err)
httpTransport := &http.Transport{DialContext: dialer.(proxy.ContextDialer).DialContext}
httpClient := http.Client{Transport: httpTransport}

// test
for range 5 {
response, err := httpClient.Get(fmt.Sprintf("http://%s/test", addr))
if expectSocksErr {
ts.Error(err)

var urlErr *url.Error
ts.ErrorAs(err, &urlErr)
var netErr *net.OpError
ts.ErrorAs(urlErr.Err, &netErr)

ts.Equal("socks connect", netErr.Op)
ts.EqualError(netErr.Err, "unknown error general SOCKS server failure")

continue
}

ts.NoError(err)
body, err := io.ReadAll(response.Body)
ts.NoError(err)
err = response.Body.Close()
ts.NoError(err)

ts.Equal("test text", string(body))
}
}

func TestTunnelPackets(t *testing.T) {
Expand Down Expand Up @@ -412,6 +474,12 @@ func (ts *TestSuite) newTestPeer(disableLogging bool) testPeer {
multiaddr.StringCast("/ip4/127.0.0.1/udp/0/quic-v1"),
})
app.Conf.P2pNode.BootstrapPeers = ts.bootstrapAddrsStr
app.Conf.SOCKS5 = config.SOCKS5Config{
ListenerEnabled: true,
ProxyingEnabled: true,
ListenAddress: pickFreeAddr(ts.t),
UsingPeerID: "",
}

testTUN := NewTestTUN()
err := app.Init(context.Background(), testTUN.TUN())
Expand Down Expand Up @@ -601,3 +669,13 @@ func (t *testTun) Close() error {
close(t.t.events)
return nil
}

func pickFreeAddr(t testing.TB) string {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer l.Close()

return l.Addr().String()
}
1 change: 1 addition & 0 deletions cmd/awl-tray/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ require (
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/haxii/socks5 v1.0.0 // indirect
github.com/huin/goupnp v1.3.0 // indirect
github.com/illarion/gonotify v1.0.1 // indirect
github.com/ipfs/boxo v0.10.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions cmd/awl-tray/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/haxii/socks5 v1.0.0 h1:78BIzd4lHibdRNOKdMwKCnnsgYLW9SeotqU+nMhWSSo=
github.com/haxii/socks5 v1.0.0/go.mod h1:6O9Ba2yrLlvuSe/L1e84eZI8cPw6H+q1Ilr4hjgm4uY=
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
github.com/illarion/gonotify v1.0.1 h1:F1d+0Fgbq/sDWjj/r66ekjDG+IDeecQKUFH4wNwsoio=
Expand Down
11 changes: 11 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ const (
AdminHttpServerIP = "127.0.0.66"
AdminHttpServerListenAddress = "127.0.0.66:80"

defaultSOCKS5ListenAddress = "127.0.0.66:8080"

DefaultPeerAlias = "peer"
)

Expand All @@ -49,6 +51,7 @@ type (
HttpListenOnAdminHost bool `json:"httpListenOnAdminHost"`
P2pNode P2pNodeConfig `json:"p2pNode"`
VPNConfig VPNConfig `json:"vpn"`
SOCKS5 SOCKS5Config `json:"socks5"`
KnownPeers map[string]KnownPeer `json:"knownPeers"`
BlockedPeers map[string]BlockedPeer `json:"blockedPeers"`
Update UpdateConfig `json:"update"`
Expand All @@ -70,6 +73,14 @@ type (
InterfaceName string `json:"interfaceName"`
IPNet string `json:"ipNet"`
}
SOCKS5Config struct {
ListenerEnabled bool `json:"listenerEnabled"`
// allow using my host as proxy
ProxyingEnabled bool `json:"proxyingEnabled"`
ListenAddress string `json:"listenAddress"`
// peer that is set as proxy
UsingPeerID string `json:"usingPeerID"`
}
KnownPeer struct {
// Hex-encoded multihash representing a peer ID
PeerID string `json:"peerId"`
Expand Down
8 changes: 8 additions & 0 deletions config/other.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,14 @@ func setDefaults(conf *Config, bus awlevent.Bus) {
}
}

if conf.SOCKS5 == (SOCKS5Config{}) {
conf.SOCKS5.ListenerEnabled = true
conf.SOCKS5.ProxyingEnabled = true
}
if conf.SOCKS5.ListenAddress == "" {
conf.SOCKS5.ListenAddress = defaultSOCKS5ListenAddress
}

uniqAliases := make(map[string]struct{}, len(conf.KnownPeers))
if conf.KnownPeers == nil {
conf.KnownPeers = make(map[string]KnownPeer)
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ require (
github.com/anywherelan/ts-dns v0.0.0-20240721135326-6d6b7b811853
github.com/go-playground/validator/v10 v10.22.0
github.com/google/go-querystring v1.1.0
github.com/haxii/socks5 v1.0.0
github.com/ipfs/go-datastore v0.6.0
github.com/ipfs/go-log/v2 v2.5.1
github.com/labstack/echo/v4 v4.12.0
github.com/libp2p/go-buffer-pool v0.1.0
github.com/libp2p/go-libp2p v0.35.1
github.com/libp2p/go-libp2p-kad-dht v0.25.2
github.com/libp2p/go-libp2p-kbucket v0.6.3
Expand Down Expand Up @@ -83,7 +85,6 @@ require (
github.com/koron/go-ssdp v0.0.4 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/libp2p/go-cidranger v1.1.0 // indirect
github.com/libp2p/go-flow-metrics v0.1.0 // indirect
github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/haxii/socks5 v1.0.0 h1:78BIzd4lHibdRNOKdMwKCnnsgYLW9SeotqU+nMhWSSo=
github.com/haxii/socks5 v1.0.0/go.mod h1:6O9Ba2yrLlvuSe/L1e84eZI8cPw6H+q1Ilr4hjgm4uY=
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
github.com/illarion/gonotify v1.0.1 h1:F1d+0Fgbq/sDWjj/r66ekjDG+IDeecQKUFH4wNwsoio=
Expand Down
5 changes: 3 additions & 2 deletions protocol/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import (
)

const (
Version = "0.3.0"
basePath = "/awl/" + Version
version = "0.3.0"
basePath = "/awl/" + version

AuthMethod protocol.ID = basePath + "/auth/"
GetStatusMethod protocol.ID = basePath + "/status/"
TunnelPacketMethod protocol.ID = basePath + "/tunnel/"
Socks5PacketMethod protocol.ID = basePath + "/socks5/"
)

type (
Expand Down
31 changes: 24 additions & 7 deletions service/auth_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,7 @@ func (s *AuthStatus) StatusStreamHandler(stream network.Stream) {
// get the latest peer config to reduce race time between get and upsert (without locking)
// TODO: fix race completely
knownPeer, _ = s.conf.GetPeer(peerID)
newPeer := s.processPeerStatusInfo(knownPeer, oppositePeerInfo)
s.conf.UpsertPeer(newPeer)
s.processPeerStatusInfo(knownPeer, oppositePeerInfo)
}

func (s *AuthStatus) ExchangeNewStatusInfo(ctx context.Context, remotePeerID peer.ID, knownPeer config.KnownPeer) error {
Expand Down Expand Up @@ -141,8 +140,7 @@ func (s *AuthStatus) ExchangeNewStatusInfo(ctx context.Context, remotePeerID pee
// get the latest peer config to reduce race time between get and upsert (without locking)
// TODO: fix race completely
knownPeer, _ = s.conf.GetPeer(remotePeerID.String())
newPeer := s.processPeerStatusInfo(knownPeer, oppositePeerInfo)
s.conf.UpsertPeer(newPeer)
s.processPeerStatusInfo(knownPeer, oppositePeerInfo)

return nil
}
Expand All @@ -168,11 +166,19 @@ func (s *AuthStatus) createPeerInfo(peer config.KnownPeer, myPeerName string, de
return myPeerInfo
}

func (s *AuthStatus) processPeerStatusInfo(peer config.KnownPeer, peerInfo protocol.PeerStatusInfo) config.KnownPeer {
func (s *AuthStatus) processPeerStatusInfo(peer config.KnownPeer, peerInfo protocol.PeerStatusInfo) {
peer.LastSeen = time.Now()
if peerInfo.Declined {
peer.Declined = true
return peer
s.conf.UpsertPeer(peer)

s.conf.Lock()
defer s.conf.Unlock()
if s.conf.SOCKS5.UsingPeerID == peer.PeerID {
s.conf.SOCKS5.UsingPeerID = ""
}

return
}
peer.Name = peerInfo.Name
peer.Confirmed = true
Expand All @@ -185,7 +191,18 @@ func (s *AuthStatus) processPeerStatusInfo(peer config.KnownPeer, peerInfo proto
}
peer.AllowedUsingAsExitNode = peerInfo.AllowUsingAsExitNode

return peer
s.conf.UpsertPeer(peer)

s.conf.Lock()
defer s.conf.Unlock()

if peer.AllowedUsingAsExitNode && s.conf.SOCKS5.UsingPeerID == "" {
s.conf.SOCKS5.UsingPeerID = peer.PeerID
}

if !peer.AllowedUsingAsExitNode && s.conf.SOCKS5.UsingPeerID == peer.PeerID {
s.conf.SOCKS5.UsingPeerID = ""
}
}

func (s *AuthStatus) AuthStreamHandler(stream network.Stream) {
Expand Down
Loading

0 comments on commit beb2814

Please sign in to comment.