diff --git a/biz/client/rpc_pull_wireguards.go b/biz/client/rpc_pull_wireguards.go index ec2e729..2743748 100644 --- a/biz/client/rpc_pull_wireguards.go +++ b/biz/client/rpc_pull_wireguards.go @@ -39,9 +39,13 @@ func PullWireGuards(appInstance app.Application, clientID, clientSecret string) wgCfg := &defs.WireGuardConfig{WireGuardConfig: wireGuard} wgSvc, ok := wgMgr.GetService(wireGuard.GetInterfaceName()) if ok { - log.Debugf("wireguard [%s] already exists, skip create, update peers if need", wireGuard.GetInterfaceName()) - wgSvc.PatchPeers(wgCfg.GetParsedPeers()) - continue + if wgSvc.NeedRecreate(wgCfg) { + wgMgr.RemoveService(wireGuard.GetInterfaceName()) + } else { + log.Debugf("wireguard [%s] already exists, skip create, update peers if need", wireGuard.GetInterfaceName()) + wgSvc.PatchPeers(wgCfg.GetParsedPeers()) + continue + } } wgSvc, err := wgMgr.CreateService(&defs.WireGuardConfig{WireGuardConfig: wireGuard}) diff --git a/defs/types_wg.go b/defs/types_wg.go index d35ea7c..730faaf 100644 --- a/defs/types_wg.go +++ b/defs/types_wg.go @@ -81,7 +81,8 @@ func (w *WireGuardPeerConfig) GetParsedPresharedKey() *wgtypes.Key { func (w *WireGuardPeerConfig) Equal(other *WireGuardPeerConfig) bool { endpointEqual := false if w.Endpoint != nil && other.Endpoint != nil { - endpointEqual = (w.Endpoint.Host == other.Endpoint.Host && w.Endpoint.Port == other.Endpoint.Port) + endpointEqual = (w.Endpoint.Host == other.Endpoint.Host && w.Endpoint.Port == other.Endpoint.Port && + w.Endpoint.Uri == other.Endpoint.Uri && w.Endpoint.Type == other.Endpoint.Type) } else if w.Endpoint == nil && other.Endpoint == nil { endpointEqual = true } diff --git a/go.mod b/go.mod index 8ca8653..101cb6d 100644 --- a/go.mod +++ b/go.mod @@ -49,6 +49,7 @@ require ( gorm.io/driver/mysql v1.5.7 gorm.io/driver/postgres v1.5.9 gorm.io/gorm v1.25.11 + gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c k8s.io/apimachinery v0.28.8 ) @@ -155,7 +156,6 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/sqlserver v1.5.3 // indirect gorm.io/plugin/dbresolver v1.5.2 // indirect - gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c // indirect k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect modernc.org/libc v1.38.0 // indirect modernc.org/mathutil v1.7.1 // indirect diff --git a/install.sh b/install.sh index dc0f105..d2ef05f 100755 --- a/install.sh +++ b/install.sh @@ -6,6 +6,7 @@ ARCH=$(uname -m) # --- Argument Parsing --- custom_proxy="" frp_panel_args=() # Use an array to hold arguments for frp-panel +version="latest" # Parse arguments while [[ "$#" -gt 0 ]]; do @@ -19,6 +20,11 @@ while [[ "$#" -gt 0 ]]; do echo "使用自定义的 GitHub 镜像: $custom_proxy" shift 2 # shift past argument name and value ;; + --version) + version="$2" + echo "使用自定义的版本: $version" + shift 2 # shift past argument name and value + ;; *) # Collect remaining arguments for frp-panel frp_panel_args+=("$1") # Add the argument to the array @@ -54,16 +60,16 @@ case "$OS" in Linux) case "$ARCH" in x86_64) - wget -O frp-panel "${prefix}https://github.com/VaalaCat/frp-panel/releases/latest/download/frp-panel-linux-amd64" + wget -O frp-panel "${prefix}https://github.com/VaalaCat/frp-panel/releases/download/${version}/frp-panel-linux-amd64" ;; aarch64) - wget -O frp-panel "${prefix}https://github.com/VaalaCat/frp-panel/releases/latest/download/frp-panel-linux-arm64" + wget -O frp-panel "${prefix}https://github.com/VaalaCat/frp-panel/releases/download/${version}/frp-panel-linux-arm64" ;; armv7l) - wget -O frp-panel "${prefix}https://github.com/VaalaCat/frp-panel/releases/latest/download/frp-panel-linux-armv7l" + wget -O frp-panel "${prefix}https://github.com/VaalaCat/frp-panel/releases/download/${version}/frp-panel-linux-armv7l" ;; armv6l) - wget -O frp-panel "${prefix}https://github.com/VaalaCat/frp-panel/releases/latest/download/frp-panel-linux-armv6l" + wget -O frp-panel "${prefix}https://github.com/VaalaCat/frp-panel/releases/download/${version}/frp-panel-linux-armv6l" ;; *) echo "Unsupported Linux architecture: $ARCH" @@ -74,10 +80,10 @@ case "$OS" in Darwin) case "$ARCH" in x86_64) - wget -O frp-panel "${prefix}https://github.com/VaalaCat/frp-panel/releases/latest/download/frp-panel-darwin-amd64" + wget -O frp-panel "${prefix}https://github.com/VaalaCat/frp-panel/releases/download/${version}/frp-panel-darwin-amd64" ;; arm64) - wget -O frp-panel "${prefix}https://github.com/VaalaCat/frp-panel/releases/latest/download/frp-panel-darwin-arm64" + wget -O frp-panel "${prefix}https://github.com/VaalaCat/frp-panel/releases/download/${version}/frp-panel-darwin-arm64" ;; *) echo "Unsupported Darwin architecture: $ARCH" diff --git a/models/wireguard.go b/models/wireguard.go index 7d081f2..1579ff3 100644 --- a/models/wireguard.go +++ b/models/wireguard.go @@ -37,6 +37,7 @@ type WireGuardEntity struct { Tags GormArray[string] `json:"tags" gorm:"type:varchar(255)"` WsListenPort uint32 `json:"ws_listen_port" gorm:"uniqueIndex:idx_client_id_ws_listen_port"` + UseGvisorNet bool `json:"use_gvisor_net"` } func (*WireGuard) TableName() string { @@ -117,7 +118,7 @@ func (w *WireGuard) FromPB(pb *pb.WireGuardConfig) { w.NetworkID = uint(pb.GetNetworkId()) w.Tags = GormArray[string](pb.GetTags()) w.WsListenPort = pb.GetWsListenPort() - + w.UseGvisorNet = pb.GetUseGvisorNet() w.AdvertisedEndpoints = make([]*Endpoint, 0, len(pb.GetAdvertisedEndpoints())) for _, e := range pb.GetAdvertisedEndpoints() { endpointModel := &Endpoint{} @@ -144,6 +145,7 @@ func (w *WireGuard) ToPB() *pb.WireGuardConfig { return e.ToPB() }), WsListenPort: w.ListenPort, + UseGvisorNet: w.UseGvisorNet, } } diff --git a/services/app/provider.go b/services/app/provider.go index d4b1367..e135361 100644 --- a/services/app/provider.go +++ b/services/app/provider.go @@ -247,6 +247,7 @@ type WireGuard interface { // Interface相关 GetIfceConfig() (*defs.WireGuardConfig, error) GetBaseIfceConfig() *defs.WireGuardConfig + NeedRecreate(newCfg *defs.WireGuardConfig) bool // Config相关 GenWGConfig() (string, error) // unimplemented diff --git a/services/wg/helper.go b/services/wg/helper.go index be86104..52951d1 100644 --- a/services/wg/helper.go +++ b/services/wg/helper.go @@ -132,11 +132,17 @@ func generateUAPIConfigString(cfg *defs.WireGuardConfig, wgPrivateKey wgtypes.Key, peerConfigs []*defs.WireGuardPeerConfig, firstStart bool, + skipListenPort bool, ) string { uapiBuilder := NewUAPIBuilder() - uapiBuilder.WithPrivateKey(wgPrivateKey). - WithListenPort(int(cfg.ListenPort)). - ReplacePeers(!firstStart). + uapiBuilder.WithPrivateKey(wgPrivateKey) + + // 只在首次启动且未跳过时设置 listen_port,避免在设备运行时更新端口导致死锁 + if firstStart && !skipListenPort { + uapiBuilder.WithListenPort(int(cfg.ListenPort)) + } + + uapiBuilder.ReplacePeers(!firstStart). AddPeers(peerConfigs) return uapiBuilder.Build() diff --git a/services/wg/multibind/multi_bind.go b/services/wg/multibind/multi_bind.go index 9853002..ad8479c 100644 --- a/services/wg/multibind/multi_bind.go +++ b/services/wg/multibind/multi_bind.go @@ -3,6 +3,7 @@ package multibind import ( "errors" "fmt" + "runtime/debug" "sync/atomic" "github.com/sirupsen/logrus" @@ -64,7 +65,10 @@ func (m *MultiBind) Close() error { // Open implements conn.Bind. func (m *MultiBind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err error) { if m.opened.Load() { - return nil, 0, conn.ErrBindAlreadyOpen + m.svcLogger.Debugf("multibind already opened, closing and reopening for port %d", port) + if closeErr := m.Close(); closeErr != nil { + m.svcLogger.WithError(closeErr).Warnf("failed to close multibind before reopening") + } } m.opened.Store(true) @@ -77,7 +81,7 @@ func (m *MultiBind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16 return nil, 0, err } - if p != 0 { + if p != 0 && t.name == "udp" { actualPort = p } @@ -146,6 +150,17 @@ func (m *MultiBind) SetMark(mark uint32) error { func (m *MultiBind) recvWrapper(trans *Transport, fns conn.ReceiveFunc) conn.ReceiveFunc { return func(packets [][]byte, sizes []int, eps []conn.Endpoint) (n int, err error) { + defer func() { + if panicRecover := recover(); panicRecover != nil { + err = fmt.Errorf("multibind recvWrapper panic: %v, debugStack: %s", panicRecover, debug.Stack()) + m.svcLogger.WithError(err).Error("multibind recvWrapper panic") + } + }() + defer func() { + if err != nil { + m.svcLogger.WithError(err).Error("multibind recvWrapper error") + } + }() log := m.svcLogger.WithField("op", "recvWrapper").WithField("transport", trans.name) tmpEps := make([]conn.Endpoint, len(eps)) diff --git a/services/wg/transport/ws/bind.go b/services/wg/transport/ws/bind.go index 68e4170..449e315 100644 --- a/services/wg/transport/ws/bind.go +++ b/services/wg/transport/ws/bind.go @@ -61,7 +61,10 @@ func (w *WSBind) Close() error { // Open implements conn.Bind. func (w *WSBind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err error) { if w.opened.Load() { - return nil, 0, conn.ErrBindAlreadyOpen + w.ctx.Logger().Debugf("ws bind already opened, closing and reopening for port %d", port) + if closeErr := w.Close(); closeErr != nil { + w.ctx.Logger().WithError(closeErr).Warnf("failed to close ws bind before reopening") + } } w.opened.Store(true) w.registerChan = make(chan *serverIncoming, defaultRegisterChanSize) diff --git a/services/wg/uapi_builder.go b/services/wg/uapi_builder.go index 7ff2491..f9a2381 100644 --- a/services/wg/uapi_builder.go +++ b/services/wg/uapi_builder.go @@ -3,6 +3,7 @@ package wg import ( "encoding/hex" "fmt" + "net" "strings" "github.com/VaalaCat/frp-panel/defs" @@ -158,7 +159,12 @@ func normalizeEndpoint(ep *pb.Endpoint) string { return ep.GetUri() } - return fmt.Sprintf("%s:%d", ep.GetHost(), ep.GetPort()) + addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", ep.GetHost(), ep.GetPort())) + if err != nil { + return "" + } + + return addr.String() } func isZeroKey(key wgtypes.Key) bool { diff --git a/services/wg/wireguard.go b/services/wg/wireguard.go index f00517b..3a8ea7a 100644 --- a/services/wg/wireguard.go +++ b/services/wg/wireguard.go @@ -115,16 +115,17 @@ func (w *wireGuard) Start() error { return errors.Join(fmt.Errorf("apply peer config failed"), err) } + // 在应用配置后再启动设备 + if err := w.wgDevice.Up(); err != nil { + return errors.Join(fmt.Errorf("wgDevice.Up '%s'", w.ifce.GetInterfaceName()), err) + } + if !w.useGvisorNet { if err := w.initNetwork(); err != nil { return errors.Join(errors.New("init network failed"), err) } } - if err := w.wgDevice.Up(); err != nil { - return errors.Join(fmt.Errorf("wgDevice.Up '%s'", w.ifce.GetInterfaceName()), err) - } - // 在 WireGuard 设备启动后配置 gvisor if w.useGvisorNet { if err := w.initGvisorNetwork(); err != nil { @@ -325,11 +326,31 @@ func (w *wireGuard) GetWGRuntimeInfo() (*pb.WGDeviceRuntimeInfo, error) { } runtimeInfo.PingMap = w.pingMap.Export() - runtimeInfo.InterfaceName = w.ifce.GetInterfaceName() + if w.useGvisorNet { + runtimeInfo.InterfaceName = w.ifce.GetInterfaceName() + } else { + link, err := netlink.LinkByName(w.ifce.GetInterfaceName()) + if err != nil { + return nil, errors.Join(fmt.Errorf("get iface '%s' via netlink", w.ifce.GetInterfaceName()), err) + } + runtimeInfo.InterfaceName = link.Attrs().Name + } return runtimeInfo, nil } +func (w *wireGuard) NeedRecreate(newCfg *defs.WireGuardConfig) bool { + return w.ifce.GetId() != newCfg.GetId() || + w.ifce.GetInterfaceName() != newCfg.GetInterfaceName() || + w.ifce.GetPrivateKey() != newCfg.GetPrivateKey() || + w.ifce.GetLocalAddress() != newCfg.GetLocalAddress() || + w.ifce.GetListenPort() != newCfg.GetListenPort() || + w.ifce.GetWsListenPort() != newCfg.GetWsListenPort() || + w.ifce.GetInterfaceMtu() != newCfg.GetInterfaceMtu() || + w.ifce.GetUseGvisorNet() != newCfg.GetUseGvisorNet() || + w.ifce.GetNetworkId() != newCfg.GetNetworkId() +} + func (w *wireGuard) initTransports() error { log := w.svcLogger.WithField("op", "initTransports") @@ -402,6 +423,15 @@ func (w *wireGuard) initWGDevice() error { } log.Debugf("TUN device '%s' (MTU %d) created successfully", w.ifce.GetInterfaceName(), w.ifce.GetInterfaceMtu()) + + // 在创建 WireGuard 设备之前,先打开 bind 并使用正确的端口,避免后续更新端口导致死锁 + log.Debugf("opening multibind with port %d before creating WireGuard device", w.ifce.GetListenPort()) + _, _, err = w.multiBind.Open(uint16(w.ifce.GetListenPort())) + if err != nil { + return errors.Join(fmt.Errorf("open multibind with port %d failed", w.ifce.GetListenPort()), err) + } + log.Debugf("multibind opened successfully with port %d", w.ifce.GetListenPort()) + log.Debugf("start to create WireGuard device '%s'", w.ifce.GetInterfaceName()) w.wgDevice = device.NewDevice(w.tunDevice, w.multiBind, &device.Logger{ @@ -430,13 +460,17 @@ func (w *wireGuard) applyPeerConfig() error { log.Debugf("wgTypedPeerConfigs: %v", wgTypedPeerConfigs) - uapiConfigString := generateUAPIConfigString(w.ifce, w.ifce.GetParsedPrivKey(), wgTypedPeerConfigs, !w.running) + // 跳过 listen_port 设置,因为我们已经在 initWGDevice 中通过 bind.Open() 设置了端口 + // 在运行时通过 UAPI 更新 listen_port 会导致死锁 + uapiConfigString := generateUAPIConfigString(w.ifce, w.ifce.GetParsedPrivKey(), wgTypedPeerConfigs, !w.running, true) log.Debugf("uapiBuilder: %s", uapiConfigString) + log.Debugf("calling IpcSet...") if err = w.wgDevice.IpcSet(uapiConfigString); err != nil { return errors.Join(errors.New("IpcSet error"), err) } + log.Debugf("IpcSet completed successfully") return nil } @@ -444,10 +478,24 @@ func (w *wireGuard) applyPeerConfig() error { func (w *wireGuard) initNetwork() error { log := w.svcLogger.WithField("op", "initNetwork") - link, err := netlink.LinkByName(w.ifce.GetInterfaceName()) - if err != nil { - return errors.Join(fmt.Errorf("get iface '%s' via netlink", w.ifce.GetInterfaceName()), err) + // 等待 TUN 设备在内核中完全注册,避免竞态条件 + var link netlink.Link + var err error + maxRetries := 10 + for i := 0; i < maxRetries; i++ { + link, err = netlink.LinkByName(w.ifce.GetInterfaceName()) + if err == nil { + break + } + if i < maxRetries-1 { + log.Debugf("attempt %d: waiting for iface '%s' to be ready, will retry...", i+1, w.ifce.GetInterfaceName()) + time.Sleep(100 * time.Millisecond) + } } + if err != nil { + return errors.Join(fmt.Errorf("get iface '%s' via netlink after %d retries", w.ifce.GetInterfaceName(), maxRetries), err) + } + log.Debugf("successfully found interface '%s' via netlink", w.ifce.GetInterfaceName()) addr, err := netlink.ParseAddr(w.ifce.GetLocalAddress()) if err != nil {