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 /internal | |
| parent | a912257ad6d4260fbea9c0e3e3fb9bbefa92bb6e (diff) | |
WireGuard: TCP support using proxyguard
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/wireguard/wireguard.go | 118 | ||||
| -rw-r--r-- | internal/wireguard/wireguard_test.go | 93 |
2 files changed, 169 insertions, 42 deletions
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) + } } } |
