Release 2.6.1: resolve AWG endpoint hostnames to IP before connect.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user