├── .gitignore ├── README.md ├── addressconfig.go ├── build.cmd ├── conf ├── admin_windows.go ├── config.go ├── dnsresolver_windows.go ├── dpapi │ ├── dpapi_windows.go │ ├── dpapi_windows_test.go │ └── test.exe ├── filewriter_windows.go ├── migration_windows.go ├── mksyscall.go ├── name.go ├── parser.go ├── parser_test.go ├── path_windows.go ├── store.go ├── store_test.go ├── storewatcher.go ├── storewatcher_windows.go ├── writer.go └── zsyscall_windows.go ├── deterministicguid.go ├── elevate ├── doas.go ├── loader.go ├── membership.go ├── privileges.go └── shellexecute.go ├── go.mod ├── go.sum ├── interfacewatcher.go ├── ipcpermissions.go ├── l18n └── l18n.go ├── main.go ├── ringlogger ├── cli_test.go ├── dump.go ├── global.go └── ringlogger.go ├── scriptrunner.go ├── service.go ├── services ├── errors.go └── names.go ├── tunnel ├── addressconfig.go ├── defaultroutemonitor.go ├── deterministicguid.go ├── firewall │ ├── blocker.go │ ├── helpers.go │ ├── mksyscall.go │ ├── rules.go │ ├── syscall_windows.go │ ├── types_windows.go │ ├── types_windows_32.go │ ├── types_windows_64.go │ ├── types_windows_test.go │ └── zsyscall_windows.go ├── interfacewatcher.go ├── ipcpermissions.go ├── mtumonitor.go ├── scriptrunner.go ├── service.go ├── winipcfg │ ├── interface_change_handler.go │ ├── luid.go │ ├── mksyscall.go │ ├── netsh.go │ ├── route_change_handler.go │ ├── types.go │ ├── types_32.go │ ├── types_64.go │ ├── types_test.go │ ├── types_test_32.go │ ├── types_test_64.go │ ├── unicast_address_change_handler.go │ ├── winipcfg.go │ ├── winipcfg_test.go │ └── zwinipcfg_windows.go └── wintun_test.go └── version ├── certificate_test.go ├── official.go ├── os.go ├── protocol.go ├── useragent.go └── version.go /.gitignore: -------------------------------------------------------------------------------- 1 | .deps 2 | tunnel.dll 3 | tunnel.h 4 | x64/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Embeddable WireGuard Tunnel Library 2 | 3 | A simple yet effective way of leveraging the existing WireGuard codebase. 4 | This is more or less the same thing as the 5 | [embeddable-dll-service](https://git.zx2c4.com/wireguard-windows/about/embeddable-dll-service/README.md) and 6 | [tunnel](https://git.zx2c4.com/wireguard-windows/tree/tunnel) modules from upstream 7 | [wireguard-windows](https://git.zx2c4.com/wireguard-windows/about/). 8 | 9 | ## Building 10 | 11 | To build the embeddable dll service, run `.\build.cmd` to produce `x64\tunnel.dll`. 12 | 13 | ## License 14 | 15 | The contents of this directory are MIT-licensed. Files which have been modified 16 | from their upstream versions will also list the Mozilla Foundation as a copyright 17 | holder. 18 | 19 | ```text 20 | Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. 21 | 22 | Permission is hereby granted, free of charge, to any person obtaining a 23 | copy of this software and associated documentation files (the "Software"), 24 | to deal in the Software without restriction, including without limitation 25 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 26 | and/or sell copies of the Software, and to permit persons to whom the 27 | Software is furnished to do so, subject to the following conditions: 28 | 29 | The above copyright notice and this permission notice shall be included in 30 | all copies or substantial portions of the Software. 31 | 32 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 33 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 34 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 35 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 36 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 37 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 38 | DEALINGS IN THE SOFTWARE. 39 | ``` -------------------------------------------------------------------------------- /addressconfig.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package main 7 | 8 | import ( 9 | "bytes" 10 | "log" 11 | "net" 12 | "sort" 13 | 14 | "github.com/amnezia-vpn/amneziawg-go/tun" 15 | "golang.org/x/sys/windows" 16 | 17 | "github.com/amnezia-vpn/amneziawg-windows/conf" 18 | "github.com/amnezia-vpn/amneziawg-windows/tunnel/firewall" 19 | "github.com/amnezia-vpn/amneziawg-windows/tunnel/winipcfg" 20 | ) 21 | 22 | func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []net.IPNet) { 23 | if len(addresses) == 0 { 24 | return 25 | } 26 | includedInAddresses := func(a net.IPNet) bool { 27 | // TODO: this makes the whole algorithm O(n^2). But we can't stick net.IPNet in a Go hashmap. Bummer! 28 | for _, addr := range addresses { 29 | ip := addr.IP 30 | if ip4 := ip.To4(); ip4 != nil { 31 | ip = ip4 32 | } 33 | mA, _ := addr.Mask.Size() 34 | mB, _ := a.Mask.Size() 35 | if bytes.Equal(ip, a.IP) && mA == mB { 36 | return true 37 | } 38 | } 39 | return false 40 | } 41 | interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagDefault) 42 | if err != nil { 43 | return 44 | } 45 | for _, iface := range interfaces { 46 | if iface.OperStatus == winipcfg.IfOperStatusUp { 47 | continue 48 | } 49 | for address := iface.FirstUnicastAddress; address != nil; address = address.Next { 50 | ip := address.Address.IP() 51 | ipnet := net.IPNet{IP: ip, Mask: net.CIDRMask(int(address.OnLinkPrefixLength), 8*len(ip))} 52 | if includedInAddresses(ipnet) { 53 | log.Printf("Cleaning up stale address %s from interface ‘%s’", ipnet.String(), iface.FriendlyName()) 54 | iface.LUID.DeleteIPAddress(ipnet) 55 | } 56 | } 57 | } 58 | } 59 | 60 | func configureInterface(family winipcfg.AddressFamily, conf *conf.Config, tun *tun.NativeTun) error { 61 | luid := winipcfg.LUID(tun.LUID()) 62 | 63 | estimatedRouteCount := 0 64 | for _, peer := range conf.Peers { 65 | estimatedRouteCount += len(peer.AllowedIPs) 66 | } 67 | routes := make([]winipcfg.RouteData, 0, estimatedRouteCount) 68 | addresses := make([]net.IPNet, len(conf.Interface.Addresses)) 69 | var haveV4Address, haveV6Address bool 70 | for i, addr := range conf.Interface.Addresses { 71 | addresses[i] = addr.IPNet() 72 | if addr.Bits() == 32 { 73 | haveV4Address = true 74 | } else if addr.Bits() == 128 { 75 | haveV6Address = true 76 | } 77 | } 78 | 79 | foundDefault4 := false 80 | foundDefault6 := false 81 | for _, peer := range conf.Peers { 82 | for _, allowedip := range peer.AllowedIPs { 83 | allowedip.MaskSelf() 84 | if (allowedip.Bits() == 32 && !haveV4Address) || (allowedip.Bits() == 128 && !haveV6Address) { 85 | continue 86 | } 87 | route := winipcfg.RouteData{ 88 | Destination: allowedip.IPNet(), 89 | Metric: 0, 90 | } 91 | if allowedip.Bits() == 32 { 92 | if allowedip.Cidr == 0 { 93 | foundDefault4 = true 94 | } 95 | route.NextHop = net.IPv4zero 96 | } else if allowedip.Bits() == 128 { 97 | if allowedip.Cidr == 0 { 98 | foundDefault6 = true 99 | } 100 | route.NextHop = net.IPv6zero 101 | } 102 | routes = append(routes, route) 103 | } 104 | } 105 | 106 | err := luid.SetIPAddressesForFamily(family, addresses) 107 | if err == windows.ERROR_OBJECT_ALREADY_EXISTS { 108 | cleanupAddressesOnDisconnectedInterfaces(family, addresses) 109 | err = luid.SetIPAddressesForFamily(family, addresses) 110 | } 111 | if err != nil { 112 | return err 113 | } 114 | 115 | deduplicatedRoutes := make([]*winipcfg.RouteData, 0, len(routes)) 116 | sort.Slice(routes, func(i, j int) bool { 117 | if routes[i].Metric != routes[j].Metric { 118 | return routes[i].Metric < routes[j].Metric 119 | } 120 | if c := bytes.Compare(routes[i].NextHop, routes[j].NextHop); c != 0 { 121 | return c < 0 122 | } 123 | if c := bytes.Compare(routes[i].Destination.IP, routes[j].Destination.IP); c != 0 { 124 | return c < 0 125 | } 126 | if c := bytes.Compare(routes[i].Destination.Mask, routes[j].Destination.Mask); c != 0 { 127 | return c < 0 128 | } 129 | return false 130 | }) 131 | for i := 0; i < len(routes); i++ { 132 | if i > 0 && routes[i].Metric == routes[i-1].Metric && 133 | bytes.Equal(routes[i].NextHop, routes[i-1].NextHop) && 134 | bytes.Equal(routes[i].Destination.IP, routes[i-1].Destination.IP) && 135 | bytes.Equal(routes[i].Destination.Mask, routes[i-1].Destination.Mask) { 136 | continue 137 | } 138 | deduplicatedRoutes = append(deduplicatedRoutes, &routes[i]) 139 | } 140 | 141 | if !conf.Interface.TableOff { 142 | err = luid.SetRoutesForFamily(family, deduplicatedRoutes) 143 | if err != nil { 144 | return err 145 | } 146 | } 147 | 148 | ipif, err := luid.IPInterface(family) 149 | if err != nil { 150 | return err 151 | } 152 | if conf.Interface.MTU > 0 { 153 | ipif.NLMTU = uint32(conf.Interface.MTU) 154 | tun.ForceMTU(int(ipif.NLMTU)) 155 | } 156 | if family == windows.AF_INET { 157 | if foundDefault4 { 158 | ipif.UseAutomaticMetric = false 159 | ipif.Metric = 0 160 | } 161 | } else if family == windows.AF_INET6 { 162 | if foundDefault6 { 163 | ipif.UseAutomaticMetric = false 164 | ipif.Metric = 0 165 | } 166 | ipif.DadTransmits = 0 167 | ipif.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled 168 | } 169 | return ipif.Set() 170 | } 171 | 172 | func enableFirewall(conf *conf.Config, tun *tun.NativeTun) error { 173 | doNotRestrict := true 174 | if len(conf.Peers) == 1 && !conf.Interface.TableOff { 175 | nextallowedip: 176 | for _, allowedip := range conf.Peers[0].AllowedIPs { 177 | if allowedip.Cidr == 0 { 178 | for _, b := range allowedip.IP { 179 | if b != 0 { 180 | continue nextallowedip 181 | } 182 | } 183 | doNotRestrict = false 184 | break 185 | } 186 | } 187 | } 188 | log.Println("Enabling firewall rules") 189 | return firewall.EnableFirewall(tun.LUID(), doNotRestrict, conf.Interface.DNS) 190 | } 191 | -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | rem SPDX-License-Identifier: MIT 3 | rem Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | 5 | setlocal 6 | set BUILDDIR=%~dp0 7 | set PATH=%BUILDDIR%.deps\llvm-mingw\bin;%BUILDDIR%.deps\go\bin;%PATH% 8 | set PATHEXT=.exe 9 | cd /d %BUILDDIR% || exit /b 1 10 | 11 | if exist .deps\prepared goto :build 12 | :installdeps 13 | rmdir /s /q .deps 2> NUL 14 | mkdir .deps || goto :error 15 | cd .deps || goto :error 16 | call :download go.zip https://go.dev/dl/go1.24.2.windows-amd64.zip 29c553aabee0743e2ffa3e9fa0cda00ef3b3cc4ff0bc92007f31f80fd69892e1 || goto :error 17 | rem Mirror of https://github.com/mstorsjo/llvm-mingw/releases/download/20201020/llvm-mingw-20201020-msvcrt-x86_64.zip 18 | call :download llvm-mingw-msvcrt.zip https://download.wireguard.com/windows-toolchain/distfiles/llvm-mingw-20201020-msvcrt-x86_64.zip 2e46593245090df96d15e360e092f0b62b97e93866e0162dca7f93b16722b844 || goto :error 19 | call :download wintun.zip https://www.wintun.net/builds/wintun-0.14.1.zip 07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51 || goto :error 20 | copy /y NUL prepared > NUL || goto :error 21 | cd .. || goto :error 22 | 23 | :build 24 | set GOOS=windows 25 | set GOARM=7 26 | set GOPATH=%BUILDDIR%.deps\gopath 27 | set GOROOT=%BUILDDIR%.deps\go 28 | set CGO_ENABLED=1 29 | set CGO_CFLAGS=-O3 -Wall -Wno-unused-function -Wno-switch -std=gnu11 -DWINVER=0x0601 30 | set CGO_LDFLAGS=-Wl,--dynamicbase -Wl,--nxcompat -Wl,--export-all-symbols 31 | set CGO_LDFLAGS=%CGO_LDFLAGS% -Wl,--high-entropy-va 32 | call :build_plat x64 x86_64 amd64 || goto :error 33 | call :build_plat x86 i686 386 || goto :error 34 | 35 | :success 36 | echo [+] Success 37 | exit /b 0 38 | 39 | :download 40 | echo [+] Downloading %1 41 | curl -#fLo %1 %2 || exit /b 1 42 | echo [+] Verifying %1 43 | for /f %%a in ('CertUtil -hashfile %1 SHA256 ^| findstr /r "^[0-9a-f]*$"') do if not "%%a"=="%~3" exit /b 1 44 | echo [+] Extracting %1 45 | tar -xf %1 %~4 || exit /b 1 46 | echo [+] Cleaning up %1 47 | del %1 || exit /b 1 48 | goto :eof 49 | 50 | :build_plat 51 | set CC=%~2-w64-mingw32-gcc 52 | set GOARCH=%~3 53 | mkdir %1 >NUL 2>&1 54 | echo [+] Building library %1 55 | go build -buildmode c-shared -ldflags="-w -s" -trimpath -v -o "%~1/tunnel.dll" || exit /b 1 56 | del "%~1\tunnel.h" 57 | goto :eof 58 | 59 | :error 60 | echo [-] Failed with error #%errorlevel%. 61 | cmd /c exit %errorlevel% 62 | 63 | -------------------------------------------------------------------------------- /conf/admin_windows.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package conf 7 | 8 | import "golang.org/x/sys/windows/registry" 9 | 10 | const adminRegKey = `Software\AmneziaWG` 11 | 12 | var adminKey registry.Key 13 | 14 | func openAdminKey() (registry.Key, error) { 15 | if adminKey != 0 { 16 | return adminKey, nil 17 | } 18 | var err error 19 | adminKey, err = registry.OpenKey(registry.LOCAL_MACHINE, adminRegKey, registry.QUERY_VALUE|registry.WOW64_64KEY) 20 | if err != nil { 21 | return 0, err 22 | } 23 | return adminKey, nil 24 | } 25 | 26 | func AdminBool(name string) bool { 27 | key, err := openAdminKey() 28 | if err != nil { 29 | return false 30 | } 31 | val, _, err := key.GetIntegerValue(name) 32 | if err != nil { 33 | return false 34 | } 35 | return val != 0 36 | } 37 | -------------------------------------------------------------------------------- /conf/config.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package conf 7 | 8 | import ( 9 | "crypto/rand" 10 | "crypto/subtle" 11 | "encoding/base64" 12 | "encoding/hex" 13 | "fmt" 14 | "net" 15 | "strings" 16 | "time" 17 | 18 | "golang.org/x/crypto/curve25519" 19 | 20 | "github.com/amnezia-vpn/amneziawg-windows/l18n" 21 | ) 22 | 23 | const KeyLength = 32 24 | 25 | type IPCidr struct { 26 | IP net.IP 27 | Cidr uint8 28 | } 29 | 30 | type Endpoint struct { 31 | Host string 32 | Port uint16 33 | } 34 | 35 | type Key [KeyLength]byte 36 | type HandshakeTime time.Duration 37 | type Bytes uint64 38 | 39 | type Config struct { 40 | Name string 41 | Interface Interface 42 | Peers []Peer 43 | } 44 | 45 | type Interface struct { 46 | PrivateKey Key 47 | Addresses []IPCidr 48 | ListenPort uint16 49 | MTU uint16 50 | DNS []net.IP 51 | DNSSearch []string 52 | PreUp string 53 | PostUp string 54 | PreDown string 55 | PostDown string 56 | TableOff bool 57 | 58 | JunkPacketCount uint16 59 | JunkPacketMinSize uint16 60 | JunkPacketMaxSize uint16 61 | InitPacketJunkSize uint16 62 | ResponsePacketJunkSize uint16 63 | InitPacketMagicHeader uint32 64 | ResponsePacketMagicHeader uint32 65 | UnderloadPacketMagicHeader uint32 66 | TransportPacketMagicHeader uint32 67 | } 68 | 69 | type Peer struct { 70 | PublicKey Key 71 | PresharedKey Key 72 | AllowedIPs []IPCidr 73 | Endpoint Endpoint 74 | PersistentKeepalive uint16 75 | 76 | RxBytes Bytes 77 | TxBytes Bytes 78 | LastHandshakeTime HandshakeTime 79 | } 80 | 81 | func (r *IPCidr) String() string { 82 | return fmt.Sprintf("%s/%d", r.IP.String(), r.Cidr) 83 | } 84 | 85 | func (r *IPCidr) Bits() uint8 { 86 | if r.IP.To4() != nil { 87 | return 32 88 | } 89 | return 128 90 | } 91 | 92 | func (r *IPCidr) IPNet() net.IPNet { 93 | return net.IPNet{ 94 | IP: r.IP, 95 | Mask: net.CIDRMask(int(r.Cidr), int(r.Bits())), 96 | } 97 | } 98 | 99 | func (r *IPCidr) MaskSelf() { 100 | bits := int(r.Bits()) 101 | mask := net.CIDRMask(int(r.Cidr), bits) 102 | for i := 0; i < bits/8; i++ { 103 | r.IP[i] &= mask[i] 104 | } 105 | } 106 | 107 | func (conf *Config) IntersectsWith(other *Config) bool { 108 | type hashableIPCidr struct { 109 | ip string 110 | cidr byte 111 | } 112 | allRoutes := make(map[hashableIPCidr]bool, len(conf.Interface.Addresses)*2+len(conf.Peers)*3) 113 | for _, a := range conf.Interface.Addresses { 114 | allRoutes[hashableIPCidr{string(a.IP), byte(len(a.IP) * 8)}] = true 115 | a.MaskSelf() 116 | allRoutes[hashableIPCidr{string(a.IP), a.Cidr}] = true 117 | } 118 | for i := range conf.Peers { 119 | for _, a := range conf.Peers[i].AllowedIPs { 120 | a.MaskSelf() 121 | allRoutes[hashableIPCidr{string(a.IP), a.Cidr}] = true 122 | } 123 | } 124 | for _, a := range other.Interface.Addresses { 125 | if allRoutes[hashableIPCidr{string(a.IP), byte(len(a.IP) * 8)}] { 126 | return true 127 | } 128 | a.MaskSelf() 129 | if allRoutes[hashableIPCidr{string(a.IP), a.Cidr}] { 130 | return true 131 | } 132 | } 133 | for i := range other.Peers { 134 | for _, a := range other.Peers[i].AllowedIPs { 135 | a.MaskSelf() 136 | if allRoutes[hashableIPCidr{string(a.IP), a.Cidr}] { 137 | return true 138 | } 139 | } 140 | } 141 | return false 142 | } 143 | 144 | func (e *Endpoint) String() string { 145 | if strings.IndexByte(e.Host, ':') > 0 { 146 | return fmt.Sprintf("[%s]:%d", e.Host, e.Port) 147 | } 148 | return fmt.Sprintf("%s:%d", e.Host, e.Port) 149 | } 150 | 151 | func (e *Endpoint) IsEmpty() bool { 152 | return len(e.Host) == 0 153 | } 154 | 155 | func (k *Key) String() string { 156 | return base64.StdEncoding.EncodeToString(k[:]) 157 | } 158 | 159 | func (k *Key) HexString() string { 160 | return hex.EncodeToString(k[:]) 161 | } 162 | 163 | func (k *Key) IsZero() bool { 164 | var zeros Key 165 | return subtle.ConstantTimeCompare(zeros[:], k[:]) == 1 166 | } 167 | 168 | func (k *Key) Public() *Key { 169 | var p [KeyLength]byte 170 | curve25519.ScalarBaseMult(&p, (*[KeyLength]byte)(k)) 171 | return (*Key)(&p) 172 | } 173 | 174 | func NewPresharedKey() (*Key, error) { 175 | var k [KeyLength]byte 176 | _, err := rand.Read(k[:]) 177 | if err != nil { 178 | return nil, err 179 | } 180 | return (*Key)(&k), nil 181 | } 182 | 183 | func NewPrivateKey() (*Key, error) { 184 | k, err := NewPresharedKey() 185 | if err != nil { 186 | return nil, err 187 | } 188 | k[0] &= 248 189 | k[31] = (k[31] & 127) | 64 190 | return k, nil 191 | } 192 | 193 | func NewPrivateKeyFromString(b64 string) (*Key, error) { 194 | return parseKeyBase64(b64) 195 | } 196 | 197 | func (t HandshakeTime) IsEmpty() bool { 198 | return t == HandshakeTime(0) 199 | } 200 | 201 | func (t HandshakeTime) String() string { 202 | u := time.Unix(0, 0).Add(time.Duration(t)).Unix() 203 | n := time.Now().Unix() 204 | if u == n { 205 | return l18n.Sprintf("Now") 206 | } else if u > n { 207 | return l18n.Sprintf("System clock wound backward!") 208 | } 209 | left := n - u 210 | years := left / (365 * 24 * 60 * 60) 211 | left = left % (365 * 24 * 60 * 60) 212 | days := left / (24 * 60 * 60) 213 | left = left % (24 * 60 * 60) 214 | hours := left / (60 * 60) 215 | left = left % (60 * 60) 216 | minutes := left / 60 217 | seconds := left % 60 218 | s := make([]string, 0, 5) 219 | if years > 0 { 220 | s = append(s, l18n.Sprintf("%d year(s)", years)) 221 | } 222 | if days > 0 { 223 | s = append(s, l18n.Sprintf("%d day(s)", days)) 224 | } 225 | if hours > 0 { 226 | s = append(s, l18n.Sprintf("%d hour(s)", hours)) 227 | } 228 | if minutes > 0 { 229 | s = append(s, l18n.Sprintf("%d minute(s)", minutes)) 230 | } 231 | if seconds > 0 { 232 | s = append(s, l18n.Sprintf("%d second(s)", seconds)) 233 | } 234 | timestamp := strings.Join(s, l18n.UnitSeparator()) 235 | return l18n.Sprintf("%s ago", timestamp) 236 | } 237 | 238 | func (b Bytes) String() string { 239 | if b < 1024 { 240 | return l18n.Sprintf("%d\u00a0B", b) 241 | } else if b < 1024*1024 { 242 | return l18n.Sprintf("%.2f\u00a0KiB", float64(b)/1024) 243 | } else if b < 1024*1024*1024 { 244 | return l18n.Sprintf("%.2f\u00a0MiB", float64(b)/(1024*1024)) 245 | } else if b < 1024*1024*1024*1024 { 246 | return l18n.Sprintf("%.2f\u00a0GiB", float64(b)/(1024*1024*1024)) 247 | } 248 | return l18n.Sprintf("%.2f\u00a0TiB", float64(b)/(1024*1024*1024)/1024) 249 | } 250 | 251 | func (conf *Config) DeduplicateNetworkEntries() { 252 | m := make(map[string]bool, len(conf.Interface.Addresses)) 253 | i := 0 254 | for _, addr := range conf.Interface.Addresses { 255 | s := addr.String() 256 | if m[s] { 257 | continue 258 | } 259 | m[s] = true 260 | conf.Interface.Addresses[i] = addr 261 | i++ 262 | } 263 | conf.Interface.Addresses = conf.Interface.Addresses[:i] 264 | 265 | m = make(map[string]bool, len(conf.Interface.DNS)) 266 | i = 0 267 | for _, addr := range conf.Interface.DNS { 268 | s := addr.String() 269 | if m[s] { 270 | continue 271 | } 272 | m[s] = true 273 | conf.Interface.DNS[i] = addr 274 | i++ 275 | } 276 | conf.Interface.DNS = conf.Interface.DNS[:i] 277 | 278 | for _, peer := range conf.Peers { 279 | m = make(map[string]bool, len(peer.AllowedIPs)) 280 | i = 0 281 | for _, addr := range peer.AllowedIPs { 282 | s := addr.String() 283 | if m[s] { 284 | continue 285 | } 286 | m[s] = true 287 | peer.AllowedIPs[i] = addr 288 | i++ 289 | } 290 | peer.AllowedIPs = peer.AllowedIPs[:i] 291 | } 292 | } 293 | 294 | func (conf *Config) Redact() { 295 | conf.Interface.PrivateKey = Key{} 296 | for i := range conf.Peers { 297 | conf.Peers[i].PublicKey = Key{} 298 | conf.Peers[i].PresharedKey = Key{} 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /conf/dnsresolver_windows.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package conf 7 | 8 | import ( 9 | "fmt" 10 | "log" 11 | "net" 12 | "syscall" 13 | "time" 14 | "unsafe" 15 | 16 | "golang.org/x/sys/windows" 17 | ) 18 | 19 | //sys internetGetConnectedState(flags *uint32, reserved uint32) (connected bool) = wininet.InternetGetConnectedState 20 | 21 | func resolveHostname(name string) (resolvedIPString string, err error) { 22 | maxTries := 10 23 | systemJustBooted := windows.DurationSinceBoot() <= time.Minute*4 24 | if systemJustBooted { 25 | maxTries *= 4 26 | } 27 | for i := 0; i < maxTries; i++ { 28 | if i > 0 { 29 | time.Sleep(time.Second * 4) 30 | } 31 | resolvedIPString, err = resolveHostnameOnce(name) 32 | if err == nil { 33 | return 34 | } 35 | if err == windows.WSATRY_AGAIN { 36 | log.Printf("Temporary DNS error when resolving %s, sleeping for 4 seconds", name) 37 | continue 38 | } 39 | var state uint32 40 | if err == windows.WSAHOST_NOT_FOUND && systemJustBooted && !internetGetConnectedState(&state, 0) { 41 | log.Printf("Host not found when resolving %s, but no Internet connection available, sleeping for 4 seconds", name) 42 | continue 43 | } 44 | return 45 | } 46 | return 47 | } 48 | 49 | func resolveHostnameOnce(name string) (resolvedIPString string, err error) { 50 | hints := windows.AddrinfoW{ 51 | Family: windows.AF_UNSPEC, 52 | Socktype: windows.SOCK_DGRAM, 53 | Protocol: windows.IPPROTO_IP, 54 | } 55 | var result *windows.AddrinfoW 56 | name16, err := windows.UTF16PtrFromString(name) 57 | if err != nil { 58 | return 59 | } 60 | err = windows.GetAddrInfoW(name16, nil, &hints, &result) 61 | if err != nil { 62 | return 63 | } 64 | if result == nil { 65 | err = windows.WSAHOST_NOT_FOUND 66 | return 67 | } 68 | defer windows.FreeAddrInfoW(result) 69 | ipv6 := "" 70 | for ; result != nil; result = result.Next { 71 | switch result.Family { 72 | case windows.AF_INET: 73 | return (net.IP)((*syscall.RawSockaddrInet4)(unsafe.Pointer(result.Addr)).Addr[:]).String(), nil 74 | case windows.AF_INET6: 75 | if len(ipv6) != 0 { 76 | continue 77 | } 78 | a := (*syscall.RawSockaddrInet6)(unsafe.Pointer(result.Addr)) 79 | ipv6 = (net.IP)(a.Addr[:]).String() 80 | if a.Scope_id != 0 { 81 | ipv6 += fmt.Sprintf("%%%d", a.Scope_id) 82 | } 83 | } 84 | } 85 | if len(ipv6) != 0 { 86 | return ipv6, nil 87 | } 88 | err = windows.WSAHOST_NOT_FOUND 89 | return 90 | } 91 | -------------------------------------------------------------------------------- /conf/dpapi/dpapi_windows.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package dpapi 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | "runtime" 12 | "unsafe" 13 | 14 | "golang.org/x/sys/windows" 15 | ) 16 | 17 | func bytesToBlob(bytes []byte) *windows.DataBlob { 18 | blob := &windows.DataBlob{Size: uint32(len(bytes))} 19 | if len(bytes) > 0 { 20 | blob.Data = &bytes[0] 21 | } 22 | return blob 23 | } 24 | 25 | func Encrypt(data []byte, name string) ([]byte, error) { 26 | out := windows.DataBlob{} 27 | err := windows.CryptProtectData(bytesToBlob(data), windows.StringToUTF16Ptr(name), nil, 0, nil, windows.CRYPTPROTECT_UI_FORBIDDEN, &out) 28 | if err != nil { 29 | return nil, fmt.Errorf("unable to encrypt DPAPI protected data: %w", err) 30 | } 31 | 32 | outSlice := *(*[]byte)(unsafe.Pointer(&(struct { 33 | addr *byte 34 | len int 35 | cap int 36 | }{out.Data, int(out.Size), int(out.Size)}))) 37 | ret := make([]byte, len(outSlice)) 38 | copy(ret, outSlice) 39 | windows.LocalFree(windows.Handle(unsafe.Pointer(out.Data))) 40 | 41 | return ret, nil 42 | } 43 | 44 | func Decrypt(data []byte, name string) ([]byte, error) { 45 | out := windows.DataBlob{} 46 | var outName *uint16 47 | utf16Name, err := windows.UTF16PtrFromString(name) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | err = windows.CryptUnprotectData(bytesToBlob(data), &outName, nil, 0, nil, windows.CRYPTPROTECT_UI_FORBIDDEN, &out) 53 | if err != nil { 54 | return nil, fmt.Errorf("unable to decrypt DPAPI protected data: %w", err) 55 | } 56 | 57 | outSlice := *(*[]byte)(unsafe.Pointer(&(struct { 58 | addr *byte 59 | len int 60 | cap int 61 | }{out.Data, int(out.Size), int(out.Size)}))) 62 | ret := make([]byte, len(outSlice)) 63 | copy(ret, outSlice) 64 | windows.LocalFree(windows.Handle(unsafe.Pointer(out.Data))) 65 | 66 | // Note: this ridiculous open-coded strcmp is not constant time. 67 | different := false 68 | a := outName 69 | b := utf16Name 70 | for { 71 | if *a != *b { 72 | different = true 73 | break 74 | } 75 | if *a == 0 || *b == 0 { 76 | break 77 | } 78 | a = (*uint16)(unsafe.Pointer(uintptr(unsafe.Pointer(a)) + 2)) 79 | b = (*uint16)(unsafe.Pointer(uintptr(unsafe.Pointer(b)) + 2)) 80 | } 81 | runtime.KeepAlive(utf16Name) 82 | windows.LocalFree(windows.Handle(unsafe.Pointer(outName))) 83 | 84 | if different { 85 | return nil, errors.New("input name does not match the stored name") 86 | } 87 | 88 | return ret, nil 89 | } 90 | -------------------------------------------------------------------------------- /conf/dpapi/dpapi_windows_test.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package dpapi 7 | 8 | import ( 9 | "bytes" 10 | "testing" 11 | "unsafe" 12 | 13 | "golang.org/x/sys/windows" 14 | ) 15 | 16 | func TestRoundTrip(t *testing.T) { 17 | name := "golang test" 18 | original := []byte("The quick brown fox jumped over the lazy dog") 19 | 20 | e, err := Encrypt(original, name) 21 | if err != nil { 22 | t.Errorf("Error encrypting: %s", err.Error()) 23 | } 24 | 25 | if len(e) < len(original) { 26 | t.Error("Encrypted data is smaller than original data.") 27 | } 28 | 29 | d, err := Decrypt(e, name) 30 | if err != nil { 31 | t.Errorf("Error decrypting: %s", err.Error()) 32 | } 33 | 34 | if !bytes.Equal(d, original) { 35 | t.Error("Decrypted content does not match original") 36 | } 37 | 38 | _, err = Decrypt(e, "bad name") 39 | if err == nil { 40 | t.Error("Decryption failed to notice ad mismatch") 41 | } 42 | 43 | eCorrupt := make([]byte, len(e)) 44 | copy(eCorrupt, e) 45 | eCorrupt[len(original)-1] = 7 46 | _, err = Decrypt(eCorrupt, name) 47 | if err == nil { 48 | t.Error("Decryption failed to notice ciphertext corruption") 49 | } 50 | 51 | copy(eCorrupt, e) 52 | nameUtf16, err := windows.UTF16FromString(name) 53 | if err != nil { 54 | t.Errorf("Unable to get utf16 chars for name: %s", err) 55 | } 56 | nameUtf16Bytes := *(*[]byte)(unsafe.Pointer(&struct { 57 | addr *byte 58 | len int 59 | cap int 60 | }{(*byte)(unsafe.Pointer(&nameUtf16[0])), len(nameUtf16) * 2, cap(nameUtf16) * 2})) 61 | i := bytes.Index(eCorrupt, nameUtf16Bytes) 62 | if i == -1 { 63 | t.Error("Unable to find ad in blob") 64 | } else { 65 | eCorrupt[i] = 7 66 | _, err = Decrypt(eCorrupt, name) 67 | if err == nil { 68 | t.Error("Decryption failed to notice ad corruption") 69 | } 70 | } 71 | 72 | // BUG: Actually, Windows doesn't report length extension of the buffer, unfortunately. 73 | // 74 | // eCorrupt = make([]byte, len(e)+1) 75 | // copy(eCorrupt, e) 76 | // _, err = Decrypt(eCorrupt, name) 77 | // if err == nil { 78 | // t.Error("Decryption failed to notice length extension") 79 | // } 80 | } 81 | -------------------------------------------------------------------------------- /conf/dpapi/test.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amnezia-vpn/amneziawg-windows/4b1b67e3b6657ee90176233b0d57070bd0e32ccb/conf/dpapi/test.exe -------------------------------------------------------------------------------- /conf/filewriter_windows.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package conf 7 | 8 | import ( 9 | "crypto/rand" 10 | "encoding/hex" 11 | "sync/atomic" 12 | "unsafe" 13 | 14 | "golang.org/x/sys/windows" 15 | ) 16 | 17 | var encryptedFileSd unsafe.Pointer 18 | 19 | func randomFileName() string { 20 | var randBytes [32]byte 21 | _, err := rand.Read(randBytes[:]) 22 | if err != nil { 23 | panic(err) 24 | } 25 | return hex.EncodeToString(randBytes[:]) + ".tmp" 26 | } 27 | 28 | func writeLockedDownFile(destination string, overwrite bool, contents []byte) error { 29 | var err error 30 | sa := &windows.SecurityAttributes{Length: uint32(unsafe.Sizeof(windows.SecurityAttributes{}))} 31 | sa.SecurityDescriptor = (*windows.SECURITY_DESCRIPTOR)(atomic.LoadPointer(&encryptedFileSd)) 32 | if sa.SecurityDescriptor == nil { 33 | sa.SecurityDescriptor, err = windows.SecurityDescriptorFromString("O:SYG:SYD:PAI(A;;FA;;;SY)(A;;SD;;;BA)") 34 | if err != nil { 35 | return err 36 | } 37 | atomic.StorePointer(&encryptedFileSd, unsafe.Pointer(sa.SecurityDescriptor)) 38 | } 39 | destination16, err := windows.UTF16FromString(destination) 40 | if err != nil { 41 | return err 42 | } 43 | tmpDestination := randomFileName() 44 | tmpDestination16, err := windows.UTF16PtrFromString(tmpDestination) 45 | if err != nil { 46 | return err 47 | } 48 | handle, err := windows.CreateFile(tmpDestination16, windows.GENERIC_WRITE|windows.DELETE, windows.FILE_SHARE_READ, sa, windows.CREATE_ALWAYS, windows.FILE_ATTRIBUTE_NORMAL, 0) 49 | if err != nil { 50 | return err 51 | } 52 | defer windows.CloseHandle(handle) 53 | deleteIt := func() { 54 | yes := byte(1) 55 | windows.SetFileInformationByHandle(handle, windows.FileDispositionInfo, &yes, 1) 56 | } 57 | n, err := windows.Write(handle, contents) 58 | if err != nil { 59 | deleteIt() 60 | return err 61 | } 62 | if n != len(contents) { 63 | deleteIt() 64 | return windows.ERROR_IO_INCOMPLETE 65 | } 66 | fileRenameInfo := &struct { 67 | replaceIfExists byte 68 | rootDirectory windows.Handle 69 | fileNameLength uint32 70 | fileName [windows.MAX_PATH]uint16 71 | }{replaceIfExists: func() byte { 72 | if overwrite { 73 | return 1 74 | } else { 75 | return 0 76 | } 77 | }(), fileNameLength: uint32(len(destination16) - 1)} 78 | if len(destination16) > len(fileRenameInfo.fileName) { 79 | deleteIt() 80 | return windows.ERROR_BUFFER_OVERFLOW 81 | } 82 | copy(fileRenameInfo.fileName[:], destination16[:]) 83 | err = windows.SetFileInformationByHandle(handle, windows.FileRenameInfo, (*byte)(unsafe.Pointer(fileRenameInfo)), uint32(unsafe.Sizeof(*fileRenameInfo))) 84 | if err != nil { 85 | deleteIt() 86 | return err 87 | } 88 | return nil 89 | } 90 | -------------------------------------------------------------------------------- /conf/migration_windows.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package conf 7 | 8 | import ( 9 | "errors" 10 | "io" 11 | "log" 12 | "os" 13 | "path/filepath" 14 | "strings" 15 | "sync" 16 | "time" 17 | 18 | "golang.org/x/sys/windows" 19 | ) 20 | 21 | var migrating sync.Mutex 22 | var lastMigrationTimer *time.Timer 23 | 24 | type MigrationCallback func(name, oldPath, newPath string) 25 | 26 | func MigrateUnencryptedConfigs(migrated MigrationCallback) { migrateUnencryptedConfigs(3, migrated) } 27 | 28 | func migrateUnencryptedConfigs(sharingBase int, migrated MigrationCallback) { 29 | if migrated == nil { 30 | migrated = func(_, _, _ string) {} 31 | } 32 | migrating.Lock() 33 | defer migrating.Unlock() 34 | configFileDir, err := tunnelConfigurationsDirectory() 35 | if err != nil { 36 | return 37 | } 38 | files, err := os.ReadDir(configFileDir) 39 | if err != nil { 40 | return 41 | } 42 | ignoreSharingViolations := false 43 | for _, file := range files { 44 | path := filepath.Join(configFileDir, file.Name()) 45 | name := filepath.Base(file.Name()) 46 | if len(name) <= len(configFileUnencryptedSuffix) || !strings.HasSuffix(name, configFileUnencryptedSuffix) { 47 | continue 48 | } 49 | if !file.Type().IsRegular() { 50 | continue 51 | } 52 | info, err := file.Info() 53 | if err != nil { 54 | continue 55 | } 56 | if info.Mode().Perm()&0444 == 0 { 57 | continue 58 | } 59 | 60 | var bytes []byte 61 | var config *Config 62 | var newPath string 63 | // We don't use os.ReadFile, because we actually want RDWR, so that we can take advantage 64 | // of Windows file locking for ensuring the file is finished being written. 65 | f, err := os.OpenFile(path, os.O_RDWR, 0) 66 | if err != nil { 67 | if errors.Is(err, windows.ERROR_SHARING_VIOLATION) { 68 | if ignoreSharingViolations { 69 | continue 70 | } else if sharingBase > 0 { 71 | if lastMigrationTimer != nil { 72 | lastMigrationTimer.Stop() 73 | } 74 | lastMigrationTimer = time.AfterFunc(time.Second/time.Duration(sharingBase*sharingBase), func() { migrateUnencryptedConfigs(sharingBase-1, migrated) }) 75 | ignoreSharingViolations = true 76 | continue 77 | } 78 | } 79 | goto error 80 | } 81 | bytes, err = io.ReadAll(f) 82 | f.Close() 83 | if err != nil { 84 | goto error 85 | } 86 | config, err = FromWgQuickWithUnknownEncoding(string(bytes), strings.TrimSuffix(name, configFileUnencryptedSuffix)) 87 | if err != nil { 88 | goto error 89 | } 90 | err = config.Save(false) 91 | if err != nil { 92 | goto error 93 | } 94 | err = os.Remove(path) 95 | if err != nil { 96 | goto error 97 | } 98 | newPath, err = config.Path() 99 | if err != nil { 100 | goto error 101 | } 102 | migrated(config.Name, path, newPath) 103 | continue 104 | error: 105 | log.Printf("Unable to ingest and encrypt %#q: %v", path, err) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /conf/mksyscall.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package conf 7 | 8 | //go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go dnsresolver_windows.go migration_windows.go storewatcher_windows.go 9 | -------------------------------------------------------------------------------- /conf/name.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package conf 7 | 8 | import ( 9 | "regexp" 10 | "strconv" 11 | "strings" 12 | ) 13 | 14 | var reservedNames = []string{ 15 | "CON", "PRN", "AUX", "NUL", 16 | "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", 17 | "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", 18 | } 19 | 20 | const serviceNameForbidden = "$" 21 | const netshellDllForbidden = "\\/:*?\"<>|\t" 22 | const specialChars = "/\\<>:\"|?*\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x00" 23 | 24 | var allowedNameFormat *regexp.Regexp 25 | 26 | func init() { 27 | allowedNameFormat = regexp.MustCompile("^[a-zA-Z0-9_=+.-]{1,32}$") 28 | } 29 | 30 | func isReserved(name string) bool { 31 | if len(name) == 0 { 32 | return false 33 | } 34 | for _, reserved := range reservedNames { 35 | if strings.EqualFold(name, reserved) { 36 | return true 37 | } 38 | } 39 | return false 40 | } 41 | 42 | func hasSpecialChars(name string) bool { 43 | return strings.ContainsAny(name, specialChars) || strings.ContainsAny(name, netshellDllForbidden) || strings.ContainsAny(name, serviceNameForbidden) 44 | } 45 | 46 | func TunnelNameIsValid(name string) bool { 47 | // Aside from our own restrictions, let's impose the Windows restrictions first 48 | if isReserved(name) || hasSpecialChars(name) { 49 | return false 50 | } 51 | return allowedNameFormat.MatchString(name) 52 | } 53 | 54 | type naturalSortToken struct { 55 | maybeString string 56 | maybeNumber int 57 | } 58 | type naturalSortString struct { 59 | originalString string 60 | tokens []naturalSortToken 61 | } 62 | 63 | var naturalSortDigitFinder = regexp.MustCompile(`\d+|\D+`) 64 | 65 | func newNaturalSortString(s string) (t naturalSortString) { 66 | t.originalString = s 67 | s = strings.ToLower(strings.Join(strings.Fields(s), " ")) 68 | x := naturalSortDigitFinder.FindAllString(s, -1) 69 | t.tokens = make([]naturalSortToken, len(x)) 70 | for i, s := range x { 71 | if n, err := strconv.Atoi(s); err == nil { 72 | t.tokens[i].maybeNumber = n 73 | } else { 74 | t.tokens[i].maybeString = s 75 | } 76 | } 77 | return 78 | } 79 | 80 | func (f1 naturalSortToken) Cmp(f2 naturalSortToken) int { 81 | if len(f1.maybeString) == 0 { 82 | if len(f2.maybeString) > 0 || f1.maybeNumber < f2.maybeNumber { 83 | return -1 84 | } else if f1.maybeNumber > f2.maybeNumber { 85 | return 1 86 | } 87 | } else if len(f2.maybeString) == 0 || f1.maybeString > f2.maybeString { 88 | return 1 89 | } else if f1.maybeString < f2.maybeString { 90 | return -1 91 | } 92 | return 0 93 | } 94 | 95 | func TunnelNameIsLess(a, b string) bool { 96 | if a == b { 97 | return false 98 | } 99 | na, nb := newNaturalSortString(a), newNaturalSortString(b) 100 | for i, t := range nb.tokens { 101 | if i == len(na.tokens) { 102 | return true 103 | } 104 | switch na.tokens[i].Cmp(t) { 105 | case -1: 106 | return true 107 | case 1: 108 | return false 109 | } 110 | } 111 | return false 112 | } 113 | -------------------------------------------------------------------------------- /conf/parser_test.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package conf 7 | 8 | import ( 9 | "net" 10 | "reflect" 11 | "runtime" 12 | "testing" 13 | ) 14 | 15 | const testInput = ` 16 | [Interface] 17 | Address = 10.192.122.1/24 18 | Address = 10.10.0.1/16 19 | PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk= 20 | ListenPort = 51820 #comments don't matter 21 | 22 | [Peer] 23 | PublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg= 24 | Endpoint = 192.95.5.67:1234 25 | AllowedIPs = 10.192.122.3/32, 10.192.124.1/24 26 | 27 | [Peer] 28 | PublicKey = TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0= 29 | Endpoint = [2607:5300:60:6b0::c05f:543]:2468 30 | AllowedIPs = 10.192.122.4/32, 192.168.0.0/16 31 | PersistentKeepalive = 100 32 | 33 | [Peer] 34 | PublicKey = gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA= 35 | PresharedKey = TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0= 36 | Endpoint = test.wireguard.com:18981 37 | AllowedIPs = 10.10.10.230/32` 38 | 39 | func noError(t *testing.T, err error) bool { 40 | if err == nil { 41 | return true 42 | } 43 | _, fn, line, _ := runtime.Caller(1) 44 | t.Errorf("Error at %s:%d: %#v", fn, line, err) 45 | return false 46 | } 47 | 48 | func equal(t *testing.T, expected, actual interface{}) bool { 49 | if reflect.DeepEqual(expected, actual) { 50 | return true 51 | } 52 | _, fn, line, _ := runtime.Caller(1) 53 | t.Errorf("Failed equals at %s:%d\nactual %#v\nexpected %#v", fn, line, actual, expected) 54 | return false 55 | } 56 | func lenTest(t *testing.T, actualO interface{}, expected int) bool { 57 | actual := reflect.ValueOf(actualO).Len() 58 | if reflect.DeepEqual(expected, actual) { 59 | return true 60 | } 61 | _, fn, line, _ := runtime.Caller(1) 62 | t.Errorf("Wrong length at %s:%d\nactual %#v\nexpected %#v", fn, line, actual, expected) 63 | return false 64 | } 65 | func contains(t *testing.T, list, element interface{}) bool { 66 | listValue := reflect.ValueOf(list) 67 | for i := 0; i < listValue.Len(); i++ { 68 | if reflect.DeepEqual(listValue.Index(i).Interface(), element) { 69 | return true 70 | } 71 | } 72 | _, fn, line, _ := runtime.Caller(1) 73 | t.Errorf("Error %s:%d\nelement not found: %#v", fn, line, element) 74 | return false 75 | } 76 | 77 | func TestFromWgQuick(t *testing.T) { 78 | conf, err := FromWgQuick(testInput, "test") 79 | if noError(t, err) { 80 | 81 | lenTest(t, conf.Interface.Addresses, 2) 82 | contains(t, conf.Interface.Addresses, IPCidr{net.IPv4(10, 10, 0, 1), uint8(16)}) 83 | contains(t, conf.Interface.Addresses, IPCidr{net.IPv4(10, 192, 122, 1), uint8(24)}) 84 | equal(t, "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=", conf.Interface.PrivateKey.String()) 85 | equal(t, uint16(51820), conf.Interface.ListenPort) 86 | 87 | lenTest(t, conf.Peers, 3) 88 | lenTest(t, conf.Peers[0].AllowedIPs, 2) 89 | equal(t, Endpoint{Host: "192.95.5.67", Port: 1234}, conf.Peers[0].Endpoint) 90 | equal(t, "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=", conf.Peers[0].PublicKey.String()) 91 | 92 | lenTest(t, conf.Peers[1].AllowedIPs, 2) 93 | equal(t, Endpoint{Host: "2607:5300:60:6b0::c05f:543", Port: 2468}, conf.Peers[1].Endpoint) 94 | equal(t, "TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0=", conf.Peers[1].PublicKey.String()) 95 | equal(t, uint16(100), conf.Peers[1].PersistentKeepalive) 96 | 97 | lenTest(t, conf.Peers[2].AllowedIPs, 1) 98 | equal(t, Endpoint{Host: "test.wireguard.com", Port: 18981}, conf.Peers[2].Endpoint) 99 | equal(t, "gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA=", conf.Peers[2].PublicKey.String()) 100 | equal(t, "TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0=", conf.Peers[2].PresharedKey.String()) 101 | } 102 | } 103 | 104 | func TestParseEndpoint(t *testing.T) { 105 | _, err := parseEndpoint("[192.168.42.0:]:51880") 106 | if err == nil { 107 | t.Error("Error was expected") 108 | } 109 | e, err := parseEndpoint("192.168.42.0:51880") 110 | if noError(t, err) { 111 | equal(t, "192.168.42.0", e.Host) 112 | equal(t, uint16(51880), e.Port) 113 | } 114 | e, err = parseEndpoint("test.wireguard.com:18981") 115 | if noError(t, err) { 116 | equal(t, "test.wireguard.com", e.Host) 117 | equal(t, uint16(18981), e.Port) 118 | } 119 | e, err = parseEndpoint("[2607:5300:60:6b0::c05f:543]:2468") 120 | if noError(t, err) { 121 | equal(t, "2607:5300:60:6b0::c05f:543", e.Host) 122 | equal(t, uint16(2468), e.Port) 123 | } 124 | _, err = parseEndpoint("[::::::invalid:18981") 125 | if err == nil { 126 | t.Error("Error was expected") 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /conf/path_windows.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package conf 7 | 8 | import ( 9 | "errors" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | "unsafe" 14 | 15 | "golang.org/x/sys/windows" 16 | ) 17 | 18 | var cachedConfigFileDir string 19 | var cachedRootDir string 20 | 21 | func tunnelConfigurationsDirectory() (string, error) { 22 | if cachedConfigFileDir != "" { 23 | return cachedConfigFileDir, nil 24 | } 25 | root, err := RootDirectory(true) 26 | if err != nil { 27 | return "", err 28 | } 29 | c := filepath.Join(root, "Configurations") 30 | err = os.Mkdir(c, os.ModeDir|0700) 31 | if err != nil && !os.IsExist(err) { 32 | return "", err 33 | } 34 | cachedConfigFileDir = c 35 | return cachedConfigFileDir, nil 36 | } 37 | 38 | // PresetRootDirectory causes RootDirectory() to not try any automatic deduction, and instead 39 | // uses what's passed to it. This isn't used by amneziawg-windows, but is useful for external 40 | // consumers of our libraries who might want to do strange things. 41 | func PresetRootDirectory(root string) { 42 | cachedRootDir = root 43 | } 44 | 45 | func RootDirectory(create bool) (string, error) { 46 | if cachedRootDir != "" { 47 | return cachedRootDir, nil 48 | } 49 | root, err := windows.KnownFolderPath(windows.FOLDERID_ProgramFiles, windows.KF_FLAG_DEFAULT) 50 | if err != nil { 51 | return "", err 52 | } 53 | root = filepath.Join(root, "AmneziaWG") 54 | if !create { 55 | return filepath.Join(root, "Data"), nil 56 | } 57 | root16, err := windows.UTF16PtrFromString(root) 58 | if err != nil { 59 | return "", err 60 | } 61 | 62 | // The root directory inherits its ACL from Program Files; we don't want to mess with that 63 | err = windows.CreateDirectory(root16, nil) 64 | if err != nil && err != windows.ERROR_ALREADY_EXISTS { 65 | return "", err 66 | } 67 | 68 | dataDirectorySd, err := windows.SecurityDescriptorFromString("O:SYG:SYD:PAI(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)") 69 | if err != nil { 70 | return "", err 71 | } 72 | dataDirectorySa := &windows.SecurityAttributes{ 73 | Length: uint32(unsafe.Sizeof(windows.SecurityAttributes{})), 74 | SecurityDescriptor: dataDirectorySd, 75 | } 76 | 77 | data := filepath.Join(root, "Data") 78 | data16, err := windows.UTF16PtrFromString(data) 79 | if err != nil { 80 | return "", err 81 | } 82 | var dataHandle windows.Handle 83 | for { 84 | err = windows.CreateDirectory(data16, dataDirectorySa) 85 | if err != nil && err != windows.ERROR_ALREADY_EXISTS { 86 | return "", err 87 | } 88 | dataHandle, err = windows.CreateFile(data16, windows.READ_CONTROL|windows.WRITE_OWNER|windows.WRITE_DAC, windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE, nil, windows.OPEN_EXISTING, windows.FILE_FLAG_BACKUP_SEMANTICS|windows.FILE_FLAG_OPEN_REPARSE_POINT|windows.FILE_ATTRIBUTE_DIRECTORY, 0) 89 | if err != nil && err != windows.ERROR_FILE_NOT_FOUND { 90 | return "", err 91 | } 92 | if err == nil { 93 | break 94 | } 95 | } 96 | defer windows.CloseHandle(dataHandle) 97 | var fileInfo windows.ByHandleFileInformation 98 | err = windows.GetFileInformationByHandle(dataHandle, &fileInfo) 99 | if err != nil { 100 | return "", err 101 | } 102 | if fileInfo.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY == 0 { 103 | return "", errors.New("Data directory is actually a file") 104 | } 105 | if fileInfo.FileAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0 { 106 | return "", errors.New("Data directory is reparse point") 107 | } 108 | buf := make([]uint16, windows.MAX_PATH+4) 109 | for { 110 | bufLen, err := windows.GetFinalPathNameByHandle(dataHandle, &buf[0], uint32(len(buf)), 0) 111 | if err != nil { 112 | return "", err 113 | } 114 | if bufLen < uint32(len(buf)) { 115 | break 116 | } 117 | buf = make([]uint16, bufLen) 118 | } 119 | if !strings.EqualFold(`\\?\`+data, windows.UTF16ToString(buf[:])) { 120 | return "", errors.New("Data directory jumped to unexpected location") 121 | } 122 | err = windows.SetKernelObjectSecurity(dataHandle, windows.DACL_SECURITY_INFORMATION|windows.GROUP_SECURITY_INFORMATION|windows.OWNER_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION, dataDirectorySd) 123 | if err != nil { 124 | return "", err 125 | } 126 | cachedRootDir = data 127 | return cachedRootDir, nil 128 | } 129 | -------------------------------------------------------------------------------- /conf/store.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package conf 7 | 8 | import ( 9 | "errors" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | 14 | "github.com/amnezia-vpn/amneziawg-windows/conf/dpapi" 15 | ) 16 | 17 | const configFileSuffix = ".conf.dpapi" 18 | const configFileUnencryptedSuffix = ".conf" 19 | 20 | func ListConfigNames() ([]string, error) { 21 | configFileDir, err := tunnelConfigurationsDirectory() 22 | if err != nil { 23 | return nil, err 24 | } 25 | files, err := os.ReadDir(configFileDir) 26 | if err != nil { 27 | return nil, err 28 | } 29 | configs := make([]string, len(files)) 30 | i := 0 31 | for _, file := range files { 32 | name := filepath.Base(file.Name()) 33 | if len(name) <= len(configFileSuffix) || !strings.HasSuffix(name, configFileSuffix) { 34 | continue 35 | } 36 | if !file.Type().IsRegular() { 37 | continue 38 | } 39 | info, err := file.Info() 40 | if err != nil { 41 | continue 42 | } 43 | if info.Mode().Perm()&0444 == 0 { 44 | continue 45 | } 46 | name = strings.TrimSuffix(name, configFileSuffix) 47 | if !TunnelNameIsValid(name) { 48 | continue 49 | } 50 | configs[i] = name 51 | i++ 52 | } 53 | return configs[:i], nil 54 | } 55 | 56 | func LoadFromName(name string) (*Config, error) { 57 | configFileDir, err := tunnelConfigurationsDirectory() 58 | if err != nil { 59 | return nil, err 60 | } 61 | return LoadFromPath(filepath.Join(configFileDir, name+configFileSuffix)) 62 | } 63 | 64 | func LoadFromPath(path string) (*Config, error) { 65 | name, err := NameFromPath(path) 66 | if err != nil { 67 | return nil, err 68 | } 69 | bytes, err := os.ReadFile(path) 70 | if err != nil { 71 | return nil, err 72 | } 73 | if strings.HasSuffix(path, configFileSuffix) { 74 | bytes, err = dpapi.Decrypt(bytes, name) 75 | if err != nil { 76 | return nil, err 77 | } 78 | } 79 | return FromWgQuickWithUnknownEncoding(string(bytes), name) 80 | } 81 | 82 | func PathIsEncrypted(path string) bool { 83 | return strings.HasSuffix(filepath.Base(path), configFileSuffix) 84 | } 85 | 86 | func NameFromPath(path string) (string, error) { 87 | name := filepath.Base(path) 88 | if !((len(name) > len(configFileSuffix) && strings.HasSuffix(name, configFileSuffix)) || 89 | (len(name) > len(configFileUnencryptedSuffix) && strings.HasSuffix(name, configFileUnencryptedSuffix))) { 90 | return "", errors.New("Path must end in either " + configFileSuffix + " or " + configFileUnencryptedSuffix) 91 | } 92 | if strings.HasSuffix(path, configFileSuffix) { 93 | name = strings.TrimSuffix(name, configFileSuffix) 94 | } else { 95 | name = strings.TrimSuffix(name, configFileUnencryptedSuffix) 96 | } 97 | if !TunnelNameIsValid(name) { 98 | return "", errors.New("Tunnel name is not valid") 99 | } 100 | return name, nil 101 | } 102 | 103 | func (config *Config) Save(overwrite bool) error { 104 | if !TunnelNameIsValid(config.Name) { 105 | return errors.New("Tunnel name is not valid") 106 | } 107 | configFileDir, err := tunnelConfigurationsDirectory() 108 | if err != nil { 109 | return err 110 | } 111 | filename := filepath.Join(configFileDir, config.Name+configFileSuffix) 112 | bytes := []byte(config.ToWgQuick()) 113 | bytes, err = dpapi.Encrypt(bytes, config.Name) 114 | if err != nil { 115 | return err 116 | } 117 | return writeLockedDownFile(filename, overwrite, bytes) 118 | } 119 | 120 | func (config *Config) Path() (string, error) { 121 | if !TunnelNameIsValid(config.Name) { 122 | return "", errors.New("Tunnel name is not valid") 123 | } 124 | configFileDir, err := tunnelConfigurationsDirectory() 125 | if err != nil { 126 | return "", err 127 | } 128 | return filepath.Join(configFileDir, config.Name+configFileSuffix), nil 129 | } 130 | 131 | func DeleteName(name string) error { 132 | if !TunnelNameIsValid(name) { 133 | return errors.New("Tunnel name is not valid") 134 | } 135 | configFileDir, err := tunnelConfigurationsDirectory() 136 | if err != nil { 137 | return err 138 | } 139 | return os.Remove(filepath.Join(configFileDir, name+configFileSuffix)) 140 | } 141 | 142 | func (config *Config) Delete() error { 143 | return DeleteName(config.Name) 144 | } 145 | -------------------------------------------------------------------------------- /conf/store_test.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package conf 7 | 8 | import ( 9 | "reflect" 10 | "testing" 11 | ) 12 | 13 | func TestStorage(t *testing.T) { 14 | c, err := FromWgQuick(testInput, "golangTest") 15 | if err != nil { 16 | t.Errorf("Unable to parse test config: %s", err.Error()) 17 | return 18 | } 19 | 20 | err = c.Save() 21 | if err != nil { 22 | t.Errorf("Unable to save config: %s", err.Error()) 23 | } 24 | 25 | configs, err := ListConfigNames() 26 | if err != nil { 27 | t.Errorf("Unable to list configs: %s", err.Error()) 28 | } 29 | 30 | found := false 31 | for _, name := range configs { 32 | if name == "golangTest" { 33 | found = true 34 | break 35 | } 36 | } 37 | if !found { 38 | t.Error("Unable to find saved config in list") 39 | } 40 | 41 | loaded, err := LoadFromName("golangTest") 42 | if err != nil { 43 | t.Errorf("Unable to load config: %s", err.Error()) 44 | return 45 | } 46 | 47 | if !reflect.DeepEqual(loaded, c) { 48 | t.Error("Loaded config is not the same as saved config") 49 | } 50 | 51 | k, err := NewPrivateKey() 52 | if err != nil { 53 | t.Errorf("Unable to generate new private key: %s", err.Error()) 54 | } 55 | c.Interface.PrivateKey = *k 56 | 57 | err = c.Save() 58 | if err != nil { 59 | t.Errorf("Unable to save config a second time: %s", err.Error()) 60 | } 61 | 62 | loaded, err = LoadFromName("golangTest") 63 | if err != nil { 64 | t.Errorf("Unable to load config a second time: %s", err.Error()) 65 | return 66 | } 67 | 68 | if !reflect.DeepEqual(loaded, c) { 69 | t.Error("Second loaded config is not the same as second saved config") 70 | } 71 | 72 | err = DeleteName("golangTest") 73 | if err != nil { 74 | t.Errorf("Unable to delete config: %s", err.Error()) 75 | } 76 | 77 | configs, err = ListConfigNames() 78 | if err != nil { 79 | t.Errorf("Unable to list configs: %s", err.Error()) 80 | } 81 | found = false 82 | for _, name := range configs { 83 | if name == "golangTest" { 84 | found = true 85 | break 86 | } 87 | } 88 | if found { 89 | t.Error("Config wasn't actually deleted") 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /conf/storewatcher.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package conf 7 | 8 | type StoreCallback struct { 9 | cb func() 10 | } 11 | 12 | var storeCallbacks = make(map[*StoreCallback]bool) 13 | 14 | func RegisterStoreChangeCallback(cb func()) *StoreCallback { 15 | startWatchingConfigDir() 16 | cb() 17 | s := &StoreCallback{cb} 18 | storeCallbacks[s] = true 19 | return s 20 | } 21 | 22 | func (cb *StoreCallback) Unregister() { 23 | delete(storeCallbacks, cb) 24 | } 25 | -------------------------------------------------------------------------------- /conf/storewatcher_windows.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package conf 7 | 8 | import ( 9 | "log" 10 | 11 | "golang.org/x/sys/windows" 12 | ) 13 | 14 | var haveStartedWatchingConfigDir bool 15 | 16 | func startWatchingConfigDir() { 17 | if haveStartedWatchingConfigDir { 18 | return 19 | } 20 | haveStartedWatchingConfigDir = true 21 | go func() { 22 | h := windows.InvalidHandle 23 | defer func() { 24 | if h != windows.InvalidHandle { 25 | windows.FindCloseChangeNotification(h) 26 | } 27 | haveStartedWatchingConfigDir = false 28 | }() 29 | startover: 30 | configFileDir, err := tunnelConfigurationsDirectory() 31 | if err != nil { 32 | return 33 | } 34 | h, err = windows.FindFirstChangeNotification(configFileDir, true, windows.FILE_NOTIFY_CHANGE_FILE_NAME|windows.FILE_NOTIFY_CHANGE_DIR_NAME|windows.FILE_NOTIFY_CHANGE_ATTRIBUTES|windows.FILE_NOTIFY_CHANGE_SIZE|windows.FILE_NOTIFY_CHANGE_LAST_WRITE|windows.FILE_NOTIFY_CHANGE_LAST_ACCESS|windows.FILE_NOTIFY_CHANGE_CREATION|windows.FILE_NOTIFY_CHANGE_SECURITY) 35 | if err != nil { 36 | log.Printf("Unable to monitor config directory: %v", err) 37 | return 38 | } 39 | for { 40 | s, err := windows.WaitForSingleObject(h, windows.INFINITE) 41 | if err != nil || s == windows.WAIT_FAILED { 42 | log.Printf("Unable to wait on config directory watcher: %v", err) 43 | windows.FindCloseChangeNotification(h) 44 | h = windows.InvalidHandle 45 | goto startover 46 | } 47 | 48 | for cb := range storeCallbacks { 49 | cb.cb() 50 | } 51 | 52 | err = windows.FindNextChangeNotification(h) 53 | if err != nil { 54 | log.Printf("Unable to monitor config directory again: %v", err) 55 | windows.FindCloseChangeNotification(h) 56 | h = windows.InvalidHandle 57 | goto startover 58 | } 59 | } 60 | }() 61 | } 62 | -------------------------------------------------------------------------------- /conf/writer.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package conf 7 | 8 | import ( 9 | "fmt" 10 | "strings" 11 | ) 12 | 13 | func (conf *Config) ToWgQuick() string { 14 | var output strings.Builder 15 | output.WriteString("[Interface]\n") 16 | 17 | output.WriteString(fmt.Sprintf("PrivateKey = %s\n", conf.Interface.PrivateKey.String())) 18 | 19 | if conf.Interface.ListenPort > 0 { 20 | output.WriteString(fmt.Sprintf("ListenPort = %d\n", conf.Interface.ListenPort)) 21 | } 22 | 23 | if conf.Interface.JunkPacketCount > 0 { 24 | output.WriteString(fmt.Sprintf("Jc = %d\n", conf.Interface.JunkPacketCount)) 25 | } 26 | 27 | if conf.Interface.JunkPacketMinSize > 0 { 28 | output.WriteString(fmt.Sprintf("Jmin = %d\n", conf.Interface.JunkPacketMinSize)) 29 | } 30 | 31 | if conf.Interface.JunkPacketMaxSize > 0 { 32 | output.WriteString(fmt.Sprintf("Jmax = %d\n", conf.Interface.JunkPacketMaxSize)) 33 | } 34 | 35 | if conf.Interface.InitPacketJunkSize > 0 { 36 | output.WriteString(fmt.Sprintf("S1 = %d\n", conf.Interface.InitPacketJunkSize)) 37 | } 38 | 39 | if conf.Interface.ResponsePacketJunkSize > 0 { 40 | output.WriteString(fmt.Sprintf("S2 = %d\n", conf.Interface.ResponsePacketJunkSize)) 41 | } 42 | 43 | if conf.Interface.InitPacketMagicHeader > 0 { 44 | output.WriteString(fmt.Sprintf("H1 = %d\n", conf.Interface.InitPacketMagicHeader)) 45 | } 46 | 47 | if conf.Interface.ResponsePacketMagicHeader > 0 { 48 | output.WriteString(fmt.Sprintf("H2 = %d\n", conf.Interface.ResponsePacketMagicHeader)) 49 | } 50 | 51 | if conf.Interface.UnderloadPacketMagicHeader > 0 { 52 | output.WriteString(fmt.Sprintf("H3 = %d\n", conf.Interface.UnderloadPacketMagicHeader)) 53 | } 54 | 55 | if conf.Interface.TransportPacketMagicHeader > 0 { 56 | output.WriteString(fmt.Sprintf("H4 = %d\n", conf.Interface.TransportPacketMagicHeader)) 57 | } 58 | 59 | if len(conf.Interface.Addresses) > 0 { 60 | addrStrings := make([]string, len(conf.Interface.Addresses)) 61 | for i, address := range conf.Interface.Addresses { 62 | addrStrings[i] = address.String() 63 | } 64 | output.WriteString(fmt.Sprintf("Address = %s\n", strings.Join(addrStrings[:], ", "))) 65 | } 66 | 67 | if len(conf.Interface.DNS)+len(conf.Interface.DNSSearch) > 0 { 68 | addrStrings := make([]string, 0, len(conf.Interface.DNS)+len(conf.Interface.DNSSearch)) 69 | for _, address := range conf.Interface.DNS { 70 | addrStrings = append(addrStrings, address.String()) 71 | } 72 | addrStrings = append(addrStrings, conf.Interface.DNSSearch...) 73 | output.WriteString(fmt.Sprintf("DNS = %s\n", strings.Join(addrStrings[:], ", "))) 74 | } 75 | 76 | if conf.Interface.MTU > 0 { 77 | output.WriteString(fmt.Sprintf("MTU = %d\n", conf.Interface.MTU)) 78 | } 79 | 80 | if len(conf.Interface.PreUp) > 0 { 81 | output.WriteString(fmt.Sprintf("PreUp = %s\n", conf.Interface.PreUp)) 82 | } 83 | if len(conf.Interface.PostUp) > 0 { 84 | output.WriteString(fmt.Sprintf("PostUp = %s\n", conf.Interface.PostUp)) 85 | } 86 | if len(conf.Interface.PreDown) > 0 { 87 | output.WriteString(fmt.Sprintf("PreDown = %s\n", conf.Interface.PreDown)) 88 | } 89 | if len(conf.Interface.PostDown) > 0 { 90 | output.WriteString(fmt.Sprintf("PostDown = %s\n", conf.Interface.PostDown)) 91 | } 92 | if conf.Interface.TableOff { 93 | output.WriteString("Table = off\n") 94 | } 95 | 96 | for _, peer := range conf.Peers { 97 | output.WriteString("\n[Peer]\n") 98 | 99 | output.WriteString(fmt.Sprintf("PublicKey = %s\n", peer.PublicKey.String())) 100 | 101 | if !peer.PresharedKey.IsZero() { 102 | output.WriteString(fmt.Sprintf("PresharedKey = %s\n", peer.PresharedKey.String())) 103 | } 104 | 105 | if len(peer.AllowedIPs) > 0 { 106 | addrStrings := make([]string, len(peer.AllowedIPs)) 107 | for i, address := range peer.AllowedIPs { 108 | addrStrings[i] = address.String() 109 | } 110 | output.WriteString(fmt.Sprintf("AllowedIPs = %s\n", strings.Join(addrStrings[:], ", "))) 111 | } 112 | 113 | if !peer.Endpoint.IsEmpty() { 114 | output.WriteString(fmt.Sprintf("Endpoint = %s\n", peer.Endpoint.String())) 115 | } 116 | 117 | if peer.PersistentKeepalive > 0 { 118 | output.WriteString(fmt.Sprintf("PersistentKeepalive = %d\n", peer.PersistentKeepalive)) 119 | } 120 | } 121 | return output.String() 122 | } 123 | 124 | func (conf *Config) ToUAPI() (uapi string, dnsErr error) { 125 | var output strings.Builder 126 | output.WriteString(fmt.Sprintf("private_key=%s\n", conf.Interface.PrivateKey.HexString())) 127 | 128 | if conf.Interface.ListenPort > 0 { 129 | output.WriteString(fmt.Sprintf("listen_port=%d\n", conf.Interface.ListenPort)) 130 | } 131 | 132 | if conf.Interface.JunkPacketCount > 0 { 133 | output.WriteString(fmt.Sprintf("jc=%d\n", conf.Interface.JunkPacketCount)) 134 | } 135 | 136 | if conf.Interface.JunkPacketMinSize > 0 { 137 | output.WriteString(fmt.Sprintf("jmin=%d\n", conf.Interface.JunkPacketMinSize)) 138 | } 139 | 140 | if conf.Interface.JunkPacketMaxSize > 0 { 141 | output.WriteString(fmt.Sprintf("jmax=%d\n", conf.Interface.JunkPacketMaxSize)) 142 | } 143 | 144 | if conf.Interface.InitPacketJunkSize > 0 { 145 | output.WriteString(fmt.Sprintf("s1=%d\n", conf.Interface.InitPacketJunkSize)) 146 | } 147 | 148 | if conf.Interface.ResponsePacketJunkSize > 0 { 149 | output.WriteString(fmt.Sprintf("s2=%d\n", conf.Interface.ResponsePacketJunkSize)) 150 | } 151 | 152 | if conf.Interface.InitPacketMagicHeader > 0 { 153 | output.WriteString(fmt.Sprintf("h1=%d\n", conf.Interface.InitPacketMagicHeader)) 154 | } 155 | 156 | if conf.Interface.ResponsePacketMagicHeader > 0 { 157 | output.WriteString(fmt.Sprintf("h2=%d\n", conf.Interface.ResponsePacketMagicHeader)) 158 | } 159 | 160 | if conf.Interface.UnderloadPacketMagicHeader > 0 { 161 | output.WriteString(fmt.Sprintf("h3=%d\n", conf.Interface.UnderloadPacketMagicHeader)) 162 | } 163 | 164 | if conf.Interface.TransportPacketMagicHeader > 0 { 165 | output.WriteString(fmt.Sprintf("h4=%d\n", conf.Interface.TransportPacketMagicHeader)) 166 | } 167 | 168 | if len(conf.Peers) > 0 { 169 | output.WriteString("replace_peers=true\n") 170 | } 171 | 172 | for _, peer := range conf.Peers { 173 | output.WriteString(fmt.Sprintf("public_key=%s\n", peer.PublicKey.HexString())) 174 | 175 | if !peer.PresharedKey.IsZero() { 176 | output.WriteString(fmt.Sprintf("preshared_key=%s\n", peer.PresharedKey.HexString())) 177 | } 178 | 179 | if !peer.Endpoint.IsEmpty() { 180 | var resolvedIP string 181 | resolvedIP, dnsErr = resolveHostname(peer.Endpoint.Host) 182 | if dnsErr != nil { 183 | return 184 | } 185 | resolvedEndpoint := Endpoint{resolvedIP, peer.Endpoint.Port} 186 | output.WriteString(fmt.Sprintf("endpoint=%s\n", resolvedEndpoint.String())) 187 | } 188 | 189 | output.WriteString(fmt.Sprintf("persistent_keepalive_interval=%d\n", peer.PersistentKeepalive)) 190 | 191 | if len(peer.AllowedIPs) > 0 { 192 | output.WriteString("replace_allowed_ips=true\n") 193 | for _, address := range peer.AllowedIPs { 194 | output.WriteString(fmt.Sprintf("allowed_ip=%s\n", address.String())) 195 | } 196 | } 197 | } 198 | return output.String(), nil 199 | } 200 | -------------------------------------------------------------------------------- /conf/zsyscall_windows.go: -------------------------------------------------------------------------------- 1 | // Code generated by 'go generate'; DO NOT EDIT. 2 | 3 | package conf 4 | 5 | import ( 6 | "syscall" 7 | "unsafe" 8 | 9 | "golang.org/x/sys/windows" 10 | ) 11 | 12 | var _ unsafe.Pointer 13 | 14 | // Do the interface allocations only once for common 15 | // Errno values. 16 | const ( 17 | errnoERROR_IO_PENDING = 997 18 | ) 19 | 20 | var ( 21 | errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) 22 | errERROR_EINVAL error = syscall.EINVAL 23 | ) 24 | 25 | // errnoErr returns common boxed Errno values, to prevent 26 | // allocations at runtime. 27 | func errnoErr(e syscall.Errno) error { 28 | switch e { 29 | case 0: 30 | return errERROR_EINVAL 31 | case errnoERROR_IO_PENDING: 32 | return errERROR_IO_PENDING 33 | } 34 | // TODO: add more here, after collecting data on the common 35 | // error values see on Windows. (perhaps when running 36 | // all.bat?) 37 | return e 38 | } 39 | 40 | var ( 41 | modwininet = windows.NewLazySystemDLL("wininet.dll") 42 | 43 | procInternetGetConnectedState = modwininet.NewProc("InternetGetConnectedState") 44 | ) 45 | 46 | func internetGetConnectedState(flags *uint32, reserved uint32) (connected bool) { 47 | r0, _, _ := syscall.Syscall(procInternetGetConnectedState.Addr(), 2, uintptr(unsafe.Pointer(flags)), uintptr(reserved), 0) 48 | connected = r0 != 0 49 | return 50 | } 51 | -------------------------------------------------------------------------------- /deterministicguid.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package main 7 | 8 | import ( 9 | "bytes" 10 | "encoding/binary" 11 | "sort" 12 | "unsafe" 13 | 14 | "golang.org/x/crypto/blake2s" 15 | "golang.org/x/sys/windows" 16 | "golang.org/x/text/unicode/norm" 17 | 18 | "github.com/amnezia-vpn/amneziawg-windows/conf" 19 | ) 20 | 21 | const deterministicGUIDLabel = "Deterministic WireGuard Windows GUID v1 jason@zx2c4.com" 22 | const fixedGUIDLabel = "Fixed WireGuard Windows GUID v1 jason@zx2c4.com" 23 | 24 | // Escape hatch for external consumers, not us. 25 | var UseFixedGUIDInsteadOfDeterministic = false 26 | 27 | /* All peer public keys and allowed ips are sorted. Length/number fields are 28 | * little endian 32-bit. Hash input is: 29 | * 30 | * label || len(interface name) || interface name || 31 | * interface public key || number of peers || 32 | * peer public key || number of peer allowed ips || 33 | * len(allowed ip string) || allowed ip/cidr in canonical string notation || 34 | * len(allowed ip string) || allowed ip/cidr in canonical string notation || 35 | * len(allowed ip string) || allowed ip/cidr in canonical string notation || 36 | * ... 37 | * peer public key || number of peer allowed ips || 38 | * len(allowed ip string) || allowed ip/cidr in canonical string notation || 39 | * len(allowed ip string) || allowed ip/cidr in canonical string notation || 40 | * len(allowed ip string) || allowed ip/cidr in canonical string notation || 41 | * ... 42 | * ... 43 | */ 44 | 45 | func deterministicGUID(c *conf.Config) *windows.GUID { 46 | b2, _ := blake2s.New256(nil) 47 | if !UseFixedGUIDInsteadOfDeterministic { 48 | b2.Write([]byte(deterministicGUIDLabel)) 49 | } else { 50 | b2.Write([]byte(fixedGUIDLabel)) 51 | } 52 | b2Number := func(i int) { 53 | if uint(i) > uint(^uint32(0)) { 54 | panic("length out of bounds") 55 | } 56 | var bytes [4]byte 57 | binary.LittleEndian.PutUint32(bytes[:], uint32(i)) 58 | b2.Write(bytes[:]) 59 | } 60 | b2String := func(s string) { 61 | bytes := []byte(s) 62 | bytes = norm.NFC.Bytes(bytes) 63 | b2Number(len(bytes)) 64 | b2.Write(bytes) 65 | } 66 | b2Key := func(k *conf.Key) { 67 | b2.Write(k[:]) 68 | } 69 | 70 | b2String(c.Name) 71 | if !UseFixedGUIDInsteadOfDeterministic { 72 | b2Key(c.Interface.PrivateKey.Public()) 73 | b2Number(len(c.Peers)) 74 | sortedPeers := c.Peers 75 | sort.Slice(sortedPeers, func(i, j int) bool { 76 | return bytes.Compare(sortedPeers[i].PublicKey[:], sortedPeers[j].PublicKey[:]) < 0 77 | }) 78 | for _, peer := range sortedPeers { 79 | b2Key(&peer.PublicKey) 80 | b2Number(len(peer.AllowedIPs)) 81 | sortedAllowedIPs := peer.AllowedIPs 82 | sort.Slice(sortedAllowedIPs, func(i, j int) bool { 83 | if bi, bj := sortedAllowedIPs[i].Bits(), sortedAllowedIPs[j].Bits(); bi != bj { 84 | return bi < bj 85 | } 86 | if sortedAllowedIPs[i].Cidr != sortedAllowedIPs[j].Cidr { 87 | return sortedAllowedIPs[i].Cidr < sortedAllowedIPs[j].Cidr 88 | } 89 | return bytes.Compare(sortedAllowedIPs[i].IP[:], sortedAllowedIPs[j].IP[:]) < 0 90 | }) 91 | for _, allowedip := range sortedAllowedIPs { 92 | b2String(allowedip.String()) 93 | } 94 | } 95 | } 96 | return (*windows.GUID)(unsafe.Pointer(&b2.Sum(nil)[0])) 97 | } 98 | -------------------------------------------------------------------------------- /elevate/doas.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package elevate 7 | 8 | import ( 9 | "errors" 10 | "os" 11 | "runtime" 12 | "strings" 13 | "unsafe" 14 | 15 | "golang.org/x/sys/windows" 16 | "golang.org/x/sys/windows/svc/mgr" 17 | ) 18 | 19 | func setAllEnv(env []string) { 20 | windows.Clearenv() 21 | for _, e := range env { 22 | kv := strings.SplitN(e, "=", 2) 23 | if len(kv) != 2 { 24 | continue 25 | } 26 | windows.Setenv(kv[0], kv[1]) 27 | } 28 | } 29 | 30 | func DoAsSystem(f func() error) error { 31 | runtime.LockOSThread() 32 | defer func() { 33 | windows.RevertToSelf() 34 | runtime.UnlockOSThread() 35 | }() 36 | privileges := windows.Tokenprivileges{ 37 | PrivilegeCount: 1, 38 | Privileges: [1]windows.LUIDAndAttributes{ 39 | { 40 | Attributes: windows.SE_PRIVILEGE_ENABLED, 41 | }, 42 | }, 43 | } 44 | err := windows.LookupPrivilegeValue(nil, windows.StringToUTF16Ptr("SeDebugPrivilege"), &privileges.Privileges[0].Luid) 45 | if err != nil { 46 | return err 47 | } 48 | err = windows.ImpersonateSelf(windows.SecurityImpersonation) 49 | if err != nil { 50 | return err 51 | } 52 | var threadToken windows.Token 53 | err = windows.OpenThreadToken(windows.CurrentThread(), windows.TOKEN_QUERY|windows.TOKEN_ADJUST_PRIVILEGES, false, &threadToken) 54 | if err != nil { 55 | return err 56 | } 57 | tokenUser, err := threadToken.GetTokenUser() 58 | if err == nil && tokenUser.User.Sid.IsWellKnown(windows.WinLocalSystemSid) { 59 | threadToken.Close() 60 | return f() 61 | } 62 | err = windows.AdjustTokenPrivileges(threadToken, false, &privileges, uint32(unsafe.Sizeof(privileges)), nil, nil) 63 | threadToken.Close() 64 | if err != nil { 65 | return err 66 | } 67 | 68 | processes, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0) 69 | if err != nil { 70 | return err 71 | } 72 | processEntry := windows.ProcessEntry32{Size: uint32(unsafe.Sizeof(windows.ProcessEntry32{}))} 73 | var impersonationError error 74 | for err = windows.Process32First(processes, &processEntry); err == nil; err = windows.Process32Next(processes, &processEntry) { 75 | if strings.ToLower(windows.UTF16ToString(processEntry.ExeFile[:])) != "winlogon.exe" { 76 | continue 77 | } 78 | winlogonProcess, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION, false, processEntry.ProcessID) 79 | if err != nil { 80 | impersonationError = err 81 | continue 82 | } 83 | var winlogonToken windows.Token 84 | err = windows.OpenProcessToken(winlogonProcess, windows.TOKEN_QUERY|windows.TOKEN_IMPERSONATE|windows.TOKEN_DUPLICATE, &winlogonToken) 85 | windows.CloseHandle(winlogonProcess) 86 | if err != nil { 87 | continue 88 | } 89 | tokenUser, err := winlogonToken.GetTokenUser() 90 | if err != nil || !tokenUser.User.Sid.IsWellKnown(windows.WinLocalSystemSid) { 91 | winlogonToken.Close() 92 | continue 93 | } 94 | windows.CloseHandle(processes) 95 | 96 | var duplicatedToken windows.Token 97 | err = windows.DuplicateTokenEx(winlogonToken, 0, nil, windows.SecurityImpersonation, windows.TokenImpersonation, &duplicatedToken) 98 | windows.CloseHandle(winlogonProcess) 99 | if err != nil { 100 | return err 101 | } 102 | newEnv, err := duplicatedToken.Environ(false) 103 | if err != nil { 104 | duplicatedToken.Close() 105 | return err 106 | } 107 | currentEnv := os.Environ() 108 | err = windows.SetThreadToken(nil, duplicatedToken) 109 | duplicatedToken.Close() 110 | if err != nil { 111 | return err 112 | } 113 | setAllEnv(newEnv) 114 | err = f() 115 | setAllEnv(currentEnv) 116 | return err 117 | } 118 | windows.CloseHandle(processes) 119 | if impersonationError != nil { 120 | return impersonationError 121 | } 122 | return errors.New("unable to find winlogon.exe process") 123 | } 124 | 125 | func DoAsService(serviceName string, f func() error) error { 126 | scm, err := mgr.Connect() 127 | if err != nil { 128 | return err 129 | } 130 | service, err := scm.OpenService(serviceName) 131 | scm.Disconnect() 132 | if err != nil { 133 | return err 134 | } 135 | status, err := service.Query() 136 | service.Close() 137 | if err != nil { 138 | return err 139 | } 140 | if status.ProcessId == 0 { 141 | return errors.New("service is not running") 142 | } 143 | return DoAsSystem(func() error { 144 | serviceProcess, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION, false, status.ProcessId) 145 | if err != nil { 146 | return err 147 | } 148 | var serviceToken windows.Token 149 | err = windows.OpenProcessToken(serviceProcess, windows.TOKEN_IMPERSONATE|windows.TOKEN_DUPLICATE, &serviceToken) 150 | windows.CloseHandle(serviceProcess) 151 | if err != nil { 152 | return err 153 | } 154 | var duplicatedToken windows.Token 155 | err = windows.DuplicateTokenEx(serviceToken, 0, nil, windows.SecurityImpersonation, windows.TokenImpersonation, &duplicatedToken) 156 | serviceToken.Close() 157 | if err != nil { 158 | return err 159 | } 160 | newEnv, err := duplicatedToken.Environ(false) 161 | if err != nil { 162 | duplicatedToken.Close() 163 | return err 164 | } 165 | currentEnv := os.Environ() 166 | err = windows.SetThreadToken(nil, duplicatedToken) 167 | duplicatedToken.Close() 168 | if err != nil { 169 | return err 170 | } 171 | setAllEnv(newEnv) 172 | err = f() 173 | setAllEnv(currentEnv) 174 | return err 175 | }) 176 | } 177 | -------------------------------------------------------------------------------- /elevate/loader.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package elevate 7 | 8 | import ( 9 | "unsafe" 10 | 11 | "golang.org/x/sys/windows" 12 | ) 13 | 14 | /* We could use the undocumented LdrFindEntryForAddress function instead, but that's undocumented, and we're trying 15 | * to be as rock-solid as possible here. */ 16 | func findCurrentDataTableEntry() (entry *windows.LDR_DATA_TABLE_ENTRY, err error) { 17 | peb := windows.RtlGetCurrentPeb() 18 | if peb == nil || peb.Ldr == nil { 19 | err = windows.ERROR_INVALID_ADDRESS 20 | return 21 | } 22 | for cur := peb.Ldr.InMemoryOrderModuleList.Flink; cur != &peb.Ldr.InMemoryOrderModuleList; cur = cur.Flink { 23 | entry = (*windows.LDR_DATA_TABLE_ENTRY)(unsafe.Pointer(uintptr(unsafe.Pointer(cur)) - unsafe.Offsetof(windows.LDR_DATA_TABLE_ENTRY{}.InMemoryOrderLinks))) 24 | if entry.DllBase == peb.ImageBaseAddress { 25 | return 26 | } 27 | } 28 | entry = nil 29 | err = windows.ERROR_OBJECT_NOT_FOUND 30 | return 31 | } 32 | -------------------------------------------------------------------------------- /elevate/membership.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package elevate 7 | 8 | import ( 9 | "golang.org/x/sys/windows" 10 | ) 11 | 12 | func isAdmin(token windows.Token) bool { 13 | builtinAdminsGroup, err := windows.CreateWellKnownSid(windows.WinBuiltinAdministratorsSid) 14 | if err != nil { 15 | return false 16 | } 17 | var checkableToken windows.Token 18 | err = windows.DuplicateTokenEx(token, windows.TOKEN_QUERY|windows.TOKEN_IMPERSONATE, nil, windows.SecurityIdentification, windows.TokenImpersonation, &checkableToken) 19 | if err != nil { 20 | return false 21 | } 22 | defer checkableToken.Close() 23 | isAdmin, err := checkableToken.IsMember(builtinAdminsGroup) 24 | return isAdmin && err == nil 25 | } 26 | 27 | func TokenIsElevatedOrElevatable(token windows.Token) bool { 28 | if token.IsElevated() && isAdmin(token) { 29 | return true 30 | } 31 | linked, err := token.GetLinkedToken() 32 | if err != nil { 33 | return false 34 | } 35 | defer linked.Close() 36 | return linked.IsElevated() && isAdmin(linked) 37 | } 38 | 39 | func IsAdminDesktop() (bool, error) { 40 | hwnd := windows.GetShellWindow() 41 | if hwnd == 0 { 42 | return false, windows.ERROR_INVALID_WINDOW_HANDLE 43 | } 44 | var pid uint32 45 | _, err := windows.GetWindowThreadProcessId(hwnd, &pid) 46 | if err != nil { 47 | return false, err 48 | } 49 | process, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION, false, pid) 50 | if err != nil { 51 | return false, err 52 | } 53 | defer windows.CloseHandle(process) 54 | var token windows.Token 55 | err = windows.OpenProcessToken(process, windows.TOKEN_QUERY|windows.TOKEN_DUPLICATE, &token) 56 | if err != nil { 57 | return false, err 58 | } 59 | defer token.Close() 60 | return TokenIsElevatedOrElevatable(token), nil 61 | } 62 | 63 | func AdminGroupName() string { 64 | builtinAdminsGroup, err := windows.CreateWellKnownSid(windows.WinBuiltinAdministratorsSid) 65 | if err != nil { 66 | return "Administrators" 67 | } 68 | name, _, _, err := builtinAdminsGroup.LookupAccount("") 69 | if err != nil { 70 | return "Administrators" 71 | } 72 | return name 73 | } 74 | -------------------------------------------------------------------------------- /elevate/privileges.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package elevate 7 | 8 | import ( 9 | "errors" 10 | "runtime" 11 | "unsafe" 12 | 13 | "golang.org/x/sys/windows" 14 | ) 15 | 16 | func DropAllPrivileges(retainDriverLoading bool) error { 17 | var luid windows.LUID 18 | if retainDriverLoading { 19 | err := windows.LookupPrivilegeValue(nil, windows.StringToUTF16Ptr("SeLoadDriverPrivilege"), &luid) 20 | if err != nil { 21 | return err 22 | } 23 | } 24 | var processToken windows.Token 25 | err := windows.OpenProcessToken(windows.CurrentProcess(), windows.TOKEN_READ|windows.TOKEN_WRITE, &processToken) 26 | if err != nil { 27 | return err 28 | } 29 | defer processToken.Close() 30 | 31 | var bufferSizeRequired uint32 32 | windows.GetTokenInformation(processToken, windows.TokenPrivileges, nil, 0, &bufferSizeRequired) 33 | if bufferSizeRequired == 0 || bufferSizeRequired < uint32(unsafe.Sizeof(windows.Tokenprivileges{}.PrivilegeCount)) { 34 | return errors.New("GetTokenInformation failed to provide a buffer size") 35 | } 36 | buffer := make([]byte, bufferSizeRequired) 37 | var bytesWritten uint32 38 | err = windows.GetTokenInformation(processToken, windows.TokenPrivileges, &buffer[0], uint32(len(buffer)), &bytesWritten) 39 | if err != nil { 40 | return err 41 | } 42 | if bytesWritten != bufferSizeRequired { 43 | return errors.New("GetTokenInformation returned incomplete data") 44 | } 45 | tokenPrivileges := (*windows.Tokenprivileges)(unsafe.Pointer(&buffer[0])) 46 | for i := uint32(0); i < tokenPrivileges.PrivilegeCount; i++ { 47 | item := (*windows.LUIDAndAttributes)(unsafe.Pointer(uintptr(unsafe.Pointer(&tokenPrivileges.Privileges[0])) + unsafe.Sizeof(tokenPrivileges.Privileges[0])*uintptr(i))) 48 | if retainDriverLoading && item.Luid == luid { 49 | continue 50 | } 51 | item.Attributes = windows.SE_PRIVILEGE_REMOVED 52 | } 53 | err = windows.AdjustTokenPrivileges(processToken, false, tokenPrivileges, 0, nil, nil) 54 | runtime.KeepAlive(buffer) 55 | return err 56 | } 57 | -------------------------------------------------------------------------------- /elevate/shellexecute.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package elevate 7 | 8 | import ( 9 | "path/filepath" 10 | "runtime" 11 | "syscall" 12 | "unsafe" 13 | 14 | "golang.org/x/sys/windows" 15 | "golang.org/x/sys/windows/registry" 16 | 17 | "github.com/amnezia-vpn/amneziawg-windows/version" 18 | ) 19 | 20 | const ( 21 | releaseOffset = 2 22 | shellExecuteOffset = 9 23 | 24 | cSEE_MASK_DEFAULT = 0 25 | ) 26 | 27 | func ShellExecute(program string, arguments string, directory string, show int32) (err error) { 28 | var ( 29 | program16 *uint16 30 | arguments16 *uint16 31 | directory16 *uint16 32 | ) 33 | 34 | if len(program) > 0 { 35 | program16, _ = windows.UTF16PtrFromString(program) 36 | } 37 | if len(arguments) > 0 { 38 | arguments16, _ = windows.UTF16PtrFromString(arguments) 39 | } 40 | if len(directory) > 0 { 41 | directory16, _ = windows.UTF16PtrFromString(directory) 42 | } 43 | 44 | defer func() { 45 | if err != nil && program16 != nil { 46 | err = windows.ShellExecute(0, windows.StringToUTF16Ptr("runas"), program16, arguments16, directory16, show) 47 | } 48 | }() 49 | 50 | if !version.IsRunningEVSigned() { 51 | err = windows.ERROR_INSUFFICIENT_LOGON_INFO 52 | return 53 | } 54 | 55 | var processToken windows.Token 56 | err = windows.OpenProcessToken(windows.CurrentProcess(), windows.TOKEN_QUERY|windows.TOKEN_DUPLICATE, &processToken) 57 | if err != nil { 58 | return 59 | } 60 | defer processToken.Close() 61 | if processToken.IsElevated() { 62 | err = windows.ERROR_SUCCESS 63 | return 64 | } 65 | if !TokenIsElevatedOrElevatable(processToken) { 66 | err = windows.ERROR_ACCESS_DENIED 67 | return 68 | } 69 | key, err := registry.OpenKey(registry.LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", registry.QUERY_VALUE) 70 | if err != nil { 71 | return 72 | } 73 | promptBehavior, _, err := key.GetIntegerValue("ConsentPromptBehaviorAdmin") 74 | key.Close() 75 | if err != nil { 76 | return 77 | } 78 | if uint32(promptBehavior) == 0 { 79 | err = windows.ERROR_SUCCESS 80 | return 81 | } 82 | if uint32(promptBehavior) != 5 { 83 | err = windows.ERROR_ACCESS_DENIED 84 | return 85 | } 86 | 87 | key, err = registry.OpenKey(registry.LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\UAC\\COMAutoApprovalList", registry.QUERY_VALUE) 88 | if err == nil { 89 | var autoApproved uint64 90 | autoApproved, _, err = key.GetIntegerValue("{3E5FC7F9-9A51-4367-9063-A120244FBEC7}") 91 | key.Close() 92 | if err != nil { 93 | return 94 | } 95 | if uint32(autoApproved) == 0 { 96 | err = windows.ERROR_ACCESS_DENIED 97 | return 98 | } 99 | } 100 | dataTableEntry, err := findCurrentDataTableEntry() 101 | if err != nil { 102 | return 103 | } 104 | windowsDirectory, err := windows.GetSystemWindowsDirectory() 105 | if err != nil { 106 | return 107 | } 108 | originalPath := dataTableEntry.FullDllName.Buffer 109 | explorerPath := windows.StringToUTF16Ptr(filepath.Join(windowsDirectory, "explorer.exe")) 110 | windows.RtlInitUnicodeString(&dataTableEntry.FullDllName, explorerPath) 111 | defer func() { 112 | windows.RtlInitUnicodeString(&dataTableEntry.FullDllName, originalPath) 113 | runtime.KeepAlive(explorerPath) 114 | }() 115 | 116 | if err = windows.CoInitializeEx(0, windows.COINIT_APARTMENTTHREADED); err == nil { 117 | defer windows.CoUninitialize() 118 | } 119 | 120 | var interfacePointer **[0xffff]uintptr 121 | if err = windows.CoGetObject( 122 | windows.StringToUTF16Ptr("Elevation:Administrator!new:{3E5FC7F9-9A51-4367-9063-A120244FBEC7}"), 123 | &windows.BIND_OPTS3{ 124 | CbStruct: uint32(unsafe.Sizeof(windows.BIND_OPTS3{})), 125 | ClassContext: windows.CLSCTX_LOCAL_SERVER, 126 | }, 127 | &windows.GUID{0x6EDD6D74, 0xC007, 0x4E75, [8]byte{0xB7, 0x6A, 0xE5, 0x74, 0x09, 0x95, 0xE2, 0x4C}}, 128 | (**uintptr)(unsafe.Pointer(&interfacePointer)), 129 | ); err != nil { 130 | return 131 | } 132 | 133 | defer syscall.Syscall((*interfacePointer)[releaseOffset], 1, uintptr(unsafe.Pointer(interfacePointer)), 0, 0) 134 | 135 | if program16 == nil { 136 | return 137 | } 138 | 139 | if ret, _, _ := syscall.Syscall6((*interfacePointer)[shellExecuteOffset], 6, 140 | uintptr(unsafe.Pointer(interfacePointer)), 141 | uintptr(unsafe.Pointer(program16)), 142 | uintptr(unsafe.Pointer(arguments16)), 143 | uintptr(unsafe.Pointer(directory16)), 144 | cSEE_MASK_DEFAULT, 145 | uintptr(show), 146 | ); ret != uintptr(windows.ERROR_SUCCESS) { 147 | err = syscall.Errno(ret) 148 | return 149 | } 150 | 151 | err = nil 152 | return 153 | } 154 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/amnezia-vpn/amneziawg-windows 2 | 3 | go 1.22.3 4 | 5 | require ( 6 | github.com/amnezia-vpn/amneziawg-go v0.2.12 7 | golang.org/x/crypto v0.21.0 8 | golang.org/x/sys v0.18.0 9 | golang.org/x/text v0.14.0 10 | ) 11 | 12 | require ( 13 | github.com/tevino/abool/v2 v2.1.0 // indirect 14 | golang.org/x/net v0.21.0 // indirect 15 | golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/amnezia-vpn/amneziawg-go v0.2.12 h1:CxIQETy5kZ0ip/dFBpmnDxAcS/KuIQaJkOxDv5OQhVI= 2 | github.com/amnezia-vpn/amneziawg-go v0.2.12/go.mod h1:d7WpNfzCRLy7ufGElJBYpD58WRmNjyLyt3IDHPY8AmM= 3 | github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= 4 | github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= 5 | github.com/tevino/abool/v2 v2.1.0 h1:7w+Vf9f/5gmKT4m4qkayb33/92M+Um45F2BkHOR+L/c= 6 | github.com/tevino/abool/v2 v2.1.0/go.mod h1:+Lmlqk6bHDWHqN1cbxqhwEAwMPXgc8I1SDEamtseuXY= 7 | golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= 8 | golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 9 | golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= 10 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 11 | golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= 12 | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 13 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 14 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 15 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= 16 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 17 | golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= 18 | golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= 19 | gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ= 20 | gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY= 21 | -------------------------------------------------------------------------------- /interfacewatcher.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | * Copyright (C) 2021 Mozilla Foundation. All Rights Reserved. 5 | */ 6 | 7 | package main 8 | 9 | import ( 10 | "log" 11 | "sync" 12 | 13 | "golang.org/x/sys/windows" 14 | 15 | "github.com/amnezia-vpn/amneziawg-go/conn" 16 | "github.com/amnezia-vpn/amneziawg-go/tun" 17 | 18 | "github.com/amnezia-vpn/amneziawg-windows/conf" 19 | "github.com/amnezia-vpn/amneziawg-windows/services" 20 | "github.com/amnezia-vpn/amneziawg-windows/tunnel/firewall" 21 | "github.com/amnezia-vpn/amneziawg-windows/tunnel/winipcfg" 22 | ) 23 | 24 | type interfaceWatcherError struct { 25 | serviceError services.Error 26 | err error 27 | } 28 | type interfaceWatcherEvent struct { 29 | luid winipcfg.LUID 30 | family winipcfg.AddressFamily 31 | } 32 | type interfaceWatcher struct { 33 | errors chan interfaceWatcherError 34 | 35 | binder conn.BindSocketToInterface 36 | conf *conf.Config 37 | tun *tun.NativeTun 38 | 39 | setupMutex sync.Mutex 40 | interfaceChangeCallback winipcfg.ChangeCallback 41 | changeCallbacks4 []winipcfg.ChangeCallback 42 | changeCallbacks6 []winipcfg.ChangeCallback 43 | storedEvents []interfaceWatcherEvent 44 | } 45 | 46 | func (iw *interfaceWatcher) setup(family winipcfg.AddressFamily) { 47 | var changeCallbacks *[]winipcfg.ChangeCallback 48 | var ipversion string 49 | if family == windows.AF_INET { 50 | changeCallbacks = &iw.changeCallbacks4 51 | ipversion = "v4" 52 | } else if family == windows.AF_INET6 { 53 | changeCallbacks = &iw.changeCallbacks6 54 | ipversion = "v6" 55 | } else { 56 | return 57 | } 58 | if len(*changeCallbacks) != 0 { 59 | for _, cb := range *changeCallbacks { 60 | cb.Unregister() 61 | } 62 | *changeCallbacks = nil 63 | } 64 | var err error 65 | 66 | log.Printf("Setting device %s addresses", ipversion) 67 | err = configureInterface(family, iw.conf, iw.tun) 68 | if err != nil { 69 | iw.errors <- interfaceWatcherError{services.ErrorSetNetConfig, err} 70 | return 71 | } 72 | } 73 | 74 | func watchInterface() (*interfaceWatcher, error) { 75 | iw := &interfaceWatcher{ 76 | errors: make(chan interfaceWatcherError, 2), 77 | } 78 | var err error 79 | iw.interfaceChangeCallback, err = winipcfg.RegisterInterfaceChangeCallback(func(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) { 80 | iw.setupMutex.Lock() 81 | defer iw.setupMutex.Unlock() 82 | 83 | if notificationType != winipcfg.MibAddInstance { 84 | return 85 | } 86 | if iw.tun == nil { 87 | iw.storedEvents = append(iw.storedEvents, interfaceWatcherEvent{iface.InterfaceLUID, iface.Family}) 88 | return 89 | } 90 | if iface.InterfaceLUID != winipcfg.LUID(iw.tun.LUID()) { 91 | return 92 | } 93 | iw.setup(iface.Family) 94 | }) 95 | if err != nil { 96 | return nil, err 97 | } 98 | return iw, nil 99 | } 100 | 101 | func (iw *interfaceWatcher) Configure(binder conn.BindSocketToInterface, conf *conf.Config, tun *tun.NativeTun) { 102 | iw.setupMutex.Lock() 103 | defer iw.setupMutex.Unlock() 104 | 105 | iw.binder, iw.conf, iw.tun = binder, conf, tun 106 | for _, event := range iw.storedEvents { 107 | if event.luid == winipcfg.LUID(iw.tun.LUID()) { 108 | iw.setup(event.family) 109 | } 110 | } 111 | iw.storedEvents = nil 112 | } 113 | 114 | func (iw *interfaceWatcher) Destroy() { 115 | iw.setupMutex.Lock() 116 | changeCallbacks4 := iw.changeCallbacks4 117 | changeCallbacks6 := iw.changeCallbacks6 118 | interfaceChangeCallback := iw.interfaceChangeCallback 119 | tun := iw.tun 120 | iw.setupMutex.Unlock() 121 | 122 | if interfaceChangeCallback != nil { 123 | interfaceChangeCallback.Unregister() 124 | } 125 | for _, cb := range changeCallbacks4 { 126 | cb.Unregister() 127 | } 128 | for _, cb := range changeCallbacks6 { 129 | cb.Unregister() 130 | } 131 | 132 | iw.setupMutex.Lock() 133 | if interfaceChangeCallback == iw.interfaceChangeCallback { 134 | iw.interfaceChangeCallback = nil 135 | } 136 | for len(changeCallbacks4) > 0 && len(iw.changeCallbacks4) > 0 { 137 | iw.changeCallbacks4 = iw.changeCallbacks4[1:] 138 | changeCallbacks4 = changeCallbacks4[1:] 139 | } 140 | for len(changeCallbacks6) > 0 && len(iw.changeCallbacks6) > 0 { 141 | iw.changeCallbacks6 = iw.changeCallbacks6[1:] 142 | changeCallbacks6 = changeCallbacks6[1:] 143 | } 144 | firewall.DisableFirewall() 145 | if tun != nil && iw.tun == tun { 146 | // It seems that the Windows networking stack doesn't like it when we destroy interfaces that have active 147 | // routes, so to be certain, just remove everything before destroying. 148 | luid := winipcfg.LUID(tun.LUID()) 149 | luid.FlushRoutes(windows.AF_INET) 150 | luid.FlushIPAddresses(windows.AF_INET) 151 | luid.FlushDNS(windows.AF_INET) 152 | luid.FlushRoutes(windows.AF_INET6) 153 | luid.FlushIPAddresses(windows.AF_INET6) 154 | luid.FlushDNS(windows.AF_INET6) 155 | } 156 | iw.setupMutex.Unlock() 157 | } 158 | -------------------------------------------------------------------------------- /ipcpermissions.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package main 7 | 8 | import ( 9 | "golang.org/x/sys/windows" 10 | 11 | "github.com/amnezia-vpn/amneziawg-go/ipc" 12 | 13 | "github.com/amnezia-vpn/amneziawg-windows/conf" 14 | ) 15 | 16 | func CopyConfigOwnerToIPCSecurityDescriptor(filename string) error { 17 | if conf.PathIsEncrypted(filename) { 18 | return nil 19 | } 20 | 21 | fileSd, err := windows.GetNamedSecurityInfo(filename, windows.SE_FILE_OBJECT, windows.OWNER_SECURITY_INFORMATION) 22 | if err != nil { 23 | return err 24 | } 25 | fileOwner, _, err := fileSd.Owner() 26 | if err != nil { 27 | return err 28 | } 29 | if fileOwner.IsWellKnown(windows.WinLocalSystemSid) { 30 | return nil 31 | } 32 | additionalEntries := []windows.EXPLICIT_ACCESS{{ 33 | AccessPermissions: windows.GENERIC_ALL, 34 | AccessMode: windows.GRANT_ACCESS, 35 | Trustee: windows.TRUSTEE{ 36 | TrusteeForm: windows.TRUSTEE_IS_SID, 37 | TrusteeType: windows.TRUSTEE_IS_USER, 38 | TrusteeValue: windows.TrusteeValueFromSID(fileOwner), 39 | }, 40 | }} 41 | 42 | sd, err := ipc.UAPISecurityDescriptor.ToAbsolute() 43 | if err != nil { 44 | return err 45 | } 46 | dacl, defaulted, _ := sd.DACL() 47 | 48 | newDacl, err := windows.ACLFromEntries(additionalEntries, dacl) 49 | if err != nil { 50 | return err 51 | } 52 | err = sd.SetDACL(newDacl, true, defaulted) 53 | if err != nil { 54 | return err 55 | } 56 | sd, err = sd.ToSelfRelative() 57 | if err != nil { 58 | return err 59 | } 60 | ipc.UAPISecurityDescriptor = sd 61 | 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /l18n/l18n.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package l18n 7 | 8 | import ( 9 | "sync" 10 | 11 | "golang.org/x/sys/windows" 12 | "golang.org/x/text/language" 13 | "golang.org/x/text/message" 14 | ) 15 | 16 | var printer *message.Printer 17 | var printerLock sync.Mutex 18 | 19 | // prn returns the printer for user preferred UI language. 20 | func prn() *message.Printer { 21 | if printer != nil { 22 | return printer 23 | } 24 | printerLock.Lock() 25 | if printer != nil { 26 | printerLock.Unlock() 27 | return printer 28 | } 29 | printer = message.NewPrinter(lang()) 30 | printerLock.Unlock() 31 | return printer 32 | } 33 | 34 | // lang returns the user preferred UI language we have most confident translation in the default catalog available. 35 | func lang() (tag language.Tag) { 36 | tag = language.English 37 | confidence := language.No 38 | languages, err := windows.GetUserPreferredUILanguages(windows.MUI_LANGUAGE_NAME) 39 | if err != nil { 40 | return 41 | } 42 | for i := range languages { 43 | t, _, c := message.DefaultCatalog.Matcher().Match(message.MatchLanguage(languages[i])) 44 | if c > confidence { 45 | tag = t 46 | confidence = c 47 | } 48 | } 49 | return 50 | } 51 | 52 | // Sprintf is like fmt.Sprintf, but using language-specific formatting. 53 | func Sprintf(key message.Reference, a ...interface{}) string { 54 | return prn().Sprintf(key, a...) 55 | } 56 | 57 | // EnumerationSeparator returns enumeration separator. For English and western languages, 58 | // enumeration separator is a comma followed by a space (i.e. ", "). For Chinese, it returns 59 | // "\u3001". 60 | func EnumerationSeparator() string { 61 | // BUG: We could just use `Sprintf(", " /* ...translator instructions... */)` and let the 62 | // individual locale catalog handle its translation. Unfortunately, the gotext utility tries to 63 | // be nice to translators and skips all strings without letters when updating catalogs. 64 | return Sprintf("[EnumerationSeparator]" /* Text to insert between items when listing - most western languages will translate ‘[EnumerationSeparator]’ into ‘, ’ to produce lists like ‘apple, orange, strawberry’. Eastern languages might translate into ‘、’ to produce lists like ‘リンゴ、オレンジ、イチゴ’. */) 65 | } 66 | 67 | // UnitSeparator returns the separator to use when concatenating multiple units of the same metric 68 | // (e.g. "1 minute, 32 seconds", "6 feet, 1 inch"). For English and western languages, unit 69 | // separator is a comma followed by a space (i.e. ", "). For Slovenian and Japanese, it returns 70 | // just space. 71 | func UnitSeparator() string { 72 | // BUG: We could just use `Sprintf(", " /* ...translator instructions... */)` and let the 73 | // individual locale catalog handle its translation. Unfortunately, the gotext utility tries to 74 | // be nice to translators and skips all strings without letters when updating catalogs. 75 | return Sprintf("[UnitSeparator]" /* Text to insert when combining units of a measure - most languages will translate ‘[UnitSeparator]’ into ‘ ’ (space) to produce lists like ‘2 minuti 30 sekund’, or empty string ‘’ to produce ‘2分30秒’. */) 76 | } 77 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package main 7 | 8 | import ( 9 | "C" 10 | 11 | "golang.org/x/crypto/curve25519" 12 | "golang.org/x/sys/windows" 13 | 14 | 15 | "crypto/rand" 16 | "log" 17 | "unsafe" 18 | ) 19 | 20 | //export WireGuardTunnelService 21 | func WireGuardTunnelService(confString16 *uint16, nameString16 *uint16) bool { 22 | confStr := windows.UTF16PtrToString(confString16) 23 | nameStr := windows.UTF16PtrToString(nameString16) 24 | UseFixedGUIDInsteadOfDeterministic = true 25 | err := Run(confStr,nameStr) 26 | if err != nil { 27 | log.Printf("Service run error: %v", err) 28 | } 29 | return err == nil 30 | } 31 | 32 | //export WireGuardGenerateKeypair 33 | func WireGuardGenerateKeypair(publicKey *byte, privateKey *byte) { 34 | publicKeyArray := (*[32]byte)(unsafe.Pointer(publicKey)) 35 | privateKeyArray := (*[32]byte)(unsafe.Pointer(privateKey)) 36 | n, err := rand.Read(privateKeyArray[:]) 37 | if err != nil || n != len(privateKeyArray) { 38 | panic("Unable to generate random bytes") 39 | } 40 | privateKeyArray[0] &= 248 41 | privateKeyArray[31] = (privateKeyArray[31] & 127) | 64 42 | 43 | curve25519.ScalarBaseMult(publicKeyArray, privateKeyArray) 44 | } 45 | 46 | func main() {} 47 | -------------------------------------------------------------------------------- /ringlogger/cli_test.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package ringlogger 7 | 8 | import ( 9 | "fmt" 10 | "os" 11 | "sync" 12 | "testing" 13 | "time" 14 | ) 15 | 16 | func TestThreads(t *testing.T) { 17 | wg := sync.WaitGroup{} 18 | wg.Add(2) 19 | go func() { 20 | rl, err := NewRinglogger("ringlogger_test.bin", "ONE") 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | for i := 0; i < 1024; i++ { 25 | fmt.Fprintf(rl, "bla bla bla %d", i) 26 | } 27 | rl.Close() 28 | wg.Done() 29 | }() 30 | go func() { 31 | rl, err := NewRinglogger("ringlogger_test.bin", "TWO") 32 | if err != nil { 33 | t.Fatal(err) 34 | } 35 | for i := 1024; i < 2047; i++ { 36 | fmt.Fprintf(rl, "bla bla bla %d", i) 37 | } 38 | rl.Close() 39 | wg.Done() 40 | }() 41 | wg.Wait() 42 | } 43 | 44 | func TestWriteText(t *testing.T) { 45 | rl, err := NewRinglogger("ringlogger_test.bin", "TXT") 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | if len(os.Args) != 3 { 50 | t.Fatal("Should pass exactly one argument") 51 | } 52 | fmt.Fprintf(rl, os.Args[2]) 53 | rl.Close() 54 | } 55 | 56 | func TestDump(t *testing.T) { 57 | rl, err := NewRinglogger("ringlogger_test.bin", "DMP") 58 | if err != nil { 59 | t.Fatal(err) 60 | } 61 | _, err = rl.WriteTo(os.Stdout) 62 | if err != nil { 63 | t.Fatal(err) 64 | } 65 | rl.Close() 66 | } 67 | 68 | func TestFollow(t *testing.T) { 69 | rl, err := NewRinglogger("ringlogger_test.bin", "FOL") 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | cursor := CursorAll 74 | for { 75 | var lines []FollowLine 76 | lines, cursor = rl.FollowFromCursor(cursor) 77 | for _, line := range lines { 78 | fmt.Printf("%v: %s\n", line.Stamp, line.Line) 79 | } 80 | time.Sleep(300 * time.Millisecond) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /ringlogger/dump.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package ringlogger 7 | 8 | import ( 9 | "io" 10 | "os" 11 | "path/filepath" 12 | 13 | "golang.org/x/sys/windows" 14 | 15 | "github.com/amnezia-vpn/amneziawg-windows/conf" 16 | ) 17 | 18 | func DumpTo(out io.Writer, notSystem bool) error { 19 | root, err := conf.RootDirectory(!notSystem) 20 | if err != nil { 21 | return err 22 | } 23 | path := filepath.Join(root, "log.bin") 24 | file, err := os.Open(path) 25 | if err != nil { 26 | return err 27 | } 28 | defer file.Close() 29 | mapping, err := windows.CreateFileMapping(windows.Handle(file.Fd()), nil, windows.PAGE_READONLY, 0, 0, nil) 30 | if err != nil && err != windows.ERROR_ALREADY_EXISTS { 31 | return err 32 | } 33 | rl, err := newRingloggerFromMappingHandle(mapping, "DMP", windows.FILE_MAP_READ) 34 | if err != nil { 35 | windows.CloseHandle(mapping) 36 | return err 37 | } 38 | defer rl.Close() 39 | _, err = rl.WriteTo(out) 40 | if err != nil { 41 | return err 42 | } 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /ringlogger/global.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package ringlogger 7 | 8 | import ( 9 | "log" 10 | "path/filepath" 11 | "unsafe" 12 | 13 | "github.com/amnezia-vpn/amneziawg-windows/conf" 14 | ) 15 | 16 | var Global *Ringlogger 17 | 18 | func InitGlobalLogger(tag string) error { 19 | if Global != nil { 20 | return nil 21 | } 22 | root, err := conf.RootDirectory(true) 23 | if err != nil { 24 | return err 25 | } 26 | Global, err = NewRinglogger(filepath.Join(root, "log.bin"), tag) 27 | if err != nil { 28 | return err 29 | } 30 | log.SetOutput(Global) 31 | log.SetFlags(0) 32 | overrideWrite = globalWrite 33 | return nil 34 | } 35 | 36 | //go:linkname overrideWrite runtime.overrideWrite 37 | var overrideWrite func(fd uintptr, p unsafe.Pointer, n int32) int32 38 | 39 | var globalBuffer [maxLogLineLength - 1 - maxTagLength - 3]byte 40 | var globalBufferLocation int 41 | 42 | //go:nosplit 43 | func globalWrite(fd uintptr, p unsafe.Pointer, n int32) int32 { 44 | b := (*[1 << 30]byte)(p)[:n] 45 | for len(b) > 0 { 46 | amountAvailable := len(globalBuffer) - globalBufferLocation 47 | amountToCopy := len(b) 48 | if amountToCopy > amountAvailable { 49 | amountToCopy = amountAvailable 50 | } 51 | copy(globalBuffer[globalBufferLocation:], b[:amountToCopy]) 52 | b = b[amountToCopy:] 53 | globalBufferLocation += amountToCopy 54 | foundNl := false 55 | for i := globalBufferLocation - amountToCopy; i < globalBufferLocation; i++ { 56 | if globalBuffer[i] == '\n' { 57 | foundNl = true 58 | break 59 | } 60 | } 61 | if foundNl || len(b) > 0 { 62 | Global.Write(globalBuffer[:globalBufferLocation]) 63 | globalBufferLocation = 0 64 | } 65 | } 66 | return n 67 | } 68 | -------------------------------------------------------------------------------- /ringlogger/ringlogger.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package ringlogger 7 | 8 | import ( 9 | "bytes" 10 | "fmt" 11 | "io" 12 | "os" 13 | "runtime" 14 | "strconv" 15 | "sync/atomic" 16 | "time" 17 | "unsafe" 18 | 19 | "golang.org/x/sys/windows" 20 | ) 21 | 22 | const ( 23 | maxLogLineLength = 512 24 | maxTagLength = 5 25 | maxLines = 2048 26 | magic = 0xbadbabe 27 | ) 28 | 29 | type logLine struct { 30 | timeNs int64 31 | line [maxLogLineLength]byte 32 | } 33 | 34 | type logMem struct { 35 | magic uint32 36 | nextIndex uint32 37 | lines [maxLines]logLine 38 | } 39 | 40 | type Ringlogger struct { 41 | tag string 42 | file *os.File 43 | mapping windows.Handle 44 | log *logMem 45 | readOnly bool 46 | } 47 | 48 | func NewRinglogger(filename string, tag string) (*Ringlogger, error) { 49 | if len(tag) > maxTagLength { 50 | return nil, windows.ERROR_LABEL_TOO_LONG 51 | } 52 | file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600) 53 | if err != nil { 54 | return nil, err 55 | } 56 | err = file.Truncate(int64(unsafe.Sizeof(logMem{}))) 57 | if err != nil { 58 | return nil, err 59 | } 60 | mapping, err := windows.CreateFileMapping(windows.Handle(file.Fd()), nil, windows.PAGE_READWRITE, 0, 0, nil) 61 | if err != nil && err != windows.ERROR_ALREADY_EXISTS { 62 | return nil, err 63 | } 64 | rl, err := newRingloggerFromMappingHandle(mapping, tag, windows.FILE_MAP_WRITE) 65 | if err != nil { 66 | return nil, err 67 | } 68 | rl.file = file 69 | return rl, nil 70 | } 71 | 72 | func NewRingloggerFromInheritedMappingHandle(handleStr string, tag string) (*Ringlogger, error) { 73 | handle, err := strconv.ParseUint(handleStr, 10, 64) 74 | if err != nil { 75 | return nil, err 76 | } 77 | return newRingloggerFromMappingHandle(windows.Handle(handle), tag, windows.FILE_MAP_READ) 78 | } 79 | 80 | func newRingloggerFromMappingHandle(mappingHandle windows.Handle, tag string, access uint32) (*Ringlogger, error) { 81 | view, err := windows.MapViewOfFile(mappingHandle, access, 0, 0, 0) 82 | if err != nil { 83 | return nil, err 84 | } 85 | if err != nil { 86 | windows.CloseHandle(mappingHandle) 87 | return nil, err 88 | } 89 | log := (*logMem)(unsafe.Pointer(view)) 90 | if log.magic != magic { 91 | bytes := (*[unsafe.Sizeof(logMem{})]byte)(unsafe.Pointer(log)) 92 | for i := range bytes { 93 | bytes[i] = 0 94 | } 95 | log.magic = magic 96 | windows.FlushViewOfFile(view, uintptr(len(bytes))) 97 | } 98 | 99 | rl := &Ringlogger{ 100 | tag: tag, 101 | mapping: mappingHandle, 102 | log: log, 103 | readOnly: access&windows.FILE_MAP_WRITE == 0, 104 | } 105 | runtime.SetFinalizer(rl, (*Ringlogger).Close) 106 | return rl, nil 107 | } 108 | 109 | func (rl *Ringlogger) Write(p []byte) (n int, err error) { 110 | if rl.readOnly { 111 | return 0, io.ErrShortWrite 112 | } 113 | ret := len(p) 114 | p = bytes.TrimSpace(p) 115 | if len(p) == 0 { 116 | return ret, nil 117 | } 118 | 119 | // Race: This isn't synchronized with the fetch_add below, so items might be slightly out of order. 120 | ts := time.Now().UnixNano() 121 | 122 | if rl.log == nil { 123 | return 0, io.EOF 124 | } 125 | 126 | // Race: More than maxLines writers and this will clash. 127 | index := atomic.AddUint32(&rl.log.nextIndex, 1) - 1 128 | line := &rl.log.lines[index%maxLines] 129 | 130 | // Race: Before this line executes, we'll display old data after new data. 131 | atomic.StoreInt64(&line.timeNs, 0) 132 | for i := range line.line { 133 | line.line[i] = 0 134 | } 135 | 136 | textLen := 3 + len(p) + len(rl.tag) 137 | if textLen > maxLogLineLength-1 { 138 | p = p[:maxLogLineLength-1-3-len(rl.tag)] 139 | textLen = maxLogLineLength - 1 140 | } 141 | line.line[textLen] = 0 142 | line.line[0] = 0 // Null out the beginning and only let it extend after the other writes have completed 143 | copy(line.line[1:], rl.tag) 144 | line.line[1+len(rl.tag)] = ']' 145 | line.line[2+len(rl.tag)] = ' ' 146 | copy(line.line[3+len(rl.tag):], p[:]) 147 | line.line[0] = '[' 148 | atomic.StoreInt64(&line.timeNs, ts) 149 | 150 | return ret, nil 151 | } 152 | 153 | func (rl *Ringlogger) WriteTo(out io.Writer) (n int64, err error) { 154 | if rl.log == nil { 155 | return 0, io.EOF 156 | } 157 | log := *rl.log 158 | i := log.nextIndex 159 | for l := uint32(0); l < maxLines; l++ { 160 | line := &log.lines[(i+l)%maxLines] 161 | if line.timeNs == 0 { 162 | continue 163 | } 164 | index := bytes.IndexByte(line.line[:], 0) 165 | if index < 1 { 166 | continue 167 | } 168 | var bytes int 169 | bytes, err = fmt.Fprintf(out, "%s: %s\n", time.Unix(0, line.timeNs).Format("2006-01-02 15:04:05.000000"), line.line[:index]) 170 | if err != nil { 171 | return 172 | } 173 | n += int64(bytes) 174 | } 175 | return 176 | } 177 | 178 | const CursorAll = ^uint32(0) 179 | 180 | type FollowLine struct { 181 | Line string 182 | Stamp time.Time 183 | } 184 | 185 | func (rl *Ringlogger) FollowFromCursor(cursor uint32) (followLines []FollowLine, nextCursor uint32) { 186 | followLines = make([]FollowLine, 0, maxLines) 187 | nextCursor = cursor 188 | 189 | if rl.log == nil { 190 | return 191 | } 192 | log := *rl.log 193 | 194 | i := cursor 195 | if cursor == CursorAll { 196 | i = log.nextIndex 197 | } 198 | 199 | for l := 0; l < maxLines; l++ { 200 | line := &log.lines[i%maxLines] 201 | if cursor != CursorAll && i%maxLines == log.nextIndex%maxLines { 202 | break 203 | } 204 | if line.timeNs == 0 { 205 | if cursor == CursorAll { 206 | i++ 207 | continue 208 | } else { 209 | break 210 | } 211 | } 212 | index := bytes.IndexByte(line.line[:], 0) 213 | if index > 0 { 214 | followLines = append(followLines, FollowLine{string(line.line[:index]), time.Unix(0, line.timeNs)}) 215 | } 216 | i++ 217 | nextCursor = i % maxLines 218 | } 219 | return 220 | } 221 | 222 | func (rl *Ringlogger) Close() error { 223 | if rl.file != nil { 224 | rl.file.Close() 225 | rl.file = nil 226 | } 227 | if rl.log != nil { 228 | windows.UnmapViewOfFile((uintptr)(unsafe.Pointer(rl.log))) 229 | rl.log = nil 230 | } 231 | if rl.mapping != 0 { 232 | windows.CloseHandle(rl.mapping) 233 | rl.mapping = 0 234 | } 235 | return nil 236 | } 237 | 238 | func (rl *Ringlogger) ExportInheritableMappingHandle() (handleToClose windows.Handle, err error) { 239 | handleToClose, err = windows.CreateFileMapping(windows.Handle(rl.file.Fd()), nil, windows.PAGE_READONLY, 0, 0, nil) 240 | if err != nil && err != windows.ERROR_ALREADY_EXISTS { 241 | return 242 | } 243 | err = windows.SetHandleInformation(handleToClose, windows.HANDLE_FLAG_INHERIT, windows.HANDLE_FLAG_INHERIT) 244 | if err != nil { 245 | windows.CloseHandle(handleToClose) 246 | handleToClose = 0 247 | return 248 | } 249 | return 250 | } 251 | -------------------------------------------------------------------------------- /scriptrunner.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package main 7 | 8 | import ( 9 | "bufio" 10 | "fmt" 11 | "log" 12 | "os" 13 | "path/filepath" 14 | "syscall" 15 | 16 | "golang.org/x/sys/windows" 17 | 18 | "github.com/amnezia-vpn/amneziawg-windows/conf" 19 | ) 20 | 21 | func runScriptCommand(command, interfaceName string) error { 22 | if len(command) == 0 { 23 | return nil 24 | } 25 | if !conf.AdminBool("DangerousScriptExecution") { 26 | log.Printf("Skipping execution of script, because dangerous script execution is safely disabled: %#q", command) 27 | return nil 28 | } 29 | log.Printf("Executing: %#q", command) 30 | comspec, _ := os.LookupEnv("COMSPEC") 31 | if len(comspec) == 0 { 32 | system32, err := windows.GetSystemDirectory() 33 | if err != nil { 34 | return err 35 | } 36 | comspec = filepath.Join(system32, "cmd.exe") 37 | } 38 | 39 | devNull, err := os.OpenFile(os.DevNull, os.O_RDWR, 0) 40 | if err != nil { 41 | return err 42 | } 43 | defer devNull.Close() 44 | reader, writer, err := os.Pipe() 45 | if err != nil { 46 | return err 47 | } 48 | process, err := os.StartProcess(comspec, nil /* CmdLine below */, &os.ProcAttr{ 49 | Files: []*os.File{devNull, writer, writer}, 50 | Env: append(os.Environ(), "AMNEZIAWG_TUNNEL_NAME="+interfaceName), 51 | Sys: &syscall.SysProcAttr{ 52 | HideWindow: true, 53 | CmdLine: fmt.Sprintf("cmd /c %s", command), 54 | }, 55 | }) 56 | writer.Close() 57 | if err != nil { 58 | reader.Close() 59 | return err 60 | } 61 | go func() { 62 | scanner := bufio.NewScanner(reader) 63 | for scanner.Scan() { 64 | log.Printf("cmd> %s", scanner.Text()) 65 | } 66 | }() 67 | state, err := process.Wait() 68 | reader.Close() 69 | if err != nil { 70 | return err 71 | } 72 | if state.ExitCode() == 0 { 73 | return nil 74 | } 75 | log.Printf("Command error exit status: %d", state.ExitCode()) 76 | return windows.ERROR_GENERIC_COMMAND_FAILED 77 | } 78 | -------------------------------------------------------------------------------- /service.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package main 7 | 8 | import ( 9 | "bytes" 10 | "fmt" 11 | "log" 12 | "net" 13 | "os" 14 | "runtime" 15 | "time" 16 | 17 | "github.com/amnezia-vpn/amneziawg-go/conn" 18 | "github.com/amnezia-vpn/amneziawg-go/device" 19 | "github.com/amnezia-vpn/amneziawg-go/ipc" 20 | "github.com/amnezia-vpn/amneziawg-go/tun" 21 | "golang.org/x/sys/windows" 22 | "golang.org/x/sys/windows/svc" 23 | "golang.org/x/sys/windows/svc/mgr" 24 | 25 | "github.com/amnezia-vpn/amneziawg-windows/conf" 26 | "github.com/amnezia-vpn/amneziawg-windows/elevate" 27 | "github.com/amnezia-vpn/amneziawg-windows/ringlogger" 28 | "github.com/amnezia-vpn/amneziawg-windows/services" 29 | "github.com/amnezia-vpn/amneziawg-windows/version" 30 | ) 31 | 32 | type tunnelService struct { 33 | ConfString string 34 | TunnelName string 35 | } 36 | 37 | func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) { 38 | changes <- svc.Status{State: svc.StartPending} 39 | 40 | var dev *device.Device 41 | var uapi net.Listener 42 | var watcher *interfaceWatcher 43 | var nativeTun *tun.NativeTun 44 | var config *conf.Config 45 | var err error 46 | serviceError := services.ErrorSuccess 47 | 48 | defer func() { 49 | svcSpecificEC, exitCode = services.DetermineErrorCode(err, serviceError) 50 | logErr := services.CombineErrors(err, serviceError) 51 | if logErr != nil { 52 | log.Println(logErr) 53 | } 54 | changes <- svc.Status{State: svc.StopPending} 55 | 56 | stopIt := make(chan bool, 1) 57 | go func() { 58 | t := time.NewTicker(time.Second * 30) 59 | for { 60 | select { 61 | case <-t.C: 62 | t.Stop() 63 | buf := make([]byte, 1024) 64 | for { 65 | n := runtime.Stack(buf, true) 66 | if n < len(buf) { 67 | buf = buf[:n] 68 | break 69 | } 70 | buf = make([]byte, 2*len(buf)) 71 | } 72 | lines := bytes.Split(buf, []byte{'\n'}) 73 | log.Println("Failed to shutdown after 30 seconds. Probably dead locked. Printing stack and killing.") 74 | for _, line := range lines { 75 | if len(bytes.TrimSpace(line)) > 0 { 76 | log.Println(string(line)) 77 | } 78 | } 79 | os.Exit(777) 80 | return 81 | case <-stopIt: 82 | t.Stop() 83 | return 84 | } 85 | } 86 | }() 87 | 88 | if logErr == nil && dev != nil && config != nil { 89 | logErr = runScriptCommand(config.Interface.PreDown, config.Name) 90 | } 91 | if watcher != nil { 92 | watcher.Destroy() 93 | } 94 | if uapi != nil { 95 | uapi.Close() 96 | } 97 | if dev != nil { 98 | dev.Close() 99 | } 100 | if logErr == nil && dev != nil && config != nil { 101 | _ = runScriptCommand(config.Interface.PostDown, config.Name) 102 | } 103 | stopIt <- true 104 | log.Println("Shutting down") 105 | }() 106 | 107 | err = ringlogger.InitGlobalLogger("TUN") 108 | if err != nil { 109 | serviceError = services.ErrorRingloggerOpen 110 | return 111 | } 112 | 113 | config, err = conf.FromWgQuickWithUnknownEncoding(service.ConfString, service.TunnelName) 114 | if err != nil { 115 | serviceError = services.ErrorLoadConfiguration 116 | return 117 | } 118 | config.DeduplicateNetworkEntries() 119 | 120 | log.SetPrefix(fmt.Sprintf("[%s] ", config.Name)) 121 | 122 | log.Println("Starting", version.UserAgent()) 123 | 124 | if m, err := mgr.Connect(); err == nil { 125 | if lockStatus, err := m.LockStatus(); err == nil && lockStatus.IsLocked { 126 | /* If we don't do this, then the Wintun installation will block forever, because 127 | * installing a Wintun device starts a service too. Apparently at boot time, Windows 128 | * 8.1 locks the SCM for each service start, creating a deadlock if we don't announce 129 | * that we're running before starting additional services. 130 | */ 131 | log.Printf("SCM locked for %v by %s, marking service as started", lockStatus.Age, lockStatus.Owner) 132 | changes <- svc.Status{State: svc.Running} 133 | } 134 | m.Disconnect() 135 | } 136 | 137 | log.Println("Watching network interfaces") 138 | watcher, err = watchInterface() 139 | if err != nil { 140 | serviceError = services.ErrorSetNetConfig 141 | return 142 | } 143 | 144 | log.Println("Resolving DNS names") 145 | uapiConf, err := config.ToUAPI() 146 | if err != nil { 147 | serviceError = services.ErrorDNSLookup 148 | return 149 | } 150 | 151 | log.Println("Creating Wintun interface") 152 | var wintun tun.Device 153 | for i := 0; i < 5; i++ { 154 | if i > 0 { 155 | time.Sleep(time.Second) 156 | log.Printf("Retrying Wintun creation after failure because system just booted (T+%v): %v", windows.DurationSinceBoot(), err) 157 | } 158 | wintun, err = tun.CreateTUNWithRequestedGUID(config.Name, deterministicGUID(config), 0) 159 | if err == nil || windows.DurationSinceBoot() > time.Minute*4 { 160 | break 161 | } 162 | } 163 | if err != nil { 164 | serviceError = services.ErrorCreateWintun 165 | return 166 | } 167 | nativeTun = wintun.(*tun.NativeTun) 168 | wintunVersion, err := nativeTun.RunningVersion() 169 | if err != nil { 170 | log.Printf("Warning: unable to determine Wintun version: %v", err) 171 | } else { 172 | log.Printf("Using Wintun/%d.%d", (wintunVersion>>16)&0xffff, wintunVersion&0xffff) 173 | } 174 | 175 | err = runScriptCommand(config.Interface.PreUp, config.Name) 176 | if err != nil { 177 | serviceError = services.ErrorRunScript 178 | return 179 | } 180 | 181 | err = enableFirewall(config, nativeTun) 182 | if err != nil { 183 | serviceError = services.ErrorFirewall 184 | return 185 | } 186 | 187 | log.Println("Dropping privileges") 188 | err = elevate.DropAllPrivileges(true) 189 | if err != nil { 190 | serviceError = services.ErrorDropPrivileges 191 | return 192 | } 193 | 194 | log.Println("Creating interface instance") 195 | bind := conn.NewDefaultBind() 196 | dev = device.NewDevice(wintun, bind, &device.Logger{log.Printf, log.Printf}) 197 | 198 | log.Println("Setting interface configuration") 199 | uapi, err = ipc.UAPIListen(config.Name) 200 | if err != nil { 201 | serviceError = services.ErrorUAPIListen 202 | return 203 | } 204 | err = dev.IpcSet(uapiConf) 205 | if err != nil { 206 | serviceError = services.ErrorDeviceSetConfig 207 | return 208 | } 209 | 210 | log.Println("Bringing peers up") 211 | dev.Up() 212 | 213 | watcher.Configure(bind.(conn.BindSocketToInterface), config, nativeTun) 214 | 215 | log.Println("Listening for UAPI requests") 216 | go func() { 217 | for { 218 | conn, err := uapi.Accept() 219 | if err != nil { 220 | continue 221 | } 222 | go dev.IpcHandle(conn) 223 | } 224 | }() 225 | 226 | err = runScriptCommand(config.Interface.PostUp, config.Name) 227 | if err != nil { 228 | serviceError = services.ErrorRunScript 229 | return 230 | } 231 | 232 | changes <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown} 233 | log.Println("Startup complete") 234 | 235 | for { 236 | select { 237 | case c := <-r: 238 | switch c.Cmd { 239 | case svc.Stop, svc.Shutdown: 240 | return 241 | case svc.Interrogate: 242 | changes <- c.CurrentStatus 243 | default: 244 | log.Printf("Unexpected service control request #%d\n", c) 245 | } 246 | case <-dev.Wait(): 247 | return 248 | case e := <-watcher.errors: 249 | serviceError, err = e.serviceError, e.err 250 | return 251 | } 252 | } 253 | } 254 | 255 | func Run(confString string, tunnelName string) error { 256 | return svc.Run(tunnelName, &tunnelService{confString, tunnelName}) 257 | } 258 | -------------------------------------------------------------------------------- /services/errors.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package services 7 | 8 | import ( 9 | "fmt" 10 | "syscall" 11 | 12 | "golang.org/x/sys/windows" 13 | ) 14 | 15 | type Error uint32 16 | 17 | const ( 18 | ErrorSuccess Error = iota 19 | ErrorRingloggerOpen 20 | ErrorLoadConfiguration 21 | ErrorCreateWintun 22 | ErrorUAPIListen 23 | ErrorDNSLookup 24 | ErrorFirewall 25 | ErrorDeviceSetConfig 26 | ErrorBindSocketsToDefaultRoutes 27 | ErrorSetNetConfig 28 | ErrorDetermineExecutablePath 29 | ErrorTrackTunnels 30 | ErrorEnumerateSessions 31 | ErrorDropPrivileges 32 | ErrorRunScript 33 | ErrorWin32 34 | ) 35 | 36 | func (e Error) Error() string { 37 | switch e { 38 | case ErrorSuccess: 39 | return "No error" 40 | case ErrorRingloggerOpen: 41 | return "Unable to open log file" 42 | case ErrorDetermineExecutablePath: 43 | return "Unable to determine path of running executable" 44 | case ErrorLoadConfiguration: 45 | return "Unable to load configuration from path" 46 | case ErrorCreateWintun: 47 | return "Unable to create Wintun interface" 48 | case ErrorUAPIListen: 49 | return "Unable to listen on named pipe" 50 | case ErrorDNSLookup: 51 | return "Unable to resolve one or more DNS hostname endpoints" 52 | case ErrorFirewall: 53 | return "Unable to enable firewall rules" 54 | case ErrorDeviceSetConfig: 55 | return "Unable to set device configuration" 56 | case ErrorBindSocketsToDefaultRoutes: 57 | return "Unable to bind sockets to default route" 58 | case ErrorSetNetConfig: 59 | return "Unable to set interface addresses, routes, dns, and/or interface settings" 60 | case ErrorTrackTunnels: 61 | return "Unable to track existing tunnels" 62 | case ErrorEnumerateSessions: 63 | return "Unable to enumerate current sessions" 64 | case ErrorDropPrivileges: 65 | return "Unable to drop privileges" 66 | case ErrorRunScript: 67 | return "An error occurred while running a configuration script command" 68 | case ErrorWin32: 69 | return "An internal Windows error has occurred" 70 | default: 71 | return "An unknown error has occurred" 72 | } 73 | } 74 | 75 | func DetermineErrorCode(err error, serviceError Error) (bool, uint32) { 76 | if syserr, ok := err.(syscall.Errno); ok { 77 | return false, uint32(syserr) 78 | } else if serviceError != ErrorSuccess { 79 | return true, uint32(serviceError) 80 | } else { 81 | return false, windows.NO_ERROR 82 | } 83 | } 84 | 85 | func CombineErrors(err error, serviceError Error) error { 86 | if serviceError != ErrorSuccess { 87 | if err != nil { 88 | return fmt.Errorf("%v: %w", serviceError, err) 89 | } 90 | return serviceError 91 | } 92 | return err 93 | } 94 | -------------------------------------------------------------------------------- /services/names.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package services 7 | 8 | import ( 9 | "errors" 10 | 11 | "github.com/amnezia-vpn/amneziawg-windows/conf" 12 | ) 13 | 14 | func ServiceNameOfTunnel(tunnelName string) (string, error) { 15 | if !conf.TunnelNameIsValid(tunnelName) { 16 | return "", errors.New("Tunnel name is not valid") 17 | } 18 | return "AmneziaWGTunnel$" + tunnelName, nil 19 | } 20 | 21 | func PipePathOfTunnel(tunnelName string) (string, error) { 22 | if !conf.TunnelNameIsValid(tunnelName) { 23 | return "", errors.New("Tunnel name is not valid") 24 | } 25 | return `\\.\pipe\ProtectedPrefix\Administrators\AmneziaWG\` + tunnelName, nil 26 | } 27 | -------------------------------------------------------------------------------- /tunnel/addressconfig.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package tunnel 7 | 8 | import ( 9 | "bytes" 10 | "log" 11 | "net" 12 | "sort" 13 | 14 | "github.com/amnezia-vpn/amneziawg-go/tun" 15 | "golang.org/x/sys/windows" 16 | 17 | "github.com/amnezia-vpn/amneziawg-windows/conf" 18 | "github.com/amnezia-vpn/amneziawg-windows/tunnel/firewall" 19 | "github.com/amnezia-vpn/amneziawg-windows/tunnel/winipcfg" 20 | ) 21 | 22 | func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []net.IPNet) { 23 | if len(addresses) == 0 { 24 | return 25 | } 26 | includedInAddresses := func(a net.IPNet) bool { 27 | // TODO: this makes the whole algorithm O(n^2). But we can't stick net.IPNet in a Go hashmap. Bummer! 28 | for _, addr := range addresses { 29 | ip := addr.IP 30 | if ip4 := ip.To4(); ip4 != nil { 31 | ip = ip4 32 | } 33 | mA, _ := addr.Mask.Size() 34 | mB, _ := a.Mask.Size() 35 | if bytes.Equal(ip, a.IP) && mA == mB { 36 | return true 37 | } 38 | } 39 | return false 40 | } 41 | interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagDefault) 42 | if err != nil { 43 | return 44 | } 45 | for _, iface := range interfaces { 46 | if iface.OperStatus == winipcfg.IfOperStatusUp { 47 | continue 48 | } 49 | for address := iface.FirstUnicastAddress; address != nil; address = address.Next { 50 | ip := address.Address.IP() 51 | ipnet := net.IPNet{IP: ip, Mask: net.CIDRMask(int(address.OnLinkPrefixLength), 8*len(ip))} 52 | if includedInAddresses(ipnet) { 53 | log.Printf("Cleaning up stale address %s from interface ‘%s’", ipnet.String(), iface.FriendlyName()) 54 | iface.LUID.DeleteIPAddress(ipnet) 55 | } 56 | } 57 | } 58 | } 59 | 60 | func configureInterface(family winipcfg.AddressFamily, conf *conf.Config, tun *tun.NativeTun) error { 61 | luid := winipcfg.LUID(tun.LUID()) 62 | 63 | estimatedRouteCount := 0 64 | for _, peer := range conf.Peers { 65 | estimatedRouteCount += len(peer.AllowedIPs) 66 | } 67 | routes := make([]winipcfg.RouteData, 0, estimatedRouteCount) 68 | addresses := make([]net.IPNet, len(conf.Interface.Addresses)) 69 | var haveV4Address, haveV6Address bool 70 | for i, addr := range conf.Interface.Addresses { 71 | addresses[i] = addr.IPNet() 72 | if addr.Bits() == 32 { 73 | haveV4Address = true 74 | } else if addr.Bits() == 128 { 75 | haveV6Address = true 76 | } 77 | } 78 | 79 | foundDefault4 := false 80 | foundDefault6 := false 81 | for _, peer := range conf.Peers { 82 | for _, allowedip := range peer.AllowedIPs { 83 | allowedip.MaskSelf() 84 | if (allowedip.Bits() == 32 && !haveV4Address) || (allowedip.Bits() == 128 && !haveV6Address) { 85 | continue 86 | } 87 | route := winipcfg.RouteData{ 88 | Destination: allowedip.IPNet(), 89 | Metric: 0, 90 | } 91 | if allowedip.Bits() == 32 { 92 | if allowedip.Cidr == 0 { 93 | foundDefault4 = true 94 | } 95 | route.NextHop = net.IPv4zero 96 | } else if allowedip.Bits() == 128 { 97 | if allowedip.Cidr == 0 { 98 | foundDefault6 = true 99 | } 100 | route.NextHop = net.IPv6zero 101 | } 102 | routes = append(routes, route) 103 | } 104 | } 105 | 106 | err := luid.SetIPAddressesForFamily(family, addresses) 107 | if err == windows.ERROR_OBJECT_ALREADY_EXISTS { 108 | cleanupAddressesOnDisconnectedInterfaces(family, addresses) 109 | err = luid.SetIPAddressesForFamily(family, addresses) 110 | } 111 | if err != nil { 112 | return err 113 | } 114 | 115 | deduplicatedRoutes := make([]*winipcfg.RouteData, 0, len(routes)) 116 | sort.Slice(routes, func(i, j int) bool { 117 | if routes[i].Metric != routes[j].Metric { 118 | return routes[i].Metric < routes[j].Metric 119 | } 120 | if c := bytes.Compare(routes[i].NextHop, routes[j].NextHop); c != 0 { 121 | return c < 0 122 | } 123 | if c := bytes.Compare(routes[i].Destination.IP, routes[j].Destination.IP); c != 0 { 124 | return c < 0 125 | } 126 | if c := bytes.Compare(routes[i].Destination.Mask, routes[j].Destination.Mask); c != 0 { 127 | return c < 0 128 | } 129 | return false 130 | }) 131 | for i := 0; i < len(routes); i++ { 132 | if i > 0 && routes[i].Metric == routes[i-1].Metric && 133 | bytes.Equal(routes[i].NextHop, routes[i-1].NextHop) && 134 | bytes.Equal(routes[i].Destination.IP, routes[i-1].Destination.IP) && 135 | bytes.Equal(routes[i].Destination.Mask, routes[i-1].Destination.Mask) { 136 | continue 137 | } 138 | deduplicatedRoutes = append(deduplicatedRoutes, &routes[i]) 139 | } 140 | 141 | if !conf.Interface.TableOff { 142 | err = luid.SetRoutesForFamily(family, deduplicatedRoutes) 143 | if err != nil { 144 | return err 145 | } 146 | } 147 | 148 | ipif, err := luid.IPInterface(family) 149 | if err != nil { 150 | return err 151 | } 152 | if conf.Interface.MTU > 0 { 153 | ipif.NLMTU = uint32(conf.Interface.MTU) 154 | tun.ForceMTU(int(ipif.NLMTU)) 155 | } 156 | if family == windows.AF_INET { 157 | if foundDefault4 { 158 | ipif.UseAutomaticMetric = false 159 | ipif.Metric = 0 160 | } 161 | } else if family == windows.AF_INET6 { 162 | if foundDefault6 { 163 | ipif.UseAutomaticMetric = false 164 | ipif.Metric = 0 165 | } 166 | ipif.DadTransmits = 0 167 | ipif.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled 168 | } 169 | err = ipif.Set() 170 | if err != nil { 171 | return err 172 | } 173 | 174 | return luid.SetDNS(family, conf.Interface.DNS, conf.Interface.DNSSearch) 175 | } 176 | 177 | func enableFirewall(conf *conf.Config, tun *tun.NativeTun) error { 178 | doNotRestrict := true 179 | if len(conf.Peers) == 1 && !conf.Interface.TableOff { 180 | nextallowedip: 181 | for _, allowedip := range conf.Peers[0].AllowedIPs { 182 | if allowedip.Cidr == 0 { 183 | for _, b := range allowedip.IP { 184 | if b != 0 { 185 | continue nextallowedip 186 | } 187 | } 188 | doNotRestrict = false 189 | break 190 | } 191 | } 192 | } 193 | log.Println("Enabling firewall rules") 194 | return firewall.EnableFirewall(tun.LUID(), doNotRestrict, conf.Interface.DNS) 195 | } 196 | -------------------------------------------------------------------------------- /tunnel/defaultroutemonitor.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package tunnel 7 | 8 | import ( 9 | "log" 10 | "sync" 11 | "time" 12 | 13 | "golang.org/x/sys/windows" 14 | 15 | "github.com/amnezia-vpn/amneziawg-go/conn" 16 | "github.com/amnezia-vpn/amneziawg-go/tun" 17 | "github.com/amnezia-vpn/amneziawg-windows/tunnel/winipcfg" 18 | ) 19 | 20 | func bindSocketRoute(family winipcfg.AddressFamily, binder conn.BindSocketToInterface, ourLUID winipcfg.LUID, lastLUID *winipcfg.LUID, lastIndex *uint32, blackholeWhenLoop bool) error { 21 | r, err := winipcfg.GetIPForwardTable2(family) 22 | if err != nil { 23 | return err 24 | } 25 | lowestMetric := ^uint32(0) 26 | index := uint32(0) // Zero is "unspecified", which for IP_UNICAST_IF resets the value, which is what we want. 27 | luid := winipcfg.LUID(0) // Hopefully luid zero is unspecified, but hard to find docs saying so. 28 | for i := range r { 29 | if r[i].DestinationPrefix.PrefixLength != 0 || r[i].InterfaceLUID == ourLUID { 30 | continue 31 | } 32 | ifrow, err := r[i].InterfaceLUID.Interface() 33 | if err != nil || ifrow.OperStatus != winipcfg.IfOperStatusUp { 34 | continue 35 | } 36 | 37 | iface, err := r[i].InterfaceLUID.IPInterface(family) 38 | if err != nil { 39 | continue 40 | } 41 | 42 | if r[i].Metric+iface.Metric < lowestMetric { 43 | lowestMetric = r[i].Metric + iface.Metric 44 | index = r[i].InterfaceIndex 45 | luid = r[i].InterfaceLUID 46 | } 47 | } 48 | if luid == *lastLUID && index == *lastIndex { 49 | return nil 50 | } 51 | *lastLUID = luid 52 | *lastIndex = index 53 | blackhole := blackholeWhenLoop && index == 0 54 | if family == windows.AF_INET { 55 | log.Printf("Binding v4 socket to interface %d (blackhole=%v)", index, blackhole) 56 | return binder.BindSocketToInterface4(index, blackhole) 57 | } else if family == windows.AF_INET6 { 58 | log.Printf("Binding v6 socket to interface %d (blackhole=%v)", index, blackhole) 59 | return binder.BindSocketToInterface6(index, blackhole) 60 | } 61 | return nil 62 | } 63 | 64 | func monitorDefaultRoutes(family winipcfg.AddressFamily, binder conn.BindSocketToInterface, autoMTU bool, blackholeWhenLoop bool, tun *tun.NativeTun) ([]winipcfg.ChangeCallback, error) { 65 | var minMTU uint32 66 | if family == windows.AF_INET { 67 | minMTU = 576 68 | } else if family == windows.AF_INET6 { 69 | minMTU = 1280 70 | } 71 | ourLUID := winipcfg.LUID(tun.LUID()) 72 | lastLUID := winipcfg.LUID(0) 73 | lastIndex := ^uint32(0) 74 | lastMTU := uint32(0) 75 | doIt := func() error { 76 | err := bindSocketRoute(family, binder, ourLUID, &lastLUID, &lastIndex, blackholeWhenLoop) 77 | if err != nil { 78 | return err 79 | } 80 | if !autoMTU { 81 | return nil 82 | } 83 | mtu := uint32(0) 84 | if lastLUID != 0 { 85 | iface, err := lastLUID.Interface() 86 | if err != nil { 87 | return err 88 | } 89 | if iface.MTU > 0 { 90 | mtu = iface.MTU 91 | } 92 | } 93 | if mtu > 0 && lastMTU != mtu { 94 | iface, err := ourLUID.IPInterface(family) 95 | if err != nil { 96 | return err 97 | } 98 | iface.NLMTU = mtu - 80 99 | if iface.NLMTU < minMTU { 100 | iface.NLMTU = minMTU 101 | } 102 | err = iface.Set() 103 | if err != nil { 104 | return err 105 | } 106 | tun.ForceMTU(int(iface.NLMTU)) // TODO: having one MTU for both v4 and v6 kind of breaks the windows model, so right now this just gets the second one which is... bad. 107 | lastMTU = mtu 108 | } 109 | return nil 110 | } 111 | err := doIt() 112 | if err != nil { 113 | return nil, err 114 | } 115 | 116 | firstBurst := time.Time{} 117 | burstMutex := sync.Mutex{} 118 | burstTimer := time.AfterFunc(time.Hour*200, func() { 119 | burstMutex.Lock() 120 | firstBurst = time.Time{} 121 | doIt() 122 | burstMutex.Unlock() 123 | }) 124 | burstTimer.Stop() 125 | bump := func() { 126 | burstMutex.Lock() 127 | burstTimer.Reset(time.Millisecond * 150) 128 | if firstBurst.IsZero() { 129 | firstBurst = time.Now() 130 | } else if time.Since(firstBurst) > time.Second*2 { 131 | firstBurst = time.Time{} 132 | burstTimer.Stop() 133 | doIt() 134 | } 135 | burstMutex.Unlock() 136 | } 137 | 138 | cbr, err := winipcfg.RegisterRouteChangeCallback(func(notificationType winipcfg.MibNotificationType, route *winipcfg.MibIPforwardRow2) { 139 | if route != nil && route.DestinationPrefix.PrefixLength == 0 { 140 | bump() 141 | } 142 | }) 143 | if err != nil { 144 | return nil, err 145 | } 146 | cbi, err := winipcfg.RegisterInterfaceChangeCallback(func(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) { 147 | if notificationType == winipcfg.MibParameterNotification { 148 | bump() 149 | } 150 | }) 151 | if err != nil { 152 | cbr.Unregister() 153 | return nil, err 154 | } 155 | return []winipcfg.ChangeCallback{cbr, cbi}, nil 156 | } 157 | -------------------------------------------------------------------------------- /tunnel/deterministicguid.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package tunnel 7 | 8 | import ( 9 | "bytes" 10 | "encoding/binary" 11 | "sort" 12 | "unsafe" 13 | 14 | "golang.org/x/crypto/blake2s" 15 | "golang.org/x/sys/windows" 16 | "golang.org/x/text/unicode/norm" 17 | 18 | "github.com/amnezia-vpn/amneziawg-windows/conf" 19 | ) 20 | 21 | const deterministicGUIDLabel = "Deterministic WireGuard Windows GUID v1 jason@zx2c4.com" 22 | const fixedGUIDLabel = "Fixed WireGuard Windows GUID v1 jason@zx2c4.com" 23 | 24 | // Escape hatch for external consumers, not us. 25 | var UseFixedGUIDInsteadOfDeterministic = false 26 | 27 | /* All peer public keys and allowed ips are sorted. Length/number fields are 28 | * little endian 32-bit. Hash input is: 29 | * 30 | * label || len(interface name) || interface name || 31 | * interface public key || number of peers || 32 | * peer public key || number of peer allowed ips || 33 | * len(allowed ip string) || allowed ip/cidr in canonical string notation || 34 | * len(allowed ip string) || allowed ip/cidr in canonical string notation || 35 | * len(allowed ip string) || allowed ip/cidr in canonical string notation || 36 | * ... 37 | * peer public key || number of peer allowed ips || 38 | * len(allowed ip string) || allowed ip/cidr in canonical string notation || 39 | * len(allowed ip string) || allowed ip/cidr in canonical string notation || 40 | * len(allowed ip string) || allowed ip/cidr in canonical string notation || 41 | * ... 42 | * ... 43 | */ 44 | 45 | func deterministicGUID(c *conf.Config) *windows.GUID { 46 | b2, _ := blake2s.New256(nil) 47 | if !UseFixedGUIDInsteadOfDeterministic { 48 | b2.Write([]byte(deterministicGUIDLabel)) 49 | } else { 50 | b2.Write([]byte(fixedGUIDLabel)) 51 | } 52 | b2Number := func(i int) { 53 | if uint(i) > uint(^uint32(0)) { 54 | panic("length out of bounds") 55 | } 56 | var bytes [4]byte 57 | binary.LittleEndian.PutUint32(bytes[:], uint32(i)) 58 | b2.Write(bytes[:]) 59 | } 60 | b2String := func(s string) { 61 | bytes := []byte(s) 62 | bytes = norm.NFC.Bytes(bytes) 63 | b2Number(len(bytes)) 64 | b2.Write(bytes) 65 | } 66 | b2Key := func(k *conf.Key) { 67 | b2.Write(k[:]) 68 | } 69 | 70 | b2String(c.Name) 71 | if !UseFixedGUIDInsteadOfDeterministic { 72 | b2Key(c.Interface.PrivateKey.Public()) 73 | b2Number(len(c.Peers)) 74 | sortedPeers := c.Peers 75 | sort.Slice(sortedPeers, func(i, j int) bool { 76 | return bytes.Compare(sortedPeers[i].PublicKey[:], sortedPeers[j].PublicKey[:]) < 0 77 | }) 78 | for _, peer := range sortedPeers { 79 | b2Key(&peer.PublicKey) 80 | b2Number(len(peer.AllowedIPs)) 81 | sortedAllowedIPs := peer.AllowedIPs 82 | sort.Slice(sortedAllowedIPs, func(i, j int) bool { 83 | if bi, bj := sortedAllowedIPs[i].Bits(), sortedAllowedIPs[j].Bits(); bi != bj { 84 | return bi < bj 85 | } 86 | if sortedAllowedIPs[i].Cidr != sortedAllowedIPs[j].Cidr { 87 | return sortedAllowedIPs[i].Cidr < sortedAllowedIPs[j].Cidr 88 | } 89 | return bytes.Compare(sortedAllowedIPs[i].IP[:], sortedAllowedIPs[j].IP[:]) < 0 90 | }) 91 | for _, allowedip := range sortedAllowedIPs { 92 | b2String(allowedip.String()) 93 | } 94 | } 95 | } 96 | return (*windows.GUID)(unsafe.Pointer(&b2.Sum(nil)[0])) 97 | } 98 | -------------------------------------------------------------------------------- /tunnel/firewall/blocker.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package firewall 7 | 8 | import ( 9 | "errors" 10 | "net" 11 | "unsafe" 12 | 13 | "golang.org/x/sys/windows" 14 | ) 15 | 16 | type wfpObjectInstaller func(uintptr) error 17 | 18 | // 19 | // Fundamental WireGuard specific WFP objects. 20 | // 21 | type baseObjects struct { 22 | provider windows.GUID 23 | filters windows.GUID 24 | } 25 | 26 | var wfpSession uintptr 27 | 28 | func createWfpSession() (uintptr, error) { 29 | sessionDisplayData, err := createWtFwpmDisplayData0("WireGuard", "WireGuard dynamic session") 30 | if err != nil { 31 | return 0, wrapErr(err) 32 | } 33 | 34 | session := wtFwpmSession0{ 35 | displayData: *sessionDisplayData, 36 | flags: cFWPM_SESSION_FLAG_DYNAMIC, 37 | txnWaitTimeoutInMSec: windows.INFINITE, 38 | } 39 | 40 | sessionHandle := uintptr(0) 41 | 42 | err = fwpmEngineOpen0(nil, cRPC_C_AUTHN_WINNT, nil, &session, unsafe.Pointer(&sessionHandle)) 43 | if err != nil { 44 | return 0, wrapErr(err) 45 | } 46 | 47 | return sessionHandle, nil 48 | } 49 | 50 | func registerBaseObjects(session uintptr) (*baseObjects, error) { 51 | bo := &baseObjects{} 52 | var err error 53 | bo.provider, err = windows.GenerateGUID() 54 | if err != nil { 55 | return nil, wrapErr(err) 56 | } 57 | bo.filters, err = windows.GenerateGUID() 58 | if err != nil { 59 | return nil, wrapErr(err) 60 | } 61 | 62 | // 63 | // Register provider. 64 | // 65 | { 66 | displayData, err := createWtFwpmDisplayData0("WireGuard", "WireGuard provider") 67 | if err != nil { 68 | return nil, wrapErr(err) 69 | } 70 | provider := wtFwpmProvider0{ 71 | providerKey: bo.provider, 72 | displayData: *displayData, 73 | } 74 | err = fwpmProviderAdd0(session, &provider, 0) 75 | if err != nil { 76 | // TODO: cleanup entire call chain of these if failure? 77 | return nil, wrapErr(err) 78 | } 79 | } 80 | 81 | // 82 | // Register filters sublayer. 83 | // 84 | { 85 | displayData, err := createWtFwpmDisplayData0("WireGuard filters", "Permissive and blocking filters") 86 | if err != nil { 87 | return nil, wrapErr(err) 88 | } 89 | sublayer := wtFwpmSublayer0{ 90 | subLayerKey: bo.filters, 91 | displayData: *displayData, 92 | providerKey: &bo.provider, 93 | weight: ^uint16(0), 94 | } 95 | err = fwpmSubLayerAdd0(session, &sublayer, 0) 96 | if err != nil { 97 | return nil, wrapErr(err) 98 | } 99 | } 100 | 101 | return bo, nil 102 | } 103 | 104 | func EnableFirewall(luid uint64, doNotRestrict bool, restrictToDNSServers []net.IP) error { 105 | if wfpSession != 0 { 106 | return errors.New("The firewall has already been enabled") 107 | } 108 | 109 | session, err := createWfpSession() 110 | if err != nil { 111 | return wrapErr(err) 112 | } 113 | 114 | objectInstaller := func(session uintptr) error { 115 | baseObjects, err := registerBaseObjects(session) 116 | if err != nil { 117 | return wrapErr(err) 118 | } 119 | 120 | err = permitWireGuardService(session, baseObjects, 15) 121 | if err != nil { 122 | return wrapErr(err) 123 | } 124 | 125 | if !doNotRestrict { 126 | if len(restrictToDNSServers) > 0 { 127 | err = blockDNS(restrictToDNSServers, session, baseObjects, 15, 14) 128 | if err != nil { 129 | return wrapErr(err) 130 | } 131 | } 132 | 133 | err = permitLoopback(session, baseObjects, 13) 134 | if err != nil { 135 | return wrapErr(err) 136 | } 137 | 138 | err = permitTunInterface(session, baseObjects, 12, luid) 139 | if err != nil { 140 | return wrapErr(err) 141 | } 142 | 143 | err = permitDHCPIPv4(session, baseObjects, 12) 144 | if err != nil { 145 | return wrapErr(err) 146 | } 147 | 148 | err = permitDHCPIPv6(session, baseObjects, 12) 149 | if err != nil { 150 | return wrapErr(err) 151 | } 152 | 153 | err = permitNdp(session, baseObjects, 12) 154 | if err != nil { 155 | return wrapErr(err) 156 | } 157 | 158 | /* TODO: actually evaluate if this does anything and if we need this. It's layer 2; our other rules are layer 3. 159 | * In other words, if somebody complains, try enabling it. For now, keep it off. 160 | err = permitHyperV(session, baseObjects, 12) 161 | if err != nil { 162 | return wrapErr(err) 163 | } 164 | */ 165 | 166 | err = blockAll(session, baseObjects, 0) 167 | if err != nil { 168 | return wrapErr(err) 169 | } 170 | } 171 | 172 | return nil 173 | } 174 | 175 | err = runTransaction(session, objectInstaller) 176 | if err != nil { 177 | fwpmEngineClose0(session) 178 | return wrapErr(err) 179 | } 180 | 181 | wfpSession = session 182 | return nil 183 | } 184 | 185 | func DisableFirewall() { 186 | if wfpSession != 0 { 187 | fwpmEngineClose0(wfpSession) 188 | wfpSession = 0 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /tunnel/firewall/helpers.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package firewall 7 | 8 | import ( 9 | "fmt" 10 | "os" 11 | "runtime" 12 | "syscall" 13 | "unsafe" 14 | 15 | "golang.org/x/sys/windows" 16 | ) 17 | 18 | func runTransaction(session uintptr, operation wfpObjectInstaller) error { 19 | err := fwpmTransactionBegin0(session, 0) 20 | if err != nil { 21 | return wrapErr(err) 22 | } 23 | 24 | err = operation(session) 25 | if err != nil { 26 | fwpmTransactionAbort0(session) 27 | return wrapErr(err) 28 | } 29 | 30 | err = fwpmTransactionCommit0(session) 31 | if err != nil { 32 | fwpmTransactionAbort0(session) 33 | return wrapErr(err) 34 | } 35 | 36 | return nil 37 | } 38 | 39 | func createWtFwpmDisplayData0(name, description string) (*wtFwpmDisplayData0, error) { 40 | namePtr, err := windows.UTF16PtrFromString(name) 41 | if err != nil { 42 | return nil, wrapErr(err) 43 | } 44 | 45 | descriptionPtr, err := windows.UTF16PtrFromString(description) 46 | if err != nil { 47 | return nil, wrapErr(err) 48 | } 49 | 50 | return &wtFwpmDisplayData0{ 51 | name: namePtr, 52 | description: descriptionPtr, 53 | }, nil 54 | } 55 | 56 | func filterWeight(weight uint8) wtFwpValue0 { 57 | return wtFwpValue0{ 58 | _type: cFWP_UINT8, 59 | value: uintptr(weight), 60 | } 61 | } 62 | 63 | func wrapErr(err error) error { 64 | if _, ok := err.(syscall.Errno); !ok { 65 | return err 66 | } 67 | _, file, line, ok := runtime.Caller(1) 68 | if !ok { 69 | return fmt.Errorf("Firewall error at unknown location: %w", err) 70 | } 71 | return fmt.Errorf("Firewall error at %s:%d: %w", file, line, err) 72 | } 73 | 74 | func getCurrentProcessSecurityDescriptor() (*windows.SECURITY_DESCRIPTOR, error) { 75 | var processToken windows.Token 76 | err := windows.OpenProcessToken(windows.CurrentProcess(), windows.TOKEN_QUERY, &processToken) 77 | if err != nil { 78 | return nil, wrapErr(err) 79 | } 80 | defer processToken.Close() 81 | gs, err := processToken.GetTokenGroups() 82 | if err != nil { 83 | return nil, wrapErr(err) 84 | } 85 | var sid *windows.SID 86 | for _, g := range gs.AllGroups() { 87 | if g.Attributes != windows.SE_GROUP_ENABLED|windows.SE_GROUP_ENABLED_BY_DEFAULT|windows.SE_GROUP_OWNER { 88 | continue 89 | } 90 | // We could be checking != 6, but hopefully Microsoft will update 91 | // RtlCreateServiceSid to use SHA2, which will then likely bump 92 | // this up. So instead just roll with a minimum. 93 | if !g.Sid.IsValid() || g.Sid.IdentifierAuthority() != windows.SECURITY_NT_AUTHORITY || g.Sid.SubAuthorityCount() < 6 || g.Sid.SubAuthority(0) != 80 { 94 | continue 95 | } 96 | sid = g.Sid 97 | break 98 | } 99 | if sid == nil { 100 | return nil, wrapErr(windows.ERROR_NO_SUCH_GROUP) 101 | } 102 | 103 | access := []windows.EXPLICIT_ACCESS{{ 104 | AccessPermissions: cFWP_ACTRL_MATCH_FILTER, 105 | AccessMode: windows.GRANT_ACCESS, 106 | Trustee: windows.TRUSTEE{ 107 | TrusteeForm: windows.TRUSTEE_IS_SID, 108 | TrusteeType: windows.TRUSTEE_IS_GROUP, 109 | TrusteeValue: windows.TrusteeValueFromSID(sid), 110 | }, 111 | }} 112 | dacl, err := windows.ACLFromEntries(access, nil) 113 | if err != nil { 114 | return nil, wrapErr(err) 115 | } 116 | sd, err := windows.NewSecurityDescriptor() 117 | if err != nil { 118 | return nil, wrapErr(err) 119 | } 120 | err = sd.SetDACL(dacl, true, false) 121 | if err != nil { 122 | return nil, wrapErr(err) 123 | } 124 | sd, err = sd.ToSelfRelative() 125 | if err != nil { 126 | return nil, wrapErr(err) 127 | } 128 | return sd, nil 129 | } 130 | 131 | func getCurrentProcessAppID() (*wtFwpByteBlob, error) { 132 | currentFile, err := os.Executable() 133 | if err != nil { 134 | return nil, wrapErr(err) 135 | } 136 | 137 | curFilePtr, err := windows.UTF16PtrFromString(currentFile) 138 | if err != nil { 139 | return nil, wrapErr(err) 140 | } 141 | 142 | var appID *wtFwpByteBlob 143 | err = fwpmGetAppIdFromFileName0(curFilePtr, unsafe.Pointer(&appID)) 144 | if err != nil { 145 | return nil, wrapErr(err) 146 | } 147 | return appID, nil 148 | } 149 | -------------------------------------------------------------------------------- /tunnel/firewall/mksyscall.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package firewall 7 | 8 | //go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go syscall_windows.go 9 | -------------------------------------------------------------------------------- /tunnel/firewall/syscall_windows.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package firewall 7 | 8 | // https://docs.microsoft.com/en-us/windows/desktop/api/fwpmu/nf-fwpmu-fwpmengineopen0 9 | //sys fwpmEngineOpen0(serverName *uint16, authnService wtRpcCAuthN, authIdentity *uintptr, session *wtFwpmSession0, engineHandle unsafe.Pointer) (err error) [failretval!=0] = fwpuclnt.FwpmEngineOpen0 10 | 11 | // https://docs.microsoft.com/en-us/windows/desktop/api/fwpmu/nf-fwpmu-fwpmengineclose0 12 | //sys fwpmEngineClose0(engineHandle uintptr) (err error) [failretval!=0] = fwpuclnt.FwpmEngineClose0 13 | 14 | // https://docs.microsoft.com/en-us/windows/desktop/api/fwpmu/nf-fwpmu-fwpmsublayeradd0 15 | //sys fwpmSubLayerAdd0(engineHandle uintptr, subLayer *wtFwpmSublayer0, sd uintptr) (err error) [failretval!=0] = fwpuclnt.FwpmSubLayerAdd0 16 | 17 | // https://docs.microsoft.com/en-us/windows/desktop/api/fwpmu/nf-fwpmu-fwpmgetappidfromfilename0 18 | //sys fwpmGetAppIdFromFileName0(fileName *uint16, appID unsafe.Pointer) (err error) [failretval!=0] = fwpuclnt.FwpmGetAppIdFromFileName0 19 | 20 | // https://docs.microsoft.com/en-us/windows/desktop/api/fwpmu/nf-fwpmu-fwpmfreememory0 21 | //sys fwpmFreeMemory0(p unsafe.Pointer) = fwpuclnt.FwpmFreeMemory0 22 | 23 | // https://docs.microsoft.com/en-us/windows/desktop/api/fwpmu/nf-fwpmu-fwpmfilteradd0 24 | //sys fwpmFilterAdd0(engineHandle uintptr, filter *wtFwpmFilter0, sd uintptr, id *uint64) (err error) [failretval!=0] = fwpuclnt.FwpmFilterAdd0 25 | 26 | // https://docs.microsoft.com/en-us/windows/desktop/api/Fwpmu/nf-fwpmu-fwpmtransactionbegin0 27 | //sys fwpmTransactionBegin0(engineHandle uintptr, flags uint32) (err error) [failretval!=0] = fwpuclnt.FwpmTransactionBegin0 28 | 29 | // https://docs.microsoft.com/en-us/windows/desktop/api/fwpmu/nf-fwpmu-fwpmtransactioncommit0 30 | //sys fwpmTransactionCommit0(engineHandle uintptr) (err error) [failretval!=0] = fwpuclnt.FwpmTransactionCommit0 31 | 32 | // https://docs.microsoft.com/en-us/windows/desktop/api/fwpmu/nf-fwpmu-fwpmtransactionabort0 33 | //sys fwpmTransactionAbort0(engineHandle uintptr) (err error) [failretval!=0] = fwpuclnt.FwpmTransactionAbort0 34 | 35 | // https://docs.microsoft.com/en-us/windows/desktop/api/fwpmu/nf-fwpmu-fwpmprovideradd0 36 | //sys fwpmProviderAdd0(engineHandle uintptr, provider *wtFwpmProvider0, sd uintptr) (err error) [failretval!=0] = fwpuclnt.FwpmProviderAdd0 37 | -------------------------------------------------------------------------------- /tunnel/firewall/types_windows_32.go: -------------------------------------------------------------------------------- 1 | //go:build 386 || arm 2 | // +build 386 arm 3 | 4 | /* SPDX-License-Identifier: MIT 5 | * 6 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 7 | */ 8 | 9 | package firewall 10 | 11 | import "golang.org/x/sys/windows" 12 | 13 | const ( 14 | wtFwpByteBlob_Size = 8 15 | wtFwpByteBlob_data_Offset = 4 16 | 17 | wtFwpConditionValue0_Size = 8 18 | wtFwpConditionValue0_uint8_Offset = 4 19 | 20 | wtFwpmDisplayData0_Size = 8 21 | wtFwpmDisplayData0_description_Offset = 4 22 | 23 | wtFwpmFilter0_Size = 152 24 | wtFwpmFilter0_displayData_Offset = 16 25 | wtFwpmFilter0_flags_Offset = 24 26 | wtFwpmFilter0_providerKey_Offset = 28 27 | wtFwpmFilter0_providerData_Offset = 32 28 | wtFwpmFilter0_layerKey_Offset = 40 29 | wtFwpmFilter0_subLayerKey_Offset = 56 30 | wtFwpmFilter0_weight_Offset = 72 31 | wtFwpmFilter0_numFilterConditions_Offset = 80 32 | wtFwpmFilter0_filterCondition_Offset = 84 33 | wtFwpmFilter0_action_Offset = 88 34 | wtFwpmFilter0_providerContextKey_Offset = 112 35 | wtFwpmFilter0_reserved_Offset = 128 36 | wtFwpmFilter0_filterID_Offset = 136 37 | wtFwpmFilter0_effectiveWeight_Offset = 144 38 | 39 | wtFwpmFilterCondition0_Size = 28 40 | wtFwpmFilterCondition0_matchType_Offset = 16 41 | wtFwpmFilterCondition0_conditionValue_Offset = 20 42 | 43 | wtFwpmSession0_Size = 48 44 | wtFwpmSession0_displayData_Offset = 16 45 | wtFwpmSession0_flags_Offset = 24 46 | wtFwpmSession0_txnWaitTimeoutInMSec_Offset = 28 47 | wtFwpmSession0_processId_Offset = 32 48 | wtFwpmSession0_sid_Offset = 36 49 | wtFwpmSession0_username_Offset = 40 50 | wtFwpmSession0_kernelMode_Offset = 44 51 | 52 | wtFwpmSublayer0_Size = 44 53 | wtFwpmSublayer0_displayData_Offset = 16 54 | wtFwpmSublayer0_flags_Offset = 24 55 | wtFwpmSublayer0_providerKey_Offset = 28 56 | wtFwpmSublayer0_providerData_Offset = 32 57 | wtFwpmSublayer0_weight_Offset = 40 58 | 59 | wtFwpProvider0_Size = 40 60 | wtFwpProvider0_displayData_Offset = 16 61 | wtFwpProvider0_flags_Offset = 24 62 | wtFwpProvider0_providerData_Offset = 28 63 | wtFwpProvider0_serviceName_Offset = 36 64 | 65 | wtFwpTokenInformation_Size = 16 66 | 67 | wtFwpValue0_Size = 8 68 | wtFwpValue0_value_Offset = 4 69 | ) 70 | 71 | // FWPM_FILTER0 defined in fwpmtypes.h 72 | // (https://docs.microsoft.com/en-us/windows/desktop/api/fwpmtypes/ns-fwpmtypes-fwpm_filter0). 73 | type wtFwpmFilter0 struct { 74 | filterKey windows.GUID // Windows type: GUID 75 | displayData wtFwpmDisplayData0 76 | flags wtFwpmFilterFlags 77 | providerKey *windows.GUID // Windows type: *GUID 78 | providerData wtFwpByteBlob 79 | layerKey windows.GUID // Windows type: GUID 80 | subLayerKey windows.GUID // Windows type: GUID 81 | weight wtFwpValue0 82 | numFilterConditions uint32 83 | filterCondition *wtFwpmFilterCondition0 84 | action wtFwpmAction0 85 | offset1 [4]byte // Layout correction field 86 | providerContextKey windows.GUID // Windows type: GUID 87 | reserved *windows.GUID // Windows type: *GUID 88 | offset2 [4]byte // Layout correction field 89 | filterID uint64 90 | effectiveWeight wtFwpValue0 91 | } 92 | -------------------------------------------------------------------------------- /tunnel/firewall/types_windows_64.go: -------------------------------------------------------------------------------- 1 | //go:build amd64 || arm64 2 | // +build amd64 arm64 3 | 4 | /* SPDX-License-Identifier: MIT 5 | * 6 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 7 | */ 8 | 9 | package firewall 10 | 11 | import "golang.org/x/sys/windows" 12 | 13 | const ( 14 | wtFwpByteBlob_Size = 16 15 | wtFwpByteBlob_data_Offset = 8 16 | 17 | wtFwpConditionValue0_Size = 16 18 | wtFwpConditionValue0_uint8_Offset = 8 19 | 20 | wtFwpmDisplayData0_Size = 16 21 | wtFwpmDisplayData0_description_Offset = 8 22 | 23 | wtFwpmFilter0_Size = 200 24 | wtFwpmFilter0_displayData_Offset = 16 25 | wtFwpmFilter0_flags_Offset = 32 26 | wtFwpmFilter0_providerKey_Offset = 40 27 | wtFwpmFilter0_providerData_Offset = 48 28 | wtFwpmFilter0_layerKey_Offset = 64 29 | wtFwpmFilter0_subLayerKey_Offset = 80 30 | wtFwpmFilter0_weight_Offset = 96 31 | wtFwpmFilter0_numFilterConditions_Offset = 112 32 | wtFwpmFilter0_filterCondition_Offset = 120 33 | wtFwpmFilter0_action_Offset = 128 34 | wtFwpmFilter0_providerContextKey_Offset = 152 35 | wtFwpmFilter0_reserved_Offset = 168 36 | wtFwpmFilter0_filterID_Offset = 176 37 | wtFwpmFilter0_effectiveWeight_Offset = 184 38 | 39 | wtFwpmFilterCondition0_Size = 40 40 | wtFwpmFilterCondition0_matchType_Offset = 16 41 | wtFwpmFilterCondition0_conditionValue_Offset = 24 42 | 43 | wtFwpmSession0_Size = 72 44 | wtFwpmSession0_displayData_Offset = 16 45 | wtFwpmSession0_flags_Offset = 32 46 | wtFwpmSession0_txnWaitTimeoutInMSec_Offset = 36 47 | wtFwpmSession0_processId_Offset = 40 48 | wtFwpmSession0_sid_Offset = 48 49 | wtFwpmSession0_username_Offset = 56 50 | wtFwpmSession0_kernelMode_Offset = 64 51 | 52 | wtFwpmSublayer0_Size = 72 53 | wtFwpmSublayer0_displayData_Offset = 16 54 | wtFwpmSublayer0_flags_Offset = 32 55 | wtFwpmSublayer0_providerKey_Offset = 40 56 | wtFwpmSublayer0_providerData_Offset = 48 57 | wtFwpmSublayer0_weight_Offset = 64 58 | 59 | wtFwpProvider0_Size = 64 60 | wtFwpProvider0_displayData_Offset = 16 61 | wtFwpProvider0_flags_Offset = 32 62 | wtFwpProvider0_providerData_Offset = 40 63 | wtFwpProvider0_serviceName_Offset = 56 64 | 65 | wtFwpValue0_Size = 16 66 | wtFwpValue0_value_Offset = 8 67 | ) 68 | 69 | // FWPM_FILTER0 defined in fwpmtypes.h 70 | // (https://docs.microsoft.com/en-us/windows/desktop/api/fwpmtypes/ns-fwpmtypes-fwpm_filter0). 71 | type wtFwpmFilter0 struct { 72 | filterKey windows.GUID // Windows type: GUID 73 | displayData wtFwpmDisplayData0 74 | flags wtFwpmFilterFlags // Windows type: UINT32 75 | providerKey *windows.GUID // Windows type: *GUID 76 | providerData wtFwpByteBlob 77 | layerKey windows.GUID // Windows type: GUID 78 | subLayerKey windows.GUID // Windows type: GUID 79 | weight wtFwpValue0 80 | numFilterConditions uint32 81 | filterCondition *wtFwpmFilterCondition0 82 | action wtFwpmAction0 83 | offset1 [4]byte // Layout correction field 84 | providerContextKey windows.GUID // Windows type: GUID 85 | reserved *windows.GUID // Windows type: *GUID 86 | filterID uint64 87 | effectiveWeight wtFwpValue0 88 | } 89 | -------------------------------------------------------------------------------- /tunnel/firewall/zsyscall_windows.go: -------------------------------------------------------------------------------- 1 | // Code generated by 'go generate'; DO NOT EDIT. 2 | 3 | package firewall 4 | 5 | import ( 6 | "syscall" 7 | "unsafe" 8 | 9 | "golang.org/x/sys/windows" 10 | ) 11 | 12 | var _ unsafe.Pointer 13 | 14 | // Do the interface allocations only once for common 15 | // Errno values. 16 | const ( 17 | errnoERROR_IO_PENDING = 997 18 | ) 19 | 20 | var ( 21 | errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) 22 | errERROR_EINVAL error = syscall.EINVAL 23 | ) 24 | 25 | // errnoErr returns common boxed Errno values, to prevent 26 | // allocations at runtime. 27 | func errnoErr(e syscall.Errno) error { 28 | switch e { 29 | case 0: 30 | return errERROR_EINVAL 31 | case errnoERROR_IO_PENDING: 32 | return errERROR_IO_PENDING 33 | } 34 | // TODO: add more here, after collecting data on the common 35 | // error values see on Windows. (perhaps when running 36 | // all.bat?) 37 | return e 38 | } 39 | 40 | var ( 41 | modfwpuclnt = windows.NewLazySystemDLL("fwpuclnt.dll") 42 | 43 | procFwpmEngineClose0 = modfwpuclnt.NewProc("FwpmEngineClose0") 44 | procFwpmEngineOpen0 = modfwpuclnt.NewProc("FwpmEngineOpen0") 45 | procFwpmFilterAdd0 = modfwpuclnt.NewProc("FwpmFilterAdd0") 46 | procFwpmFreeMemory0 = modfwpuclnt.NewProc("FwpmFreeMemory0") 47 | procFwpmGetAppIdFromFileName0 = modfwpuclnt.NewProc("FwpmGetAppIdFromFileName0") 48 | procFwpmProviderAdd0 = modfwpuclnt.NewProc("FwpmProviderAdd0") 49 | procFwpmSubLayerAdd0 = modfwpuclnt.NewProc("FwpmSubLayerAdd0") 50 | procFwpmTransactionAbort0 = modfwpuclnt.NewProc("FwpmTransactionAbort0") 51 | procFwpmTransactionBegin0 = modfwpuclnt.NewProc("FwpmTransactionBegin0") 52 | procFwpmTransactionCommit0 = modfwpuclnt.NewProc("FwpmTransactionCommit0") 53 | ) 54 | 55 | func fwpmEngineClose0(engineHandle uintptr) (err error) { 56 | r1, _, e1 := syscall.Syscall(procFwpmEngineClose0.Addr(), 1, uintptr(engineHandle), 0, 0) 57 | if r1 != 0 { 58 | err = errnoErr(e1) 59 | } 60 | return 61 | } 62 | 63 | func fwpmEngineOpen0(serverName *uint16, authnService wtRpcCAuthN, authIdentity *uintptr, session *wtFwpmSession0, engineHandle unsafe.Pointer) (err error) { 64 | r1, _, e1 := syscall.Syscall6(procFwpmEngineOpen0.Addr(), 5, uintptr(unsafe.Pointer(serverName)), uintptr(authnService), uintptr(unsafe.Pointer(authIdentity)), uintptr(unsafe.Pointer(session)), uintptr(engineHandle), 0) 65 | if r1 != 0 { 66 | err = errnoErr(e1) 67 | } 68 | return 69 | } 70 | 71 | func fwpmFilterAdd0(engineHandle uintptr, filter *wtFwpmFilter0, sd uintptr, id *uint64) (err error) { 72 | r1, _, e1 := syscall.Syscall6(procFwpmFilterAdd0.Addr(), 4, uintptr(engineHandle), uintptr(unsafe.Pointer(filter)), uintptr(sd), uintptr(unsafe.Pointer(id)), 0, 0) 73 | if r1 != 0 { 74 | err = errnoErr(e1) 75 | } 76 | return 77 | } 78 | 79 | func fwpmFreeMemory0(p unsafe.Pointer) { 80 | syscall.Syscall(procFwpmFreeMemory0.Addr(), 1, uintptr(p), 0, 0) 81 | return 82 | } 83 | 84 | func fwpmGetAppIdFromFileName0(fileName *uint16, appID unsafe.Pointer) (err error) { 85 | r1, _, e1 := syscall.Syscall(procFwpmGetAppIdFromFileName0.Addr(), 2, uintptr(unsafe.Pointer(fileName)), uintptr(appID), 0) 86 | if r1 != 0 { 87 | err = errnoErr(e1) 88 | } 89 | return 90 | } 91 | 92 | func fwpmProviderAdd0(engineHandle uintptr, provider *wtFwpmProvider0, sd uintptr) (err error) { 93 | r1, _, e1 := syscall.Syscall(procFwpmProviderAdd0.Addr(), 3, uintptr(engineHandle), uintptr(unsafe.Pointer(provider)), uintptr(sd)) 94 | if r1 != 0 { 95 | err = errnoErr(e1) 96 | } 97 | return 98 | } 99 | 100 | func fwpmSubLayerAdd0(engineHandle uintptr, subLayer *wtFwpmSublayer0, sd uintptr) (err error) { 101 | r1, _, e1 := syscall.Syscall(procFwpmSubLayerAdd0.Addr(), 3, uintptr(engineHandle), uintptr(unsafe.Pointer(subLayer)), uintptr(sd)) 102 | if r1 != 0 { 103 | err = errnoErr(e1) 104 | } 105 | return 106 | } 107 | 108 | func fwpmTransactionAbort0(engineHandle uintptr) (err error) { 109 | r1, _, e1 := syscall.Syscall(procFwpmTransactionAbort0.Addr(), 1, uintptr(engineHandle), 0, 0) 110 | if r1 != 0 { 111 | err = errnoErr(e1) 112 | } 113 | return 114 | } 115 | 116 | func fwpmTransactionBegin0(engineHandle uintptr, flags uint32) (err error) { 117 | r1, _, e1 := syscall.Syscall(procFwpmTransactionBegin0.Addr(), 2, uintptr(engineHandle), uintptr(flags), 0) 118 | if r1 != 0 { 119 | err = errnoErr(e1) 120 | } 121 | return 122 | } 123 | 124 | func fwpmTransactionCommit0(engineHandle uintptr) (err error) { 125 | r1, _, e1 := syscall.Syscall(procFwpmTransactionCommit0.Addr(), 1, uintptr(engineHandle), 0, 0) 126 | if r1 != 0 { 127 | err = errnoErr(e1) 128 | } 129 | return 130 | } 131 | -------------------------------------------------------------------------------- /tunnel/interfacewatcher.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package tunnel 7 | 8 | import ( 9 | "log" 10 | "sync" 11 | 12 | "golang.org/x/sys/windows" 13 | 14 | "github.com/amnezia-vpn/amneziawg-go/conn" 15 | "github.com/amnezia-vpn/amneziawg-go/tun" 16 | 17 | "github.com/amnezia-vpn/amneziawg-windows/conf" 18 | "github.com/amnezia-vpn/amneziawg-windows/services" 19 | "github.com/amnezia-vpn/amneziawg-windows/tunnel/firewall" 20 | "github.com/amnezia-vpn/amneziawg-windows/tunnel/winipcfg" 21 | ) 22 | 23 | type interfaceWatcherError struct { 24 | serviceError services.Error 25 | err error 26 | } 27 | type interfaceWatcherEvent struct { 28 | luid winipcfg.LUID 29 | family winipcfg.AddressFamily 30 | } 31 | type interfaceWatcher struct { 32 | errors chan interfaceWatcherError 33 | 34 | binder conn.BindSocketToInterface 35 | conf *conf.Config 36 | tun *tun.NativeTun 37 | 38 | setupMutex sync.Mutex 39 | interfaceChangeCallback winipcfg.ChangeCallback 40 | changeCallbacks4 []winipcfg.ChangeCallback 41 | changeCallbacks6 []winipcfg.ChangeCallback 42 | storedEvents []interfaceWatcherEvent 43 | } 44 | 45 | func hasDefaultRoute(family winipcfg.AddressFamily, peers []conf.Peer) bool { 46 | var ( 47 | foundV401 bool 48 | foundV41281 bool 49 | foundV600001 bool 50 | foundV680001 bool 51 | foundV400 bool 52 | foundV600 bool 53 | v40 = [4]byte{} 54 | v60 = [16]byte{} 55 | v48 = [4]byte{0x80} 56 | v68 = [16]byte{0x80} 57 | ) 58 | for _, peer := range peers { 59 | for _, allowedip := range peer.AllowedIPs { 60 | if allowedip.Cidr == 1 && len(allowedip.IP) == 16 && allowedip.IP.Equal(v60[:]) { 61 | foundV600001 = true 62 | } else if allowedip.Cidr == 1 && len(allowedip.IP) == 16 && allowedip.IP.Equal(v68[:]) { 63 | foundV680001 = true 64 | } else if allowedip.Cidr == 1 && len(allowedip.IP) == 4 && allowedip.IP.Equal(v40[:]) { 65 | foundV401 = true 66 | } else if allowedip.Cidr == 1 && len(allowedip.IP) == 4 && allowedip.IP.Equal(v48[:]) { 67 | foundV41281 = true 68 | } else if allowedip.Cidr == 0 && len(allowedip.IP) == 16 && allowedip.IP.Equal(v60[:]) { 69 | foundV600 = true 70 | } else if allowedip.Cidr == 0 && len(allowedip.IP) == 4 && allowedip.IP.Equal(v40[:]) { 71 | foundV400 = true 72 | } 73 | } 74 | } 75 | if family == windows.AF_INET { 76 | return foundV400 || (foundV401 && foundV41281) 77 | } else if family == windows.AF_INET6 { 78 | return foundV600 || (foundV600001 && foundV680001) 79 | } 80 | return false 81 | } 82 | 83 | func (iw *interfaceWatcher) setup(family winipcfg.AddressFamily) { 84 | var changeCallbacks *[]winipcfg.ChangeCallback 85 | var ipversion string 86 | if family == windows.AF_INET { 87 | changeCallbacks = &iw.changeCallbacks4 88 | ipversion = "v4" 89 | } else if family == windows.AF_INET6 { 90 | changeCallbacks = &iw.changeCallbacks6 91 | ipversion = "v6" 92 | } else { 93 | return 94 | } 95 | if len(*changeCallbacks) != 0 { 96 | for _, cb := range *changeCallbacks { 97 | cb.Unregister() 98 | } 99 | *changeCallbacks = nil 100 | } 101 | var err error 102 | 103 | log.Printf("Monitoring default %s routes", ipversion) 104 | *changeCallbacks, err = monitorDefaultRoutes(family, iw.binder, iw.conf.Interface.MTU == 0, hasDefaultRoute(family, iw.conf.Peers), iw.tun) 105 | if err != nil { 106 | iw.errors <- interfaceWatcherError{services.ErrorBindSocketsToDefaultRoutes, err} 107 | return 108 | } 109 | 110 | log.Printf("Setting device %s addresses", ipversion) 111 | err = configureInterface(family, iw.conf, iw.tun) 112 | if err != nil { 113 | iw.errors <- interfaceWatcherError{services.ErrorSetNetConfig, err} 114 | return 115 | } 116 | } 117 | 118 | func watchInterface() (*interfaceWatcher, error) { 119 | iw := &interfaceWatcher{ 120 | errors: make(chan interfaceWatcherError, 2), 121 | } 122 | var err error 123 | iw.interfaceChangeCallback, err = winipcfg.RegisterInterfaceChangeCallback(func(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) { 124 | iw.setupMutex.Lock() 125 | defer iw.setupMutex.Unlock() 126 | 127 | if notificationType != winipcfg.MibAddInstance { 128 | return 129 | } 130 | if iw.tun == nil { 131 | iw.storedEvents = append(iw.storedEvents, interfaceWatcherEvent{iface.InterfaceLUID, iface.Family}) 132 | return 133 | } 134 | if iface.InterfaceLUID != winipcfg.LUID(iw.tun.LUID()) { 135 | return 136 | } 137 | iw.setup(iface.Family) 138 | }) 139 | if err != nil { 140 | return nil, err 141 | } 142 | return iw, nil 143 | } 144 | 145 | func (iw *interfaceWatcher) Configure(binder conn.BindSocketToInterface, conf *conf.Config, tun *tun.NativeTun) { 146 | iw.setupMutex.Lock() 147 | defer iw.setupMutex.Unlock() 148 | 149 | iw.binder, iw.conf, iw.tun = binder, conf, tun 150 | for _, event := range iw.storedEvents { 151 | if event.luid == winipcfg.LUID(iw.tun.LUID()) { 152 | iw.setup(event.family) 153 | } 154 | } 155 | iw.storedEvents = nil 156 | } 157 | 158 | func (iw *interfaceWatcher) Destroy() { 159 | iw.setupMutex.Lock() 160 | changeCallbacks4 := iw.changeCallbacks4 161 | changeCallbacks6 := iw.changeCallbacks6 162 | interfaceChangeCallback := iw.interfaceChangeCallback 163 | tun := iw.tun 164 | iw.setupMutex.Unlock() 165 | 166 | if interfaceChangeCallback != nil { 167 | interfaceChangeCallback.Unregister() 168 | } 169 | for _, cb := range changeCallbacks4 { 170 | cb.Unregister() 171 | } 172 | for _, cb := range changeCallbacks6 { 173 | cb.Unregister() 174 | } 175 | 176 | iw.setupMutex.Lock() 177 | if interfaceChangeCallback == iw.interfaceChangeCallback { 178 | iw.interfaceChangeCallback = nil 179 | } 180 | for len(changeCallbacks4) > 0 && len(iw.changeCallbacks4) > 0 { 181 | iw.changeCallbacks4 = iw.changeCallbacks4[1:] 182 | changeCallbacks4 = changeCallbacks4[1:] 183 | } 184 | for len(changeCallbacks6) > 0 && len(iw.changeCallbacks6) > 0 { 185 | iw.changeCallbacks6 = iw.changeCallbacks6[1:] 186 | changeCallbacks6 = changeCallbacks6[1:] 187 | } 188 | firewall.DisableFirewall() 189 | if tun != nil && iw.tun == tun { 190 | // It seems that the Windows networking stack doesn't like it when we destroy interfaces that have active 191 | // routes, so to be certain, just remove everything before destroying. 192 | luid := winipcfg.LUID(tun.LUID()) 193 | luid.FlushRoutes(windows.AF_INET) 194 | luid.FlushIPAddresses(windows.AF_INET) 195 | luid.FlushDNS(windows.AF_INET) 196 | luid.FlushRoutes(windows.AF_INET6) 197 | luid.FlushIPAddresses(windows.AF_INET6) 198 | luid.FlushDNS(windows.AF_INET6) 199 | } 200 | iw.setupMutex.Unlock() 201 | } 202 | -------------------------------------------------------------------------------- /tunnel/ipcpermissions.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package tunnel 7 | 8 | import ( 9 | "golang.org/x/sys/windows" 10 | 11 | "github.com/amnezia-vpn/amneziawg-go/ipc" 12 | 13 | "github.com/amnezia-vpn/amneziawg-windows/conf" 14 | ) 15 | 16 | func CopyConfigOwnerToIPCSecurityDescriptor(filename string) error { 17 | if conf.PathIsEncrypted(filename) { 18 | return nil 19 | } 20 | 21 | fileSd, err := windows.GetNamedSecurityInfo(filename, windows.SE_FILE_OBJECT, windows.OWNER_SECURITY_INFORMATION) 22 | if err != nil { 23 | return err 24 | } 25 | fileOwner, _, err := fileSd.Owner() 26 | if err != nil { 27 | return err 28 | } 29 | if fileOwner.IsWellKnown(windows.WinLocalSystemSid) { 30 | return nil 31 | } 32 | additionalEntries := []windows.EXPLICIT_ACCESS{{ 33 | AccessPermissions: windows.GENERIC_ALL, 34 | AccessMode: windows.GRANT_ACCESS, 35 | Trustee: windows.TRUSTEE{ 36 | TrusteeForm: windows.TRUSTEE_IS_SID, 37 | TrusteeType: windows.TRUSTEE_IS_USER, 38 | TrusteeValue: windows.TrusteeValueFromSID(fileOwner), 39 | }, 40 | }} 41 | 42 | sd, err := ipc.UAPISecurityDescriptor.ToAbsolute() 43 | if err != nil { 44 | return err 45 | } 46 | dacl, defaulted, _ := sd.DACL() 47 | 48 | newDacl, err := windows.ACLFromEntries(additionalEntries, dacl) 49 | if err != nil { 50 | return err 51 | } 52 | err = sd.SetDACL(newDacl, true, defaulted) 53 | if err != nil { 54 | return err 55 | } 56 | sd, err = sd.ToSelfRelative() 57 | if err != nil { 58 | return err 59 | } 60 | ipc.UAPISecurityDescriptor = sd 61 | 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /tunnel/mtumonitor.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package tunnel 7 | 8 | import ( 9 | "github.com/amnezia-vpn/amneziawg-windows/tunnel/winipcfg" 10 | "golang.org/x/sys/windows" 11 | ) 12 | 13 | func findDefaultLUID(family winipcfg.AddressFamily, ourLUID winipcfg.LUID, lastLUID *winipcfg.LUID, lastIndex *uint32) error { 14 | r, err := winipcfg.GetIPForwardTable2(family) 15 | if err != nil { 16 | return err 17 | } 18 | lowestMetric := ^uint32(0) 19 | index := uint32(0) 20 | luid := winipcfg.LUID(0) 21 | for i := range r { 22 | if r[i].DestinationPrefix.PrefixLength != 0 || r[i].InterfaceLUID == ourLUID { 23 | continue 24 | } 25 | ifrow, err := r[i].InterfaceLUID.Interface() 26 | if err != nil || ifrow.OperStatus != winipcfg.IfOperStatusUp { 27 | continue 28 | } 29 | 30 | iface, err := r[i].InterfaceLUID.IPInterface(family) 31 | if err != nil { 32 | continue 33 | } 34 | 35 | if r[i].Metric+iface.Metric < lowestMetric { 36 | lowestMetric = r[i].Metric + iface.Metric 37 | index = r[i].InterfaceIndex 38 | luid = r[i].InterfaceLUID 39 | } 40 | } 41 | if luid == *lastLUID && index == *lastIndex { 42 | return nil 43 | } 44 | *lastLUID = luid 45 | *lastIndex = index 46 | return nil 47 | } 48 | 49 | func monitorMTU(family winipcfg.AddressFamily, ourLUID winipcfg.LUID) ([]winipcfg.ChangeCallback, error) { 50 | var minMTU uint32 51 | if family == windows.AF_INET { 52 | minMTU = 576 53 | } else if family == windows.AF_INET6 { 54 | minMTU = 1280 55 | } 56 | lastLUID := winipcfg.LUID(0) 57 | lastIndex := ^uint32(0) 58 | lastMTU := uint32(0) 59 | doIt := func() error { 60 | err := findDefaultLUID(family, ourLUID, &lastLUID, &lastIndex) 61 | if err != nil { 62 | return err 63 | } 64 | mtu := uint32(0) 65 | if lastLUID != 0 { 66 | iface, err := lastLUID.Interface() 67 | if err != nil { 68 | return err 69 | } 70 | if iface.MTU > 0 { 71 | mtu = iface.MTU 72 | } 73 | } 74 | if mtu > 0 && lastMTU != mtu { 75 | iface, err := ourLUID.IPInterface(family) 76 | if err != nil { 77 | return err 78 | } 79 | iface.NLMTU = mtu - 80 80 | if iface.NLMTU < minMTU { 81 | iface.NLMTU = minMTU 82 | } 83 | err = iface.Set() 84 | if err != nil { 85 | return err 86 | } 87 | lastMTU = mtu 88 | } 89 | return nil 90 | } 91 | err := doIt() 92 | if err != nil { 93 | return nil, err 94 | } 95 | cbr, err := winipcfg.RegisterRouteChangeCallback(func(notificationType winipcfg.MibNotificationType, route *winipcfg.MibIPforwardRow2) { 96 | if route != nil && route.DestinationPrefix.PrefixLength == 0 { 97 | doIt() 98 | } 99 | }) 100 | if err != nil { 101 | return nil, err 102 | } 103 | cbi, err := winipcfg.RegisterInterfaceChangeCallback(func(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) { 104 | if notificationType == winipcfg.MibParameterNotification { 105 | doIt() 106 | } 107 | }) 108 | if err != nil { 109 | cbr.Unregister() 110 | return nil, err 111 | } 112 | return []winipcfg.ChangeCallback{cbr, cbi}, nil 113 | } 114 | -------------------------------------------------------------------------------- /tunnel/scriptrunner.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package tunnel 7 | 8 | import ( 9 | "bufio" 10 | "fmt" 11 | "log" 12 | "os" 13 | "path/filepath" 14 | "syscall" 15 | 16 | "golang.org/x/sys/windows" 17 | 18 | "github.com/amnezia-vpn/amneziawg-windows/conf" 19 | ) 20 | 21 | func runScriptCommand(command, interfaceName string) error { 22 | if len(command) == 0 { 23 | return nil 24 | } 25 | if !conf.AdminBool("DangerousScriptExecution") { 26 | log.Printf("Skipping execution of script, because dangerous script execution is safely disabled: %#q", command) 27 | return nil 28 | } 29 | log.Printf("Executing: %#q", command) 30 | comspec, _ := os.LookupEnv("COMSPEC") 31 | if len(comspec) == 0 { 32 | system32, err := windows.GetSystemDirectory() 33 | if err != nil { 34 | return err 35 | } 36 | comspec = filepath.Join(system32, "cmd.exe") 37 | } 38 | 39 | devNull, err := os.OpenFile(os.DevNull, os.O_RDWR, 0) 40 | if err != nil { 41 | return err 42 | } 43 | defer devNull.Close() 44 | reader, writer, err := os.Pipe() 45 | if err != nil { 46 | return err 47 | } 48 | process, err := os.StartProcess(comspec, nil /* CmdLine below */, &os.ProcAttr{ 49 | Files: []*os.File{devNull, writer, writer}, 50 | Env: append(os.Environ(), "AMNEZIAWG_TUNNEL_NAME="+interfaceName), 51 | Sys: &syscall.SysProcAttr{ 52 | HideWindow: true, 53 | CmdLine: fmt.Sprintf("cmd /c %s", command), 54 | }, 55 | }) 56 | writer.Close() 57 | if err != nil { 58 | reader.Close() 59 | return err 60 | } 61 | go func() { 62 | scanner := bufio.NewScanner(reader) 63 | for scanner.Scan() { 64 | log.Printf("cmd> %s", scanner.Text()) 65 | } 66 | }() 67 | state, err := process.Wait() 68 | reader.Close() 69 | if err != nil { 70 | return err 71 | } 72 | if state.ExitCode() == 0 { 73 | return nil 74 | } 75 | log.Printf("Command error exit status: %d", state.ExitCode()) 76 | return windows.ERROR_GENERIC_COMMAND_FAILED 77 | } 78 | -------------------------------------------------------------------------------- /tunnel/service.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package tunnel 7 | 8 | import ( 9 | "bytes" 10 | "fmt" 11 | "log" 12 | "net" 13 | "os" 14 | "runtime" 15 | "time" 16 | 17 | "github.com/amnezia-vpn/amneziawg-go/conn" 18 | "github.com/amnezia-vpn/amneziawg-go/device" 19 | "github.com/amnezia-vpn/amneziawg-go/ipc" 20 | "github.com/amnezia-vpn/amneziawg-go/tun" 21 | "golang.org/x/sys/windows" 22 | "golang.org/x/sys/windows/svc" 23 | "golang.org/x/sys/windows/svc/mgr" 24 | 25 | "github.com/amnezia-vpn/amneziawg-windows/conf" 26 | "github.com/amnezia-vpn/amneziawg-windows/elevate" 27 | "github.com/amnezia-vpn/amneziawg-windows/ringlogger" 28 | "github.com/amnezia-vpn/amneziawg-windows/services" 29 | "github.com/amnezia-vpn/amneziawg-windows/version" 30 | ) 31 | 32 | type tunnelService struct { 33 | Path string 34 | } 35 | 36 | func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) { 37 | changes <- svc.Status{State: svc.StartPending} 38 | 39 | var dev *device.Device 40 | var uapi net.Listener 41 | var watcher *interfaceWatcher 42 | var nativeTun *tun.NativeTun 43 | var config *conf.Config 44 | var err error 45 | serviceError := services.ErrorSuccess 46 | 47 | defer func() { 48 | svcSpecificEC, exitCode = services.DetermineErrorCode(err, serviceError) 49 | logErr := services.CombineErrors(err, serviceError) 50 | if logErr != nil { 51 | log.Println(logErr) 52 | } 53 | changes <- svc.Status{State: svc.StopPending} 54 | 55 | stopIt := make(chan bool, 1) 56 | go func() { 57 | t := time.NewTicker(time.Second * 30) 58 | for { 59 | select { 60 | case <-t.C: 61 | t.Stop() 62 | buf := make([]byte, 1024) 63 | for { 64 | n := runtime.Stack(buf, true) 65 | if n < len(buf) { 66 | buf = buf[:n] 67 | break 68 | } 69 | buf = make([]byte, 2*len(buf)) 70 | } 71 | lines := bytes.Split(buf, []byte{'\n'}) 72 | log.Println("Failed to shutdown after 30 seconds. Probably dead locked. Printing stack and killing.") 73 | for _, line := range lines { 74 | if len(bytes.TrimSpace(line)) > 0 { 75 | log.Println(string(line)) 76 | } 77 | } 78 | os.Exit(777) 79 | return 80 | case <-stopIt: 81 | t.Stop() 82 | return 83 | } 84 | } 85 | }() 86 | 87 | if logErr == nil && dev != nil && config != nil { 88 | logErr = runScriptCommand(config.Interface.PreDown, config.Name) 89 | } 90 | if watcher != nil { 91 | watcher.Destroy() 92 | } 93 | if uapi != nil { 94 | uapi.Close() 95 | } 96 | if dev != nil { 97 | dev.Close() 98 | } 99 | if logErr == nil && dev != nil && config != nil { 100 | _ = runScriptCommand(config.Interface.PostDown, config.Name) 101 | } 102 | stopIt <- true 103 | log.Println("Shutting down") 104 | }() 105 | 106 | err = ringlogger.InitGlobalLogger("TUN") 107 | if err != nil { 108 | serviceError = services.ErrorRingloggerOpen 109 | return 110 | } 111 | 112 | config, err = conf.LoadFromPath(service.Path) 113 | if err != nil { 114 | serviceError = services.ErrorLoadConfiguration 115 | return 116 | } 117 | config.DeduplicateNetworkEntries() 118 | err = CopyConfigOwnerToIPCSecurityDescriptor(service.Path) 119 | if err != nil { 120 | serviceError = services.ErrorLoadConfiguration 121 | return 122 | } 123 | 124 | log.SetPrefix(fmt.Sprintf("[%s] ", config.Name)) 125 | 126 | log.Println("Starting", version.UserAgent()) 127 | 128 | if m, err := mgr.Connect(); err == nil { 129 | if lockStatus, err := m.LockStatus(); err == nil && lockStatus.IsLocked { 130 | /* If we don't do this, then the Wintun installation will block forever, because 131 | * installing a Wintun device starts a service too. Apparently at boot time, Windows 132 | * 8.1 locks the SCM for each service start, creating a deadlock if we don't announce 133 | * that we're running before starting additional services. 134 | */ 135 | log.Printf("SCM locked for %v by %s, marking service as started", lockStatus.Age, lockStatus.Owner) 136 | changes <- svc.Status{State: svc.Running} 137 | } 138 | m.Disconnect() 139 | } 140 | 141 | log.Println("Watching network interfaces") 142 | watcher, err = watchInterface() 143 | if err != nil { 144 | serviceError = services.ErrorSetNetConfig 145 | return 146 | } 147 | 148 | log.Println("Resolving DNS names") 149 | uapiConf, err := config.ToUAPI() 150 | if err != nil { 151 | serviceError = services.ErrorDNSLookup 152 | return 153 | } 154 | 155 | log.Println("Creating Wintun interface") 156 | var wintun tun.Device 157 | for i := 0; i < 5; i++ { 158 | if i > 0 { 159 | time.Sleep(time.Second) 160 | log.Printf("Retrying Wintun creation after failure because system just booted (T+%v): %v", windows.DurationSinceBoot(), err) 161 | } 162 | wintun, err = tun.CreateTUNWithRequestedGUID(config.Name, deterministicGUID(config), 0) 163 | if err == nil || windows.DurationSinceBoot() > time.Minute*4 { 164 | break 165 | } 166 | } 167 | if err != nil { 168 | serviceError = services.ErrorCreateWintun 169 | return 170 | } 171 | nativeTun = wintun.(*tun.NativeTun) 172 | wintunVersion, err := nativeTun.RunningVersion() 173 | if err != nil { 174 | log.Printf("Warning: unable to determine Wintun version: %v", err) 175 | } else { 176 | log.Printf("Using Wintun/%d.%d", (wintunVersion>>16)&0xffff, wintunVersion&0xffff) 177 | } 178 | 179 | err = runScriptCommand(config.Interface.PreUp, config.Name) 180 | if err != nil { 181 | serviceError = services.ErrorRunScript 182 | return 183 | } 184 | 185 | err = enableFirewall(config, nativeTun) 186 | if err != nil { 187 | serviceError = services.ErrorFirewall 188 | return 189 | } 190 | 191 | log.Println("Dropping privileges") 192 | err = elevate.DropAllPrivileges(true) 193 | if err != nil { 194 | serviceError = services.ErrorDropPrivileges 195 | return 196 | } 197 | 198 | log.Println("Creating interface instance") 199 | bind := conn.NewDefaultBind() 200 | dev = device.NewDevice(wintun, bind, &device.Logger{log.Printf, log.Printf}) 201 | 202 | log.Println("Setting interface configuration") 203 | uapi, err = ipc.UAPIListen(config.Name) 204 | if err != nil { 205 | serviceError = services.ErrorUAPIListen 206 | return 207 | } 208 | err = dev.IpcSet(uapiConf) 209 | if err != nil { 210 | serviceError = services.ErrorDeviceSetConfig 211 | return 212 | } 213 | 214 | log.Println("Bringing peers up") 215 | dev.Up() 216 | 217 | watcher.Configure(bind.(conn.BindSocketToInterface), config, nativeTun) 218 | 219 | log.Println("Listening for UAPI requests") 220 | go func() { 221 | for { 222 | conn, err := uapi.Accept() 223 | if err != nil { 224 | continue 225 | } 226 | go dev.IpcHandle(conn) 227 | } 228 | }() 229 | 230 | err = runScriptCommand(config.Interface.PostUp, config.Name) 231 | if err != nil { 232 | serviceError = services.ErrorRunScript 233 | return 234 | } 235 | 236 | changes <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown} 237 | log.Println("Startup complete") 238 | 239 | for { 240 | select { 241 | case c := <-r: 242 | switch c.Cmd { 243 | case svc.Stop, svc.Shutdown: 244 | return 245 | case svc.Interrogate: 246 | changes <- c.CurrentStatus 247 | default: 248 | log.Printf("Unexpected service control request #%d\n", c) 249 | } 250 | case <-dev.Wait(): 251 | return 252 | case e := <-watcher.errors: 253 | serviceError, err = e.serviceError, e.err 254 | return 255 | } 256 | } 257 | } 258 | 259 | func Run(confPath string) error { 260 | name, err := conf.NameFromPath(confPath) 261 | if err != nil { 262 | return err 263 | } 264 | serviceName, err := services.ServiceNameOfTunnel(name) 265 | if err != nil { 266 | return err 267 | } 268 | return svc.Run(serviceName, &tunnelService{confPath}) 269 | } 270 | -------------------------------------------------------------------------------- /tunnel/winipcfg/interface_change_handler.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package winipcfg 7 | 8 | import ( 9 | "sync" 10 | 11 | "golang.org/x/sys/windows" 12 | ) 13 | 14 | // InterfaceChangeCallback structure allows interface change callback handling. 15 | type InterfaceChangeCallback struct { 16 | cb func(notificationType MibNotificationType, iface *MibIPInterfaceRow) 17 | wait sync.WaitGroup 18 | } 19 | 20 | var ( 21 | interfaceChangeAddRemoveMutex = sync.Mutex{} 22 | interfaceChangeMutex = sync.Mutex{} 23 | interfaceChangeCallbacks = make(map[*InterfaceChangeCallback]bool) 24 | interfaceChangeHandle = windows.Handle(0) 25 | ) 26 | 27 | // RegisterInterfaceChangeCallback registers a new InterfaceChangeCallback. If this particular callback is already 28 | // registered, the function will silently return. Returned InterfaceChangeCallback.Unregister method should be used 29 | // to unregister. 30 | func RegisterInterfaceChangeCallback(callback func(notificationType MibNotificationType, iface *MibIPInterfaceRow)) (*InterfaceChangeCallback, error) { 31 | s := &InterfaceChangeCallback{cb: callback} 32 | 33 | interfaceChangeAddRemoveMutex.Lock() 34 | defer interfaceChangeAddRemoveMutex.Unlock() 35 | 36 | interfaceChangeMutex.Lock() 37 | defer interfaceChangeMutex.Unlock() 38 | 39 | interfaceChangeCallbacks[s] = true 40 | 41 | if interfaceChangeHandle == 0 { 42 | err := notifyIPInterfaceChange(windows.AF_UNSPEC, windows.NewCallback(interfaceChanged), 0, false, &interfaceChangeHandle) 43 | if err != nil { 44 | delete(interfaceChangeCallbacks, s) 45 | interfaceChangeHandle = 0 46 | return nil, err 47 | } 48 | } 49 | 50 | return s, nil 51 | } 52 | 53 | // Unregister unregisters the callback. 54 | func (callback *InterfaceChangeCallback) Unregister() error { 55 | interfaceChangeAddRemoveMutex.Lock() 56 | defer interfaceChangeAddRemoveMutex.Unlock() 57 | 58 | interfaceChangeMutex.Lock() 59 | delete(interfaceChangeCallbacks, callback) 60 | removeIt := len(interfaceChangeCallbacks) == 0 && interfaceChangeHandle != 0 61 | interfaceChangeMutex.Unlock() 62 | 63 | callback.wait.Wait() 64 | 65 | if removeIt { 66 | err := cancelMibChangeNotify2(interfaceChangeHandle) 67 | if err != nil { 68 | return err 69 | } 70 | interfaceChangeHandle = 0 71 | } 72 | 73 | return nil 74 | } 75 | 76 | func interfaceChanged(callerContext uintptr, row *MibIPInterfaceRow, notificationType MibNotificationType) uintptr { 77 | rowCopy := *row 78 | interfaceChangeMutex.Lock() 79 | for cb := range interfaceChangeCallbacks { 80 | cb.wait.Add(1) 81 | go func(cb *InterfaceChangeCallback) { 82 | cb.cb(notificationType, &rowCopy) 83 | cb.wait.Done() 84 | }(cb) 85 | } 86 | interfaceChangeMutex.Unlock() 87 | return 0 88 | } 89 | -------------------------------------------------------------------------------- /tunnel/winipcfg/mksyscall.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package winipcfg 7 | 8 | //go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zwinipcfg_windows.go winipcfg.go 9 | -------------------------------------------------------------------------------- /tunnel/winipcfg/netsh.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package winipcfg 7 | 8 | import ( 9 | "bytes" 10 | "errors" 11 | "fmt" 12 | "io" 13 | "net" 14 | "os/exec" 15 | "path/filepath" 16 | "strings" 17 | "syscall" 18 | 19 | "golang.org/x/sys/windows" 20 | "golang.org/x/sys/windows/registry" 21 | ) 22 | 23 | func runNetsh(cmds []string) error { 24 | system32, err := windows.GetSystemDirectory() 25 | if err != nil { 26 | return err 27 | } 28 | cmd := exec.Command(filepath.Join(system32, "netsh.exe")) 29 | cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} 30 | 31 | stdin, err := cmd.StdinPipe() 32 | if err != nil { 33 | return fmt.Errorf("runNetsh stdin pipe - %w", err) 34 | } 35 | go func() { 36 | defer stdin.Close() 37 | io.WriteString(stdin, strings.Join(append(cmds, "exit\r\n"), "\r\n")) 38 | }() 39 | output, err := cmd.CombinedOutput() 40 | // Horrible kludges, sorry. 41 | cleaned := bytes.ReplaceAll(output, []byte{'\r', '\n'}, []byte{'\n'}) 42 | cleaned = bytes.ReplaceAll(cleaned, []byte("netsh>"), []byte{}) 43 | cleaned = bytes.ReplaceAll(cleaned, []byte("There are no Domain Name Servers (DNS) configured on this computer."), []byte{}) 44 | cleaned = bytes.TrimSpace(cleaned) 45 | if len(cleaned) != 0 && err == nil { 46 | return fmt.Errorf("netsh: %#q", string(cleaned)) 47 | } else if err != nil { 48 | return fmt.Errorf("netsh: %v: %#q", err, string(cleaned)) 49 | } 50 | return nil 51 | } 52 | 53 | const ( 54 | netshCmdTemplateFlush4 = "interface ipv4 set dnsservers name=%d source=static address=none validate=no register=both" 55 | netshCmdTemplateFlush6 = "interface ipv6 set dnsservers name=%d source=static address=none validate=no register=both" 56 | netshCmdTemplateAdd4 = "interface ipv4 add dnsservers name=%d address=%s validate=no" 57 | netshCmdTemplateAdd6 = "interface ipv6 add dnsservers name=%d address=%s validate=no" 58 | ) 59 | 60 | func (luid LUID) fallbackSetDNSForFamily(family AddressFamily, dnses []net.IP) error { 61 | var templateFlush string 62 | if family == windows.AF_INET { 63 | templateFlush = netshCmdTemplateFlush4 64 | } else if family == windows.AF_INET6 { 65 | templateFlush = netshCmdTemplateFlush6 66 | } 67 | 68 | cmds := make([]string, 0, 1+len(dnses)) 69 | ipif, err := luid.IPInterface(family) 70 | if err != nil { 71 | return err 72 | } 73 | cmds = append(cmds, fmt.Sprintf(templateFlush, ipif.InterfaceIndex)) 74 | for i := 0; i < len(dnses); i++ { 75 | if v4 := dnses[i].To4(); v4 != nil && family == windows.AF_INET { 76 | cmds = append(cmds, fmt.Sprintf(netshCmdTemplateAdd4, ipif.InterfaceIndex, v4.String())) 77 | } else if v6 := dnses[i].To16(); v4 == nil && v6 != nil && family == windows.AF_INET6 { 78 | cmds = append(cmds, fmt.Sprintf(netshCmdTemplateAdd6, ipif.InterfaceIndex, v6.String())) 79 | } 80 | } 81 | return runNetsh(cmds) 82 | } 83 | 84 | func (luid LUID) fallbackSetDNSDomain(domain string) error { 85 | guid, err := luid.GUID() 86 | if err != nil { 87 | return fmt.Errorf("Error converting luid to guid: %w", err) 88 | } 89 | key, err := registry.OpenKey(registry.LOCAL_MACHINE, fmt.Sprintf("SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Adapters\\%v", guid), registry.QUERY_VALUE) 90 | if err != nil { 91 | return fmt.Errorf("Error opening adapter-specific TCP/IP network registry key: %w", err) 92 | } 93 | paths, _, err := key.GetStringsValue("IpConfig") 94 | key.Close() 95 | if err != nil { 96 | return fmt.Errorf("Error reading IpConfig registry key: %w", err) 97 | } 98 | if len(paths) == 0 { 99 | return errors.New("No TCP/IP interfaces found on adapter") 100 | } 101 | key, err = registry.OpenKey(registry.LOCAL_MACHINE, fmt.Sprintf("SYSTEM\\CurrentControlSet\\Services\\%s", paths[0]), registry.SET_VALUE) 102 | if err != nil { 103 | return fmt.Errorf("Unable to open TCP/IP network registry key: %w", err) 104 | } 105 | err = key.SetStringValue("Domain", domain) 106 | key.Close() 107 | return err 108 | } 109 | -------------------------------------------------------------------------------- /tunnel/winipcfg/route_change_handler.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package winipcfg 7 | 8 | import ( 9 | "sync" 10 | 11 | "golang.org/x/sys/windows" 12 | ) 13 | 14 | // RouteChangeCallback structure allows route change callback handling. 15 | type RouteChangeCallback struct { 16 | cb func(notificationType MibNotificationType, route *MibIPforwardRow2) 17 | wait sync.WaitGroup 18 | } 19 | 20 | var ( 21 | routeChangeAddRemoveMutex = sync.Mutex{} 22 | routeChangeMutex = sync.Mutex{} 23 | routeChangeCallbacks = make(map[*RouteChangeCallback]bool) 24 | routeChangeHandle = windows.Handle(0) 25 | ) 26 | 27 | // RegisterRouteChangeCallback registers a new RouteChangeCallback. If this particular callback is already 28 | // registered, the function will silently return. Returned RouteChangeCallback.Unregister method should be used 29 | // to unregister. 30 | func RegisterRouteChangeCallback(callback func(notificationType MibNotificationType, route *MibIPforwardRow2)) (*RouteChangeCallback, error) { 31 | s := &RouteChangeCallback{cb: callback} 32 | 33 | routeChangeAddRemoveMutex.Lock() 34 | defer routeChangeAddRemoveMutex.Unlock() 35 | 36 | routeChangeMutex.Lock() 37 | defer routeChangeMutex.Unlock() 38 | 39 | routeChangeCallbacks[s] = true 40 | 41 | if routeChangeHandle == 0 { 42 | err := notifyRouteChange2(windows.AF_UNSPEC, windows.NewCallback(routeChanged), 0, false, &routeChangeHandle) 43 | if err != nil { 44 | delete(routeChangeCallbacks, s) 45 | routeChangeHandle = 0 46 | return nil, err 47 | } 48 | } 49 | 50 | return s, nil 51 | } 52 | 53 | // Unregister unregisters the callback. 54 | func (callback *RouteChangeCallback) Unregister() error { 55 | routeChangeAddRemoveMutex.Lock() 56 | defer routeChangeAddRemoveMutex.Unlock() 57 | 58 | routeChangeMutex.Lock() 59 | delete(routeChangeCallbacks, callback) 60 | removeIt := len(routeChangeCallbacks) == 0 && routeChangeHandle != 0 61 | routeChangeMutex.Unlock() 62 | 63 | callback.wait.Wait() 64 | 65 | if removeIt { 66 | err := cancelMibChangeNotify2(routeChangeHandle) 67 | if err != nil { 68 | return err 69 | } 70 | routeChangeHandle = 0 71 | } 72 | 73 | return nil 74 | } 75 | 76 | func routeChanged(callerContext uintptr, row *MibIPforwardRow2, notificationType MibNotificationType) uintptr { 77 | rowCopy := *row 78 | routeChangeMutex.Lock() 79 | for cb := range routeChangeCallbacks { 80 | cb.wait.Add(1) 81 | go func(cb *RouteChangeCallback) { 82 | cb.cb(notificationType, &rowCopy) 83 | cb.wait.Done() 84 | }(cb) 85 | } 86 | routeChangeMutex.Unlock() 87 | return 0 88 | } 89 | -------------------------------------------------------------------------------- /tunnel/winipcfg/types_test_32.go: -------------------------------------------------------------------------------- 1 | //go:build 386 || arm 2 | // +build 386 arm 3 | 4 | /* SPDX-License-Identifier: MIT 5 | * 6 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 7 | */ 8 | 9 | package winipcfg 10 | 11 | const ( 12 | ipAdapterWINSServerAddressSize = 24 13 | ipAdapterWINSServerAddressNextOffset = 8 14 | ipAdapterWINSServerAddressAddressOffset = 12 15 | 16 | ipAdapterGatewayAddressSize = 24 17 | ipAdapterGatewayAddressNextOffset = 8 18 | ipAdapterGatewayAddressAddressOffset = 12 19 | 20 | ipAdapterDNSSuffixSize = 516 21 | ipAdapterDNSSuffixStringOffset = 4 22 | 23 | ipAdapterAddressesSize = 376 24 | ipAdapterAddressesIfIndexOffset = 4 25 | ipAdapterAddressesNextOffset = 8 26 | ipAdapterAddressesAdapterNameOffset = 12 27 | ipAdapterAddressesFirstUnicastAddressOffset = 16 28 | ipAdapterAddressesFirstAnycastAddressOffset = 20 29 | ipAdapterAddressesFirstMulticastAddressOffset = 24 30 | ipAdapterAddressesFirstDNSServerAddressOffset = 28 31 | ipAdapterAddressesDNSSuffixOffset = 32 32 | ipAdapterAddressesDescriptionOffset = 36 33 | ipAdapterAddressesFriendlyNameOffset = 40 34 | ipAdapterAddressesPhysicalAddressOffset = 44 35 | ipAdapterAddressesPhysicalAddressLengthOffset = 52 36 | ipAdapterAddressesFlagsOffset = 56 37 | ipAdapterAddressesMTUOffset = 60 38 | ipAdapterAddressesIfTypeOffset = 64 39 | ipAdapterAddressesOperStatusOffset = 68 40 | ipAdapterAddressesIPv6IfIndexOffset = 72 41 | ipAdapterAddressesZoneIndicesOffset = 76 42 | ipAdapterAddressesFirstPrefixOffset = 140 43 | ipAdapterAddressesTransmitLinkSpeedOffset = 144 44 | ipAdapterAddressesReceiveLinkSpeedOffset = 152 45 | ipAdapterAddressesFirstWINSServerAddressOffset = 160 46 | ipAdapterAddressesFirstGatewayAddressOffset = 164 47 | ipAdapterAddressesIPv4MetricOffset = 168 48 | ipAdapterAddressesIPv6MetricOffset = 172 49 | ipAdapterAddressesLUIDOffset = 176 50 | ipAdapterAddressesDHCPv4ServerOffset = 184 51 | ipAdapterAddressesCompartmentIDOffset = 192 52 | ipAdapterAddressesNetworkGUIDOffset = 196 53 | ipAdapterAddressesConnectionTypeOffset = 212 54 | ipAdapterAddressesTunnelTypeOffset = 216 55 | ipAdapterAddressesDHCPv6ServerOffset = 220 56 | ipAdapterAddressesDHCPv6ClientDUIDOffset = 228 57 | ipAdapterAddressesDHCPv6ClientDUIDLengthOffset = 360 58 | ipAdapterAddressesDHCPv6IAIDOffset = 364 59 | ipAdapterAddressesFirstDNSSuffixOffset = 368 60 | ) 61 | -------------------------------------------------------------------------------- /tunnel/winipcfg/types_test_64.go: -------------------------------------------------------------------------------- 1 | //go:build amd64 || arm64 2 | // +build amd64 arm64 3 | 4 | /* SPDX-License-Identifier: MIT 5 | * 6 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 7 | */ 8 | 9 | package winipcfg 10 | 11 | const ( 12 | ipAdapterWINSServerAddressSize = 32 13 | ipAdapterWINSServerAddressNextOffset = 8 14 | ipAdapterWINSServerAddressAddressOffset = 16 15 | 16 | ipAdapterGatewayAddressSize = 32 17 | ipAdapterGatewayAddressNextOffset = 8 18 | ipAdapterGatewayAddressAddressOffset = 16 19 | 20 | ipAdapterDNSSuffixSize = 520 21 | ipAdapterDNSSuffixStringOffset = 8 22 | 23 | ipAdapterAddressesSize = 448 24 | ipAdapterAddressesIfIndexOffset = 4 25 | ipAdapterAddressesNextOffset = 8 26 | ipAdapterAddressesAdapterNameOffset = 16 27 | ipAdapterAddressesFirstUnicastAddressOffset = 24 28 | ipAdapterAddressesFirstAnycastAddressOffset = 32 29 | ipAdapterAddressesFirstMulticastAddressOffset = 40 30 | ipAdapterAddressesFirstDNSServerAddressOffset = 48 31 | ipAdapterAddressesDNSSuffixOffset = 56 32 | ipAdapterAddressesDescriptionOffset = 64 33 | ipAdapterAddressesFriendlyNameOffset = 72 34 | ipAdapterAddressesPhysicalAddressOffset = 80 35 | ipAdapterAddressesPhysicalAddressLengthOffset = 88 36 | ipAdapterAddressesFlagsOffset = 92 37 | ipAdapterAddressesMTUOffset = 96 38 | ipAdapterAddressesIfTypeOffset = 100 39 | ipAdapterAddressesOperStatusOffset = 104 40 | ipAdapterAddressesIPv6IfIndexOffset = 108 41 | ipAdapterAddressesZoneIndicesOffset = 112 42 | ipAdapterAddressesFirstPrefixOffset = 176 43 | ipAdapterAddressesTransmitLinkSpeedOffset = 184 44 | ipAdapterAddressesReceiveLinkSpeedOffset = 192 45 | ipAdapterAddressesFirstWINSServerAddressOffset = 200 46 | ipAdapterAddressesFirstGatewayAddressOffset = 208 47 | ipAdapterAddressesIPv4MetricOffset = 216 48 | ipAdapterAddressesIPv6MetricOffset = 220 49 | ipAdapterAddressesLUIDOffset = 224 50 | ipAdapterAddressesDHCPv4ServerOffset = 232 51 | ipAdapterAddressesCompartmentIDOffset = 248 52 | ipAdapterAddressesNetworkGUIDOffset = 252 53 | ipAdapterAddressesConnectionTypeOffset = 268 54 | ipAdapterAddressesTunnelTypeOffset = 272 55 | ipAdapterAddressesDHCPv6ServerOffset = 280 56 | ipAdapterAddressesDHCPv6ClientDUIDOffset = 296 57 | ipAdapterAddressesDHCPv6ClientDUIDLengthOffset = 428 58 | ipAdapterAddressesDHCPv6IAIDOffset = 432 59 | ipAdapterAddressesFirstDNSSuffixOffset = 440 60 | ) 61 | -------------------------------------------------------------------------------- /tunnel/winipcfg/unicast_address_change_handler.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package winipcfg 7 | 8 | import ( 9 | "sync" 10 | 11 | "golang.org/x/sys/windows" 12 | ) 13 | 14 | // UnicastAddressChangeCallback structure allows unicast address change callback handling. 15 | type UnicastAddressChangeCallback struct { 16 | cb func(notificationType MibNotificationType, unicastAddress *MibUnicastIPAddressRow) 17 | wait sync.WaitGroup 18 | } 19 | 20 | var ( 21 | unicastAddressChangeAddRemoveMutex = sync.Mutex{} 22 | unicastAddressChangeMutex = sync.Mutex{} 23 | unicastAddressChangeCallbacks = make(map[*UnicastAddressChangeCallback]bool) 24 | unicastAddressChangeHandle = windows.Handle(0) 25 | ) 26 | 27 | // RegisterUnicastAddressChangeCallback registers a new UnicastAddressChangeCallback. If this particular callback is already 28 | // registered, the function will silently return. Returned UnicastAddressChangeCallback.Unregister method should be used 29 | // to unregister. 30 | func RegisterUnicastAddressChangeCallback(callback func(notificationType MibNotificationType, unicastAddress *MibUnicastIPAddressRow)) (*UnicastAddressChangeCallback, error) { 31 | s := &UnicastAddressChangeCallback{cb: callback} 32 | 33 | unicastAddressChangeAddRemoveMutex.Lock() 34 | defer unicastAddressChangeAddRemoveMutex.Unlock() 35 | 36 | unicastAddressChangeMutex.Lock() 37 | defer unicastAddressChangeMutex.Unlock() 38 | 39 | unicastAddressChangeCallbacks[s] = true 40 | 41 | if unicastAddressChangeHandle == 0 { 42 | err := notifyUnicastIPAddressChange(windows.AF_UNSPEC, windows.NewCallback(unicastAddressChanged), 0, false, &unicastAddressChangeHandle) 43 | if err != nil { 44 | delete(unicastAddressChangeCallbacks, s) 45 | unicastAddressChangeHandle = 0 46 | return nil, err 47 | } 48 | } 49 | 50 | return s, nil 51 | } 52 | 53 | // Unregister unregisters the callback. 54 | func (callback *UnicastAddressChangeCallback) Unregister() error { 55 | unicastAddressChangeAddRemoveMutex.Lock() 56 | defer unicastAddressChangeAddRemoveMutex.Unlock() 57 | 58 | unicastAddressChangeMutex.Lock() 59 | delete(unicastAddressChangeCallbacks, callback) 60 | removeIt := len(unicastAddressChangeCallbacks) == 0 && unicastAddressChangeHandle != 0 61 | unicastAddressChangeMutex.Unlock() 62 | 63 | callback.wait.Wait() 64 | 65 | if removeIt { 66 | err := cancelMibChangeNotify2(unicastAddressChangeHandle) 67 | if err != nil { 68 | return err 69 | } 70 | unicastAddressChangeHandle = 0 71 | } 72 | 73 | return nil 74 | } 75 | 76 | func unicastAddressChanged(callerContext uintptr, row *MibUnicastIPAddressRow, notificationType MibNotificationType) uintptr { 77 | rowCopy := *row 78 | unicastAddressChangeMutex.Lock() 79 | for cb := range unicastAddressChangeCallbacks { 80 | cb.wait.Add(1) 81 | go func(cb *UnicastAddressChangeCallback) { 82 | cb.cb(notificationType, &rowCopy) 83 | cb.wait.Done() 84 | }(cb) 85 | } 86 | unicastAddressChangeMutex.Unlock() 87 | return 0 88 | } 89 | -------------------------------------------------------------------------------- /tunnel/winipcfg/winipcfg.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package winipcfg 7 | 8 | import ( 9 | "runtime" 10 | "unsafe" 11 | 12 | "golang.org/x/sys/windows" 13 | ) 14 | 15 | // 16 | // Common functions 17 | // 18 | 19 | //sys freeMibTable(memory unsafe.Pointer) = iphlpapi.FreeMibTable 20 | 21 | // 22 | // Interface-related functions 23 | // 24 | 25 | //sys initializeIPInterfaceEntry(row *MibIPInterfaceRow) = iphlpapi.InitializeIpInterfaceEntry 26 | //sys getIPInterfaceTable(family AddressFamily, table **mibIPInterfaceTable) (ret error) = iphlpapi.GetIpInterfaceTable 27 | //sys getIPInterfaceEntry(row *MibIPInterfaceRow) (ret error) = iphlpapi.GetIpInterfaceEntry 28 | //sys setIPInterfaceEntry(row *MibIPInterfaceRow) (ret error) = iphlpapi.SetIpInterfaceEntry 29 | //sys getIfEntry2(row *MibIfRow2) (ret error) = iphlpapi.GetIfEntry2 30 | //sys getIfTable2Ex(level MibIfEntryLevel, table **mibIfTable2) (ret error) = iphlpapi.GetIfTable2Ex 31 | //sys convertInterfaceLUIDToGUID(interfaceLUID *LUID, interfaceGUID *windows.GUID) (ret error) = iphlpapi.ConvertInterfaceLuidToGuid 32 | //sys convertInterfaceGUIDToLUID(interfaceGUID *windows.GUID, interfaceLUID *LUID) (ret error) = iphlpapi.ConvertInterfaceGuidToLuid 33 | //sys convertInterfaceIndexToLUID(interfaceIndex uint32, interfaceLUID *LUID) (ret error) = iphlpapi.ConvertInterfaceIndexToLuid 34 | 35 | // GetAdaptersAddresses function retrieves the addresses associated with the adapters on the local computer. 36 | // https://docs.microsoft.com/en-us/windows/desktop/api/iphlpapi/nf-iphlpapi-getadaptersaddresses 37 | func GetAdaptersAddresses(family AddressFamily, flags GAAFlags) ([]*IPAdapterAddresses, error) { 38 | var b []byte 39 | size := uint32(15000) 40 | 41 | for { 42 | b = make([]byte, size) 43 | err := windows.GetAdaptersAddresses(uint32(family), uint32(flags), 0, (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])), &size) 44 | if err == nil { 45 | break 46 | } 47 | if err != windows.ERROR_BUFFER_OVERFLOW || size <= uint32(len(b)) { 48 | return nil, err 49 | } 50 | } 51 | 52 | result := make([]*IPAdapterAddresses, 0, uintptr(size)/unsafe.Sizeof(IPAdapterAddresses{})) 53 | for wtiaa := (*IPAdapterAddresses)(unsafe.Pointer(&b[0])); wtiaa != nil; wtiaa = wtiaa.Next { 54 | result = append(result, wtiaa) 55 | } 56 | 57 | return result, nil 58 | } 59 | 60 | // GetIPInterfaceTable function retrieves the IP interface entries on the local computer. 61 | // https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getipinterfacetable 62 | func GetIPInterfaceTable(family AddressFamily) ([]MibIPInterfaceRow, error) { 63 | var tab *mibIPInterfaceTable 64 | err := getIPInterfaceTable(family, &tab) 65 | if err != nil { 66 | return nil, err 67 | } 68 | t := append(make([]MibIPInterfaceRow, 0, tab.numEntries), tab.get()...) 69 | tab.free() 70 | return t, nil 71 | } 72 | 73 | // GetIfTable2Ex function retrieves the MIB-II interface table. 74 | // https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getiftable2ex 75 | func GetIfTable2Ex(level MibIfEntryLevel) ([]MibIfRow2, error) { 76 | var tab *mibIfTable2 77 | err := getIfTable2Ex(level, &tab) 78 | if err != nil { 79 | return nil, err 80 | } 81 | t := append(make([]MibIfRow2, 0, tab.numEntries), tab.get()...) 82 | tab.free() 83 | return t, nil 84 | } 85 | 86 | // 87 | // Unicast IP address-related functions 88 | // 89 | 90 | //sys getUnicastIPAddressTable(family AddressFamily, table **mibUnicastIPAddressTable) (ret error) = iphlpapi.GetUnicastIpAddressTable 91 | //sys initializeUnicastIPAddressEntry(row *MibUnicastIPAddressRow) = iphlpapi.InitializeUnicastIpAddressEntry 92 | //sys getUnicastIPAddressEntry(row *MibUnicastIPAddressRow) (ret error) = iphlpapi.GetUnicastIpAddressEntry 93 | //sys setUnicastIPAddressEntry(row *MibUnicastIPAddressRow) (ret error) = iphlpapi.SetUnicastIpAddressEntry 94 | //sys createUnicastIPAddressEntry(row *MibUnicastIPAddressRow) (ret error) = iphlpapi.CreateUnicastIpAddressEntry 95 | //sys deleteUnicastIPAddressEntry(row *MibUnicastIPAddressRow) (ret error) = iphlpapi.DeleteUnicastIpAddressEntry 96 | 97 | // GetUnicastIPAddressTable function retrieves the unicast IP address table on the local computer. 98 | // https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getunicastipaddresstable 99 | func GetUnicastIPAddressTable(family AddressFamily) ([]MibUnicastIPAddressRow, error) { 100 | var tab *mibUnicastIPAddressTable 101 | err := getUnicastIPAddressTable(family, &tab) 102 | if err != nil { 103 | return nil, err 104 | } 105 | t := append(make([]MibUnicastIPAddressRow, 0, tab.numEntries), tab.get()...) 106 | tab.free() 107 | return t, nil 108 | } 109 | 110 | // 111 | // Anycast IP address-related functions 112 | // 113 | 114 | //sys getAnycastIPAddressTable(family AddressFamily, table **mibAnycastIPAddressTable) (ret error) = iphlpapi.GetAnycastIpAddressTable 115 | //sys getAnycastIPAddressEntry(row *MibAnycastIPAddressRow) (ret error) = iphlpapi.GetAnycastIpAddressEntry 116 | //sys createAnycastIPAddressEntry(row *MibAnycastIPAddressRow) (ret error) = iphlpapi.CreateAnycastIpAddressEntry 117 | //sys deleteAnycastIPAddressEntry(row *MibAnycastIPAddressRow) (ret error) = iphlpapi.DeleteAnycastIpAddressEntry 118 | 119 | // GetAnycastIPAddressTable function retrieves the anycast IP address table on the local computer. 120 | // https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getanycastipaddresstable 121 | func GetAnycastIPAddressTable(family AddressFamily) ([]MibAnycastIPAddressRow, error) { 122 | var tab *mibAnycastIPAddressTable 123 | err := getAnycastIPAddressTable(family, &tab) 124 | if err != nil { 125 | return nil, err 126 | } 127 | t := append(make([]MibAnycastIPAddressRow, 0, tab.numEntries), tab.get()...) 128 | tab.free() 129 | return t, nil 130 | } 131 | 132 | // 133 | // Routing-related functions 134 | // 135 | 136 | //sys getIPForwardTable2(family AddressFamily, table **mibIPforwardTable2) (ret error) = iphlpapi.GetIpForwardTable2 137 | //sys initializeIPForwardEntry(route *MibIPforwardRow2) = iphlpapi.InitializeIpForwardEntry 138 | //sys getIPForwardEntry2(route *MibIPforwardRow2) (ret error) = iphlpapi.GetIpForwardEntry2 139 | //sys setIPForwardEntry2(route *MibIPforwardRow2) (ret error) = iphlpapi.SetIpForwardEntry2 140 | //sys createIPForwardEntry2(route *MibIPforwardRow2) (ret error) = iphlpapi.CreateIpForwardEntry2 141 | //sys deleteIPForwardEntry2(route *MibIPforwardRow2) (ret error) = iphlpapi.DeleteIpForwardEntry2 142 | 143 | // GetIPForwardTable2 function retrieves the IP route entries on the local computer. 144 | // https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getipforwardtable2 145 | func GetIPForwardTable2(family AddressFamily) ([]MibIPforwardRow2, error) { 146 | var tab *mibIPforwardTable2 147 | err := getIPForwardTable2(family, &tab) 148 | if err != nil { 149 | return nil, err 150 | } 151 | t := append(make([]MibIPforwardRow2, 0, tab.numEntries), tab.get()...) 152 | tab.free() 153 | return t, nil 154 | } 155 | 156 | // 157 | // Notifications-related functions 158 | // 159 | 160 | // https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-notifyipinterfacechange 161 | //sys notifyIPInterfaceChange(family AddressFamily, callback uintptr, callerContext uintptr, initialNotification bool, notificationHandle *windows.Handle) (ret error) = iphlpapi.NotifyIpInterfaceChange 162 | 163 | // https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-notifyunicastipaddresschange 164 | //sys notifyUnicastIPAddressChange(family AddressFamily, callback uintptr, callerContext uintptr, initialNotification bool, notificationHandle *windows.Handle) (ret error) = iphlpapi.NotifyUnicastIpAddressChange 165 | 166 | // https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-notifyroutechange2 167 | //sys notifyRouteChange2(family AddressFamily, callback uintptr, callerContext uintptr, initialNotification bool, notificationHandle *windows.Handle) (ret error) = iphlpapi.NotifyRouteChange2 168 | 169 | // https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-cancelmibchangenotify2 170 | //sys cancelMibChangeNotify2(notificationHandle windows.Handle) (ret error) = iphlpapi.CancelMibChangeNotify2 171 | 172 | // 173 | // Undocumented DNS API 174 | // 175 | 176 | //sys setInterfaceDnsSettingsByPtr(guid *windows.GUID, settings *dnsInterfaceSettings) (ret error) = iphlpapi.SetInterfaceDnsSettings? 177 | //sys setInterfaceDnsSettingsByQwords(guid1 uintptr, guid2 uintptr, settings *dnsInterfaceSettings) (ret error) = iphlpapi.SetInterfaceDnsSettings? 178 | //sys setInterfaceDnsSettingsByDwords(guid1 uintptr, guid2 uintptr, guid3 uintptr, guid4 uintptr, settings *dnsInterfaceSettings) (ret error) = iphlpapi.SetInterfaceDnsSettings? 179 | 180 | // The GUID is passed by value, not by reference, which means different 181 | // things on different calling conventions. On amd64, this means it's 182 | // passed by reference anyway, while on arm, arm64, and 386, it's split 183 | // into words. 184 | func setInterfaceDnsSettings(guid windows.GUID, settings *dnsInterfaceSettings) error { 185 | words := (*[4]uintptr)(unsafe.Pointer(&guid)) 186 | switch runtime.GOARCH { 187 | case "amd64": 188 | return setInterfaceDnsSettingsByPtr(&guid, settings) 189 | case "arm64": 190 | return setInterfaceDnsSettingsByQwords(words[0], words[1], settings) 191 | case "arm", "386": 192 | return setInterfaceDnsSettingsByDwords(words[0], words[1], words[2], words[3], settings) 193 | default: 194 | panic("unknown calling convention") 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /tunnel/wintun_test.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package tunnel_test 7 | 8 | import ( 9 | "bytes" 10 | "crypto/rand" 11 | "encoding/binary" 12 | "fmt" 13 | "net" 14 | "sync" 15 | "testing" 16 | "time" 17 | 18 | "golang.org/x/sys/windows" 19 | 20 | "github.com/amnezia-vpn/amneziawg-go/tun" 21 | 22 | "github.com/amnezia-vpn/amneziawg-windows/elevate" 23 | "github.com/amnezia-vpn/amneziawg-windows/tunnel/winipcfg" 24 | ) 25 | 26 | func TestWintunOrdering(t *testing.T) { 27 | var tunDevice tun.Device 28 | err := elevate.DoAsSystem(func() error { 29 | var err error 30 | tunDevice, err = tun.CreateTUNWithRequestedGUID("tunordertest", &windows.GUID{12, 12, 12, [8]byte{12, 12, 12, 12, 12, 12, 12, 12}}, 1500) 31 | return err 32 | }) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | defer tunDevice.Close() 37 | nativeTunDevice := tunDevice.(*tun.NativeTun) 38 | luid := winipcfg.LUID(nativeTunDevice.LUID()) 39 | ip, ipnet, _ := net.ParseCIDR("10.82.31.4/24") 40 | err = luid.SetIPAddresses([]net.IPNet{{ip, ipnet.Mask}}) 41 | if err != nil { 42 | t.Fatal(err) 43 | } 44 | err = luid.SetRoutes([]*winipcfg.RouteData{{*ipnet, ipnet.IP, 0}}) 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | var token [32]byte 49 | _, err = rand.Read(token[:]) 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | var sockWrite net.Conn 54 | for i := 0; i < 1000; i++ { 55 | sockWrite, err = net.Dial("udp", "10.82.31.5:9999") 56 | if err == nil { 57 | defer sockWrite.Close() 58 | break 59 | } 60 | time.Sleep(time.Millisecond * 100) 61 | } 62 | if err != nil { 63 | t.Fatal(err) 64 | } 65 | var sockRead *net.UDPConn 66 | for i := 0; i < 1000; i++ { 67 | var listenAddress *net.UDPAddr 68 | listenAddress, err = net.ResolveUDPAddr("udp", "10.82.31.4:9999") 69 | if err != nil { 70 | continue 71 | } 72 | sockRead, err = net.ListenUDP("udp", listenAddress) 73 | if err == nil { 74 | defer sockRead.Close() 75 | break 76 | } 77 | time.Sleep(time.Millisecond * 100) 78 | } 79 | if err != nil { 80 | t.Fatal(err) 81 | } 82 | var wait sync.WaitGroup 83 | wait.Add(4) 84 | doneSockWrite := false 85 | doneTunWrite := false 86 | fatalErrors := make(chan error, 2) 87 | errors := make(chan error, 2) 88 | go func() { 89 | defer wait.Done() 90 | buffer := append(token[:], 0, 0, 0, 0, 0, 0, 0, 0) 91 | for sendingIndex := uint64(0); !doneSockWrite; sendingIndex++ { 92 | binary.LittleEndian.PutUint64(buffer[32:], sendingIndex) 93 | _, err := sockWrite.Write(buffer[:]) 94 | if err != nil { 95 | fatalErrors <- err 96 | } 97 | } 98 | }() 99 | go func() { 100 | defer wait.Done() 101 | packet := [20 + 8 + 32 + 8]byte{ 102 | 0x45, 0, 0, 20 + 8 + 32 + 8, 103 | 0, 0, 0, 0, 104 | 0x80, 0x11, 0, 0, 105 | 10, 82, 31, 5, 106 | 10, 82, 31, 4, 107 | 8888 >> 8, 8888 & 0xff, 9999 >> 8, 9999 & 0xff, 0, 8 + 32 + 8, 0, 0, 108 | } 109 | copy(packet[28:], token[:]) 110 | for sendingIndex := uint64(0); !doneTunWrite; sendingIndex++ { 111 | binary.BigEndian.PutUint16(packet[4:], uint16(sendingIndex)) 112 | var checksum uint32 113 | for i := 0; i < 20; i += 2 { 114 | if i != 10 { 115 | checksum += uint32(binary.BigEndian.Uint16(packet[i:])) 116 | } 117 | } 118 | binary.BigEndian.PutUint16(packet[10:], ^(uint16(checksum>>16) + uint16(checksum&0xffff))) 119 | binary.LittleEndian.PutUint64(packet[20+8+32:], sendingIndex) 120 | n, err := tunDevice.Write(packet[:], 0) 121 | if err != nil { 122 | fatalErrors <- err 123 | } 124 | if n == 0 { 125 | time.Sleep(time.Millisecond * 300) 126 | } 127 | } 128 | }() 129 | const packetsPerTest = 1 << 21 130 | go func() { 131 | defer func() { 132 | doneSockWrite = true 133 | wait.Done() 134 | }() 135 | var expectedIndex uint64 136 | for i := uint64(0); i < packetsPerTest; { 137 | var buffer [(1 << 16) - 1]byte 138 | bytesRead, err := tunDevice.Read(buffer[:], 0) 139 | if err != nil { 140 | fatalErrors <- err 141 | } 142 | if bytesRead < 0 || bytesRead > len(buffer) { 143 | continue 144 | } 145 | packet := buffer[:bytesRead] 146 | tokenPos := bytes.Index(packet, token[:]) 147 | if tokenPos == -1 || tokenPos+32+8 > len(packet) { 148 | continue 149 | } 150 | foundIndex := binary.LittleEndian.Uint64(packet[tokenPos+32:]) 151 | if foundIndex < expectedIndex { 152 | errors <- fmt.Errorf("Sock write, tun read: expected packet %d, received packet %d", expectedIndex, foundIndex) 153 | } 154 | expectedIndex = foundIndex + 1 155 | i++ 156 | } 157 | }() 158 | go func() { 159 | defer func() { 160 | doneTunWrite = true 161 | wait.Done() 162 | }() 163 | var expectedIndex uint64 164 | for i := uint64(0); i < packetsPerTest; { 165 | var buffer [(1 << 16) - 1]byte 166 | bytesRead, err := sockRead.Read(buffer[:]) 167 | if err != nil { 168 | fatalErrors <- err 169 | } 170 | if bytesRead < 0 || bytesRead > len(buffer) { 171 | continue 172 | } 173 | packet := buffer[:bytesRead] 174 | if len(packet) != 32+8 || !bytes.HasPrefix(packet, token[:]) { 175 | continue 176 | } 177 | foundIndex := binary.LittleEndian.Uint64(packet[32:]) 178 | if foundIndex < expectedIndex { 179 | errors <- fmt.Errorf("Tun write, sock read: expected packet %d, received packet %d", expectedIndex, foundIndex) 180 | } 181 | expectedIndex = foundIndex + 1 182 | i++ 183 | } 184 | }() 185 | done := make(chan bool, 2) 186 | doneFunc := func() { 187 | wait.Wait() 188 | done <- true 189 | } 190 | defer doneFunc() 191 | go doneFunc() 192 | for { 193 | select { 194 | case err := <-fatalErrors: 195 | t.Fatal(err) 196 | case err := <-errors: 197 | t.Error(err) 198 | case <-done: 199 | return 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /version/certificate_test.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package version 7 | 8 | import ( 9 | "fmt" 10 | "path/filepath" 11 | "testing" 12 | 13 | "golang.org/x/sys/windows" 14 | ) 15 | 16 | func TestExtractCertificateNames(t *testing.T) { 17 | system32, err := windows.GetSystemDirectory() 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | names, err := extractCertificateNames(filepath.Join(system32, "ntoskrnl.exe")) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | for i, name := range names { 26 | fmt.Printf("%d: %s\n", i, name) 27 | } 28 | } 29 | 30 | func TestExtractCertificateExtension(t *testing.T) { 31 | system32, err := windows.GetSystemDirectory() 32 | if err != nil { 33 | t.Fatal(err) 34 | } 35 | policies, err := extractCertificatePolicies(filepath.Join(system32, "ntoskrnl.exe"), "2.5.29.32") 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | for i, policy := range policies { 40 | fmt.Printf("%d: %s\n", i, policy) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /version/official.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package version 7 | 8 | import ( 9 | "errors" 10 | "os" 11 | "unsafe" 12 | 13 | "golang.org/x/sys/windows" 14 | ) 15 | 16 | const ( 17 | officialCommonName = "WireGuard LLC" 18 | evPolicyOid = "2.23.140.1.3" 19 | policyExtensionOid = "2.5.29.32" 20 | ) 21 | 22 | // These are easily by-passable checks, which do not serve serve security purposes. 23 | // DO NOT PLACE SECURITY-SENSITIVE FUNCTIONS IN THIS FILE 24 | 25 | func IsRunningOfficialVersion() bool { 26 | path, err := os.Executable() 27 | if err != nil { 28 | return false 29 | } 30 | 31 | names, err := extractCertificateNames(path) 32 | if err != nil { 33 | return false 34 | } 35 | for _, name := range names { 36 | if name == officialCommonName { 37 | return true 38 | } 39 | } 40 | return false 41 | } 42 | 43 | func IsRunningEVSigned() bool { 44 | path, err := os.Executable() 45 | if err != nil { 46 | return false 47 | } 48 | 49 | policies, err := extractCertificatePolicies(path, policyExtensionOid) 50 | if err != nil { 51 | return false 52 | } 53 | for _, policy := range policies { 54 | if policy == evPolicyOid { 55 | return true 56 | } 57 | } 58 | return false 59 | } 60 | 61 | func extractCertificateNames(path string) ([]string, error) { 62 | path16, err := windows.UTF16PtrFromString(path) 63 | if err != nil { 64 | return nil, err 65 | } 66 | var certStore windows.Handle 67 | err = windows.CryptQueryObject(windows.CERT_QUERY_OBJECT_FILE, unsafe.Pointer(path16), windows.CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, windows.CERT_QUERY_FORMAT_FLAG_ALL, 0, nil, nil, nil, &certStore, nil, nil) 68 | if err != nil { 69 | return nil, err 70 | } 71 | defer windows.CertCloseStore(certStore, 0) 72 | var cert *windows.CertContext 73 | var names []string 74 | for { 75 | cert, err = windows.CertEnumCertificatesInStore(certStore, cert) 76 | if err != nil { 77 | if errors.Is(err, windows.Errno(windows.CRYPT_E_NOT_FOUND)) { 78 | break 79 | } 80 | return nil, err 81 | } 82 | if cert == nil { 83 | break 84 | } 85 | nameLen := windows.CertGetNameString(cert, windows.CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nil, nil, 0) 86 | if nameLen == 0 { 87 | continue 88 | } 89 | name16 := make([]uint16, nameLen) 90 | if windows.CertGetNameString(cert, windows.CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nil, &name16[0], nameLen) != nameLen { 91 | continue 92 | } 93 | if name16[0] == 0 { 94 | continue 95 | } 96 | names = append(names, windows.UTF16ToString(name16)) 97 | } 98 | if names == nil { 99 | return nil, windows.Errno(windows.CRYPT_E_NOT_FOUND) 100 | } 101 | return names, nil 102 | } 103 | 104 | func extractCertificatePolicies(path string, oid string) ([]string, error) { 105 | path16, err := windows.UTF16PtrFromString(path) 106 | if err != nil { 107 | return nil, err 108 | } 109 | oid8, err := windows.BytePtrFromString(oid) 110 | if err != nil { 111 | return nil, err 112 | } 113 | var certStore windows.Handle 114 | err = windows.CryptQueryObject(windows.CERT_QUERY_OBJECT_FILE, unsafe.Pointer(path16), windows.CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, windows.CERT_QUERY_FORMAT_FLAG_ALL, 0, nil, nil, nil, &certStore, nil, nil) 115 | if err != nil { 116 | return nil, err 117 | } 118 | defer windows.CertCloseStore(certStore, 0) 119 | var cert *windows.CertContext 120 | var policies []string 121 | for { 122 | cert, err = windows.CertEnumCertificatesInStore(certStore, cert) 123 | if err != nil { 124 | if errors.Is(err, windows.Errno(windows.CRYPT_E_NOT_FOUND)) { 125 | break 126 | } 127 | return nil, err 128 | } 129 | if cert == nil { 130 | break 131 | } 132 | ci := (*windows.CertInfo)(unsafe.Pointer(cert.CertInfo)) 133 | ext := windows.CertFindExtension(oid8, ci.CountExtensions, ci.Extensions) 134 | if ext == nil { 135 | continue 136 | } 137 | var decodedLen uint32 138 | err = windows.CryptDecodeObject(windows.X509_ASN_ENCODING|windows.PKCS_7_ASN_ENCODING, ext.ObjId, ext.Value.Data, ext.Value.Size, 0, nil, &decodedLen) 139 | if err != nil { 140 | return nil, err 141 | } 142 | bytes := make([]byte, decodedLen) 143 | certPoliciesInfo := (*windows.CertPoliciesInfo)(unsafe.Pointer(&bytes[0])) 144 | err = windows.CryptDecodeObject(windows.X509_ASN_ENCODING|windows.PKCS_7_ASN_ENCODING, ext.ObjId, ext.Value.Data, ext.Value.Size, 0, unsafe.Pointer(&bytes[0]), &decodedLen) 145 | if err != nil { 146 | return nil, err 147 | } 148 | for i := uintptr(0); i < uintptr(certPoliciesInfo.Count); i++ { 149 | cp := (*windows.CertPolicyInfo)(unsafe.Pointer(uintptr(unsafe.Pointer(certPoliciesInfo.PolicyInfos)) + i*unsafe.Sizeof(*certPoliciesInfo.PolicyInfos))) 150 | policies = append(policies, windows.BytePtrToString(cp.Identifier)) 151 | } 152 | } 153 | if policies == nil { 154 | return nil, windows.Errno(windows.CRYPT_E_NOT_FOUND) 155 | } 156 | return policies, nil 157 | } 158 | -------------------------------------------------------------------------------- /version/protocol.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package version 7 | 8 | import ( 9 | "runtime/debug" 10 | "strings" 11 | ) 12 | 13 | func ProtoImplementation() string { 14 | info, ok := debug.ReadBuildInfo() 15 | if !ok { 16 | return "unknown" 17 | } 18 | for _, dep := range info.Deps { 19 | if dep.Path == "golang.zx2c4.com/wireguard" { 20 | parts := strings.Split(dep.Version, "-") 21 | if len(parts) == 3 && len(parts[2]) == 12 { 22 | return parts[2][:7] 23 | } 24 | return dep.Version 25 | } 26 | } 27 | return "unknown" 28 | } 29 | -------------------------------------------------------------------------------- /version/useragent.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package version 7 | 8 | import ( 9 | "fmt" 10 | "runtime" 11 | ) 12 | 13 | func Arch() string { 14 | switch runtime.GOARCH { 15 | case "arm", "arm64", "amd64": 16 | return runtime.GOARCH 17 | case "386": 18 | return "x86" 19 | default: 20 | panic("Unrecognized GOARCH") 21 | } 22 | } 23 | 24 | func UserAgent() string { 25 | return fmt.Sprintf("WireGuard/%s (%s; %s)", Number, OsName(), Arch()) 26 | } 27 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT 2 | * 3 | * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. 4 | */ 5 | 6 | package version 7 | 8 | const ( 9 | Number = "0.3.15" 10 | ) 11 | --------------------------------------------------------------------------------