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"`