fix: add forward port occupancy validation and runtime residual cleanup (#278)

* fix: tolerate service not found during forward deletion

- Refactor deleteForwardServicesOnNode to handle not-found errors gracefully
- Extract deleteForwardServiceCandidates helper for reuse
- Add tests for not-found tolerance scenarios
- Ensures compatibility with legacy node versions

Entire-Checkpoint: a3bacf836c57

* fix: add forward port occupancy validation and runtime residual cleanup

- Add forward port occupancy validation on create/update paths
- Extend self-occupy recovery to clean residual candidate service names
- Add regression tests for address-in-use recovery with legacy runtime residue

Fixes port conflict issues when upgrading from 2.1.6 to later versions

Entire-Checkpoint: fb0a2aee4cb5
This commit is contained in:
sagit
2026-03-06 10:57:07 +08:00
committed by GitHub
parent c10f894afd
commit 1db5452be9
5 changed files with 131 additions and 6 deletions
@@ -1191,6 +1191,10 @@ func (h *Handler) forwardCreate(w http.ResponseWriter, r *http.Request) {
response.WriteJSON(w, response.ErrDefault(err.Error()))
return
}
if err := h.validateForwardPortAvailability(node, port, 0); err != nil {
response.WriteJSON(w, response.ErrDefault(err.Error()))
return
}
}
now := time.Now().UnixMilli()
inx := h.repo.NextIndex("forward")
@@ -1326,6 +1330,10 @@ func (h *Handler) forwardUpdate(w http.ResponseWriter, r *http.Request) {
response.WriteJSON(w, response.ErrDefault(err.Error()))
return
}
if err := h.validateForwardPortAvailability(node, port, id); err != nil {
response.WriteJSON(w, response.ErrDefault(err.Error()))
return
}
}
now := time.Now().UnixMilli()
if err := h.repo.UpdateForward(id, name, tunnelID, remoteAddr, strategy, now, newSpeedID); err != nil {
@@ -3532,6 +3540,20 @@ func validateLocalNodePort(node *nodeRecord, port int) error {
return nil
}
func (h *Handler) validateForwardPortAvailability(node *nodeRecord, port int, currentForwardID int64) error {
if h == nil || h.repo == nil || node == nil || port <= 0 {
return nil
}
occupied, err := h.repo.HasOtherForwardOnNodePort(node.ID, port, currentForwardID)
if err != nil {
return err
}
if occupied {
return fmt.Errorf("节点 %s 端口 %d 已被其他转发占用", node.Name, port)
}
return nil
}
func parsePortRangeMinMax(input string) (int, int) {
input = strings.TrimSpace(input)
if input == "" {