From 06cd75abe0d5e4857a3b9cae3f26d5a582f73652 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Wed, 15 Jul 2026 03:48:05 +0200 Subject: [PATCH] fix(settings): detect a wildcard listen collision between the web and sub ports The web/sub same-port check compared the two listen addresses as raw strings, so binding both on all interfaces with different spellings (webListen 0.0.0.0 vs an empty subListen) slipped past validation and only failed at startup with an opaque bind error. Treat any wildcard listen ('', 0.0.0.0, ::) as overlapping so the clash is reported up front, while still allowing two distinct specific addresses to share a port. --- internal/web/entity/check_valid_test.go | 14 ++++++++++++++ internal/web/entity/entity.go | 23 ++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/internal/web/entity/check_valid_test.go b/internal/web/entity/check_valid_test.go index 78744f65f..e126b4729 100644 --- a/internal/web/entity/check_valid_test.go +++ b/internal/web/entity/check_valid_test.go @@ -27,3 +27,17 @@ func TestCheckValidSmtpFrom(t *testing.T) { } } } + +func TestCheckValidWildcardListenPortConflict(t *testing.T) { + // Same port, both bind all interfaces but spelled differently -> conflict. + s := &AllSetting{WebPort: 2053, SubPort: 2053, WebListen: "0.0.0.0", SubListen: ""} + if err := s.CheckValid(); err == nil { + t.Error("CheckValid must reject the same port bound on 0.0.0.0 and \"\" (both wildcard)") + } + + // Same port on two distinct specific addresses can coexist and must be allowed. + ok := &AllSetting{WebPort: 2053, SubPort: 2053, WebListen: "127.0.0.1", SubListen: "192.168.1.1"} + if err := ok.CheckValid(); err != nil { + t.Errorf("distinct specific listens on the same port should be allowed: %v", err) + } +} diff --git a/internal/web/entity/entity.go b/internal/web/entity/entity.go index eda23449f..1d7116090 100644 --- a/internal/web/entity/entity.go +++ b/internal/web/entity/entity.go @@ -172,7 +172,7 @@ func (s *AllSetting) CheckValid() error { return common.NewError("Sub port is not a valid port:", s.SubPort) } - if (s.SubPort == s.WebPort) && (s.WebListen == s.SubListen) { + if (s.SubPort == s.WebPort) && listenAddressesConflict(s.WebListen, s.SubListen) { return common.NewError("Sub and Web could not use same ip:port, ", s.SubListen, ":", s.SubPort, " & ", s.WebListen, ":", s.WebPort) } @@ -258,6 +258,27 @@ func (s *AllSetting) CheckValid() error { return nil } +// listenAddressesConflict reports whether two listen addresses on the same port +// would collide at bind time. A wildcard listen ("", "0.0.0.0", "::") overlaps +// every address, so it conflicts with anything on that port; two specific +// addresses conflict only when identical. +func listenAddressesConflict(a, b string) bool { + if a == b { + return true + } + return isWildcardListen(a) || isWildcardListen(b) +} + +func isWildcardListen(listen string) bool { + if listen == "" { + return true + } + if ip := net.ParseIP(listen); ip != nil { + return ip.IsUnspecified() + } + return false +} + type HostGroup struct { GroupId string `json:"groupId"` InboundIds []int `json:"inboundIds" validate:"required,min=1"`