LjhAUMEM
2026-09-20 19:07:51 +00:00
committed by RPRX
parent 8267cf953a
commit efc9e6da62
6 changed files with 271 additions and 262 deletions
+7 -23
View File
@@ -59,14 +59,13 @@ func (c *WireGuardPeerConfig) Build() (*wireguard.PeerConfig, error) {
type WireGuardConfig struct {
IsClient bool `json:""`
NoKernelTun bool `json:"noKernelTun"`
SecretKey string `json:"secretKey"`
Address []string `json:"address"`
Peers []*WireGuardPeerConfig `json:"peers"`
MTU int32 `json:"mtu"`
Reserved []byte `json:"reserved"`
DomainStrategy string `json:"domainStrategy"`
DNS []string `json:"remoteDNS"`
NoKernelTun bool `json:"noKernelTun"`
SecretKey string `json:"secretKey"`
Address []string `json:"address"`
Peers []*WireGuardPeerConfig `json:"peers"`
MTU int32 `json:"mtu"`
Reserved []byte `json:"reserved"`
DNS []string `json:"remoteDNS"`
}
func (c *WireGuardConfig) Build() (proto.Message, error) {
@@ -125,21 +124,6 @@ func (c *WireGuardConfig) Build() (proto.Message, error) {
}
config.Reserved = c.Reserved
switch strings.ToLower(c.DomainStrategy) {
case "forceip", "":
config.DomainStrategy = wireguard.DeviceConfig_FORCE_IP
case "forceipv4":
config.DomainStrategy = wireguard.DeviceConfig_FORCE_IP4
case "forceipv6":
config.DomainStrategy = wireguard.DeviceConfig_FORCE_IP6
case "forceipv4v6":
config.DomainStrategy = wireguard.DeviceConfig_FORCE_IP46
case "forceipv6v4":
config.DomainStrategy = wireguard.DeviceConfig_FORCE_IP64
default:
return nil, errors.New("unsupported domain strategy: ", c.DomainStrategy)
}
config.IsClient = c.IsClient
config.NoKernelTun = c.NoKernelTun
config.DNS = c.DNS
+1 -1
View File
@@ -123,7 +123,7 @@ func (t *Handler) Start() error {
iface := updater.Get()
if iface == nil {
errors.LogInfo(context.Background(), "[tun] falied to set interface > iface == nil")
return nil
return errors.New("iface not found")
}
return c.Control(func(fd uintptr) {
addrPort, _ := netip.ParseAddrPort(address)
+93 -119
View File
@@ -3,7 +3,6 @@ package wireguard
import (
"context"
"fmt"
gonet "net"
"net/netip"
"reflect"
"strings"
@@ -32,11 +31,6 @@ import (
"golang.zx2c4.com/wireguard/device"
)
type entry struct {
got []net.IP
time time.Time
}
type Handler struct {
conf *DeviceConfig
policyManager policy.Manager
@@ -50,11 +44,6 @@ type Handler struct {
tnet *Net
dev *device.Device
mu sync.Mutex
// TODO: cache cleanup loop
local bool
cache map[string]entry
cacheMu sync.Mutex
}
func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
@@ -110,15 +99,10 @@ func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
return nil, err
}
local := false
dns := conf.DNS
if len(dns) == 0 {
dns = []string{"1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001"}
}
if len(dns) == 1 && dns[0] == "local" {
local = true
dns = nil
}
dnses := make([]netip.Addr, 0, len(dns))
for _, dns := range dns {
dnses = append(dnses, netip.MustParseAddr(dns))
@@ -152,9 +136,6 @@ func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
tun: tun,
tnet: tnet,
local: local,
cache: make(map[string]entry),
}, nil
}
@@ -173,22 +154,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
return err
}
var addr netip.Addr
if ob.Target.Address.Family().IsDomain() {
ip, err := h.resolveRemote(ob.Target.Address.String())
if err != nil {
return errors.New("failed to resolve domain").Base(err)
}
addr, _ = netip.AddrFromSlice(ip)
} else {
addr, _ = netip.AddrFromSlice(ob.Target.Address.IP())
}
addrPort := netip.AddrPortFrom(addr, ob.Target.Port.Value())
if !addrPort.IsValid() {
return errors.New("invalid target ", ob.Target)
}
var newCtx context.Context
var newCancel context.CancelFunc
if session.TimeoutOnlyFromContext(ctx) {
@@ -217,10 +182,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
var err error
if sessionPolicy.Timeouts.Handshake != 0 {
timeoutCtx, timeoutCancel := context.WithTimeout(ctx, sessionPolicy.Timeouts.Handshake)
conn, err = h.tnet.DialContextTCPAddrPort(timeoutCtx, addrPort)
conn, err = h.tnet.DialContext(timeoutCtx, "tcp", ob.Target.NetAddr())
timeoutCancel()
} else {
conn, err = h.tnet.DialContextTCPAddrPort(ctx, addrPort)
conn, err = h.tnet.Dial("tcp", ob.Target.NetAddr())
}
if err != nil {
return errors.New("failed to create TCP connection").Base(err)
@@ -229,15 +194,14 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
reader = buf.NewReader(conn)
writer = buf.NewWriter(conn)
case net.Network_UDP:
conn, err := h.tnet.DialUDPAddrPort(netip.AddrPort{}, addrPort)
conn, err := h.tnet.Dial("udp", ob.Target.NetAddr())
if err != nil {
return errors.New("failed to create UDP connection").Base(err)
}
defer conn.Close()
c := &udpConnClient{
PacketConn: conn.(*internet.PacketConnWrapper).PacketConn,
resolveFunc: h.resolveRemote,
dest: gonet.UDPAddrFromAddrPort(addrPort),
PacketConn: conn.(*internet.PacketConnWrapper).PacketConn,
dest: conn.RemoteAddr().(*net.UDPAddr),
}
reader = c
writer = c
@@ -372,87 +336,48 @@ func (h *Handler) init(ctx context.Context) error {
}
func (h *Handler) resolveLocal(host string) (net.IP, error) {
return h.resolveDomain(host, h.conf.DomainStrategy, func(host string) ([]net.IP, uint32, error) {
return h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
})
}
func (h *Handler) resolveRemote(host string) (net.IP, error) {
return h.resolveDomain(host, h.conf.DomainStrategy, func(host string) ([]net.IP, uint32, error) {
if h.local {
return h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
}
return h.tnet.LookupHost(host)
})
}
func (h *Handler) resolveDomain(host string, strategy DeviceConfig_DomainStrategy, lookupIP func(host string) ([]net.IP, uint32, error)) (net.IP, error) {
if ip := net.ParseIP(host); ip != nil {
return ip, nil
}
h.cacheMu.Lock()
if entry, ok := h.cache[host]; ok {
if time.Now().Before(entry.time) {
h.cacheMu.Unlock()
return entry.got[dice.Roll(len(entry.got))], nil
}
delete(h.cache, host)
}
h.cacheMu.Unlock()
ips, ttl, err := lookupIP(host)
ips, _, err := h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
if err != nil {
return nil, err
}
if len(ips) == 0 {
return nil, dns.ErrEmptyResponse
}
var got4, got6 []net.IP
for _, ip := range ips {
if ip.To4() != nil {
got4 = append(got4, ip)
} else {
got6 = append(got6, ip)
got := ips
if h.streamSettings.SocketSettings != nil {
var got4, got6 []net.IP
for _, ip := range ips {
if ip.To4() != nil {
got4 = append(got4, ip)
} else {
got6 = append(got6, ip)
}
}
}
var got []net.IP
switch strategy {
case DeviceConfig_FORCE_IP:
got = ips
return ips[dice.Roll(len(ips))], nil
case DeviceConfig_FORCE_IP4:
got = got4
case DeviceConfig_FORCE_IP6:
got = got6
case DeviceConfig_FORCE_IP46:
got = got4
if len(got) == 0 {
got = got6
}
case DeviceConfig_FORCE_IP64:
got = got6
if len(got) == 0 {
switch h.streamSettings.SocketSettings.DomainStrategy {
case internet.DomainStrategy_AS_IS, internet.DomainStrategy_USE_IP, internet.DomainStrategy_FORCE_IP:
got = ips
case internet.DomainStrategy_USE_IP4, internet.DomainStrategy_FORCE_IP4:
got = got4
case internet.DomainStrategy_USE_IP6, internet.DomainStrategy_FORCE_IP6:
got = got6
case internet.DomainStrategy_USE_IP46, internet.DomainStrategy_FORCE_IP46:
got = got4
if len(got) == 0 {
got = got6
}
case internet.DomainStrategy_USE_IP64, internet.DomainStrategy_FORCE_IP64:
got = got6
if len(got) == 0 {
got = got4
}
}
if len(got) == 0 {
return nil, dns.ErrEmptyResponse
}
default:
panic(strategy)
}
if len(got) == 0 {
return nil, dns.ErrEmptyResponse
}
entry := entry{
got: got,
time: time.Now().Add(time.Duration(ttl) * time.Second),
}
h.cacheMu.Lock()
h.cache[host] = entry
h.cacheMu.Unlock()
return got[dice.Roll(len(got))], nil
}
type udpConnClient struct {
net.PacketConn
resolveFunc func(host string) (net.IP, error)
dest *net.UDPAddr
dest *net.UDPAddr
}
func (c *udpConnClient) ReadMultiBuffer() (buf.MultiBuffer, error) {
@@ -479,15 +404,8 @@ func (c *udpConnClient) WriteMultiBuffer(mb buf.MultiBuffer) error {
dst := c.dest
if b.UDP != nil {
if b.UDP.Address.Family().IsDomain() {
ip, err := c.resolveFunc(b.UDP.Address.String())
if err != nil {
errors.LogErrorInner(context.Background(), err, "drop packet to ", b.UDP, " with size ", len(b.Bytes()))
b.Release()
continue
}
dst = &net.UDPAddr{
IP: ip,
Port: int(b.UDP.Port),
if b.UDP.Port != net.Port(dst.Port) {
dst = &net.UDPAddr{IP: dst.IP, Port: int(b.UDP.Port)}
}
} else {
dst = b.UDP.RawNetAddr().(*net.UDPAddr)
@@ -524,3 +442,59 @@ func (c *PacketCounterConnection) WriteTo(p []byte, addr net.Addr) (n int, err e
}
return
}
type entry struct {
saddr []string
deadline time.Time
}
type cache struct {
running bool
m map[string]entry
mu sync.Mutex
}
func (c *cache) run() {
if c.running {
return
}
c.running = true
c.m = make(map[string]entry)
go c.gc()
}
func (c *cache) gc() {
ticker := time.NewTicker(time.Minute)
for {
now := <-ticker.C
c.mu.Lock()
for key, entry := range c.m {
if now.After(entry.deadline) {
delete(c.m, key)
}
}
c.mu.Unlock()
}
}
func (c *cache) LookupHost(host string) []string {
c.mu.Lock()
defer c.mu.Unlock()
c.run()
if entry, ok := c.m[host]; ok {
if time.Now().Before(entry.deadline) {
return entry.saddr
}
delete(c.m, host)
}
return nil
}
func (c *cache) Cache(host string, saddr []string, ttl uint32) {
c.mu.Lock()
defer c.mu.Unlock()
c.m[host] = entry{
saddr: saddr,
deadline: time.Now().Add(time.Second * time.Duration(ttl)),
}
}
+26 -102
View File
@@ -22,61 +22,6 @@ const (
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type DeviceConfig_DomainStrategy int32
const (
DeviceConfig_FORCE_IP DeviceConfig_DomainStrategy = 0
DeviceConfig_FORCE_IP4 DeviceConfig_DomainStrategy = 1
DeviceConfig_FORCE_IP6 DeviceConfig_DomainStrategy = 2
DeviceConfig_FORCE_IP46 DeviceConfig_DomainStrategy = 3
DeviceConfig_FORCE_IP64 DeviceConfig_DomainStrategy = 4
)
// Enum value maps for DeviceConfig_DomainStrategy.
var (
DeviceConfig_DomainStrategy_name = map[int32]string{
0: "FORCE_IP",
1: "FORCE_IP4",
2: "FORCE_IP6",
3: "FORCE_IP46",
4: "FORCE_IP64",
}
DeviceConfig_DomainStrategy_value = map[string]int32{
"FORCE_IP": 0,
"FORCE_IP4": 1,
"FORCE_IP6": 2,
"FORCE_IP46": 3,
"FORCE_IP64": 4,
}
)
func (x DeviceConfig_DomainStrategy) Enum() *DeviceConfig_DomainStrategy {
p := new(DeviceConfig_DomainStrategy)
*p = x
return p
}
func (x DeviceConfig_DomainStrategy) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (DeviceConfig_DomainStrategy) Descriptor() protoreflect.EnumDescriptor {
return file_proxy_wireguard_config_proto_enumTypes[0].Descriptor()
}
func (DeviceConfig_DomainStrategy) Type() protoreflect.EnumType {
return &file_proxy_wireguard_config_proto_enumTypes[0]
}
func (x DeviceConfig_DomainStrategy) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use DeviceConfig_DomainStrategy.Descriptor instead.
func (DeviceConfig_DomainStrategy) EnumDescriptor() ([]byte, []int) {
return file_proxy_wireguard_config_proto_rawDescGZIP(), []int{1, 0}
}
type PeerConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
PublicKey string `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"`
@@ -154,19 +99,18 @@ func (x *PeerConfig) GetAllowedIps() []string {
}
type DeviceConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
SecretKey string `protobuf:"bytes,1,opt,name=secret_key,json=secretKey,proto3" json:"secret_key,omitempty"`
Endpoint []string `protobuf:"bytes,2,rep,name=endpoint,proto3" json:"endpoint,omitempty"`
Peers []*PeerConfig `protobuf:"bytes,3,rep,name=peers,proto3" json:"peers,omitempty"`
Users []*protocol.User `protobuf:"bytes,5,rep,name=users,proto3" json:"users,omitempty"`
Mtu int32 `protobuf:"varint,4,opt,name=mtu,proto3" json:"mtu,omitempty"`
Reserved []byte `protobuf:"bytes,6,opt,name=reserved,proto3" json:"reserved,omitempty"`
DomainStrategy DeviceConfig_DomainStrategy `protobuf:"varint,7,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.proxy.wireguard.DeviceConfig_DomainStrategy" json:"domain_strategy,omitempty"`
IsClient bool `protobuf:"varint,8,opt,name=is_client,json=isClient,proto3" json:"is_client,omitempty"`
NoKernelTun bool `protobuf:"varint,9,opt,name=no_kernel_tun,json=noKernelTun,proto3" json:"no_kernel_tun,omitempty"`
DNS []string `protobuf:"bytes,10,rep,name=DNS,proto3" json:"DNS,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
SecretKey string `protobuf:"bytes,1,opt,name=secret_key,json=secretKey,proto3" json:"secret_key,omitempty"`
Endpoint []string `protobuf:"bytes,2,rep,name=endpoint,proto3" json:"endpoint,omitempty"`
Peers []*PeerConfig `protobuf:"bytes,3,rep,name=peers,proto3" json:"peers,omitempty"`
Users []*protocol.User `protobuf:"bytes,5,rep,name=users,proto3" json:"users,omitempty"`
Mtu int32 `protobuf:"varint,4,opt,name=mtu,proto3" json:"mtu,omitempty"`
Reserved []byte `protobuf:"bytes,6,opt,name=reserved,proto3" json:"reserved,omitempty"`
IsClient bool `protobuf:"varint,8,opt,name=is_client,json=isClient,proto3" json:"is_client,omitempty"`
NoKernelTun bool `protobuf:"varint,9,opt,name=no_kernel_tun,json=noKernelTun,proto3" json:"no_kernel_tun,omitempty"`
DNS []string `protobuf:"bytes,10,rep,name=DNS,proto3" json:"DNS,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *DeviceConfig) Reset() {
@@ -241,13 +185,6 @@ func (x *DeviceConfig) GetReserved() []byte {
return nil
}
func (x *DeviceConfig) GetDomainStrategy() DeviceConfig_DomainStrategy {
if x != nil {
return x.DomainStrategy
}
return DeviceConfig_FORCE_IP
}
func (x *DeviceConfig) GetIsClient() bool {
if x != nil {
return x.IsClient
@@ -283,7 +220,7 @@ const file_proxy_wireguard_config_proto_rawDesc = "" +
"\n" +
"keep_alive\x18\x04 \x01(\tR\tkeepAlive\x12\x1f\n" +
"\vallowed_ips\x18\x05 \x03(\tR\n" +
"allowedIps\"\xee\x03\n" +
"allowedIps\"\xb4\x02\n" +
"\fDeviceConfig\x12\x1d\n" +
"\n" +
"secret_key\x18\x01 \x01(\tR\tsecretKey\x12\x1a\n" +
@@ -291,20 +228,11 @@ const file_proxy_wireguard_config_proto_rawDesc = "" +
"\x05peers\x18\x03 \x03(\v2 .xray.proxy.wireguard.PeerConfigR\x05peers\x120\n" +
"\x05users\x18\x05 \x03(\v2\x1a.xray.common.protocol.UserR\x05users\x12\x10\n" +
"\x03mtu\x18\x04 \x01(\x05R\x03mtu\x12\x1a\n" +
"\breserved\x18\x06 \x01(\fR\breserved\x12Z\n" +
"\x0fdomain_strategy\x18\a \x01(\x0e21.xray.proxy.wireguard.DeviceConfig.DomainStrategyR\x0edomainStrategy\x12\x1b\n" +
"\breserved\x18\x06 \x01(\fR\breserved\x12\x1b\n" +
"\tis_client\x18\b \x01(\bR\bisClient\x12\"\n" +
"\rno_kernel_tun\x18\t \x01(\bR\vnoKernelTun\x12\x10\n" +
"\x03DNS\x18\n" +
" \x03(\tR\x03DNS\"\\\n" +
"\x0eDomainStrategy\x12\f\n" +
"\bFORCE_IP\x10\x00\x12\r\n" +
"\tFORCE_IP4\x10\x01\x12\r\n" +
"\tFORCE_IP6\x10\x02\x12\x0e\n" +
"\n" +
"FORCE_IP46\x10\x03\x12\x0e\n" +
"\n" +
"FORCE_IP64\x10\x04B^\n" +
" \x03(\tR\x03DNSB^\n" +
"\x18com.xray.proxy.wireguardP\x01Z)github.com/xtls/xray-core/proxy/wireguard\xaa\x02\x14Xray.Proxy.WireGuardb\x06proto3"
var (
@@ -319,23 +247,20 @@ func file_proxy_wireguard_config_proto_rawDescGZIP() []byte {
return file_proxy_wireguard_config_proto_rawDescData
}
var file_proxy_wireguard_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_proxy_wireguard_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_proxy_wireguard_config_proto_goTypes = []any{
(DeviceConfig_DomainStrategy)(0), // 0: xray.proxy.wireguard.DeviceConfig.DomainStrategy
(*PeerConfig)(nil), // 1: xray.proxy.wireguard.PeerConfig
(*DeviceConfig)(nil), // 2: xray.proxy.wireguard.DeviceConfig
(*protocol.User)(nil), // 3: xray.common.protocol.User
(*PeerConfig)(nil), // 0: xray.proxy.wireguard.PeerConfig
(*DeviceConfig)(nil), // 1: xray.proxy.wireguard.DeviceConfig
(*protocol.User)(nil), // 2: xray.common.protocol.User
}
var file_proxy_wireguard_config_proto_depIdxs = []int32{
1, // 0: xray.proxy.wireguard.DeviceConfig.peers:type_name -> xray.proxy.wireguard.PeerConfig
3, // 1: xray.proxy.wireguard.DeviceConfig.users:type_name -> xray.common.protocol.User
0, // 2: xray.proxy.wireguard.DeviceConfig.domain_strategy:type_name -> xray.proxy.wireguard.DeviceConfig.DomainStrategy
3, // [3:3] is the sub-list for method output_type
3, // [3:3] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
0, // 0: xray.proxy.wireguard.DeviceConfig.peers:type_name -> xray.proxy.wireguard.PeerConfig
2, // 1: xray.proxy.wireguard.DeviceConfig.users:type_name -> xray.common.protocol.User
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_proxy_wireguard_config_proto_init() }
@@ -348,14 +273,13 @@ func file_proxy_wireguard_config_proto_init() {
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_wireguard_config_proto_rawDesc), len(file_proxy_wireguard_config_proto_rawDesc)),
NumEnums: 1,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_proxy_wireguard_config_proto_goTypes,
DependencyIndexes: file_proxy_wireguard_config_proto_depIdxs,
EnumInfos: file_proxy_wireguard_config_proto_enumTypes,
MessageInfos: file_proxy_wireguard_config_proto_msgTypes,
}.Build()
File_proxy_wireguard_config_proto = out.File
-8
View File
@@ -17,13 +17,6 @@ message PeerConfig {
}
message DeviceConfig {
enum DomainStrategy {
FORCE_IP = 0;
FORCE_IP4 = 1;
FORCE_IP6 = 2;
FORCE_IP46 = 3;
FORCE_IP64 = 4;
}
string secret_key = 1;
repeated string endpoint = 2;
repeated PeerConfig peers = 3;
@@ -31,7 +24,6 @@ message DeviceConfig {
int32 mtu = 4;
bytes reserved = 6;
DomainStrategy domain_strategy = 7;
bool is_client = 8;
bool no_kernel_tun = 9;
repeated string DNS = 10;
+144 -9
View File
@@ -15,6 +15,8 @@ import (
"net"
"net/netip"
"os"
"regexp"
"strconv"
"strings"
"syscall"
"time"
@@ -219,6 +221,7 @@ type Net struct {
DialUDPAddrPort func(laddr, raddr netip.AddrPort) (net.Conn, error)
dnsServers []netip.Addr
hasV4, hasV6 bool
cache cache
}
func convertToFullAddr(endpoint netip.AddrPort) (tcpip.FullAddress, tcpip.NetworkProtocolNumber) {
@@ -246,9 +249,12 @@ var (
errServerTemporarilyMisbehaving = errors.New("server misbehaving")
errCanceled = errors.New("operation was canceled")
errTimeout = errors.New("i/o timeout")
errNumericPort = errors.New("port must be numeric")
errNoSuitableAddress = errors.New("no suitable address found")
errMissingAddress = errors.New("missing address")
)
func (net *Net) LookupHost(host string) (addrs []net.IP, ttl uint32, err error) {
func (net *Net) LookupHost(host string) (addrs []string, err error) {
return net.LookupContextHost(context.Background(), host)
}
@@ -567,9 +573,12 @@ func (tnet *Net) tryOneName(ctx context.Context, name string, qtype dnsmessage.T
return dnsmessage.Parser{}, "", lastErr
}
func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]net.IP, uint32, error) {
func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string, error) {
if saddr := tnet.cache.LookupHost(host); saddr != nil {
return saddr, nil
}
if host == "" || (!tnet.hasV6 && !tnet.hasV4) {
return nil, 0, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
return nil, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
}
zlen := len(host)
if strings.IndexByte(host, ':') != -1 {
@@ -578,11 +587,11 @@ func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]net.IP,
}
}
if ip, err := netip.ParseAddr(host[:zlen]); err == nil {
return []net.IP{ip.AsSlice()}, 0, nil
return []string{ip.String()}, nil
}
if !isDomainName(host) {
return nil, 0, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
return nil, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
}
type result struct {
p dnsmessage.Parser
@@ -683,11 +692,137 @@ func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]net.IP,
}
if len(addrs) == 0 && lastErr != nil {
return nil, 0, lastErr
return nil, lastErr
}
ips := make([]net.IP, 0, len(addrs))
saddrs := make([]string, 0, len(addrs))
for _, ip := range addrs {
ips = append(ips, ip.AsSlice())
saddrs = append(saddrs, ip.String())
}
return ips, ttl, nil
tnet.cache.Cache(host, saddrs, ttl)
return saddrs, nil
}
func partialDeadline(now, deadline time.Time, addrsRemaining int) (time.Time, error) {
if deadline.IsZero() {
return deadline, nil
}
timeRemaining := deadline.Sub(now)
if timeRemaining <= 0 {
return time.Time{}, errTimeout
}
timeout := timeRemaining / time.Duration(addrsRemaining)
const saneMinimum = 2 * time.Second
if timeout < saneMinimum {
if timeRemaining < saneMinimum {
timeout = timeRemaining
} else {
timeout = saneMinimum
}
}
return now.Add(timeout), nil
}
var protoSplitter = regexp.MustCompile(`^(tcp|udp|ping)(4|6)?$`)
func (tnet *Net) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
if ctx == nil {
panic("nil context")
}
var acceptV4, acceptV6 bool
matches := protoSplitter.FindStringSubmatch(network)
if matches == nil {
return nil, &net.OpError{Op: "dial", Err: net.UnknownNetworkError(network)}
} else if len(matches[2]) == 0 {
acceptV4 = true
acceptV6 = true
} else {
acceptV4 = matches[2][0] == '4'
acceptV6 = !acceptV4
}
var host string
var port int
if matches[1] == "ping" {
host = address
} else {
var sport string
var err error
host, sport, err = net.SplitHostPort(address)
if err != nil {
return nil, &net.OpError{Op: "dial", Err: err}
}
port, err = strconv.Atoi(sport)
if err != nil || port < 0 || port > 65535 {
return nil, &net.OpError{Op: "dial", Err: errNumericPort}
}
}
allAddr, err := tnet.LookupContextHost(ctx, host)
if err != nil {
return nil, &net.OpError{Op: "dial", Err: err}
}
var addrs []netip.AddrPort
for _, addr := range allAddr {
ip, err := netip.ParseAddr(addr)
if err == nil && ((ip.Is4() && acceptV4) || (ip.Is6() && acceptV6)) {
addrs = append(addrs, netip.AddrPortFrom(ip, uint16(port)))
}
}
if len(addrs) == 0 && len(allAddr) != 0 {
return nil, &net.OpError{Op: "dial", Err: errNoSuitableAddress}
}
var firstErr error
for i, addr := range addrs {
select {
case <-ctx.Done():
err := ctx.Err()
if err == context.Canceled {
err = errCanceled
} else if err == context.DeadlineExceeded {
err = errTimeout
}
return nil, &net.OpError{Op: "dial", Err: err}
default:
}
dialCtx := ctx
if deadline, hasDeadline := ctx.Deadline(); hasDeadline {
partialDeadline, err := partialDeadline(time.Now(), deadline, len(addrs)-i)
if err != nil {
if firstErr == nil {
firstErr = &net.OpError{Op: "dial", Err: err}
}
break
}
if partialDeadline.Before(deadline) {
var cancel context.CancelFunc
dialCtx, cancel = context.WithDeadline(ctx, partialDeadline)
defer cancel()
}
}
var c net.Conn
switch matches[1] {
case "tcp":
c, err = tnet.DialContextTCPAddrPort(dialCtx, addr)
case "udp":
c, err = tnet.DialUDPAddrPort(netip.AddrPort{}, addr)
case "ping":
err = errors.New("not support")
// c, err = tnet.DialPingAddr(netip.Addr{}, addr.Addr())
}
if err == nil {
return c, nil
}
if firstErr == nil {
firstErr = err
}
}
if firstErr == nil {
firstErr = &net.OpError{Op: "dial", Err: errMissingAddress}
}
return nil, firstErr
}
func (tnet *Net) Dial(network, address string) (net.Conn, error) {
return tnet.DialContext(context.Background(), network, address)
}