diff --git a/adapter/outbound/wireguard.go b/adapter/outbound/wireguard.go index 56ade71c..2e34dd83 100644 --- a/adapter/outbound/wireguard.go +++ b/adapter/outbound/wireguard.go @@ -12,6 +12,7 @@ import ( "strconv" "strings" "sync" + "time" "github.com/metacubex/mihomo/common/atomic" CN "github.com/metacubex/mihomo/common/net" @@ -48,6 +49,10 @@ type WireGuard struct { connectAddr M.Socksaddr localPrefixes []netip.Prefix + serverAddrMap map[M.Socksaddr]netip.AddrPort + serverAddrTime atomic.TypedValue[time.Time] + serverAddrMutex sync.Mutex + closeCh chan struct{} // for test } @@ -67,6 +72,8 @@ type WireGuardOption struct { RemoteDnsResolve bool `proxy:"remote-dns-resolve,omitempty"` Dns []string `proxy:"dns,omitempty"` + + RefreshServerIPInterval int `proxy:"refresh-server-ip-interval,omitempty"` } type WireGuardPeerOption struct { @@ -287,6 +294,15 @@ func (w *WireGuard) resolve(ctx context.Context, address M.Socksaddr) (netip.Add } func (w *WireGuard) init(ctx context.Context) error { + err := w.init0(ctx) + if err != nil { + return err + } + w.updateServerAddr(ctx) + return nil +} + +func (w *WireGuard) init0(ctx context.Context) error { if w.initOk.Load() { return nil } @@ -301,44 +317,118 @@ func (w *WireGuard) init(ctx context.Context) error { } w.bind.ResetReservedForEndpoint() - ipcConf := "private_key=" + w.option.PrivateKey + w.serverAddrMap = make(map[M.Socksaddr]netip.AddrPort) + ipcConf, err := w.genIpcConf(ctx, false) + if err != nil { + // !!! do not set initErr here !!! + // let us can retry domain resolve in next time + return err + } + + if debug.Enabled { + log.SingLogger.Trace(fmt.Sprintf("[WG](%s) created wireguard ipc conf: \n %s", w.option.Name, ipcConf)) + } + err = w.device.IpcSet(ipcConf) + if err != nil { + w.initErr = E.Cause(err, "setup wireguard") + return w.initErr + } + w.serverAddrTime.Store(time.Now()) + + err = w.tunDevice.Start() + if err != nil { + w.initErr = err + return w.initErr + } + + w.initOk.Store(true) + return nil +} + +func (w *WireGuard) updateServerAddr(ctx context.Context) { + if w.option.RefreshServerIPInterval != 0 && time.Since(w.serverAddrTime.Load()) > time.Second*time.Duration(w.option.RefreshServerIPInterval) { + if w.serverAddrMutex.TryLock() { + defer w.serverAddrMutex.Unlock() + ipcConf, err := w.genIpcConf(ctx, true) + if err != nil { + log.Warnln("[WG](%s)UpdateServerAddr failed to generate wireguard ipc conf: %s", w.option.Name, err) + return + } + err = w.device.IpcSet(ipcConf) + if err != nil { + log.Warnln("[WG](%s)UpdateServerAddr failed to update wireguard ipc conf: %s", w.option.Name, err) + return + } + w.serverAddrTime.Store(time.Now()) + } + } +} + +func (w *WireGuard) genIpcConf(ctx context.Context, updateOnly bool) (string, error) { + ipcConf := "" + if !updateOnly { + ipcConf += "private_key=" + w.option.PrivateKey + "\n" + } if len(w.option.Peers) > 0 { for i, peer := range w.option.Peers { - ipcConf += "\npublic_key=" + peer.PublicKey - destination, err := w.resolve(ctx, peer.Addr()) + peerAddr := peer.Addr() + destination, err := w.resolve(ctx, peerAddr) 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) + return "", E.Cause(err, "resolve endpoint domain for peer ", i) } + if w.serverAddrMap[peerAddr] != destination { + w.serverAddrMap[peerAddr] = destination + } else if updateOnly { + continue + } + if len(w.option.Peers) == 1 { // must call SetConnectAddr if isConnect == true w.bind.SetConnectAddr(destination) } - ipcConf += "\nendpoint=" + destination.String() - if peer.PreSharedKey != "" { - ipcConf += "\npreshared_key=" + peer.PreSharedKey - } - for _, allowedIP := range peer.AllowedIPs { - ipcConf += "\nallowed_ip=" + allowedIP + ipcConf += "public_key=" + peer.PublicKey + "\n" + if updateOnly { + ipcConf += "update_only=true\n" } + ipcConf += "endpoint=" + destination.String() + "\n" if len(peer.Reserved) > 0 { var reserved [3]uint8 copy(reserved[:], w.option.Reserved) w.bind.SetReservedForEndpoint(destination, reserved) } + if updateOnly { + continue + } + if peer.PreSharedKey != "" { + ipcConf += "preshared_key=" + peer.PreSharedKey + "\n" + } + for _, allowedIP := range peer.AllowedIPs { + ipcConf += "allowed_ip=" + allowedIP + "\n" + } + if w.option.PersistentKeepalive != 0 { + ipcConf += fmt.Sprintf("persistent_keepalive_interval=%d\n", w.option.PersistentKeepalive) + } } } else { - ipcConf += "\npublic_key=" + w.option.PublicKey destination, err := w.resolve(ctx, w.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") + return "", E.Cause(err, "resolve endpoint domain") + } + if w.serverAddrMap[w.connectAddr] != destination { + w.serverAddrMap[w.connectAddr] = destination + } else if updateOnly { + return "", nil } w.bind.SetConnectAddr(destination) // must call SetConnectAddr if isConnect == true - ipcConf += "\nendpoint=" + destination.String() + ipcConf += "public_key=" + w.option.PublicKey + "\n" + if updateOnly { + ipcConf += "update_only=true\n" + } + ipcConf += "endpoint=" + destination.String() + "\n" + if updateOnly { + return ipcConf, nil + } if w.option.PreSharedKey != "" { - ipcConf += "\npreshared_key=" + w.option.PreSharedKey + ipcConf += "preshared_key=" + w.option.PreSharedKey + "\n" } var has4, has6 bool for _, address := range w.localPrefixes { @@ -349,34 +439,17 @@ func (w *WireGuard) init(ctx context.Context) error { } } if has4 { - ipcConf += "\nallowed_ip=0.0.0.0/0" + ipcConf += "allowed_ip=0.0.0.0/0\n" } if has6 { - ipcConf += "\nallowed_ip=::/0" + ipcConf += "allowed_ip=::/0\n" + } + + if w.option.PersistentKeepalive != 0 { + ipcConf += fmt.Sprintf("persistent_keepalive_interval=%d\n", w.option.PersistentKeepalive) } } - - if w.option.PersistentKeepalive != 0 { - ipcConf += fmt.Sprintf("\npersistent_keepalive_interval=%d", w.option.PersistentKeepalive) - } - - if debug.Enabled { - log.SingLogger.Trace(fmt.Sprintf("[WG](%s) created wireguard ipc conf: \n %s", w.option.Name, ipcConf)) - } - err := w.device.IpcSet(ipcConf) - if err != nil { - w.initErr = E.Cause(err, "setup wireguard") - return w.initErr - } - - err = w.tunDevice.Start() - if err != nil { - w.initErr = err - return w.initErr - } - - w.initOk.Store(true) - return nil + return ipcConf, nil } func closeWireGuard(w *WireGuard) { diff --git a/docs/config.yaml b/docs/config.yaml index bd263c14..462b3a44 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -728,6 +728,7 @@ proxies: # socks5 # dialer-proxy: "ss1" # remote-dns-resolve: true # 强制 dns 远程解析,默认值为 false # dns: [ 1.1.1.1, 8.8.8.8 ] # 仅在 remote-dns-resolve 为 true 时生效 + # refresh-server-ip-interval: 60 # 重新解析server ip的间隔,单位为秒,默认值为0即仅第一次链接时解析server域名,仅应在server域名对应的IP会发生变化时启用该选项(如家宽ddns) # 如果 peers 不为空,该段落中的 allowed-ips 不可为空;前面段落的 server,port,public-key,pre-shared-key 均会被忽略,但 private-key 会被保留且只能在顶层指定 # peers: # - server: 162.159.192.1