golang.zx2c4.com/wireguard/windows@v0.5.4-0.20230123132234-dcc0eb72a04b/ui/editdialog.go (about) 1 /* SPDX-License-Identifier: MIT 2 * 3 * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. 4 */ 5 6 package ui 7 8 import ( 9 "net/netip" 10 "strings" 11 12 "github.com/lxn/walk" 13 "github.com/lxn/win" 14 "golang.org/x/sys/windows" 15 16 "golang.zx2c4.com/wireguard/windows/conf" 17 "golang.zx2c4.com/wireguard/windows/l18n" 18 "golang.zx2c4.com/wireguard/windows/manager" 19 "golang.zx2c4.com/wireguard/windows/ui/syntax" 20 ) 21 22 type EditDialog struct { 23 *walk.Dialog 24 nameEdit *walk.LineEdit 25 pubkeyEdit *walk.LineEdit 26 syntaxEdit *syntax.SyntaxEdit 27 blockUntunneledTrafficCB *walk.CheckBox 28 saveButton *walk.PushButton 29 config conf.Config 30 lastPrivateKey string 31 blockUntunneledTraficCheckGuard bool 32 } 33 34 func runEditDialog(owner walk.Form, tunnel *manager.Tunnel) *conf.Config { 35 dlg, err := newEditDialog(owner, tunnel) 36 if showError(err, owner) { 37 return nil 38 } 39 40 if dlg.Run() == walk.DlgCmdOK { 41 return &dlg.config 42 } 43 44 return nil 45 } 46 47 func newEditDialog(owner walk.Form, tunnel *manager.Tunnel) (*EditDialog, error) { 48 var err error 49 var disposables walk.Disposables 50 defer disposables.Treat() 51 52 dlg := new(EditDialog) 53 54 var title string 55 if tunnel == nil { 56 title = l18n.Sprintf("Create new tunnel") 57 } else { 58 title = l18n.Sprintf("Edit tunnel") 59 } 60 61 if tunnel == nil { 62 // Creating a new tunnel, create a new private key and use the default template 63 pk, _ := conf.NewPrivateKey() 64 dlg.config = conf.Config{Interface: conf.Interface{PrivateKey: *pk}} 65 } else { 66 dlg.config, _ = tunnel.StoredConfig() 67 } 68 69 layout := walk.NewGridLayout() 70 layout.SetSpacing(6) 71 layout.SetMargins(walk.Margins{10, 10, 10, 10}) 72 layout.SetColumnStretchFactor(1, 3) 73 74 if dlg.Dialog, err = walk.NewDialog(owner); err != nil { 75 return nil, err 76 } 77 disposables.Add(dlg) 78 dlg.SetIcon(owner.Icon()) 79 dlg.SetTitle(title) 80 dlg.SetLayout(layout) 81 dlg.SetMinMaxSize(walk.Size{500, 400}, walk.Size{0, 0}) 82 if icon, err := loadSystemIcon("imageres", -114, 32); err == nil { 83 dlg.SetIcon(icon) 84 } 85 86 nameLabel, err := walk.NewTextLabel(dlg) 87 if err != nil { 88 return nil, err 89 } 90 layout.SetRange(nameLabel, walk.Rectangle{0, 0, 1, 1}) 91 nameLabel.SetTextAlignment(walk.AlignHFarVCenter) 92 nameLabel.SetText(l18n.Sprintf("&Name:")) 93 94 if dlg.nameEdit, err = walk.NewLineEdit(dlg); err != nil { 95 return nil, err 96 } 97 layout.SetRange(dlg.nameEdit, walk.Rectangle{1, 0, 1, 1}) 98 dlg.nameEdit.SetText(dlg.config.Name) 99 100 pubkeyLabel, err := walk.NewTextLabel(dlg) 101 if err != nil { 102 return nil, err 103 } 104 layout.SetRange(pubkeyLabel, walk.Rectangle{0, 1, 1, 1}) 105 pubkeyLabel.SetTextAlignment(walk.AlignHFarVCenter) 106 pubkeyLabel.SetText(l18n.Sprintf("&Public key:")) 107 108 if dlg.pubkeyEdit, err = walk.NewLineEdit(dlg); err != nil { 109 return nil, err 110 } 111 layout.SetRange(dlg.pubkeyEdit, walk.Rectangle{1, 1, 1, 1}) 112 dlg.pubkeyEdit.SetReadOnly(true) 113 dlg.pubkeyEdit.SetText(l18n.Sprintf("(unknown)")) 114 dlg.pubkeyEdit.Accessibility().SetRole(walk.AccRoleStatictext) 115 116 if dlg.syntaxEdit, err = syntax.NewSyntaxEdit(dlg); err != nil { 117 return nil, err 118 } 119 layout.SetRange(dlg.syntaxEdit, walk.Rectangle{0, 2, 2, 1}) 120 121 buttonsContainer, err := walk.NewComposite(dlg) 122 if err != nil { 123 return nil, err 124 } 125 layout.SetRange(buttonsContainer, walk.Rectangle{0, 3, 2, 1}) 126 buttonsContainer.SetLayout(walk.NewHBoxLayout()) 127 buttonsContainer.Layout().SetMargins(walk.Margins{}) 128 129 if dlg.blockUntunneledTrafficCB, err = walk.NewCheckBox(buttonsContainer); err != nil { 130 return nil, err 131 } 132 dlg.blockUntunneledTrafficCB.SetText(l18n.Sprintf("&Block untunneled traffic (kill-switch)")) 133 dlg.blockUntunneledTrafficCB.SetToolTipText(l18n.Sprintf("When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, and the interface does not have table off, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.")) 134 dlg.blockUntunneledTrafficCB.SetVisible(false) 135 dlg.blockUntunneledTrafficCB.CheckedChanged().Attach(dlg.onBlockUntunneledTrafficCBCheckedChanged) 136 137 walk.NewHSpacer(buttonsContainer) 138 139 if dlg.saveButton, err = walk.NewPushButton(buttonsContainer); err != nil { 140 return nil, err 141 } 142 dlg.saveButton.SetText(l18n.Sprintf("&Save")) 143 dlg.saveButton.Clicked().Attach(dlg.onSaveButtonClicked) 144 145 cancelButton, err := walk.NewPushButton(buttonsContainer) 146 if err != nil { 147 return nil, err 148 } 149 cancelButton.SetText(l18n.Sprintf("Cancel")) 150 cancelButton.Clicked().Attach(dlg.Cancel) 151 152 dlg.SetCancelButton(cancelButton) 153 dlg.SetDefaultButton(dlg.saveButton) 154 155 dlg.syntaxEdit.PrivateKeyChanged().Attach(dlg.onSyntaxEditPrivateKeyChanged) 156 dlg.syntaxEdit.BlockUntunneledTrafficStateChanged().Attach(dlg.onBlockUntunneledTrafficStateChanged) 157 dlg.syntaxEdit.SetText(dlg.config.ToWgQuick()) 158 159 // Insert a dummy label immediately preceding syntaxEdit to have screen readers read it. 160 // Otherwise they fallback to "RichEdit Control". 161 syntaxEditWnd := dlg.syntaxEdit.Handle() 162 parentWnd := win.GetParent(syntaxEditWnd) 163 labelWnd := win.CreateWindowEx(0, 164 windows.StringToUTF16Ptr("STATIC"), windows.StringToUTF16Ptr(l18n.Sprintf("&Configuration:")), 165 win.WS_CHILD|win.WS_GROUP|win.SS_LEFT, 0, 0, 0, 0, 166 parentWnd, win.HMENU(^uintptr(0)), win.HINSTANCE(win.GetWindowLongPtr(parentWnd, win.GWLP_HINSTANCE)), nil) 167 prevWnd := win.GetWindow(syntaxEditWnd, win.GW_HWNDPREV) 168 nextWnd := win.GetWindow(syntaxEditWnd, win.GW_HWNDNEXT) 169 win.SetWindowPos(labelWnd, prevWnd, 0, 0, 0, 0, win.SWP_NOSIZE|win.SWP_NOMOVE) 170 win.SetWindowPos(syntaxEditWnd, labelWnd, 0, 0, 0, 0, win.SWP_NOSIZE|win.SWP_NOMOVE) 171 win.SetWindowPos(nextWnd, syntaxEditWnd, 0, 0, 0, 0, win.SWP_NOSIZE|win.SWP_NOMOVE) 172 173 if tunnel != nil { 174 dlg.Starting().Attach(func() { 175 dlg.syntaxEdit.SetFocus() 176 }) 177 } 178 179 disposables.Spare() 180 181 return dlg, nil 182 } 183 184 func (dlg *EditDialog) onBlockUntunneledTrafficCBCheckedChanged() { 185 if dlg.blockUntunneledTraficCheckGuard { 186 return 187 } 188 var ( 189 v400 = netip.PrefixFrom(netip.IPv4Unspecified(), 0) 190 v600000 = netip.PrefixFrom(netip.IPv6Unspecified(), 0) 191 v401 = netip.PrefixFrom(netip.AddrFrom4([4]byte{}), 1) 192 v600001 = netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 1) 193 v41281 = netip.PrefixFrom(netip.AddrFrom4([4]byte{0x80}), 1) 194 v680001 = netip.PrefixFrom(netip.AddrFrom16([16]byte{0x80}), 1) 195 ) 196 197 block := dlg.blockUntunneledTrafficCB.Checked() 198 cfg, err := conf.FromWgQuick(dlg.syntaxEdit.Text(), "temporary") 199 var newAllowedIPs []netip.Prefix 200 201 if err != nil { 202 goto err 203 } 204 if len(cfg.Peers) != 1 { 205 goto err 206 } 207 208 newAllowedIPs = make([]netip.Prefix, 0, len(cfg.Peers[0].AllowedIPs)) 209 if block { 210 var ( 211 foundV401 bool 212 foundV41281 bool 213 foundV600001 bool 214 foundV680001 bool 215 ) 216 for _, allowedip := range cfg.Peers[0].AllowedIPs { 217 if allowedip == v600001 { 218 foundV600001 = true 219 } else if allowedip == v680001 { 220 foundV680001 = true 221 } else if allowedip == v401 { 222 foundV401 = true 223 } else if allowedip == v41281 { 224 foundV41281 = true 225 } else { 226 newAllowedIPs = append(newAllowedIPs, allowedip) 227 } 228 } 229 if !((foundV401 && foundV41281) || (foundV600001 && foundV680001)) { 230 goto err 231 } 232 if foundV401 && foundV41281 { 233 newAllowedIPs = append(newAllowedIPs, v400) 234 } else if foundV401 { 235 newAllowedIPs = append(newAllowedIPs, v401) 236 } else if foundV41281 { 237 newAllowedIPs = append(newAllowedIPs, v41281) 238 } 239 if foundV600001 && foundV680001 { 240 newAllowedIPs = append(newAllowedIPs, v600000) 241 } else if foundV600001 { 242 newAllowedIPs = append(newAllowedIPs, v600001) 243 } else if foundV680001 { 244 newAllowedIPs = append(newAllowedIPs, v680001) 245 } 246 cfg.Peers[0].AllowedIPs = newAllowedIPs 247 } else { 248 var ( 249 foundV400 bool 250 foundV600000 bool 251 ) 252 for _, allowedip := range cfg.Peers[0].AllowedIPs { 253 if allowedip == v600000 { 254 foundV600000 = true 255 } else if allowedip == v400 { 256 foundV400 = true 257 } else { 258 newAllowedIPs = append(newAllowedIPs, allowedip) 259 } 260 } 261 if !(foundV400 || foundV600000) { 262 goto err 263 } 264 if foundV400 { 265 newAllowedIPs = append(newAllowedIPs, v401) 266 newAllowedIPs = append(newAllowedIPs, v41281) 267 } 268 if foundV600000 { 269 newAllowedIPs = append(newAllowedIPs, v600001) 270 newAllowedIPs = append(newAllowedIPs, v680001) 271 } 272 cfg.Peers[0].AllowedIPs = newAllowedIPs 273 } 274 dlg.syntaxEdit.SetText(cfg.ToWgQuick()) 275 return 276 277 err: 278 text := dlg.syntaxEdit.Text() 279 dlg.syntaxEdit.SetText("") 280 dlg.syntaxEdit.SetText(text) 281 } 282 283 func (dlg *EditDialog) onBlockUntunneledTrafficStateChanged(state int) { 284 dlg.blockUntunneledTraficCheckGuard = true 285 switch syntax.BlockState(state) { 286 case syntax.InevaluableBlockingUntunneledTraffic: 287 dlg.blockUntunneledTrafficCB.SetVisible(false) 288 case syntax.BlockingUntunneledTraffic: 289 dlg.blockUntunneledTrafficCB.SetVisible(true) 290 dlg.blockUntunneledTrafficCB.SetChecked(true) 291 case syntax.NotBlockingUntunneledTraffic: 292 dlg.blockUntunneledTrafficCB.SetVisible(true) 293 dlg.blockUntunneledTrafficCB.SetChecked(false) 294 } 295 dlg.blockUntunneledTraficCheckGuard = false 296 } 297 298 func (dlg *EditDialog) onSyntaxEditPrivateKeyChanged(privateKey string) { 299 if privateKey == dlg.lastPrivateKey { 300 return 301 } 302 dlg.lastPrivateKey = privateKey 303 key, _ := conf.NewPrivateKeyFromString(privateKey) 304 if key != nil { 305 dlg.pubkeyEdit.SetText(key.Public().String()) 306 } else { 307 dlg.pubkeyEdit.SetText(l18n.Sprintf("(unknown)")) 308 } 309 } 310 311 func (dlg *EditDialog) onSaveButtonClicked() { 312 newName := dlg.nameEdit.Text() 313 if newName == "" { 314 showWarningCustom(dlg, l18n.Sprintf("Invalid name"), l18n.Sprintf("A name is required.")) 315 return 316 } 317 if !conf.TunnelNameIsValid(newName) { 318 showWarningCustom(dlg, l18n.Sprintf("Invalid name"), l18n.Sprintf("Tunnel name ā%sā is invalid.", newName)) 319 return 320 } 321 newNameLower := strings.ToLower(newName) 322 323 if newNameLower != strings.ToLower(dlg.config.Name) { 324 existingTunnelList, err := manager.IPCClientTunnels() 325 if err != nil { 326 showWarningCustom(dlg, l18n.Sprintf("Unable to list existing tunnels"), err.Error()) 327 return 328 } 329 for _, tunnel := range existingTunnelList { 330 if strings.ToLower(tunnel.Name) == newNameLower { 331 showWarningCustom(dlg, l18n.Sprintf("Tunnel already exists"), l18n.Sprintf("Another tunnel already exists with the name ā%sā.", newName)) 332 return 333 } 334 } 335 } 336 337 cfg, err := conf.FromWgQuick(dlg.syntaxEdit.Text(), newName) 338 if err != nil { 339 showErrorCustom(dlg, l18n.Sprintf("Unable to create new configuration"), err.Error()) 340 return 341 } 342 343 dlg.config = *cfg 344 dlg.Accept() 345 }