Merge branch 'main' into fix/upstream-friendly-ui-3pack

This commit is contained in:
sagit
2026-03-07 17:36:29 +08:00
committed by GitHub
4 changed files with 68 additions and 14 deletions
@@ -527,15 +527,17 @@ func controlForwardServiceCommand(bases []string, commandType string, send func(
} }
func deleteForwardServiceCandidates(bases []string, send func(name string) error) error { func deleteForwardServiceCandidates(bases []string, send func(name string) error) error {
handled, lastNotFoundErr, err := controlForwardServiceCommand(bases, "DeleteService", send) for _, base := range bases {
if err != nil { for _, name := range append([]string{base + "_tcp", base + "_udp", base}, []string{}...) {
return err err := send(name)
} if err == nil {
if handled { continue
return nil }
} if isNotFoundError(err) {
if lastNotFoundErr != nil { continue
return nil }
return err
}
} }
return nil return nil
} }
@@ -1477,10 +1479,11 @@ func isAlreadyExistsMessage(message string) bool {
if msg == "" { if msg == "" {
return false return false
} }
if strings.Contains(msg, "address already in use") { if isAddressAlreadyInUseMessage(msg) {
return false return false
} }
return strings.Contains(msg, "already exists") || strings.Contains(msg, "已存在") compact := compactErrorMessage(msg)
return strings.Contains(msg, "already exists") || strings.Contains(msg, "已存在") || strings.Contains(compact, "alreadyexists")
} }
func isBindAddressInUseError(err error) bool { func isBindAddressInUseError(err error) bool {
@@ -1505,7 +1508,10 @@ func isAddressAlreadyInUseMessage(msg string) bool {
if msg == "" { if msg == "" {
return false return false
} }
return strings.Contains(msg, "address already in use") if strings.Contains(msg, "address already in use") {
return true
}
return strings.Contains(compactErrorMessage(msg), "addressalreadyinuse")
} }
func isCannotAssignRequestedAddressError(err error) bool { func isCannotAssignRequestedAddressError(err error) bool {
@@ -1516,7 +1522,18 @@ func isCannotAssignRequestedAddressError(err error) bool {
if msg == "" { if msg == "" {
return false return false
} }
return strings.Contains(msg, "cannot assign requested address") if strings.Contains(msg, "cannot assign requested address") {
return true
}
return strings.Contains(compactErrorMessage(msg), "cannotassignrequestedaddress")
}
func compactErrorMessage(msg string) string {
msg = strings.TrimSpace(msg)
if msg == "" {
return ""
}
return strings.Join(strings.Fields(strings.ToLower(msg)), "")
} }
func buildForwardServiceConfigs(baseName string, forward *forwardRecord, tunnel *tunnelRecord, node *nodeRecord, port int, bindIP string, limiterID *int64, tunnelTLSProtocol bool) []map[string]interface{} { func buildForwardServiceConfigs(baseName string, forward *forwardRecord, tunnel *tunnelRecord, node *nodeRecord, port int, bindIP string, limiterID *int64, tunnelTLSProtocol bool) []map[string]interface{} {
@@ -180,6 +180,27 @@ func TestDeleteForwardServiceBasesOnNodeRetriesLegacyZeroResidue(t *testing.T) {
} }
} }
func TestDeleteForwardServiceCandidatesDeletesAllMatchingVariants(t *testing.T) {
bases := []string{"57_7_7", "57_7_0"}
called := make([]string, 0)
err := deleteForwardServiceCandidates(bases, func(name string) error {
called = append(called, name)
switch name {
case "57_7_7_tcp", "57_7_7_udp", "57_7_0_tcp", "57_7_0_udp":
return nil
default:
return errors.New("service " + name + " not found")
}
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
want := []string{"57_7_7_tcp", "57_7_7_udp", "57_7_7", "57_7_0_tcp", "57_7_0_udp", "57_7_0"}
if !reflect.DeepEqual(called, want) {
t.Fatalf("expected calls %v, got %v", want, called)
}
}
func TestValidateForwardPortAvailabilityRejectsOtherForwardOccupancy(t *testing.T) { func TestValidateForwardPortAvailabilityRejectsOtherForwardOccupancy(t *testing.T) {
h := &Handler{repo: nil} h := &Handler{repo: nil}
node := &nodeRecord{ID: 9, Name: "test-node"} node := &nodeRecord{ID: 9, Name: "test-node"}
@@ -235,9 +256,15 @@ func TestIsAlreadyExistsMessage(t *testing.T) {
if !isAlreadyExistsMessage("服务已存在") { if !isAlreadyExistsMessage("服务已存在") {
t.Fatalf("expected Chinese already exists message to be tolerated") t.Fatalf("expected Chinese already exists message to be tolerated")
} }
if !isAlreadyExistsMessage("service demo alreadyexists") {
t.Fatalf("missing-space alreadyexists should be tolerated")
}
if isAlreadyExistsMessage("listen tcp [::]:10001: bind: address already in use") { if isAlreadyExistsMessage("listen tcp [::]:10001: bind: address already in use") {
t.Fatalf("address already in use must not be treated as already exists") t.Fatalf("address already in use must not be treated as already exists")
} }
if isAlreadyExistsMessage("create service 57_7_7_tcp failed: listen tcp4 0.0.0.0:46222: bind: address alreadyin use") {
t.Fatalf("alreadyin-use variant must not be treated as already exists")
}
} }
func TestIsBindAddressInUseError(t *testing.T) { func TestIsBindAddressInUseError(t *testing.T) {
@@ -259,6 +286,9 @@ func TestIsAddressAlreadyInUseError(t *testing.T) {
if !isAddressAlreadyInUseError(errors.New("listen tcp [::]:10001: bind: address already in use")) { if !isAddressAlreadyInUseError(errors.New("listen tcp [::]:10001: bind: address already in use")) {
t.Fatalf("address already in use should be detected") t.Fatalf("address already in use should be detected")
} }
if !isAddressAlreadyInUseError(errors.New("create service 57_7_7_tcp failed: listen tcp4 0.0.0.0:46222: bind: address alreadyin use")) {
t.Fatalf("missing-space alreadyin-use variant should be detected")
}
if isAddressAlreadyInUseError(errors.New("listen tcp4 13.228.170.187:16765: bind: cannot assign requested address")) { if isAddressAlreadyInUseError(errors.New("listen tcp4 13.228.170.187:16765: bind: cannot assign requested address")) {
t.Fatalf("cannot assign requested address should not be treated as address-in-use") t.Fatalf("cannot assign requested address should not be treated as address-in-use")
} }
@@ -268,6 +298,9 @@ func TestIsCannotAssignRequestedAddressError(t *testing.T) {
if !isCannotAssignRequestedAddressError(errors.New("listen tcp4 13.228.170.187:16765: bind: cannot assign requested address")) { if !isCannotAssignRequestedAddressError(errors.New("listen tcp4 13.228.170.187:16765: bind: cannot assign requested address")) {
t.Fatalf("cannot assign requested address should be detected") t.Fatalf("cannot assign requested address should be detected")
} }
if !isCannotAssignRequestedAddressError(errors.New("listen tcp4 13.228.170.187:16765: bind: cannotassignrequestedaddress")) {
t.Fatalf("missing-space cannotassignrequestedaddress variant should be detected")
}
if isCannotAssignRequestedAddressError(errors.New("listen tcp [::]:10001: bind: address already in use")) { if isCannotAssignRequestedAddressError(errors.New("listen tcp [::]:10001: bind: address already in use")) {
t.Fatalf("address already in use should not be treated as cannot-assign") t.Fatalf("address already in use should not be treated as cannot-assign")
} }
@@ -1006,7 +1006,7 @@ func TestForwardUpdateRecoversFromAddressInUseContract(t *testing.T) {
} }
mu.Unlock() mu.Unlock()
if shouldFail { if shouldFail {
return true, "listen tcp 10.42.0.9:44001: bind: address already in use" return true, "create service 57_7_7_tcp failed: listen tcp4 0.0.0.0:46222: bind: address alreadyin use"
} }
return false, "" return false, ""
}) })
@@ -20,8 +20,12 @@
- Result: passed. - Result: passed.
- Command: `cd go-backend && go test ./tests/contract/... -run 'TestForwardUpdateRecoversFromAddressInUseContract|TestTunnelUpdateRecoversFromAddressInUseContract'` - Command: `cd go-backend && go test ./tests/contract/... -run 'TestForwardUpdateRecoversFromAddressInUseContract|TestTunnelUpdateRecoversFromAddressInUseContract'`
- Result: passed. - Result: passed.
- Command: `cd go-backend && go test ./internal/http/handler/... && go test ./tests/contract/... -run 'TestForwardUpdateRecoversFromAddressInUseContract|TestTunnelUpdateRecoversFromAddressInUseContract'`
- Result: passed.
## Investigation Note ## Investigation Note
- Forward update still has its own independent `address already in use` recovery path in `syncForwardServicesWithWarnings` / `rebindForwardServiceOnSelfOccupiedPort`; tunnel update linkage is not the only possible source of the symptom. - Forward update still has its own independent `address already in use` recovery path in `syncForwardServicesWithWarnings` / `rebindForwardServiceOnSelfOccupiedPort`; tunnel update linkage is not the only possible source of the symptom.
- Tunnel update also triggers downstream forward `UpdateService` for bound forwards, so users can still observe the same error around a tunnel edit even when the failing runtime is on the tunnel side. - Tunnel update also triggers downstream forward `UpdateService` for bound forwards, so users can still observe the same error around a tunnel edit even when the failing runtime is on the tunnel side.
- Real node output can collapse spaces into variants like `address alreadyin use` / `cannotassignrequestedaddress`; bind-conflict detection now normalizes whitespace before classifying the error.
- Forward self-heal cleanup now deletes every candidate runtime name variant instead of stopping after the first successful delete, which avoids leaving sibling `_tcp`/`_udp` services behind to keep the port occupied.