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
+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))