From 92c019abcba6ea7dc1434f007dc33d2481d1562d Mon Sep 17 00:00:00 2001 From: jwijenbergh Date: Wed, 23 Oct 2024 11:31:39 +0200 Subject: [PATCH 1/2] All: Refactor to latest ProxyGuard --- client/client.go | 8 -- client/proxy.go | 125 ------------------------ client/proxy_test.go | 36 ------- exports/exports.go | 82 +++++++++++----- exports/exports.h | 11 +-- go.mod | 10 +- go.sum | 10 ++ internal/api/api_test.go | 4 +- internal/server/server.go | 2 +- internal/wireguard/wireguard.go | 15 +-- proxy/proxy.go | 60 ++++++++++++ types/server/server.go | 5 +- wrappers/python/eduvpn_common/loader.py | 23 ++++- wrappers/python/eduvpn_common/main.py | 40 +++++--- wrappers/python/eduvpn_common/types.py | 29 +++++- 15 files changed, 224 insertions(+), 236 deletions(-) delete mode 100644 client/proxy.go delete mode 100644 client/proxy_test.go create mode 100644 proxy/proxy.go diff --git a/client/client.go b/client/client.go index e5a39c07..e8fc02ce 100644 --- a/client/client.go +++ b/client/client.go @@ -49,9 +49,6 @@ type Client struct { // cfg is the config cfg *config.Config - // proxy is proxyguard - proxy Proxy - mu sync.Mutex discoMan *discovery.Manager @@ -557,11 +554,6 @@ func (c *Client) retrieveTokens(sid string, t srvtypes.Type) (*eduoauth.Token, e // Cleanup cleans up the VPN connection by sending a /disconnect func (c *Client) Cleanup(ck *cookie.Cookie) error { defer c.TrySave() - // cleanup proxyguard - cerr := c.proxy.Cancel() - if cerr != nil { - log.Logger.Debugf("ProxyGuard cancel gave an error: %v", cerr) - } srv, err := c.Servers.CurrentServer() if err != nil { return i18nerr.WrapInternal(err, "The current server was not found when cleaning up the connection") diff --git a/client/proxy.go b/client/proxy.go deleted file mode 100644 index 2a900e06..00000000 --- a/client/proxy.go +++ /dev/null @@ -1,125 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "errors" - "sync" - - "codeberg.org/eduVPN/proxyguard" - - "github.com/eduvpn/eduvpn-common/i18nerr" - httpw "github.com/eduvpn/eduvpn-common/internal/http" - "github.com/eduvpn/eduvpn-common/internal/log" - "github.com/eduvpn/eduvpn-common/types/cookie" -) - -// ProxyLogger is defined here such that we can update the proxyguard logger -type ProxyLogger struct{} - -// Logf logs a message with parameters -func (pl *ProxyLogger) Logf(msg string, params ...interface{}) { - log.Logger.Infof("[Proxyguard] "+msg, params...) -} - -// Log logs a message -func (pl *ProxyLogger) Log(msg string) { - log.Logger.Infof("[Proxyguard] %s", msg) -} - -// Proxy is a wrapper around ProxyGuard -// that has the client -// and a cancel for cancellation by common -// and a mutex to protect against race conditions -type Proxy struct { - c *proxyguard.Client - mu sync.Mutex - cancel context.CancelFunc -} - -// NewClient creates a new ProxyGuard wrapper from client `c` -func (p *Proxy) NewClient(c *proxyguard.Client) { - p.mu.Lock() - defer p.mu.Unlock() - p.c = c -} - -// Delete sets the inner client to nil -func (p *Proxy) Delete() { - p.mu.Lock() - defer p.mu.Unlock() - p.c = nil -} - -// ErrNoProxyGuardCancel indicates that no ProxyGuard cancel function -// was ever defined. You probably forgot to call `Tunnel` -var ErrNoProxyGuardCancel = errors.New("no ProxyGuard cancel function") - -// Cancel cancels a running ProxyGuard tunnel -// it returns an error if it cannot be canceled -func (p *Proxy) Cancel() error { - p.mu.Lock() - defer p.mu.Unlock() - if p.cancel == nil { - return ErrNoProxyGuardCancel - } - p.cancel() - p.cancel = nil - return nil -} - -// ErrNoProxyGuardClient is an error that is returned when no ProxyGuard client is created -var ErrNoProxyGuardClient = errors.New("no ProxyGuard client created") - -// Tunnel is a wrapper around ProxyGuard tunnel that -// that creates a new context that can be canceled -func (p *Proxy) Tunnel(ctx context.Context, peer string) error { - p.mu.Lock() - if p.c == nil { - p.mu.Unlock() - return ErrNoProxyGuardClient - } - cctx, cf := context.WithCancel(ctx) - p.cancel = cf - client := *p.c - p.mu.Unlock() - defer func() { - p.mu.Lock() - p.cancel = nil - p.mu.Unlock() - }() - // we set peer IPs to nil here as proxyguard already does a DNS request for us - return client.Tunnel(cctx, peer, nil) -} - -// StartProxyguard starts proxyguard for proxied WireGuard connections -func (c *Client) StartProxyguard(ck *cookie.Cookie, listen string, tcpsp int, peer string, gotFD func(fd int, pips string), ready func()) error { - var err error - proxyguard.UpdateLogger(&ProxyLogger{}) - - proxyc := proxyguard.Client{ - Listen: listen, - TCPSourcePort: tcpsp, - SetupSocket: func(fd int, pips []string) { - if gotFD == nil { - return - } - b, err := json.Marshal(pips) - if err != nil { - log.Logger.Errorf("marshalling peer IPs failed: %v", err) - return - } - gotFD(fd, string(b)) - }, - UserAgent: httpw.UserAgent, - Ready: ready, - } - - c.proxy.NewClient(&proxyc) - defer c.proxy.Delete() - err = c.proxy.Tunnel(ck.Context(), peer) - if err != nil { - return i18nerr.WrapInternal(err, "The VPN proxy exited") - } - return err -} diff --git a/client/proxy_test.go b/client/proxy_test.go deleted file mode 100644 index ddb0c4fb..00000000 --- a/client/proxy_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package client - -import ( - "context" - "errors" - "testing" - - "codeberg.org/eduVPN/proxyguard" -) - -func TestProxy(t *testing.T) { - // test race - p := Proxy{} - p.NewClient(&proxyguard.Client{}) - go func() { - // connect to localhost will fail - // but we don't care about the error - _ = p.Tunnel(context.Background(), "127.0.0.1") - }() - // race! - _ = p.Cancel() - - // cancel before tunneling - p.NewClient(&proxyguard.Client{}) - if !errors.Is(p.Cancel(), ErrNoProxyGuardCancel) { - t.Fatalf("proxyguard cancel err not equal") - } - _ = p.Tunnel(context.Background(), "127.0.0.1") - p.Delete() - - // tunnel without client - gerr := p.Tunnel(context.Background(), "127.0.0.1") - if !errors.Is(gerr, ErrNoProxyGuardClient) { - t.Fatalf("no proxyguard client err not equal") - } -} diff --git a/exports/exports.go b/exports/exports.go index 511a1f98..6df8c332 100644 --- a/exports/exports.go +++ b/exports/exports.go @@ -29,6 +29,7 @@ import ( "github.com/eduvpn/eduvpn-common/client" "github.com/eduvpn/eduvpn-common/i18nerr" "github.com/eduvpn/eduvpn-common/internal/log" + "github.com/eduvpn/eduvpn-common/proxy" "github.com/eduvpn/eduvpn-common/types/cookie" errtypes "github.com/eduvpn/eduvpn-common/types/error" srvtypes "github.com/eduvpn/eduvpn-common/types/server" @@ -876,50 +877,85 @@ func StartFailover(c C.uintptr_t, gateway *C.char, mtu C.int, readRxBytes C.Read return droppedC, nil } -// StartProxyguard starts the 'proxyguard' procedure in eduvpn-common. +// NewProxyguard creates the 'proxyguard' procedure in eduvpn-common. // eduvpn-common currently also cleans up the running ProxyGuard process in `cleanup`. -// If the proxy cannot be started it returns an error. +// If the proxy cannot be created it returns an error. // // This function proxies WireGuard UDP connections over HTTP: [ProxyGuard on Codeberg](https://codeberg.org/eduvpn/proxyguard). // // These input variables can be gotten from the configuration that is retrieved using the `proxy` JSON key // // - `c` is the cookie. Note that if you cancel/delete the cookie, ProxyGuard gets cleaned up. Common automatically cleans up ProxyGuard when `Cleanup` is called, but it is good to cleanup yourself too. -// - `listen` is the `ip:port` of the local udp connection, this is what is set to the WireGuard endpoint +// - `lp` is the `port` of the local udp ProxyGuard connection, this is what is set to the WireGuard endpoint // - `tcpsp` is the TCP source port. Pass 0 if you do not route based on source port, so far only the Linux client has to pass non-zero. // - `peer` is the `ip:port` of the remote server // - `proxySetup` is a callback which is called when the socket is setting up, this can be used for configuring routing in the client. It takes two arguments: the file descriptor (integer) and a JSON list of IPs the client connects to -// - `proxyReady` is a callback when the proxy is ready to be used. This is only called when the client is not connected yet. Use this to determine when the actual wireguard connection can be started. This callback returns and takes no arguments // -// Example Input: ```StartProxyGuard(myCookie, "127.0.0.1:1337", 0, "5.5.5.5:51820", proxySetupCB, proxyReadyCB)``` +// Example Input: ```StartProxyGuard(myCookie, 1337, 0, "5.5.5.5:51820", proxySetupCB)``` // // Example Output: ```null``` // -//export StartProxyguard -func StartProxyguard(c C.uintptr_t, listen *C.char, tcpsp C.int, peer *C.char, proxySetup C.ProxySetup, proxyReady C.ProxyReady) *C.char { - state, stateErr := getVPNState() - if stateErr != nil { - return getCError(stateErr) - } +//export NewProxyguard +func NewProxyguard(c C.uintptr_t, lp C.int, tcpsp C.int, peer *C.char, proxySetup C.ProxySetup) (C.uintptr_t, *C.char) { ck, err := getCookie(c) if err != nil { - return getCError(err) + return 0, getCError(err) } - - proxyErr := state.StartProxyguard(ck, C.GoString(listen), int(tcpsp), C.GoString(peer), func(fd int, pips string) { + proxy, proxyErr := proxy.NewProxyguard(ck.Context(), int(lp), int(tcpsp), C.GoString(peer), func(fd int) { if proxySetup == nil { return } - cpip := C.CString(pips) - C.call_proxy_setup(proxySetup, C.int(fd), cpip) - FreeString(cpip) - }, func() { - if proxyReady == nil { - return - } - C.call_proxy_ready(proxyReady) + C.call_proxy_setup(proxySetup, C.int(fd)) }) - return getCError(proxyErr) + if proxyErr != nil { + return 0, getCError(proxyErr) + } + return C.uintptr_t(cgo.NewHandle(proxy)), nil +} + +func getProxy(proxyH C.uintptr_t) (*proxy.Proxy, error) { + h := cgo.Handle(proxyH) + v, ok := h.Value().(*proxy.Proxy) + if !ok { + return nil, i18nerr.NewInternal("value is not a proxyguard wrapper") + } + return v, nil +} + +//export ProxyguardTunnel +func ProxyguardTunnel(c C.uintptr_t, proxyH C.uintptr_t, wglisten C.int) *C.char { + ck, err := getCookie(c) + if err != nil { + return getCError(err) + } + pr, err := getProxy(proxyH) + if err != nil { + return getCError(err) + } + tunnelErr := pr.Tunnel(ck.Context(), int(wglisten)) + + // after tunneling is done, the handle should be deleted + cgo.Handle(proxyH).Delete() + return getCError(tunnelErr) +} + +//export ProxyguardPeerIPs +func ProxyguardPeerIPs(proxyH C.uintptr_t) (*C.char, *C.char) { + pr, err := getProxy(proxyH) + if err != nil { + return nil, getCError(err) + } + pips := pr.PeerIPS + + b, err := json.Marshal(pips) + if err != nil { + return nil, getCError(i18nerr.WrapInternal(err, "failed converting Peer IPs to JSON")) + } + ret, err := getReturnData(string(b)) + if err != nil { + return nil, getCError(err) + } + return C.CString(ret), nil } // SetState sets the state of the state machine. diff --git a/exports/exports.h b/exports/exports.h index a31920be..9ec39e41 100644 --- a/exports/exports.h +++ b/exports/exports.h @@ -11,8 +11,7 @@ typedef int (*StateCB)(int oldstate, int newstate, void* data); typedef void (*RefreshList)(); typedef void (*TokenGetter)(const char* server_id, int server_type, char* out, size_t len); typedef void (*TokenSetter)(const char* server_id, int server_type, const char* tokens); -typedef void (*ProxySetup)(int fd, const char* peer_ips); -typedef void (*ProxyReady)(); +typedef void (*ProxySetup)(int fd); static long long int get_read_rx_bytes(ReadRxBytes read) { @@ -34,13 +33,9 @@ static void call_token_setter(TokenSetter setter, const char* server_id, int ser { setter(server_id, server_type, tokens); } -static void call_proxy_setup(ProxySetup proxysetup, int fd, const char* peer_ips) +static void call_proxy_setup(ProxySetup proxysetup, int fd) { - proxysetup(fd, peer_ips); -} -static void call_proxy_ready(ProxyReady ready) -{ - ready(); + proxysetup(fd); } #endif /* EXPORTS_H */ diff --git a/go.mod b/go.mod index b9a6225a..eeaa9053 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,11 @@ module github.com/eduvpn/eduvpn-common go 1.18 require ( - codeberg.org/eduVPN/proxyguard v0.0.0-20240924084349-c0250730030d + codeberg.org/eduVPN/proxyguard v0.0.0-20241028155505-e9ee8522373e github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 github.com/jwijenbergh/eduoauth-go v1.1.1 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c - golang.org/x/text v0.18.0 + golang.org/x/text v0.19.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 ) @@ -18,7 +18,7 @@ require ( ) require ( - golang.org/x/crypto v0.27.0 // indirect - golang.org/x/net v0.29.0 - golang.org/x/sys v0.25.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.30.0 + golang.org/x/sys v0.26.0 // indirect ) diff --git a/go.sum b/go.sum index 505f58a7..4aab924e 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,8 @@ codeberg.org/eduVPN/proxyguard v0.0.0-20240723101427-d0b2383c372c h1:C+CRKtZb8pE codeberg.org/eduVPN/proxyguard v0.0.0-20240723101427-d0b2383c372c/go.mod h1:fc7DsdgdLmrO7DN45HNp+ekVewlRcikSOkAvUeGUvWk= codeberg.org/eduVPN/proxyguard v0.0.0-20240924084349-c0250730030d h1:Vgak68rEnG1+dbarzckv3yvQeHI+tlGIvKy7opS5DKs= codeberg.org/eduVPN/proxyguard v0.0.0-20240924084349-c0250730030d/go.mod h1:fc7DsdgdLmrO7DN45HNp+ekVewlRcikSOkAvUeGUvWk= +codeberg.org/eduVPN/proxyguard v0.0.0-20241028155505-e9ee8522373e h1:YdupOqJKsVGJf9cgjGbMk8/DDQZp/YdlsYB7sTX7iMs= +codeberg.org/eduVPN/proxyguard v0.0.0-20241028155505-e9ee8522373e/go.mod h1:fc7DsdgdLmrO7DN45HNp+ekVewlRcikSOkAvUeGUvWk= github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 h1:TMtDYDHKYY15rFihtRfck/bfFqNfvcabqvXAFQfAUpY= github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267/go.mod h1:h1nSAbGFqGVzn6Jyl1R/iCcBUHN4g+gW1u9CoBTrb9E= github.com/jwijenbergh/eduoauth-go v0.0.0-20240315135955-9f1f5b2fd78e h1:KOWbMxOd6qRF6Zw7PfDwUy0HFhks1KXU/7ai98WKFVY= @@ -40,6 +42,8 @@ golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= @@ -56,6 +60,8 @@ 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/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= @@ -70,6 +76,8 @@ golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= @@ -78,6 +86,8 @@ golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= diff --git a/internal/api/api_test.go b/internal/api/api_test.go index f15bac4e..9648544b 100644 --- a/internal/api/api_test.go +++ b/internal/api/api_test.go @@ -497,8 +497,8 @@ PrivateKey = .*`, if gcd.Proxy.SourcePort <= 0 { t.Fatalf("got proxy source port is smaller or equal to 0: %v", gcd.Proxy.SourcePort) } - if !strings.Contains(gcd.Proxy.Listen, "127.0.0.1") { - t.Fatalf("proxy listen does not contain 127.0.0.1: %s", gcd.Proxy.Listen) + if gcd.Proxy.ListenPort <= 0 { + t.Fatalf("proxy listen port is smaller or equal to 0: %v", gcd.Proxy.ListenPort) } c.cd.Proxy = gcd.Proxy } diff --git a/internal/server/server.go b/internal/server/server.go index eac1c2a7..8ec7a3ff 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -142,7 +142,7 @@ func (s *Server) connect(ctx context.Context, pTCP bool) (*srvtypes.Configuratio if apicfg.Proxy != nil { proxy = &srvtypes.Proxy{ SourcePort: apicfg.Proxy.SourcePort, - Listen: apicfg.Proxy.Listen, + ListenPort: apicfg.Proxy.ListenPort, Peer: apicfg.Proxy.Peer, } } diff --git a/internal/wireguard/wireguard.go b/internal/wireguard/wireguard.go index a70f21a3..92398352 100644 --- a/internal/wireguard/wireguard.go +++ b/internal/wireguard/wireguard.go @@ -40,8 +40,8 @@ func availableUDPPort() (int, error) { type Proxy struct { // SourcePort is the source port of the TCP socket SourcePort int - // Listen is the IP:PORT of the udp listener - Listen string + // ListenPort is the PORT of the udp listener + ListenPort int // Peer is the hostname/ip:port of the WireGuard peer Peer string } @@ -54,22 +54,23 @@ func Config(cfg string, key *wgtypes.Key, proxy bool) (string, *Proxy, error) { } var tcpp int - var plisten string + var udpp int var err error + var udpl string if proxy { tcpp, err = availableTCPPort() if err != nil { return "", nil, err } - udpp, err := availableUDPPort() + udpp, err = availableUDPPort() if err != nil { return "", nil, err } - plisten = fmt.Sprintf("127.0.0.1:%d", udpp) + udpl = fmt.Sprintf("127.0.0.1:%d", udpp) } - rcfg, peer, err := configReplace(cfg, *key, plisten) + rcfg, peer, err := configReplace(cfg, *key, udpl) if err != nil { return "", nil, err } @@ -77,7 +78,7 @@ func Config(cfg string, key *wgtypes.Key, proxy bool) (string, *Proxy, error) { if proxy { retP = &Proxy{ SourcePort: tcpp, - Listen: plisten, + ListenPort: udpp, Peer: peer, } } diff --git a/proxy/proxy.go b/proxy/proxy.go new file mode 100644 index 00000000..8df539a4 --- /dev/null +++ b/proxy/proxy.go @@ -0,0 +1,60 @@ +// package proxy is a wrapper around proxyguard that integrates it with eduvpn-common settings +// - leaves out some options not applicable to the common integration, e.g. fwmark +// - integrates with eduvpn-common's logger +// - integrates eduvpn-common's user agent +package proxy + +import ( + "context" + + "codeberg.org/eduVPN/proxyguard" + + "github.com/eduvpn/eduvpn-common/i18nerr" + httpw "github.com/eduvpn/eduvpn-common/internal/http" + "github.com/eduvpn/eduvpn-common/internal/log" +) + +// Logger is defined here such that we can update the proxyguard logger +type Logger struct{} + +// Logf logs a message with parameters +func (l *Logger) Logf(msg string, params ...interface{}) { + log.Logger.Infof("[Proxyguard] "+msg, params...) +} + +// Log logs a message +func (l *Logger) Log(msg string) { + log.Logger.Infof("[Proxyguard] %s", msg) +} + +type Proxy struct { + proxyguard.Client +} + +// NewProxyguard sets up proxyguard for proxied WireGuard connections +func NewProxyguard(ctx context.Context, lp int, tcpsp int, peer string, setupSocket func(fd int)) (*Proxy, error) { + proxyguard.UpdateLogger(&Logger{}) + proxy := Proxy{ + proxyguard.Client{ + Peer: peer, + ListenPort: lp, + TCPSourcePort: tcpsp, + SetupSocket: setupSocket, + UserAgent: httpw.UserAgent, + }, + } + err := proxy.Client.SetupDNS(ctx) + if err != nil { + return nil, i18nerr.WrapInternal(err, "The ProxyGuard DNS could not be resolved") + } + + return &proxy, nil +} + +func (p *Proxy) Tunnel(ctx context.Context, wglisten int) error { + err := p.Client.Tunnel(ctx, wglisten) + if err != nil { + return i18nerr.WrapInternal(err, "The VPN proxy exited") + } + return nil +} diff --git a/types/server/server.go b/types/server/server.go index 1d55ea76..b8805435 100644 --- a/types/server/server.go +++ b/types/server/server.go @@ -164,8 +164,9 @@ type List struct { type Proxy struct { // SourcePort is the source port for the client TCP connection SourcePort int `json:"source_port"` - // Listen is the ip:port for the client UDP connection, this is the value that is replaced in the config - Listen string `json:"listen"` + // ListenPort is the port for the client UDP connection, this is the value that is set as the WireGuard endpoint prepended with "127.0.0.1:" + // So if the listen port is set to 1337, the WireGuard endpoint would be 127.0.0.1:1337 + ListenPort int `json:"listen_port"` // Peer is the URI of the upstream server // Note that this exactly matches the "ProxyEndpoint" key in the WireGuard config Peer string `json:"peer"` diff --git a/wrappers/python/eduvpn_common/loader.py b/wrappers/python/eduvpn_common/loader.py index e1631487..8ac6372a 100644 --- a/wrappers/python/eduvpn_common/loader.py +++ b/wrappers/python/eduvpn_common/loader.py @@ -5,7 +5,7 @@ from eduvpn_common.types import ( BoolError, DataError, - ProxyReady, + HandlerError, ProxySetup, ReadRxBytes, RefreshList, @@ -134,14 +134,27 @@ def initialize_functions(lib: CDLL) -> None: ], BoolError, ) - lib.StartProxyguard.argtypes, lib.StartProxyguard.restype = ( + lib.NewProxyguard.argtypes, lib.NewProxyguard.restype = ( [ c_int, - c_char_p, + c_int, c_int, c_char_p, ProxySetup, - ProxyReady, ], - c_void_p, + HandlerError, + ) + lib.ProxyguardTunnel.argtypes, lib.ProxyguardTunnel.restype = ( + [ + c_int, + c_int, + c_int, + ], + c_char_p, + ) + lib.ProxyguardPeerIPs.argtypes, lib.ProxyguardPeerIPs.restype = ( + [ + c_int, + ], + DataError, ) diff --git a/wrappers/python/eduvpn_common/main.py b/wrappers/python/eduvpn_common/main.py index ce520246..04a2302e 100644 --- a/wrappers/python/eduvpn_common/main.py +++ b/wrappers/python/eduvpn_common/main.py @@ -1,13 +1,12 @@ import ctypes import json from enum import IntEnum -from typing import Any, Callable, Iterator, Optional +from typing import Any, Callable, Iterator, List, Optional from eduvpn_common.event import EventHandler from eduvpn_common.loader import initialize_functions, load_lib from eduvpn_common.state import State from eduvpn_common.types import ( - ProxyReady, ProxySetup, ReadRxBytes, RefreshList, @@ -21,6 +20,24 @@ global_object = None +class Proxyguard(object): + def __init__(self, parent, handler): + self.parent = parent + self.handler = handler + + def tunnel(self, wglisten: int): + tunnel_err = self.parent.go_cookie_function(self.parent.lib.ProxyguardTunnel, self.handler, wglisten) + if tunnel_err: + forwardError(tunnel_err) + + @property + def peer_ips(self) -> List[str]: + peer_ips, peer_ips_err = self.parent.go_function(self.parent.lib.ProxyguardPeerIPs, self.handler) + if peer_ips_err: + forwardError(peer_ips_err) + return json.loads(peer_ips) + + class WrappedError(Exception): def __init__(self, translations, language, misc): self.translations = translations @@ -359,24 +376,23 @@ def start_failover(self, gateway: str, wg_mtu: int, readrxbytes: ReadRxBytes) -> forwardError(dropped_err) return dropped - def start_proxyguard( + def new_proxyguard( self, - listen: str, - source_port: int, + listen_port: int, + tcp_source_port: int, peer: str, setup: ProxySetup, - ready: ProxyReady, - ): - proxy_err = self.go_cookie_function( - self.lib.StartProxyguard, - listen, - source_port, + ) -> Proxyguard: + proxy, proxy_err = self.go_cookie_function( + self.lib.NewProxyguard, + listen_port, + tcp_source_port, peer, setup, - ready, ) if proxy_err: forwardError(proxy_err) + return Proxyguard(self, proxy) def cancel(self): self.jar.cancel() diff --git a/wrappers/python/eduvpn_common/types.py b/wrappers/python/eduvpn_common/types.py index 716556e2..5e23d612 100644 --- a/wrappers/python/eduvpn_common/types.py +++ b/wrappers/python/eduvpn_common/types.py @@ -25,6 +25,15 @@ class DataError(Structure): _fields_ = [("data", c_void_p), ("error", c_void_p)] +class HandlerError(Structure): + """The C type that represents a tuple of a CGO handler and error (string) as returned by the Go library + + :meta private: + """ + + _fields_ = [("handler", c_int), ("error", c_void_p)] + + class BoolError(Structure): """The C type that represents a tuple of boolean and error as returned by the Go library @@ -36,8 +45,7 @@ class BoolError(Structure): # The type for a Go state change callback VPNStateChange = CFUNCTYPE(c_int, c_int, c_int, c_char_p) -ProxySetup = CFUNCTYPE(c_void_p, c_int, c_char_p) -ProxyReady = CFUNCTYPE(c_void_p) +ProxySetup = CFUNCTYPE(c_void_p, c_int) ReadRxBytes = CFUNCTYPE(c_ulonglong) RefreshList = CFUNCTYPE(c_void_p) TokenGetter = CFUNCTYPE(c_void_p, c_char_p, c_int, POINTER(c_char), c_size_t) @@ -84,6 +92,7 @@ def decode_res(res: Any) -> Any: """ decode_map = { c_void_p: get_ptr_string, + HandlerError: get_handler_error, DataError: get_data_error, BoolError: get_bool_error, } @@ -126,6 +135,22 @@ def get_data_error(lib: CDLL, data_error: DataError) -> Tuple[str, str]: return data, error +def get_handler_error(lib: CDLL, handler_error: HandlerError) -> Tuple[int, str]: + """Convert a C handler+error structure to a Python usable handler+error structure + + :param lib: CDLL: The Go shared library + :param handler_error: HandlerError: The handler error C structure + + :meta private: + + :return: The handler and error + :rtype: Tuple[int, str] + """ + handler = int(handler_error.handler) + error = get_ptr_string(lib, handler_error.error) + return handler, error + + def get_bool_error(lib: CDLL, bool_error: BoolError) -> Tuple[bool, str]: """Convert a C boolean (c int)+error structure to a Python usable boolean+error structure From 347997535be8c32f016084b56683b0591992edd5 Mon Sep 17 00:00:00 2001 From: jwijenbergh Date: Tue, 29 Oct 2024 11:47:02 +0100 Subject: [PATCH 2/2] Changes: Update --- CHANGES.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 39bcb8ec..63864dc9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,17 @@ - Cleanup function comments * Util: - Add a function to calculate the gateway address for a given IPv4/IPv6 subnet +* ProxyGuard: + - Updated to the latest version + - API breakage: + - `StartProxyguard` function has been removed, use `NewProxyguard` instead + - `NewProxyguard` function has been added which returns a ProxyGuard instance + - `ProxyguardTunnel` to establish a tunnel for an existing ProxyGuard instance + - `ProxyguardPeerIPs` to get the Peer IPs ProxyGuard will attempt to connect to + - types.Server.Proxy JSON no longer returns `listen` but `listen_port` +* HTTP: Enforce TLS >= 1.3 transport +* Exports: Add tests to test the public API +* Translations: Update from Weblate # 2.1.0 (2024-07-25) * Discovery: