mirror of
https://github.com/VaalaCat/frp-panel.git
synced 2025-12-24 11:51:06 +08:00
fix: deadlock when wg open port
This commit is contained in:
@@ -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})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
2
go.mod
2
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
|
||||
|
||||
18
install.sh
18
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"
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user