From 48098ab608248a613da960d64dad4a653194aa41 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Thu, 2 May 2019 18:32:04 +0200 Subject: ui: rename things to say tunnel less --- ui/editdialog.go | 323 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 323 insertions(+) create mode 100644 ui/editdialog.go (limited to 'ui/editdialog.go') diff --git a/ui/editdialog.go b/ui/editdialog.go new file mode 100644 index 00000000..3cd5b606 --- /dev/null +++ b/ui/editdialog.go @@ -0,0 +1,323 @@ +/* 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 ( + configKeyDNS = "DNS" + configKeyAllowedIPs = "AllowedIPs" +) + +var ( + ipv4Wildcard = orderedStringSetFromSlice([]string{"0.0.0.0/0"}) + ipv4PublicNetworks = orderedStringSetFromSlice([]string{ + "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", + }) +) + +type allowedIPsState int + +const ( + allowedIPsStateInvalid allowedIPsState = iota + allowedIPsStateContainsIPV4Wildcard + allowedIPsStateContainsIPV4PublicNetworks + allowedIPsStateOther +) + +type EditDialog struct { + *walk.Dialog + nameEdit *walk.LineEdit + pubkeyEdit *walk.LineEdit + syntaxEdit *syntax.SyntaxEdit + excludePrivateIPsCB *walk.CheckBox + saveButton *walk.PushButton + tunnel *service.Tunnel + config conf.Config + allowedIPsState allowedIPsState + lastPrivateKey string + inCheckedChanged bool +} + +func runTunnelEditDialog(owner walk.Form, tunnel *service.Tunnel) *conf.Config { + var ( + title string + name string + ) + + dlg := &EditDialog{tunnel: tunnel} + + if tunnel == nil { + // Creating a new tunnel, create a new private key and use the default template + title = "Create 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{10, 10, 10, 10}) + layout.SetColumnStretchFactor(1, 3) + + dlg.Dialog, _ = walk.NewDialog(owner) + dlg.SetIcon(owner.Icon()) + dlg.SetTitle(title) + dlg.SetLayout(layout) + dlg.SetMinMaxSize(walk.Size{500, 400}, walk.Size{0, 0}) + + 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}) + 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.PrivateKeyChanged().Attach(dlg.onSyntaxEditPrivateKeyChanged) + dlg.syntaxEdit.SetText(dlg.config.ToWgQuick()) + 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.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) + + dlg.updateAllowedIPsState() + + if dlg.Run() == walk.DlgCmdOK { + // Save + return &dlg.config + } + + return nil +} + +func (dlg *EditDialog) updateAllowedIPsState() { + var newState allowedIPsState + if len(dlg.config.Peers) == 1 { + if allowedIPs := dlg.allowedIPsSet(); allowedIPs.IsSupersetOf(ipv4Wildcard) { + newState = allowedIPsStateContainsIPV4Wildcard + } else if allowedIPs.IsSupersetOf(ipv4PublicNetworks) { + newState = allowedIPsStateContainsIPV4PublicNetworks + } else { + newState = allowedIPsStateOther + } + } else { + newState = allowedIPsStateInvalid + } + + if newState != dlg.allowedIPsState { + dlg.allowedIPsState = newState + + dlg.excludePrivateIPsCB.SetVisible(dlg.canExcludePrivateIPs()) + dlg.excludePrivateIPsCB.SetChecked(dlg.privateIPsExcluded()) + } +} + +func (dlg *EditDialog) canExcludePrivateIPs() bool { + return dlg.allowedIPsState == allowedIPsStateContainsIPV4PublicNetworks || + dlg.allowedIPsState == allowedIPsStateContainsIPV4Wildcard +} + +func (dlg *EditDialog) privateIPsExcluded() bool { + return dlg.allowedIPsState == allowedIPsStateContainsIPV4PublicNetworks +} + +func (dlg *EditDialog) setPrivateIPsExcluded(excluded bool) { + if !dlg.canExcludePrivateIPs() || dlg.privateIPsExcluded() == excluded { + return + } + + var oldNetworks, newNetworks *orderedStringSet + if excluded { + oldNetworks, newNetworks = ipv4Wildcard, ipv4PublicNetworks + } else { + oldNetworks, newNetworks = ipv4PublicNetworks, ipv4Wildcard + } + input := dlg.allowedIPs() + output := newOrderedStringSet() + var replaced bool + + // Replace the first instance of the wildcard with the public network list, or vice versa. + for _, network := range input { + if oldNetworks.Contains(network) { + if !replaced { + output.UniteWith(newNetworks) + replaced = true + } + } else { + output.Add(network) + } + } + + // DNS servers only need to be handled specially when we're excluding private IPs. + for _, route := range dlg.dnsRoutes() { + if excluded { + output.Add(route) + } else { + output.Remove(route) + output.Remove(route + "/32") + } + } + + if excluded { + dlg.allowedIPsState = allowedIPsStateContainsIPV4PublicNetworks + } else { + dlg.allowedIPsState = allowedIPsStateContainsIPV4Wildcard + } + + dlg.replaceLine(configKeyAllowedIPs, strings.Join(output.ToSlice(), ", ")) +} + +func (dlg *EditDialog) replaceLine(key, value string) { + text := dlg.syntaxEdit.Text() + + start := strings.Index(text, key) + end := start + strings.Index(text[start:], "\n") + oldLine := text[start:end] + newLine := fmt.Sprintf("%s = %s", key, value) + + dlg.syntaxEdit.SetText(strings.ReplaceAll(text, oldLine, newLine)) +} + +func (dlg *EditDialog) updateExcludePrivateIPsCBVisible() { + dlg.updateAllowedIPsState() + + dlg.excludePrivateIPsCB.SetVisible(dlg.canExcludePrivateIPs()) +} + +func (dlg *EditDialog) dnsRoutes() []string { + return dlg.routes(configKeyDNS) +} + +func (dlg *EditDialog) allowedIPs() []string { + return dlg.routes(configKeyAllowedIPs) +} + +func (dlg *EditDialog) allowedIPsSet() *orderedStringSet { + return orderedStringSetFromSlice(dlg.allowedIPs()) +} + +func (dlg *EditDialog) routes(key string) []string { + var routes []string + + lines := strings.Split(dlg.syntaxEdit.Text(), "\n") + for _, line := range lines { + if strings.HasPrefix(strings.TrimSpace(line), key) { + routesMaybeWithSpace := strings.Split(strings.TrimSpace(line[strings.IndexByte(line, '=')+1:]), ",") + routes = make([]string, len(routesMaybeWithSpace)) + for i, route := range routesMaybeWithSpace { + routes[i] = strings.TrimSpace(route) + } + break + } + } + + return routes +} + +func (dlg *EditDialog) onExcludePrivateIPsCBCheckedChanged() { + dlg.setPrivateIPsExcluded(dlg.excludePrivateIPsCB.Checked()) +} + +func (dlg *EditDialog) 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 *EditDialog) 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 strings.ToLower(name) == strings.ToLower(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() +} -- cgit v1.2.3-59-g8ed1b