summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.md3
-rw-r--r--docs/md/apidocs.md57
-rw-r--r--exports/exports.go107
-rw-r--r--exports/exports.h5
-rw-r--r--go.mod1
-rw-r--r--go.sum2
-rw-r--r--internal/wireguard/ini/ini.go2
-rw-r--r--proxy/proxy.go84
-rw-r--r--wrappers/python/eduvpn_common/loader.py31
-rw-r--r--wrappers/python/eduvpn_common/main.py44
-rw-r--r--wrappers/python/eduvpn_common/types.py1
11 files changed, 333 insertions, 4 deletions
diff --git a/CHANGES.md b/CHANGES.md
index f659aab..29b9061 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -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 */
diff --git a/go.mod b/go.mod
index 8fe68c4..dea4a59 100644
--- a/go.mod
+++ b/go.mod
@@ -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
diff --git a/go.sum b/go.sum
index 04542e5..4bc2089 100644
--- a/go.sum
+++ b/go.sum
@@ -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)