diff options
| author | Jeroen Wijenbergh <jeroen.wijenbergh@geant.org> | 2025-09-03 10:13:46 +0200 |
|---|---|---|
| committer | Jeroen Wijenbergh <jeroen.wijenbergh@geant.org> | 2025-09-03 10:53:42 +0200 |
| commit | c3318fb386096170282e831eb3b616a5a7e9dda8 (patch) | |
| tree | 0367fdad43a02da49b278f38db9b8e15d3de908a | |
| parent | 5e05784cab953b0e24609398106dd33da7738d21 (diff) | |
Revert "All: Remove ProxyGuard integration"
This partially reverts commit 6b939462fb1064abd42e8cb8316700ec844172ea.
It keeps the proxyguard functions but leaves GetConfig alone. E.g. no WireGuard config replacing and querying is happening for ProxyGuard.
Needed for the Linux client as I have not found a good way to have a daemon with NetworkManager integration
| -rw-r--r-- | CHANGES.md | 3 | ||||
| -rw-r--r-- | docs/md/apidocs.md | 57 | ||||
| -rw-r--r-- | exports/exports.go | 107 | ||||
| -rw-r--r-- | exports/exports.h | 5 | ||||
| -rw-r--r-- | go.mod | 1 | ||||
| -rw-r--r-- | go.sum | 2 | ||||
| -rw-r--r-- | internal/wireguard/ini/ini.go | 2 | ||||
| -rw-r--r-- | proxy/proxy.go | 84 | ||||
| -rw-r--r-- | wrappers/python/eduvpn_common/loader.py | 31 | ||||
| -rw-r--r-- | wrappers/python/eduvpn_common/main.py | 44 | ||||
| -rw-r--r-- | wrappers/python/eduvpn_common/types.py | 1 |
11 files changed, 333 insertions, 4 deletions
@@ -3,8 +3,7 @@ - Escape changelog newlines * Lint: - lint use staticcheck instead of stylecheck with golangci-lint -* ProxyGuard functionality removed, this is implemented inside of the eduVPN clients separately either using a wireguard fork or a daemon in case of linux - - Removed function: `NewProxyguard`, `ProxyguardTunnel`, `ProxyguardRestart`, `ProxyguardPeerIPs` +* API: - The `Configuration` type no longer returns proxy information, in case of ProxyGuard the `ProxyEndpoint` in the INI config is filled in as returned by the eduVPN server # 3.0.0 (2025-03-21) diff --git a/docs/md/apidocs.md b/docs/md/apidocs.md index 71713e6..2769a27 100644 --- a/docs/md/apidocs.md +++ b/docs/md/apidocs.md @@ -605,6 +605,63 @@ Example Input: ```InState(5)``` Example Output: ```1, null``` +## NewProxyguard +Signature: + +```go +func NewProxyguard(c C.uintptr_t, lp C.int, tcpsp C.int, peer *C.char, proxySetup C.ProxySetup) (C.uintptr_t, *C.char) +``` + +NewProxyguard creates the 'proxyguard' procedure in eduvpn-common. 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. + - `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 + +Example Input: ```NewProxyguard(myCookie, 1337, 0, "5.5.5.5:51820", +proxySetupCB)``` + +Example Output: ```null``` + +## ProxyguardPeerIPs +Signature: + +```go +func ProxyguardPeerIPs(proxyH C.uintptr_t) (*C.char, *C.char) +``` + +ProxyguardPeerIPs gets the Peer IPs configured by ProxyGuard Example Input: +```ProxyguardPeerIPs(handle)``` + +Example Output: ```["1.1.1.1"], null``` + +## ProxyguardTunnel +Signature: + +```go +func ProxyguardTunnel(c C.uintptr_t, proxyH C.uintptr_t, wglisten C.int) *C.char +``` + +ProxyguardTunnel starts the tunneling for ProxyGuard `c` is the cookie +`proxyH` is the proxy handle `wglisten` is the port WireGuard is listening +on + ## Register Signature: diff --git a/exports/exports.go b/exports/exports.go index a20ffb8..3ef6781 100644 --- a/exports/exports.go +++ b/exports/exports.go @@ -27,6 +27,7 @@ import ( "codeberg.org/eduVPN/eduvpn-common/client" "codeberg.org/eduVPN/eduvpn-common/i18n/err" + "codeberg.org/eduVPN/eduvpn-common/proxy" "codeberg.org/eduVPN/eduvpn-common/types/cookie" errtypes "codeberg.org/eduVPN/eduvpn-common/types/error" srvtypes "codeberg.org/eduVPN/eduvpn-common/types/server" @@ -876,6 +877,112 @@ func StartFailover(c C.uintptr_t, gateway *C.char, mtu C.int, readRxBytes C.Read return droppedC, nil } +// NewProxyguard creates the 'proxyguard' procedure in eduvpn-common. +// 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. +// - `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 +// +// Example Input: ```NewProxyguard(myCookie, 1337, 0, "5.5.5.5:51820", proxySetupCB)``` +// +// Example Output: ```null``` +// +//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 0, getCError(err) + } + proxy, proxyErr := proxy.NewProxyguard(ck.Context(), int(lp), int(tcpsp), C.GoString(peer), func(fd int) { + if proxySetup == nil { + return + } + C.call_proxy_setup(proxySetup, C.int(fd)) + }) + if proxyErr != nil { + return 0, getCError(proxyErr) + } + return C.uintptr_t(cgo.NewHandle(proxy)), nil +} + +// ProxyguardRestart restarts ProxyGuard, call this when a network change happens +// +// Example Input: ```ProxyguardRestart(proxyHandle)``` +// +// Example Output: ```"failed restarting ProxyGuard"``` +// +//export ProxyguardRestart +func ProxyguardRestart(proxyH C.uintptr_t) *C.char { + pr, err := getProxy(proxyH) + if err != nil { + return getCError(err) + } + pr.Restart() + return 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 +} + +// ProxyguardTunnel starts the tunneling for ProxyGuard +// `c` is the cookie +// `proxyH` is the proxy handle +// `wglisten` is the port WireGuard is listening on +// +//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) +} + +// ProxyguardPeerIPs gets the Peer IPs configured by ProxyGuard +// Example Input: ```ProxyguardPeerIPs(handle)``` +// +// Example Output: ```["1.1.1.1"], null``` +// +//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. // // Note: this transitions the FSM into the new state without passing any data to it. diff --git a/exports/exports.h b/exports/exports.h index 13630a6..9ec39e4 100644 --- a/exports/exports.h +++ b/exports/exports.h @@ -11,6 +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); static long long int get_read_rx_bytes(ReadRxBytes read) { @@ -32,5 +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) +{ + proxysetup(fd); +} #endif /* EXPORTS_H */ @@ -3,6 +3,7 @@ module codeberg.org/eduVPN/eduvpn-common go 1.23.4 require ( + codeberg.org/eduVPN/proxyguard v0.0.0-20250814100601-abc5db189743 github.com/jedisct1/go-minisign v0.0.0-20241212093149-d2f9f49435c7 github.com/jwijenbergh/eduoauth-go v1.1.2 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c @@ -1,3 +1,5 @@ +codeberg.org/eduVPN/proxyguard v0.0.0-20250814100601-abc5db189743 h1:orR4wKonLoCeKWQz3mk268ZuYOKwrIu/pdSA5Wz44Zg= +codeberg.org/eduVPN/proxyguard v0.0.0-20250814100601-abc5db189743/go.mod h1:fc7DsdgdLmrO7DN45HNp+ekVewlRcikSOkAvUeGUvWk= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/jedisct1/go-minisign v0.0.0-20241212093149-d2f9f49435c7 h1:FWpSWRD8FbVkKQu8M1DM9jF5oXFLyE+XpisIYfdzbic= diff --git a/internal/wireguard/ini/ini.go b/internal/wireguard/ini/ini.go index 46c6f8b..c7eb971 100644 --- a/internal/wireguard/ini/ini.go +++ b/internal/wireguard/ini/ini.go @@ -53,7 +53,7 @@ type Section struct { keys OrderedKeys } -// KeyValue gets a value for key `key` +// keyValue gets a value for key `key` // It returns an error if the key does not exist func (sec *Section) keyValue(key string) (string, error) { if v, ok := sec.keyValues[key]; ok { diff --git a/proxy/proxy.go b/proxy/proxy.go new file mode 100644 index 0000000..af0cd89 --- /dev/null +++ b/proxy/proxy.go @@ -0,0 +1,84 @@ +// 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" + "fmt" + "log/slog" + + "codeberg.org/eduVPN/proxyguard" + + "codeberg.org/eduVPN/eduvpn-common/i18n/err" + httpw "codeberg.org/eduVPN/eduvpn-common/internal/http" +) + +// 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 ...any) { + slog.Info("Proxyguard log", "msg", fmt.Sprintf(msg, params...)) +} + +// Log logs a message +func (l *Logger) Log(msg string) { + slog.Info("Proxyguard log", "msg", msg) +} + +// Proxy is the ProxyGuard client with a channel used for restarting +type Proxy struct { + proxyguard.Client + resChan chan struct{} +} + +// 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{ + Client: proxyguard.Client{ + Peer: peer, + ListenPort: lp, + TCPSourcePort: tcpsp, + SetupSocket: setupSocket, + UserAgent: httpw.UserAgent, + }, + resChan: make(chan struct{}), + } + _, err := proxy.Setup(ctx) + if err != nil { + return nil, i18nerr.WrapInternal(err, "The ProxyGuard DNS could not be resolved") + } + + return &proxy, nil +} + +// Tunnel tunnels the ProxyGuard connection. `wglisten` is the WireGuard listen port +func (p *Proxy) Tunnel(ctx context.Context, wglisten int) error { + errChan := make(chan error, 1) + gctx, cancel := context.WithCancel(ctx) + go func() { + err := p.Client.Tunnel(gctx, wglisten) + if err != nil { + err = i18nerr.WrapInternal(err, "The VPN proxy exited") + } + errChan <- err + }() + + select { + case err := <-errChan: + cancel() + return err + case <-p.resChan: + cancel() + <-errChan + return p.Tunnel(ctx, wglisten) + } +} + +// Restart restarts the existing ProxyGuard process, for e.g. roaming +func (p *Proxy) Restart() { + p.resChan <- struct{}{} +} diff --git a/wrappers/python/eduvpn_common/loader.py b/wrappers/python/eduvpn_common/loader.py index 888b53f..d902453 100644 --- a/wrappers/python/eduvpn_common/loader.py +++ b/wrappers/python/eduvpn_common/loader.py @@ -6,6 +6,7 @@ from eduvpn_common.types import ( BoolError, DataError, HandlerError, + ProxySetup, ReadRxBytes, RefreshList, TokenGetter, @@ -131,3 +132,33 @@ def initialize_functions(lib: CDLL) -> None: ], BoolError, ) + lib.NewProxyguard.argtypes, lib.NewProxyguard.restype = ( + [ + c_int, + c_int, + c_int, + c_char_p, + ProxySetup, + ], + HandlerError, + ) + lib.ProxyguardRestart.argtypes, lib.ProxyguardRestart.restype = ( + [ + c_int, + ], + c_char_p, + ) + 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 2bd221d..e63ea92 100644 --- a/wrappers/python/eduvpn_common/main.py +++ b/wrappers/python/eduvpn_common/main.py @@ -1,12 +1,13 @@ 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 ( + ProxySetup, ReadRxBytes, RefreshList, TokenGetter, @@ -19,6 +20,29 @@ from eduvpn_common.types import ( 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) + + def restart(self): + restart_err = self.parent.go_function(self.parent.lib.ProxyguardRestart, self.handler) + if restart_err: + forwardError(restart_err) + + class WrappedError(Exception): def __init__(self, translations, language, misc): self.translations = translations @@ -350,6 +374,24 @@ class EduVPN(object): forwardError(dropped_err) return dropped + def new_proxyguard( + self, + listen_port: int, + tcp_source_port: int, + peer: str, + setup: ProxySetup, + ) -> Proxyguard: + proxy, proxy_err = self.go_cookie_function( + self.lib.NewProxyguard, + listen_port, + tcp_source_port, + peer, + setup, + ) + 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 999fbdd..5e23d61 100644 --- a/wrappers/python/eduvpn_common/types.py +++ b/wrappers/python/eduvpn_common/types.py @@ -45,6 +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) 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) |
