Release 2.6.1: resolve AWG endpoint hostnames to IP before connect.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Navis
2026-07-29 15:42:55 +03:00
co-authored by Cursor
parent 99bbac20e2
commit 4ecfb9bed7
23 changed files with 233 additions and 34 deletions
+3
View File
@@ -243,6 +243,9 @@ https://evilfox.win/
Некоторые AV (Bkav, Microsoft Wacapew/Wacatac, Ikarus Trojan.WinGo.Agent, Google, Trapmine) часто помечают **любые** неподписанные Go-бинарники с сетью и сменой системного прокси.
В 2.6.1:
- AmneziaWG: перед подключением Endpoint-хост резолвится в IP (предпочтительно IPv4) — обход сбоя IpcSet на Windows («No such host is known»).
В 2.6.0:
- исправлен разбор AmneziaWG: ключ в `awg://KEY@host`, JSON/`vpn://`, пробелы в именах полей, URL-safe base64.
+2 -2
View File
@@ -12,8 +12,8 @@ android {
applicationId = "win.evilfox.navis"
minSdk = 26
targetSdk = 35
versionCode = 260
versionName = "2.6.0"
versionCode = 261
versionName = "2.6.1"
vectorDrawables.useSupportLibrary = true
ndk {
abiFilters += listOf("arm64-v8a", "armeabi-v7a", "x86_64")
@@ -97,7 +97,7 @@ fun NavisScreen(
Text("Navis", fontSize = 28.sp, fontWeight = FontWeight.Black, color = Ink)
Spacer(Modifier.width(8.dp))
Surface(color = Soft, shape = RoundedCornerShape(999.dp)) {
Text("2.6", Modifier.padding(horizontal = 8.dp, vertical = 2.dp), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = AccentDeep)
Text("2.6.1", Modifier.padding(horizontal = 8.dp, vertical = 2.dp), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = AccentDeep)
}
}
Text("Android · Naive · Hy2 · AWG · Xray", color = Muted, fontSize = 13.sp)
+3 -3
View File
@@ -28,11 +28,11 @@ if errorlevel 1 exit /b 1
go build -o "tools\packmac\packmac.exe" .\tools\packmac
if errorlevel 1 exit /b 1
tools\packmac\packmac.exe -bin "dist\navis-release\darwin-arm64\Navis" -out "dist\navis-release\darwin-arm64" -version 2.6.0 -arch arm64
tools\packmac\packmac.exe -bin "dist\navis-release\darwin-arm64\Navis" -out "dist\navis-release\darwin-arm64" -version 2.6.1 -arch arm64
if errorlevel 1 exit /b 1
tools\packmac\packmac.exe -bin "dist\navis-release\darwin-amd64\Navis" -out "dist\navis-release\darwin-amd64" -version 2.6.0 -arch amd64
tools\packmac\packmac.exe -bin "dist\navis-release\darwin-amd64\Navis" -out "dist\navis-release\darwin-amd64" -version 2.6.1 -arch amd64
if errorlevel 1 exit /b 1
tools\packmac\packmac.exe -bin "dist\navis-release\darwin-universal\Navis" -out "dist\navis-release\darwin-universal" -version 2.6.0 -arch universal
tools\packmac\packmac.exe -bin "dist\navis-release\darwin-universal\Navis" -out "dist\navis-release\darwin-universal" -version 2.6.1 -arch universal
if errorlevel 1 exit /b 1
echo Built:
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+7 -7
View File
@@ -1,22 +1,22 @@
{
"version": "2.6.0",
"notes": "Исправление AmneziaWG: ключи в awg://KEY@host, JSON/vpn://, пробелы в полях; без автоподключения по умолчанию",
"version": "2.6.1",
"notes": "AWG: любой hostname Endpoint резолвится в IP до подключения (фикс No such host is known)",
"platform": "windows-amd64",
"os": "windows",
"arch": "amd64",
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
"sha256": "5d7d262c7804779aff8ccc4ef48ed3b280eb460ead3cc95386added909fb3fb2",
"sha256": "08f001a3850ca5bd4f73b7ba4d21e8664ee9fc8120a87877f35d8f20a5ed6993",
"mandatory": false,
"platforms": {
"windows-amd64": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
"sha256": "5d7d262c7804779aff8ccc4ef48ed3b280eb460ead3cc95386added909fb3fb2",
"sha256": "08f001a3850ca5bd4f73b7ba4d21e8664ee9fc8120a87877f35d8f20a5ed6993",
"os": "windows",
"arch": "amd64"
},
"darwin-arm64": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis",
"sha256": "31ae3cca6cdfd9239b1812251a2c55e7c8ea879ae543020d1ca7f4138585a0b8",
"sha256": "b67e8b3fb5db8b97f85dba4c0d307c137c67fb6b56b36f8b2a7adf327ff226d4",
"os": "darwin",
"arch": "arm64",
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg",
@@ -24,7 +24,7 @@
},
"darwin-amd64": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis",
"sha256": "d4ed12f647c2ce87a266999f73c66a54873eb9bd77f75dc49d5cb4f36f94aff1",
"sha256": "37385c52cb397f60acc699adecb0ffc1843106d50288b1304393daf63083b4b7",
"os": "darwin",
"arch": "amd64",
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.dmg",
@@ -32,7 +32,7 @@
},
"darwin-universal": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-universal/Navis",
"sha256": "e1dd86693e9b002afa7fa2921010e2d9909b94492dcd0d957165e46304c5bcdb",
"sha256": "ce250733cc7f5ed50b42f5cc106be9872b057966e7a73bcee78974d85748c2fb",
"os": "darwin",
"arch": "universal",
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-universal/Navis.dmg",
+7 -7
View File
@@ -1,22 +1,22 @@
{
"version": "2.6.0",
"notes": "Исправление AmneziaWG: ключи в awg://KEY@host, JSON/vpn://, пробелы в полях; без автоподключения по умолчанию",
"version": "2.6.1",
"notes": "AWG: любой hostname Endpoint резолвится в IP до подключения (фикс No such host is known)",
"platform": "windows-amd64",
"os": "windows",
"arch": "amd64",
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
"sha256": "5d7d262c7804779aff8ccc4ef48ed3b280eb460ead3cc95386added909fb3fb2",
"sha256": "08f001a3850ca5bd4f73b7ba4d21e8664ee9fc8120a87877f35d8f20a5ed6993",
"mandatory": false,
"platforms": {
"windows-amd64": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
"sha256": "5d7d262c7804779aff8ccc4ef48ed3b280eb460ead3cc95386added909fb3fb2",
"sha256": "08f001a3850ca5bd4f73b7ba4d21e8664ee9fc8120a87877f35d8f20a5ed6993",
"os": "windows",
"arch": "amd64"
},
"darwin-arm64": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis",
"sha256": "31ae3cca6cdfd9239b1812251a2c55e7c8ea879ae543020d1ca7f4138585a0b8",
"sha256": "b67e8b3fb5db8b97f85dba4c0d307c137c67fb6b56b36f8b2a7adf327ff226d4",
"os": "darwin",
"arch": "arm64",
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg",
@@ -24,7 +24,7 @@
},
"darwin-amd64": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis",
"sha256": "d4ed12f647c2ce87a266999f73c66a54873eb9bd77f75dc49d5cb4f36f94aff1",
"sha256": "37385c52cb397f60acc699adecb0ffc1843106d50288b1304393daf63083b4b7",
"os": "darwin",
"arch": "amd64",
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.dmg",
@@ -32,7 +32,7 @@
},
"darwin-universal": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-universal/Navis",
"sha256": "e1dd86693e9b002afa7fa2921010e2d9909b94492dcd0d957165e46304c5bcdb",
"sha256": "ce250733cc7f5ed50b42f5cc106be9872b057966e7a73bcee78974d85748c2fb",
"os": "darwin",
"arch": "universal",
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-universal/Navis.dmg",
+1 -1
View File
@@ -725,7 +725,7 @@
<div class="brand-wrap">
<div class="brand-row">
<h1 class="brand">Navis</h1>
<span class="badge-ver">2.6</span>
<span class="badge-ver">2.6.1</span>
</div>
<p class="tagline">Быстрый клиент · Naive · Hy2 · AWG · Xray</p>
</div>
+117 -1
View File
@@ -1,6 +1,7 @@
package awg
import (
"context"
"encoding/base64"
"encoding/hex"
"encoding/json"
@@ -9,6 +10,7 @@ import (
"net/netip"
"strconv"
"strings"
"time"
)
// Conf is a parsed AmneziaWG / WireGuard client configuration (AWG 2.0 fields included).
@@ -444,7 +446,117 @@ func HostPort(raw string) (host, port string, err error) {
return h, p, nil
}
// lookupIP is overridable in tests.
var lookupIP = defaultLookupIP
func defaultLookupIP(host string) ([]net.IP, error) {
ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
defer cancel()
try := func(r *net.Resolver) ([]net.IP, error) {
addrs, err := r.LookupIPAddr(ctx, host)
if err != nil {
return nil, err
}
out := make([]net.IP, 0, len(addrs))
for _, a := range addrs {
if a.IP != nil {
out = append(out, a.IP)
}
}
if len(out) == 0 {
return nil, fmt.Errorf("нет IP-адресов")
}
return out, nil
}
// PreferGo avoids flaky Windows WinDNS "No such host" for some A/AAAA shapes.
if ips, err := try(&net.Resolver{PreferGo: true, Dial: resolverDial}); err == nil {
return ips, nil
}
if ips, err := try(net.DefaultResolver); err == nil {
return ips, nil
}
// Last resort: classic LookupIP (system).
return net.LookupIP(host)
}
func resolverDial(ctx context.Context, network, _ string) (net.Conn, error) {
d := net.Dialer{Timeout: 3 * time.Second}
var last error
for _, dns := range []string{"1.1.1.1:53", "8.8.8.8:53", "9.9.9.9:53"} {
c, err := d.DialContext(ctx, network, dns)
if err == nil {
return c, nil
}
last = err
}
return nil, last
}
// ResolveEndpoint turns "hostname:port" into "ip:port" for amneziawg-go IpcSet
// (which fails on bare hostnames with "No such host is known"). Prefers IPv4.
func ResolveEndpoint(endpoint string) (string, error) {
return resolveEndpointWith(endpoint, lookupIP)
}
// ResolveEndpoint rewrites c.Endpoint to a resolved IP:port (IPv4 preferred).
func (c *Conf) ResolveEndpoint() error {
resolved, err := ResolveEndpoint(c.Endpoint)
if err != nil {
return err
}
c.Endpoint = resolved
return nil
}
func resolveEndpointWith(endpoint string, lookup func(string) ([]net.IP, error)) (string, error) {
endpoint = strings.TrimSpace(endpoint)
if endpoint == "" {
return "", fmt.Errorf("awg: нет Endpoint")
}
host, port, err := net.SplitHostPort(endpoint)
if err != nil {
host = trimBrackets(endpoint)
port = "51820"
} else {
host = trimBrackets(host)
}
if host == "" {
return "", fmt.Errorf("awg: нет Endpoint")
}
if ip := net.ParseIP(host); ip != nil {
return net.JoinHostPort(ip.String(), port), nil
}
ips, err := lookup(host)
if err != nil {
return "", fmt.Errorf("awg: не удалось разрешить DNS для Endpoint %q: %v", host, err)
}
var v4, v6 net.IP
for _, ip := range ips {
if ip == nil {
continue
}
if ip4 := ip.To4(); ip4 != nil {
v4 = ip4
break
}
if v6 == nil {
v6 = ip
}
}
chosen := v4
if chosen == nil {
chosen = v6
}
if chosen == nil {
return "", fmt.Errorf("awg: не удалось разрешить DNS для Endpoint %q: нет IP-адресов", host)
}
return net.JoinHostPort(chosen.String(), port), nil
}
// ToIPC builds the amneziawg-go UAPI configuration string.
// Endpoint hostnames are resolved to IP first (IPv4 preferred).
func (c Conf) ToIPC() (string, error) {
priv, err := decodeKey(c.PrivateKey)
if err != nil {
@@ -454,6 +566,10 @@ func (c Conf) ToIPC() (string, error) {
if err != nil {
return "", fmt.Errorf("public key: %w", err)
}
endpoint, err := ResolveEndpoint(c.Endpoint)
if err != nil {
return "", err
}
var b strings.Builder
b.WriteString("private_key=")
b.WriteString(hex.EncodeToString(priv))
@@ -477,7 +593,7 @@ func (c Conf) ToIPC() (string, error) {
b.WriteString("\npublic_key=")
b.WriteString(hex.EncodeToString(pub))
b.WriteString("\nendpoint=")
b.WriteString(c.Endpoint)
b.WriteString(endpoint)
for _, ip := range c.AllowedIPs {
b.WriteString("\nallowed_ip=")
b.WriteString(strings.TrimSpace(ip))
+76
View File
@@ -2,6 +2,7 @@ package awg
import (
"encoding/base64"
"net"
"testing"
)
@@ -139,6 +140,81 @@ func TestDecodeKeyURLSafe(t *testing.T) {
}
}
func TestResolveEndpointPrefersIPv4(t *testing.T) {
got, err := resolveEndpointWith("peer.example:41421", func(host string) ([]net.IP, error) {
if host != "peer.example" {
t.Fatalf("host %q", host)
}
return []net.IP{
net.ParseIP("2001:db8::1"),
net.ParseIP("203.0.113.44"),
}, nil
})
if err != nil {
t.Fatal(err)
}
if got != "203.0.113.44:41421" {
t.Fatalf("got %q", got)
}
}
func TestResolveEndpointAlreadyIP(t *testing.T) {
got, err := resolveEndpointWith("203.0.113.10:51820", func(string) ([]net.IP, error) {
t.Fatal("lookup should not run for IP")
return nil, nil
})
if err != nil || got != "203.0.113.10:51820" {
t.Fatalf("%q %v", got, err)
}
}
func TestResolveEndpointDNSError(t *testing.T) {
_, err := resolveEndpointWith("missing.invalid:41421", func(string) ([]net.IP, error) {
return nil, &net.DNSError{Err: "no such host", Name: "missing.invalid", IsNotFound: true}
})
if err == nil || !stringsContains(err.Error(), "не удалось разрешить DNS") {
t.Fatalf("expected Russian DNS error, got %v", err)
}
}
func TestToIPCResolvesEndpoint(t *testing.T) {
old := lookupIP
lookupIP = func(host string) ([]net.IP, error) {
return []net.IP{net.ParseIP("198.51.100.9")}, nil
}
defer func() { lookupIP = old }()
c := Conf{
PrivateKey: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
PublicKey: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEE=",
Address: []string{"10.8.0.2/32"},
Endpoint: "peer.example:41421",
}
ipc, err := c.ToIPC()
if err != nil {
t.Fatal(err)
}
if !stringsContains(ipc, "endpoint=198.51.100.9:41421") {
t.Fatalf("ipc missing resolved endpoint: %s", ipc)
}
}
func TestConfResolveEndpointMutates(t *testing.T) {
old := lookupIP
lookupIP = func(string) ([]net.IP, error) {
return []net.IP{net.ParseIP("198.51.100.1")}, nil
}
defer func() { lookupIP = old }()
c := Conf{Endpoint: "host.example:41421"}
if err := c.ResolveEndpoint(); err != nil {
t.Fatal(err)
}
if c.Endpoint != "198.51.100.1:41421" {
t.Fatalf("endpoint %q", c.Endpoint)
}
}
func containsAll(s string, parts ...string) bool {
for _, p := range parts {
if !stringsContains(s, p) {
+4
View File
@@ -46,6 +46,10 @@ func (e *Engine) Start(ctx context.Context, profile config.Profile, _ string) er
if err != nil {
return err
}
// amneziawg-go IpcSet does not resolve hostnames reliably (Windows WSAHOST_NOT_FOUND).
if err := cfg.ResolveEndpoint(); err != nil {
return err
}
ipc, err := cfg.ToIPC()
if err != nil {
return err
+1 -1
View File
@@ -17,7 +17,7 @@ import (
)
// CurrentVersion is the shipped client version.
const CurrentVersion = "2.6.0"
const CurrentVersion = "2.6.1"
// DefaultManifestURL is the update feed (hosted in the project git repo).
const DefaultManifestURL = "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/update.json"
+7 -7
View File
@@ -1,22 +1,22 @@
{
"version": "2.6.0",
"notes": "Исправление AmneziaWG: ключи в awg://KEY@host, JSON/vpn://, пробелы в полях; без автоподключения по умолчанию",
"version": "2.6.1",
"notes": "AWG: любой hostname Endpoint резолвится в IP до подключения (фикс No such host is known)",
"platform": "windows-amd64",
"os": "windows",
"arch": "amd64",
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
"sha256": "5d7d262c7804779aff8ccc4ef48ed3b280eb460ead3cc95386added909fb3fb2",
"sha256": "08f001a3850ca5bd4f73b7ba4d21e8664ee9fc8120a87877f35d8f20a5ed6993",
"mandatory": false,
"platforms": {
"windows-amd64": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe",
"sha256": "5d7d262c7804779aff8ccc4ef48ed3b280eb460ead3cc95386added909fb3fb2",
"sha256": "08f001a3850ca5bd4f73b7ba4d21e8664ee9fc8120a87877f35d8f20a5ed6993",
"os": "windows",
"arch": "amd64"
},
"darwin-arm64": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis",
"sha256": "31ae3cca6cdfd9239b1812251a2c55e7c8ea879ae543020d1ca7f4138585a0b8",
"sha256": "b67e8b3fb5db8b97f85dba4c0d307c137c67fb6b56b36f8b2a7adf327ff226d4",
"os": "darwin",
"arch": "arm64",
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg",
@@ -24,7 +24,7 @@
},
"darwin-amd64": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis",
"sha256": "d4ed12f647c2ce87a266999f73c66a54873eb9bd77f75dc49d5cb4f36f94aff1",
"sha256": "37385c52cb397f60acc699adecb0ffc1843106d50288b1304393daf63083b4b7",
"os": "darwin",
"arch": "amd64",
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.dmg",
@@ -32,7 +32,7 @@
},
"darwin-universal": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-universal/Navis",
"sha256": "e1dd86693e9b002afa7fa2921010e2d9909b94492dcd0d957165e46304c5bcdb",
"sha256": "ce250733cc7f5ed50b42f5cc106be9872b057966e7a73bcee78974d85748c2fb",
"os": "darwin",
"arch": "universal",
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-universal/Navis.dmg",
+4 -4
View File
@@ -1,7 +1,7 @@
{
"FixedFileInfo": {
"FileVersion": { "Major": 2, "Minor": 6, "Patch": 0, "Build": 0 },
"ProductVersion": { "Major": 2, "Minor": 6, "Patch": 0, "Build": 0 },
"FileVersion": { "Major": 2, "Minor": 6, "Patch": 1, "Build": 0 },
"ProductVersion": { "Major": 2, "Minor": 6, "Patch": 1, "Build": 0 },
"FileFlagsMask": "3f",
"FileFlags": "00",
"FileOS": "40004",
@@ -11,12 +11,12 @@
"StringFileInfo": {
"CompanyName": "EvilFox",
"FileDescription": "Navis 2 — VPN client (Naive / Hy2 / AWG / VLESS / VMess / Trojan)",
"FileVersion": "2.6.0.0",
"FileVersion": "2.6.1.0",
"InternalName": "Navis",
"LegalCopyright": "Copyright (c) EvilFox",
"OriginalFilename": "Navis.exe",
"ProductName": "Navis",
"ProductVersion": "2.6.0.0",
"ProductVersion": "2.6.1.0",
"Comments": "Open-source VPN/proxy client. https://evilfox.win/"
},
"VarFileInfo": {