diff options
| author | jwijenbergh <jeroenwijenbergh@protonmail.com> | 2024-02-07 13:43:52 +0100 |
|---|---|---|
| committer | Jeroen Wijenbergh <46386452+jwijenbergh@users.noreply.github.com> | 2024-02-19 14:15:07 +0100 |
| commit | c8e7424f0b9ca963c7454e3297a8d001d67d729d (patch) | |
| tree | 979341ca6b67badc451aa58d3790704b3bf04386 | |
| parent | a912257ad6d4260fbea9c0e3e3fb9bbefa92bb6e (diff) | |
WireGuard: TCP support using proxyguard
| -rw-r--r-- | exports/exports.go | 17 | ||||
| -rw-r--r-- | internal/wireguard/wireguard.go | 118 | ||||
| -rw-r--r-- | internal/wireguard/wireguard_test.go | 93 | ||||
| -rw-r--r-- | types/protocol/protocol.go | 2 | ||||
| -rw-r--r-- | types/server/server.go | 9 | ||||
| -rw-r--r-- | wrappers/python/eduvpn_common/loader.py | 6 | ||||
| -rw-r--r-- | wrappers/python/eduvpn_common/main.py | 10 |
7 files changed, 213 insertions, 42 deletions
diff --git a/exports/exports.go b/exports/exports.go index c7d4b8c..9ae85c8 100644 --- a/exports/exports.go +++ b/exports/exports.go @@ -879,6 +879,23 @@ 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 +// +//export StartProxyguard +func StartProxyguard(c C.uintptr_t, listen *C.char, tcpsp C.int, peer *C.char) *C.char { + state, stateErr := getVPNState() + if stateErr != nil { + return getCError(stateErr) + } + ck, err := getCookie(c) + if err != nil { + return getCError(err) + } + + proxyErr := state.StartProxyguard(ck, C.GoString(listen), int(tcpsp), C.GoString(peer)) + return getCError(proxyErr) +} + // SetState sets the state of the statemachine // // Note: this transitions the FSM into the new state without passing any data to it. diff --git a/internal/wireguard/wireguard.go b/internal/wireguard/wireguard.go index cc6c577..af290ea 100644 --- a/internal/wireguard/wireguard.go +++ b/internal/wireguard/wireguard.go @@ -2,34 +2,110 @@ package wireguard import ( + "errors" "fmt" - "regexp" + "net" - "github.com/go-errors/errors" + "github.com/eduvpn/eduvpn-common/internal/wireguard/ini" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) -// GenerateKey generates a WireGuard private key using wgctrl -// It returns an error if key generation failed. -func GenerateKey() (wgtypes.Key, error) { - key, err := wgtypes.GeneratePrivateKey() +func availableTCPPort() (int, error) { + tcpaddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0") if err != nil { - return key, errors.WrapPrefix(err, "failed generating WireGuard key", 0) + return -1, err } - return key, nil + ltcp, err := net.ListenTCP("tcp", tcpaddr) + if err != nil { + return -1, err + } + defer ltcp.Close() + return ltcp.Addr().(*net.TCPAddr).Port, nil +} + +func availableUDPPort() (int, error) { + udpaddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0") + if err != nil { + return -1, err + } + ludp, err := net.ListenUDP("udp", udpaddr) + if err != nil { + return -1, err + } + defer ludp.Close() + return ludp.LocalAddr().(*net.UDPAddr).Port, nil +} + +type Proxy struct { + SourcePort int + Listen string + Peer string +} + +func Config(cfg string, key *wgtypes.Key, tcp bool) (string, *Proxy, error) { + // the key is nil if the client does not accept WireGuard + if key == nil { + return "", nil, errors.New("the server sent us a WireGuard profile but the client does not accept WireGuard") + } + + var tcpp int + var proxy string + var err error + + if tcp { + tcpp, err = availableTCPPort() + if err != nil { + return "", nil, err + } + udpp, err := availableUDPPort() + if err != nil { + return "", nil, err + } + proxy = fmt.Sprintf("127.0.0.1:%d", udpp) + } + + rcfg, peer, err := configReplace(cfg, *key, proxy) + if err != nil { + return "", nil, err + } + var retP *Proxy + if tcp { + retP = &Proxy{ + SourcePort: tcpp, + Listen: proxy, + Peer: peer, + } + } + return rcfg, retP, nil } -// ConfigAddKey takes the WireGuard configuration and adds the PrivateKey to the right section -// FIXME: Instead of doing a regex replace, decide if we should use a parser. -func ConfigAddKey(config string, key wgtypes.Key) string { - interfaceSection := "[Interface]" - InterfaceSectionEscaped := regexp.QuoteMeta(interfaceSection) - - // (?m) enables multi line mode - // ^ match from beginning of line - // $ match till end of line - // So it matches [Interface] section exactly - InterfaceRe := regexp.MustCompile(fmt.Sprintf("(?m)^%s$", InterfaceSectionEscaped)) - toReplace := fmt.Sprintf("%s\nPrivateKey = %s", interfaceSection, key.String()) - return InterfaceRe.ReplaceAllString(config, toReplace) +// ConfigReplace replaces the wireguard config with our private key and proxy in case of TCP +func configReplace(cfg string, key wgtypes.Key, proxy string) (string, string, error) { + // first parse the config + secs := ini.Parse(cfg) + if secs.Empty() { + return "", "", errors.New("parsed ini is empty") + } + + // find the interface section + // and set the private key + is, err := secs.Section("Interface") + if err != nil { + return "", "", err + } + is.AddOrReplaceKeyValue("PrivateKey", key.String()) + peer := "" + if proxy != "" { + ps, err := secs.Section("Peer") + if err != nil { + return "", "", err + } + peer, err = ps.RemoveKey("TCPEndpoint") + if err != nil { + return "", "", err + } + ps.AddOrReplaceKeyValue("Endpoint", proxy) + } + + return secs.String(), peer, nil } diff --git a/internal/wireguard/wireguard_test.go b/internal/wireguard/wireguard_test.go index 026658e..ddb9d56 100644 --- a/internal/wireguard/wireguard_test.go +++ b/internal/wireguard/wireguard_test.go @@ -3,12 +3,36 @@ package wireguard import ( "fmt" "testing" + + "github.com/eduvpn/eduvpn-common/internal/test" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) -func Test_ConfigAddKey(t *testing.T) { - config := ` -[Interface] +func TestConfigReplace(t *testing.T) { + k, err := wgtypes.GeneratePrivateKey() + if err != nil { + t.Fatalf("Failed to generate key for wg config replace: %v", err) + } + cases := []struct { + config string + proxy string + want string + wantep string + werr string + }{ + { + config: ` +`, + want: "", + wantep: "", + proxy: "", + werr: "parsed ini is empty", + }, + { + config: ` +[Interface] +PrivateKey = bla [interface] [interface2] @@ -18,29 +42,56 @@ interface [Interface] [Interface]test -` - wgKey, wgKeyErr := GenerateKey() - - if wgKeyErr != nil { - t.Fatalf("WireGuard config add key, generate key error: %v", wgKeyErr) - } - expectedConfig := fmt.Sprintf(` -[Interface] +`, + want: fmt.Sprintf(`[Interface] PrivateKey = %s - [interface] - [interface2] +`, k.String()), + wantep: "", + proxy: "", + werr: "", + }, + { + config: ` +[Interface] +MTU = 1392 +PrivateKey = +Address = 10.146.176.5/24,fdee:1ead:29e8:22a2::5/64 +DNS = 9.9.9.9,2620:fe::fe -interface - - [Interface] +[Peer] +PublicKey = +AllowedIPs = 0.0.0.0/0,::/0 +# TCPEndpoint is a proprietary eduVPN / Let's Connect! extension +# See https://docs.eduvpn.org/server/v3/proxyguard.html#client on how to use the TCP proxy +TCPEndpoint = vpn.example.org:51820 +`, + want: fmt.Sprintf(`[Interface] +MTU = 1392 +PrivateKey = %s +Address = 10.146.176.5/24,fdee:1ead:29e8:22a2::5/64 +DNS = 9.9.9.9,2620:fe::fe +[Peer] +PublicKey = +AllowedIPs = 0.0.0.0/0,::/0 +Endpoint = 127.0.0.1:1337 +`, k.String()), + wantep: "vpn.example.org:51820", + proxy: "127.0.0.1:1337", + werr: "", + }, + } -[Interface]test -`, wgKey.String()) - gotConfig := ConfigAddKey(config, wgKey) + for _, c := range cases { + gcfg, gep, err := configReplace(c.config, k, c.proxy) + test.AssertError(t, err, c.werr) + if gcfg != c.want { + t.Fatalf("Got config: %s, not equal to config: %s", gcfg, c.want) + } - if gotConfig != expectedConfig { - t.Fatalf("Got: %s, Want: %s", gotConfig, expectedConfig) + if gep != c.wantep { + t.Fatalf("Got endpoint: %s, not equal to endpoint: %s", gep, c.wantep) + } } } diff --git a/types/protocol/protocol.go b/types/protocol/protocol.go index e35de2b..3967141 100644 --- a/types/protocol/protocol.go +++ b/types/protocol/protocol.go @@ -11,6 +11,8 @@ const ( OpenVPN // WireGuard indicates that the protocol is WireGuard WireGuard + // WireGuardTCP indicates that the protocol is WireGuard with a TCP proxy + WireGuardTCP ) // New creates a new protocol type from a string diff --git a/types/server/server.go b/types/server/server.go index 5371197..5a340b1 100644 --- a/types/server/server.go +++ b/types/server/server.go @@ -157,6 +157,12 @@ type List struct { Custom []Server `json:"custom_servers,omitempty"` } +type Proxy struct { + SourcePort int `json:"source_port"` + Listen string `json:"listen"` + Peer string `json:"peer"` +} + // Configuration is the configuration that you get back when you call the get config function type Configuration struct { // VPNConfig is the VPN Configuration, a WireGuard or OpenVPN Configuration @@ -169,6 +175,9 @@ type Configuration struct { DefaultGateway bool `json:"default_gateway"` // DNSSearchDomains are the list of dns search domains DNSSearchDomains []string `json:"dns_search_domains,omitempty"` + // Proxy returns information for proxied VPN connections + // If this is non-nil a proxy must be started using StartProxyguard + Proxy *Proxy `json:"proxy,omitempty"` } // Current is the struct that defines the current server diff --git a/wrappers/python/eduvpn_common/loader.py b/wrappers/python/eduvpn_common/loader.py index 51286d2..38d0bb5 100644 --- a/wrappers/python/eduvpn_common/loader.py +++ b/wrappers/python/eduvpn_common/loader.py @@ -125,3 +125,9 @@ def initialize_functions(lib: CDLL) -> None: c_int, ReadRxBytes, ], BoolError + lib.StartProxyguard.argtypes, lib.StartProxyguard.restype = [ + c_int, + c_char_p, + c_int, + c_char_p, + ], c_void_p diff --git a/wrappers/python/eduvpn_common/main.py b/wrappers/python/eduvpn_common/main.py index 0daf0b6..847819d 100644 --- a/wrappers/python/eduvpn_common/main.py +++ b/wrappers/python/eduvpn_common/main.py @@ -345,6 +345,16 @@ class EduVPN(object): forwardError(dropped_err) return dropped + def start_proxyguard(self, listen: str, source_port: int, peer: str): + proxy_err = self.go_cookie_function( + self.lib.StartProxyguard, + listen, + source_port, + peer, + ) + if proxy_err: + forwardError(proxy_err) + def cancel(self): self.jar.cancel() |
