diff --git a/adapter/outbound/wireguard.go b/adapter/outbound/wireguard.go index 7c021c87..3a21c938 100644 --- a/adapter/outbound/wireguard.go +++ b/adapter/outbound/wireguard.go @@ -13,6 +13,7 @@ import ( "strings" "sync" + "github.com/metacubex/mihomo/common/atomic" CN "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/proxydialer" @@ -37,8 +38,7 @@ type WireGuard struct { device *device.Device tunDevice wireguard.Device dialer proxydialer.SingDialer - startOnce sync.Once - startErr error + init func(ctx context.Context) error resolver *dns.Resolver refP *refProxyAdapter } @@ -47,6 +47,8 @@ type WireGuardOption struct { BasicOption WireGuardPeerOption Name string `proxy:"name"` + Ip string `proxy:"ip,omitempty"` + Ipv6 string `proxy:"ipv6,omitempty"` PrivateKey string `proxy:"private-key"` Workers int `proxy:"workers,omitempty"` MTU int `proxy:"mtu,omitempty"` @@ -62,8 +64,6 @@ type WireGuardOption struct { type WireGuardPeerOption struct { Server string `proxy:"server"` Port int `proxy:"port"` - Ip string `proxy:"ip,omitempty"` - Ipv6 string `proxy:"ipv6,omitempty"` PublicKey string `proxy:"public-key,omitempty"` PreSharedKey string `proxy:"pre-shared-key,omitempty"` Reserved []uint8 `proxy:"reserved,omitempty"` @@ -98,7 +98,7 @@ func (option WireGuardPeerOption) Addr() M.Socksaddr { return M.ParseSocksaddrHostPort(option.Server, uint16(option.Port)) } -func (option WireGuardPeerOption) Prefixes() ([]netip.Prefix, error) { +func (option WireGuardOption) Prefixes() ([]netip.Prefix, error) { localPrefixes := make([]netip.Prefix, 0, 2) if len(option.Ip) > 0 { if !strings.Contains(option.Ip, "/") { @@ -141,6 +141,14 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) { } runtime.SetFinalizer(outbound, closeWireGuard) + resolv := func(ctx context.Context, address M.Socksaddr) (netip.AddrPort, error) { + if address.Addr.IsValid() { + return address.AddrPort(), nil + } + udpAddr, err := resolveUDPAddrWithPrefer(ctx, "udp", address.String(), outbound.prefer) + return udpAddr.AddrPort(), err + } + var reserved [3]uint8 if len(option.Reserved) > 0 { if len(option.Reserved) != 3 { @@ -158,9 +166,12 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) { connectAddr = option.Addr() } } - outbound.bind = wireguard.NewClientBind(context.Background(), wgSingErrorHandler{outbound.Name()}, outbound.dialer, isConnect, connectAddr, reserved) + outbound.bind = wireguard.NewClientBind(context.Background(), wgSingErrorHandler{outbound.Name()}, outbound.dialer, isConnect, connectAddr.AddrPort(), reserved) - var localPrefixes []netip.Prefix + localPrefixes, err := option.Prefixes() + if err != nil { + return nil, err + } var privateKey string { @@ -170,95 +181,145 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) { } privateKey = hex.EncodeToString(bytes) } - ipcConf := "private_key=" + privateKey - if peersLen := len(option.Peers); peersLen > 0 { - localPrefixes = make([]netip.Prefix, 0, peersLen*2) + + if len(option.Peers) > 0 { for i, peer := range option.Peers { - var peerPublicKey, preSharedKey string - { - bytes, err := base64.StdEncoding.DecodeString(peer.PublicKey) - if err != nil { - return nil, E.Cause(err, "decode public key for peer ", i) - } - peerPublicKey = hex.EncodeToString(bytes) + bytes, err := base64.StdEncoding.DecodeString(peer.PublicKey) + if err != nil { + return nil, E.Cause(err, "decode public key for peer ", i) } + peer.PublicKey = hex.EncodeToString(bytes) + if peer.PreSharedKey != "" { bytes, err := base64.StdEncoding.DecodeString(peer.PreSharedKey) if err != nil { return nil, E.Cause(err, "decode pre shared key for peer ", i) } - preSharedKey = hex.EncodeToString(bytes) - } - destination := peer.Addr() - ipcConf += "\npublic_key=" + peerPublicKey - ipcConf += "\nendpoint=" + destination.String() - if preSharedKey != "" { - ipcConf += "\npreshared_key=" + preSharedKey + peer.PreSharedKey = hex.EncodeToString(bytes) } + if len(peer.AllowedIPs) == 0 { return nil, E.New("missing allowed_ips for peer ", i) } - for _, allowedIP := range peer.AllowedIPs { - ipcConf += "\nallowed_ip=" + allowedIP - } + if len(peer.Reserved) > 0 { if len(peer.Reserved) != 3 { return nil, E.New("invalid reserved value for peer ", i, ", required 3 bytes, got ", len(peer.Reserved)) } - copy(reserved[:], option.Reserved) - outbound.bind.SetReservedForEndpoint(destination, reserved) } - prefixes, err := peer.Prefixes() - if err != nil { - return nil, err - } - localPrefixes = append(localPrefixes, prefixes...) } } else { - var peerPublicKey, preSharedKey string { bytes, err := base64.StdEncoding.DecodeString(option.PublicKey) if err != nil { return nil, E.Cause(err, "decode peer public key") } - peerPublicKey = hex.EncodeToString(bytes) + option.PublicKey = hex.EncodeToString(bytes) } if option.PreSharedKey != "" { bytes, err := base64.StdEncoding.DecodeString(option.PreSharedKey) if err != nil { return nil, E.Cause(err, "decode pre shared key") } - preSharedKey = hex.EncodeToString(bytes) - } - ipcConf += "\npublic_key=" + peerPublicKey - ipcConf += "\nendpoint=" + connectAddr.String() - if preSharedKey != "" { - ipcConf += "\npreshared_key=" + preSharedKey - } - var err error - localPrefixes, err = option.Prefixes() - if err != nil { - return nil, err - } - var has4, has6 bool - for _, address := range localPrefixes { - if address.Addr().Is4() { - has4 = true - } else { - has6 = true - } - } - if has4 { - ipcConf += "\nallowed_ip=0.0.0.0/0" - } - if has6 { - ipcConf += "\nallowed_ip=::/0" + option.PreSharedKey = hex.EncodeToString(bytes) } } - if option.PersistentKeepalive != 0 { - ipcConf += fmt.Sprintf("\npersistent_keepalive_interval=%d", option.PersistentKeepalive) + var ( + initOk atomic.Bool + initMutex sync.Mutex + initErr error + ) + + outbound.init = func(ctx context.Context) error { + if initOk.Load() { + return nil + } + initMutex.Lock() + defer initMutex.Unlock() + // double check like sync.Once + if initOk.Load() { + return nil + } + if initErr != nil { + return initErr + } + + outbound.bind.ResetReservedForEndpoint() + ipcConf := "private_key=" + privateKey + if len(option.Peers) > 0 { + for i, peer := range option.Peers { + destination, err := resolv(ctx, peer.Addr()) + if err != nil { + // !!! do not set initErr here !!! + // let us can retry domain resolve in next time + return E.Cause(err, "resolve endpoint domain for peer ", i) + } + ipcConf += "\npublic_key=" + peer.PublicKey + ipcConf += "\nendpoint=" + destination.String() + if peer.PreSharedKey != "" { + ipcConf += "\npreshared_key=" + peer.PreSharedKey + } + for _, allowedIP := range peer.AllowedIPs { + ipcConf += "\nallowed_ip=" + allowedIP + } + if len(peer.Reserved) > 0 { + copy(reserved[:], option.Reserved) + outbound.bind.SetReservedForEndpoint(destination, reserved) + } + } + } else { + ipcConf += "\npublic_key=" + option.PublicKey + destination, err := resolv(ctx, connectAddr) + if err != nil { + // !!! do not set initErr here !!! + // let us can retry domain resolve in next time + return E.Cause(err, "resolve endpoint domain") + } + outbound.bind.SetConnectAddr(destination) + ipcConf += "\nendpoint=" + destination.String() + if option.PreSharedKey != "" { + ipcConf += "\npreshared_key=" + option.PreSharedKey + } + var has4, has6 bool + for _, address := range localPrefixes { + if address.Addr().Is4() { + has4 = true + } else { + has6 = true + } + } + if has4 { + ipcConf += "\nallowed_ip=0.0.0.0/0" + } + if has6 { + ipcConf += "\nallowed_ip=::/0" + } + } + + if option.PersistentKeepalive != 0 { + ipcConf += fmt.Sprintf("\npersistent_keepalive_interval=%d", option.PersistentKeepalive) + } + + if debug.Enabled { + log.SingLogger.Trace(fmt.Sprintf("[WG](%s) created wireguard ipc conf: \n %s", option.Name, ipcConf)) + } + err = outbound.device.IpcSet(ipcConf) + if err != nil { + initErr = E.Cause(err, "setup wireguard") + return initErr + } + + err = outbound.tunDevice.Start() + if err != nil { + initErr = err + return initErr + } + + initOk.Store(true) + return nil } + mtu := option.MTU if mtu == 0 { mtu = 1408 @@ -266,7 +327,6 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) { if len(localPrefixes) == 0 { return nil, E.New("missing local address") } - var err error outbound.tunDevice, err = wireguard.NewStackDevice(localPrefixes, uint32(mtu)) if err != nil { return nil, E.Cause(err, "create WireGuard device") @@ -279,14 +339,6 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) { log.SingLogger.Error(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...))) }, }, option.Workers) - if debug.Enabled { - log.SingLogger.Trace(fmt.Sprintf("[WG](%s) created wireguard ipc conf: \n %s", option.Name, ipcConf)) - } - err = outbound.device.IpcSet(ipcConf) - if err != nil { - return nil, E.Cause(err, "setup wireguard") - } - //err = outbound.tunDevice.Start() var has6 bool for _, address := range localPrefixes { @@ -326,11 +378,8 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts options := w.Base.DialOptions(opts...) w.dialer.SetDialer(dialer.NewDialer(options...)) var conn net.Conn - w.startOnce.Do(func() { - w.startErr = w.tunDevice.Start() - }) - if w.startErr != nil { - return nil, w.startErr + if err = w.init(ctx); err != nil { + return nil, err } if !metadata.Resolved() || w.resolver != nil { r := resolver.DefaultResolver @@ -358,11 +407,8 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat options := w.Base.DialOptions(opts...) w.dialer.SetDialer(dialer.NewDialer(options...)) var pc net.PacketConn - w.startOnce.Do(func() { - w.startErr = w.tunDevice.Start() - }) - if w.startErr != nil { - return nil, w.startErr + if err = w.init(ctx); err != nil { + return nil, err } if err != nil { return nil, err diff --git a/docs/config.yaml b/docs/config.yaml index d912eb65..4d0e995e 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -708,12 +708,10 @@ proxies: # socks5 # dialer-proxy: "ss1" # remote-dns-resolve: true # 强制dns远程解析,默认值为false # dns: [ 1.1.1.1, 8.8.8.8 ] # 仅在remote-dns-resolve为true时生效 - # 如果peers不为空,该段落中的allowed-ips不可为空;前面段落的server,port,ip,ipv6,public-key,pre-shared-key均会被忽略,但private-key会被保留且只能在顶层指定 + # 如果peers不为空,该段落中的allowed-ips不可为空;前面段落的server,port,public-key,pre-shared-key均会被忽略,但private-key会被保留且只能在顶层指定 # peers: # - server: 162.159.192.1 # port: 2480 - # ip: 172.16.0.2 - # ipv6: fd01:5ca1:ab1e:80fa:ab85:6eea:213f:f4a5 # public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo= # # pre-shared-key: 31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM= # allowed-ips: ['0.0.0.0/0'] diff --git a/go.mod b/go.mod index b6f5ff54..7ff43a9f 100644 --- a/go.mod +++ b/go.mod @@ -23,9 +23,9 @@ require ( github.com/metacubex/sing-quic v0.0.0-20240310154810-47bca850fc01 github.com/metacubex/sing-shadowsocks v0.2.6 github.com/metacubex/sing-shadowsocks2 v0.2.0 - github.com/metacubex/sing-tun v0.2.1-0.20240214100323-23e40bfb9067 + github.com/metacubex/sing-tun v0.2.1-0.20240320004934-5d2b35447bfd github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f - github.com/metacubex/sing-wireguard v0.0.0-20231209125515-0594297f7232 + github.com/metacubex/sing-wireguard v0.0.0-20240320043244-d6a8de454284 github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 github.com/miekg/dns v1.1.58 github.com/mroth/weightedrand/v2 v2.1.0 @@ -84,7 +84,7 @@ require ( github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mdlayher/socket v0.4.1 // indirect - github.com/metacubex/gvisor v0.0.0-20240214095142-666a73bcf165 // indirect + github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec // indirect github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect @@ -93,7 +93,6 @@ require ( github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect - github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect diff --git a/go.sum b/go.sum index 7c1e2425..8f557996 100644 --- a/go.sum +++ b/go.sum @@ -102,8 +102,8 @@ github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= -github.com/metacubex/gvisor v0.0.0-20240214095142-666a73bcf165 h1:QIQI4gEm+gTwVNdiAyF4EIz5cHm7kSlfDGFpYlAa5dg= -github.com/metacubex/gvisor v0.0.0-20240214095142-666a73bcf165/go.mod h1:SKY70wiF1UTSoyuDZyKPMsUC6MsMxh8Y3ZNkIa6J3fU= +github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc= +github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec/go.mod h1:8BVmQ+3cxjqzWElafm24rb2Ae4jRI6vAXNXWqWjfrXw= github.com/metacubex/quic-go v0.42.1-0.20240319071510-a251e5c66a5c h1:AhaPKvVqF3N/jXFmlW51Cf1+KddslKAsZqcdgGhZjr0= github.com/metacubex/quic-go v0.42.1-0.20240319071510-a251e5c66a5c/go.mod h1:iGx3Y1zynls/FjFgykLSqDcM81U0IKePRTXEz5g3iiQ= github.com/metacubex/sing v0.0.0-20240313064558-c197257f6542 h1:e9nBnrJBv3HzZVeSzJN0G2SADjebd2ZLF1F5dmsjUTc= @@ -114,12 +114,12 @@ github.com/metacubex/sing-shadowsocks v0.2.6 h1:6oEB3QcsFYnNiFeoevcXrCwJ3sAablwV github.com/metacubex/sing-shadowsocks v0.2.6/go.mod h1:zIkMeSnb8Mbf4hdqhw0pjzkn1d99YJ3JQm/VBg5WMTg= github.com/metacubex/sing-shadowsocks2 v0.2.0 h1:hqwT/AfI5d5UdPefIzR6onGHJfDXs5zgOM5QSgaM/9A= github.com/metacubex/sing-shadowsocks2 v0.2.0/go.mod h1:LCKF6j1P94zN8ZS+LXRK1gmYTVGB3squivBSXAFnOg8= -github.com/metacubex/sing-tun v0.2.1-0.20240214100323-23e40bfb9067 h1:sB9Hiq/Fgq94WD1mAFDUTDaQAJ6y3WZ5nZMEavcK0/o= -github.com/metacubex/sing-tun v0.2.1-0.20240214100323-23e40bfb9067/go.mod h1:HWyO52kAVvuSUN2nms4ZlRfiAwgXO9wGQBJFjymqvOQ= +github.com/metacubex/sing-tun v0.2.1-0.20240320004934-5d2b35447bfd h1:NgLb6Lvr8ZxX0inWswVYjal2SUzsJJ54dFQNOluUJuE= +github.com/metacubex/sing-tun v0.2.1-0.20240320004934-5d2b35447bfd/go.mod h1:GfLZG/QgGpW9+BPjltzONrL5vVms86TWqmZ23J68ISc= github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f h1:QjXrHKbTMBip/C+R79bvbfr42xH1gZl3uFb0RELdZiQ= github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY= -github.com/metacubex/sing-wireguard v0.0.0-20231209125515-0594297f7232 h1:loWjR+k9dxqBSgruGyT5hE8UCRMmCEjxqZbryfY9no4= -github.com/metacubex/sing-wireguard v0.0.0-20231209125515-0594297f7232/go.mod h1:NGCrBZ+fUmp81yaA1kVskcNWBnwl5z4UHxz47A01zm8= +github.com/metacubex/sing-wireguard v0.0.0-20240320043244-d6a8de454284 h1:aort+t6Hb+umsOFODT/P5fzTWr/4Bypp70jXUHhryR8= +github.com/metacubex/sing-wireguard v0.0.0-20240320043244-d6a8de454284/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo= github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 h1:as/aO/fM8nv4W4pOr9EETP6kV/Oaujk3fUNyQSJK61c= github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts= github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= @@ -169,8 +169,6 @@ github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e h1:iGH0RMv2F github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e/go.mod h1:YbL4TKHRR6APYQv3U2RGfwLDpPYSyWz6oUlpISBEzBE= github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= -github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg= -github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s= github.com/shirou/gopsutil/v3 v3.24.2 h1:kcR0erMbLg5/3LcInpw0X/rrPSqq4CDPyI6A6ZRC18Y= github.com/shirou/gopsutil/v3 v3.24.2/go.mod h1:tSg/594BcA+8UdQU2XcW803GWYgdtauFFPgJCJKZlVk= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=