aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--ui/manage_tunnels.go133
-rw-r--r--ui/tunnelconfigdialog.go268
2 files changed, 270 insertions, 131 deletions
diff --git a/ui/manage_tunnels.go b/ui/manage_tunnels.go
index 12e5c973..bd94c551 100644
--- a/ui/manage_tunnels.go
+++ b/ui/manage_tunnels.go
@@ -19,7 +19,6 @@ import (
"golang.zx2c4.com/wireguard/windows/conf"
"golang.zx2c4.com/wireguard/windows/ringlogger"
"golang.zx2c4.com/wireguard/windows/service"
- "golang.zx2c4.com/wireguard/windows/ui/syntax"
)
type ManageTunnelsWindow struct {
@@ -202,134 +201,6 @@ func (mtw *ManageTunnelsWindow) updateConfView() {
mtw.confView.SetTunnel(mtw.tunnelsView.CurrentTunnel())
}
-func (mtw *ManageTunnelsWindow) runTunnelEdit(tunnel *service.Tunnel) *conf.Config {
- var (
- title string
- name string
- config conf.Config
- )
-
- if tunnel == nil {
- // Creating a new tunnel, create a new private key and use the default template
- title = "Create new tunnel"
- name = "New tunnel"
- pk, _ := conf.NewPrivateKey()
- config = conf.Config{Interface: conf.Interface{PrivateKey: *pk}}
- } else {
- title = "Edit tunnel"
- name = tunnel.Name
- config, _ = tunnel.StoredConfig()
- }
-
- dlg, _ := walk.NewDialog(mtw)
- dlg.SetIcon(mtw.icon)
- dlg.SetTitle(title)
- dlg.SetLayout(walk.NewGridLayout())
- // TODO: use size hints in layout elements to communicate the minimal width
- dlg.SetMinMaxSize(walk.Size{500, 400}, walk.Size{9999, 9999})
- dlg.Layout().(*walk.GridLayout).SetColumnStretchFactor(1, 3)
- dlg.Layout().SetSpacing(6)
- dlg.Layout().SetMargins(walk.Margins{18, 18, 18, 18})
-
- nameLabel, _ := walk.NewTextLabel(dlg)
- dlg.Layout().(*walk.GridLayout).SetRange(nameLabel, walk.Rectangle{0, 0, 1, 1})
- nameLabel.SetTextAlignment(walk.AlignHFarVCenter)
- nameLabel.SetText("Name:")
-
- nameEdit, _ := walk.NewLineEdit(dlg)
- dlg.Layout().(*walk.GridLayout).SetRange(nameEdit, walk.Rectangle{1, 0, 1, 1})
- // TODO: compute the next available tunnel name ?
- nameEdit.SetText(name)
-
- pubkeyLabel, _ := walk.NewTextLabel(dlg)
- dlg.Layout().(*walk.GridLayout).SetRange(pubkeyLabel, walk.Rectangle{0, 1, 1, 1})
- pubkeyLabel.SetTextAlignment(walk.AlignHFarVCenter)
- pubkeyLabel.SetText("Public key:")
-
- pubkeyEdit, _ := walk.NewLineEdit(dlg)
- dlg.Layout().(*walk.GridLayout).SetRange(pubkeyEdit, walk.Rectangle{1, 1, 1, 1})
- pubkeyEdit.SetReadOnly(true)
- pubkeyEdit.SetText("(unknown)")
-
- syntaxEdit, _ := syntax.NewSyntaxEdit(dlg)
- dlg.Layout().(*walk.GridLayout).SetRange(syntaxEdit, walk.Rectangle{0, 2, 2, 1})
- lastPrivate := ""
- syntaxEdit.PrivateKeyChanged().Attach(func(privateKey string) {
- if privateKey == lastPrivate {
- return
- }
- lastPrivate = privateKey
- key, _ := conf.NewPrivateKeyFromString(privateKey)
- if key != nil {
- pubkeyEdit.SetText(key.Public().String())
- } else {
- pubkeyEdit.SetText("(unknown)")
- }
- })
- syntaxEdit.SetText(config.ToWgQuick())
-
- buttonsContainer, _ := walk.NewComposite(dlg)
- dlg.Layout().(*walk.GridLayout).SetRange(buttonsContainer, walk.Rectangle{0, 3, 2, 1})
- buttonsContainer.SetLayout(walk.NewHBoxLayout())
- buttonsContainer.Layout().SetMargins(walk.Margins{})
-
- walk.NewHSpacer(buttonsContainer)
-
- saveButton, _ := walk.NewPushButton(buttonsContainer)
- saveButton.SetText("Save")
- saveButton.Clicked().Attach(func() {
- newName := nameEdit.Text()
- if newName == "" {
- walk.MsgBox(mtw, "Invalid configuration", "Name is required", walk.MsgBoxIconWarning)
- return
- }
-
- if tunnel != nil && tunnel.Name != newName {
- names, err := conf.ListConfigNames()
- if err != nil {
- walk.MsgBox(mtw, "Error", err.Error(), walk.MsgBoxIconError)
- return
- }
-
- for _, name := range names {
- if name == newName {
- walk.MsgBox(mtw, "Invalid configuration", fmt.Sprintf("Another tunnel already exists with the name ‘%s’.", newName), walk.MsgBoxIconWarning)
- return
- }
- }
- }
-
- if !conf.TunnelNameIsValid(newName) {
- walk.MsgBox(mtw, "Invalid configuration", fmt.Sprintf("Tunnel name ‘%s’ is invalid.", newName), walk.MsgBoxIconWarning)
- return
- }
-
- cfg, err := conf.FromWgQuick(syntaxEdit.Text(), newName)
- if err != nil {
- walk.MsgBox(mtw, "Error", err.Error(), walk.MsgBoxIconError)
- return
- }
-
- config = *cfg
-
- dlg.Accept()
- })
-
- cancelButton, _ := walk.NewPushButton(buttonsContainer)
- cancelButton.SetText("Cancel")
- cancelButton.Clicked().Attach(dlg.Cancel)
-
- dlg.SetCancelButton(cancelButton)
- dlg.SetDefaultButton(saveButton)
-
- if dlg.Run() == walk.DlgCmdOK {
- // Save
- return &config
- }
-
- return nil
-}
-
// importFiles tries to import a list of configurations.
func (mtw *ManageTunnelsWindow) importFiles(paths []string) {
type unparsedConfig struct {
@@ -539,7 +410,7 @@ func (mtw *ManageTunnelsWindow) onEditTunnel() {
return
}
- if config := mtw.runTunnelEdit(tunnel); config != nil {
+ if config := runTunnelConfigDialog(mtw, tunnel); config != nil {
// Delete old one
mtw.deleteTunnel(tunnel)
@@ -549,7 +420,7 @@ func (mtw *ManageTunnelsWindow) onEditTunnel() {
}
func (mtw *ManageTunnelsWindow) onAddTunnel() {
- if config := mtw.runTunnelEdit(nil); config != nil {
+ if config := runTunnelConfigDialog(mtw, nil); config != nil {
// Save new
mtw.addTunnel(config)
}
diff --git a/ui/tunnelconfigdialog.go b/ui/tunnelconfigdialog.go
new file mode 100644
index 00000000..8c2fc274
--- /dev/null
+++ b/ui/tunnelconfigdialog.go
@@ -0,0 +1,268 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ */
+
+package ui
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/lxn/walk"
+ "golang.zx2c4.com/wireguard/windows/conf"
+ "golang.zx2c4.com/wireguard/windows/service"
+ "golang.zx2c4.com/wireguard/windows/ui/syntax"
+)
+
+const ipv4DefaultRouteString = "0.0.0.0/0"
+
+type TunnelConfigDialog struct {
+ *walk.Dialog
+ nameEdit *walk.LineEdit
+ pubkeyEdit *walk.LineEdit
+ syntaxEdit *syntax.SyntaxEdit
+ excludePrivateIPsCB *walk.CheckBox
+ saveButton *walk.PushButton
+ tunnel *service.Tunnel
+ config conf.Config
+ ipv4DefaultRouteModRFC1918CIDRs []string
+ ipv4DefaultRouteModRFC1918String string
+ lastPrivateKey string
+ inCheckedChanged bool
+}
+
+func runTunnelConfigDialog(owner walk.Form, tunnel *service.Tunnel) *conf.Config {
+ var (
+ title string
+ name string
+ )
+
+ dlg := &TunnelConfigDialog{tunnel: tunnel}
+
+ if tunnel == nil {
+ // Creating a new tunnel, create a new private key and use the default template
+ title = "Create new tunnel"
+ name = "New tunnel"
+ pk, _ := conf.NewPrivateKey()
+ dlg.config = conf.Config{Interface: conf.Interface{PrivateKey: *pk}}
+ } else {
+ title = "Edit tunnel"
+ name = tunnel.Name
+ dlg.config, _ = tunnel.StoredConfig()
+ }
+
+ layout := walk.NewGridLayout()
+ layout.SetSpacing(6)
+ layout.SetMargins(walk.Margins{18, 18, 18, 18})
+ layout.SetColumnStretchFactor(1, 3)
+
+ dlg.Dialog, _ = walk.NewDialog(owner)
+ dlg.SetIcon(owner.Icon())
+ dlg.SetTitle(title)
+ dlg.SetLayout(layout)
+ // TODO: use size hints in layout elements to communicate the minimal width
+ dlg.SetMinMaxSize(walk.Size{500, 400}, walk.Size{9999, 9999})
+
+ dlg.ipv4DefaultRouteModRFC1918CIDRs = []string{ // Set of all non-private IPv4 IPs
+ "0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4", "32.0.0.0/3",
+ "64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6", "172.0.0.0/12",
+ "172.32.0.0/11", "172.64.0.0/10", "172.128.0.0/9", "173.0.0.0/8", "174.0.0.0/7",
+ "176.0.0.0/4", "192.0.0.0/9", "192.128.0.0/11", "192.160.0.0/13", "192.169.0.0/16",
+ "192.170.0.0/15", "192.172.0.0/14", "192.176.0.0/12", "192.192.0.0/10",
+ "193.0.0.0/8", "194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4",
+ }
+ dlg.ipv4DefaultRouteModRFC1918String = strings.Join(dlg.ipv4DefaultRouteModRFC1918CIDRs, ", ")
+
+ nameLabel, _ := walk.NewTextLabel(dlg)
+ layout.SetRange(nameLabel, walk.Rectangle{0, 0, 1, 1})
+ nameLabel.SetTextAlignment(walk.AlignHFarVCenter)
+ nameLabel.SetText("Name:")
+
+ dlg.nameEdit, _ = walk.NewLineEdit(dlg)
+ layout.SetRange(dlg.nameEdit, walk.Rectangle{1, 0, 1, 1})
+ // TODO: compute the next available tunnel name ?
+ dlg.nameEdit.SetText(name)
+
+ pubkeyLabel, _ := walk.NewTextLabel(dlg)
+ layout.SetRange(pubkeyLabel, walk.Rectangle{0, 1, 1, 1})
+ pubkeyLabel.SetTextAlignment(walk.AlignHFarVCenter)
+ pubkeyLabel.SetText("Public key:")
+
+ dlg.pubkeyEdit, _ = walk.NewLineEdit(dlg)
+ layout.SetRange(dlg.pubkeyEdit, walk.Rectangle{1, 1, 1, 1})
+ dlg.pubkeyEdit.SetReadOnly(true)
+ dlg.pubkeyEdit.SetText("(unknown)")
+
+ dlg.syntaxEdit, _ = syntax.NewSyntaxEdit(dlg)
+ layout.SetRange(dlg.syntaxEdit, walk.Rectangle{0, 2, 2, 1})
+ dlg.syntaxEdit.SetText(dlg.config.ToWgQuick())
+ dlg.syntaxEdit.PrivateKeyChanged().Attach(dlg.onSyntaxEditPrivateKeyChanged)
+ dlg.syntaxEdit.TextChanged().Attach(dlg.updateExcludePrivateIPsCBVisible)
+
+ buttonsContainer, _ := walk.NewComposite(dlg)
+ layout.SetRange(buttonsContainer, walk.Rectangle{0, 3, 2, 1})
+ buttonsContainer.SetLayout(walk.NewHBoxLayout())
+ buttonsContainer.Layout().SetMargins(walk.Margins{})
+
+ dlg.excludePrivateIPsCB, _ = walk.NewCheckBox(buttonsContainer)
+ dlg.excludePrivateIPsCB.SetText("Exclude private IPs")
+ dlg.excludePrivateIPsCB.SetChecked(dlg.privateIPsExcluded())
+ dlg.excludePrivateIPsCB.CheckedChanged().Attach(dlg.onExcludePrivateIPsCBCheckedChanged)
+ dlg.updateExcludePrivateIPsCBVisible()
+
+ walk.NewHSpacer(buttonsContainer)
+
+ dlg.saveButton, _ = walk.NewPushButton(buttonsContainer)
+ dlg.saveButton.SetText("Save")
+ dlg.saveButton.Clicked().Attach(dlg.onSaveButtonClicked)
+
+ cancelButton, _ := walk.NewPushButton(buttonsContainer)
+ cancelButton.SetText("Cancel")
+ cancelButton.Clicked().Attach(dlg.Cancel)
+
+ dlg.SetCancelButton(cancelButton)
+ dlg.SetDefaultButton(dlg.saveButton)
+
+ if dlg.Run() == walk.DlgCmdOK {
+ // Save
+ return &dlg.config
+ }
+
+ return nil
+}
+
+func (dlg *TunnelConfigDialog) updateExcludePrivateIPsCBVisible() {
+ if dlg.inCheckedChanged {
+ return
+ }
+
+ visible := len(dlg.config.Peers) == 1
+
+ if visible {
+ cidrsSlice := dlg.allowedCIDRsSlice()
+ cidrsSet := dlg.setFromSlice(cidrsSlice)
+
+ if len(cidrsSet) != 1 || !cidrsSet[ipv4DefaultRouteString] {
+ for _, cidr := range dlg.ipv4DefaultRouteModRFC1918CIDRs {
+ if !cidrsSet[cidr] {
+ visible = false
+ break
+ }
+ }
+ }
+ }
+
+ dlg.excludePrivateIPsCB.SetVisible(visible)
+}
+
+func (dlg *TunnelConfigDialog) allowedCIDRsSlice() []string {
+ var cidrs []string
+
+ lines := strings.Split(dlg.syntaxEdit.Text(), "\n")
+ for _, line := range lines {
+ if strings.Contains(line, "AllowedIPs") {
+ cidrsMaybeWithSpace := strings.Split(strings.TrimSpace(line[strings.IndexByte(line, '=')+1:]), ",")
+ cidrs = make([]string, len(cidrsMaybeWithSpace))
+ for i, cidr := range cidrsMaybeWithSpace {
+ cidrs[i] = strings.TrimSpace(cidr)
+ }
+ break
+ }
+ }
+
+ return cidrs
+}
+
+func (dlg *TunnelConfigDialog) setFromSlice(slice []string) map[string]bool {
+ set := make(map[string]bool)
+
+ for _, s := range slice {
+ set[s] = true
+ }
+
+ return set
+}
+
+func (dlg *TunnelConfigDialog) privateIPsExcluded() bool {
+ allowedCIDRs := dlg.setFromSlice(dlg.allowedCIDRsSlice())
+
+ if len(allowedCIDRs) != len(dlg.ipv4DefaultRouteModRFC1918CIDRs) {
+ return false
+ }
+
+ for _, cidr := range dlg.ipv4DefaultRouteModRFC1918CIDRs {
+ if !allowedCIDRs[cidr] {
+ return false
+ }
+ }
+
+ return true
+}
+
+func (dlg *TunnelConfigDialog) onSyntaxEditPrivateKeyChanged(privateKey string) {
+ if privateKey == dlg.lastPrivateKey {
+ return
+ }
+ dlg.lastPrivateKey = privateKey
+ key, _ := conf.NewPrivateKeyFromString(privateKey)
+ if key != nil {
+ dlg.pubkeyEdit.SetText(key.Public().String())
+ } else {
+ dlg.pubkeyEdit.SetText("(unknown)")
+ }
+}
+
+func (dlg *TunnelConfigDialog) onExcludePrivateIPsCBCheckedChanged() {
+ dlg.inCheckedChanged = true
+ defer func() {
+ dlg.inCheckedChanged = false
+ }()
+
+ var before, after string
+ if dlg.excludePrivateIPsCB.Checked() {
+ before, after = ipv4DefaultRouteString, dlg.ipv4DefaultRouteModRFC1918String
+ } else {
+ before, after = dlg.ipv4DefaultRouteModRFC1918String, ipv4DefaultRouteString
+ }
+ // TODO: Preserve changes the user may have done to the list?
+ dlg.syntaxEdit.SetText(strings.ReplaceAll(dlg.syntaxEdit.Text(), "AllowedIPs = "+before, "AllowedIPs = "+after))
+}
+
+func (dlg *TunnelConfigDialog) onSaveButtonClicked() {
+ newName := dlg.nameEdit.Text()
+ if newName == "" {
+ walk.MsgBox(dlg, "Invalid configuration", "Name is required", walk.MsgBoxIconWarning)
+ return
+ }
+
+ if dlg.tunnel != nil && dlg.tunnel.Name != newName {
+ names, err := conf.ListConfigNames()
+ if err != nil {
+ walk.MsgBox(dlg, "Error", err.Error(), walk.MsgBoxIconError)
+ return
+ }
+
+ for _, name := range names {
+ if name == newName {
+ walk.MsgBox(dlg, "Invalid configuration", fmt.Sprintf("Another tunnel already exists with the name ‘%s’.", newName), walk.MsgBoxIconWarning)
+ return
+ }
+ }
+ }
+
+ if !conf.TunnelNameIsValid(newName) {
+ walk.MsgBox(dlg, "Invalid configuration", fmt.Sprintf("Tunnel name ‘%s’ is invalid.", newName), walk.MsgBoxIconWarning)
+ return
+ }
+
+ cfg, err := conf.FromWgQuick(dlg.syntaxEdit.Text(), newName)
+ if err != nil {
+ walk.MsgBox(dlg, "Error", err.Error(), walk.MsgBoxIconError)
+ return
+ }
+
+ dlg.config = *cfg
+
+ dlg.Accept()
+}