mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-24 05:26:08 +00:00
fix(sub): keep listen/bind IP out of subscription page URLs
The subscription page leaked an inbound's server-side Listen IP into the client-facing URLs when a bind address was set: - Per-config links: resolveInboundAddress returned the bind Listen IP (loopback/private/public alike) instead of the host the subscriber reached the panel on. It now returns the node address for node-managed inbounds, otherwise the subscriber host; the bind Listen is ignored (External Proxy remains the way to advertise a specific endpoint). - Subscription Copy URL (SUB/JSON/CLASH): BuildURLs composed the base differently from the panel's Client Information page and never normalized the request host, so a loopback/bind request leaked the raw IP. The composition is extracted into the shared SettingService.BuildSubURIBase, used by both the panel and the sub page so they render identically, and fed the already-normalized subscriber host.
This commit is contained in:
+14
-48
@@ -663,24 +663,17 @@ func (s *SubService) loadNodes() {
|
||||
s.nodesByID = m
|
||||
}
|
||||
|
||||
// resolveInboundAddress picks the host an external client should
|
||||
// connect to. Order:
|
||||
// 1. If the inbound is node-managed and the node has an address, use
|
||||
// the node's address — central panel's hostname doesn't speak xray
|
||||
// for that inbound.
|
||||
// 2. If the inbound binds to a non-wildcard listen address, use it.
|
||||
// 3. Otherwise fall back to the request's host (whatever the client
|
||||
// subscribed against).
|
||||
// resolveInboundAddress returns the node's address for node-managed inbounds,
|
||||
// otherwise the subscriber's host (s.address). The inbound's bind Listen is
|
||||
// deliberately ignored: it's a server-side address, not a client-reachable
|
||||
// host, so operators advertise a specific endpoint via External Proxy instead.
|
||||
func (s *SubService) resolveInboundAddress(inbound *model.Inbound) string {
|
||||
if inbound.NodeID != nil && s.nodesByID != nil {
|
||||
if n, ok := s.nodesByID[*inbound.NodeID]; ok && n.Address != "" {
|
||||
return n.Address
|
||||
}
|
||||
}
|
||||
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
||||
return s.address
|
||||
}
|
||||
return inbound.Listen
|
||||
return s.address
|
||||
}
|
||||
|
||||
func findClientIndex(clients []model.Client, email string) int {
|
||||
@@ -1866,7 +1859,7 @@ func (s *SubService) ResolveRequest(c *gin.Context) (scheme string, host string,
|
||||
|
||||
// BuildURLs constructs absolute subscription and JSON subscription URLs for a given subscription ID.
|
||||
// It prioritizes configured URIs, then individual settings, and finally falls back to request-derived components.
|
||||
func (s *SubService) BuildURLs(scheme, hostWithPort, subPath, subJsonPath, subClashPath, subId string) (subURL, subJsonURL, subClashURL string) {
|
||||
func (s *SubService) BuildURLs(subPath, subJsonPath, subClashPath, subId string) (subURL, subJsonURL, subClashURL string) {
|
||||
if subId == "" {
|
||||
return "", "", ""
|
||||
}
|
||||
@@ -1875,50 +1868,23 @@ func (s *SubService) BuildURLs(scheme, hostWithPort, subPath, subJsonPath, subCl
|
||||
configuredSubJsonURI, _ := s.settingService.GetSubJsonURI()
|
||||
configuredSubClashURI, _ := s.settingService.GetSubClashURI()
|
||||
|
||||
var baseScheme, baseHostWithPort string
|
||||
if configuredSubURI == "" || configuredSubJsonURI == "" || configuredSubClashURI == "" {
|
||||
baseScheme, baseHostWithPort = s.getBaseSchemeAndHost(scheme, hostWithPort)
|
||||
}
|
||||
// Same base as the panel's Client Information page; s.address is the
|
||||
// subscriber's host already normalized away from any loopback/bind IP.
|
||||
base := s.settingService.BuildSubURIBase(s.address)
|
||||
|
||||
subURL = s.buildSingleURL(configuredSubURI, baseScheme, baseHostWithPort, subPath, subId)
|
||||
subJsonURL = s.buildSingleURL(configuredSubJsonURI, baseScheme, baseHostWithPort, subJsonPath, subId)
|
||||
subClashURL = s.buildSingleURL(configuredSubClashURI, baseScheme, baseHostWithPort, subClashPath, subId)
|
||||
subURL = s.buildSingleURL(configuredSubURI, base, subPath, subId)
|
||||
subJsonURL = s.buildSingleURL(configuredSubJsonURI, base, subJsonPath, subId)
|
||||
subClashURL = s.buildSingleURL(configuredSubClashURI, base, subClashPath, subId)
|
||||
|
||||
return subURL, subJsonURL, subClashURL
|
||||
}
|
||||
|
||||
// getBaseSchemeAndHost determines the base scheme and host from settings or falls back to request values
|
||||
func (s *SubService) getBaseSchemeAndHost(requestScheme, requestHostWithPort string) (string, string) {
|
||||
subDomain, err := s.settingService.GetSubDomain()
|
||||
if err != nil || subDomain == "" {
|
||||
return requestScheme, requestHostWithPort
|
||||
}
|
||||
|
||||
// Get port and TLS settings
|
||||
subPort, _ := s.settingService.GetSubPort()
|
||||
subKeyFile, _ := s.settingService.GetSubKeyFile()
|
||||
subCertFile, _ := s.settingService.GetSubCertFile()
|
||||
|
||||
// Determine scheme from TLS configuration
|
||||
scheme := "http"
|
||||
if subKeyFile != "" && subCertFile != "" {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
// Build host:port, always include port for clarity
|
||||
hostWithPort := fmt.Sprintf("%s:%d", subDomain, subPort)
|
||||
|
||||
return scheme, hostWithPort
|
||||
}
|
||||
|
||||
// buildSingleURL constructs a single URL using configured URI or base components
|
||||
func (s *SubService) buildSingleURL(configuredURI, baseScheme, baseHostWithPort, basePath, subId string) string {
|
||||
func (s *SubService) buildSingleURL(configuredURI, base, basePath, subId string) string {
|
||||
if configuredURI != "" {
|
||||
return s.joinPathWithID(configuredURI, subId)
|
||||
}
|
||||
|
||||
baseURL := fmt.Sprintf("%s://%s", baseScheme, baseHostWithPort)
|
||||
return s.joinPathWithID(baseURL+basePath, subId)
|
||||
return s.joinPathWithID(base+basePath, subId)
|
||||
}
|
||||
|
||||
// joinPathWithID safely joins a base path with a subscription ID
|
||||
|
||||
Reference in New Issue
Block a user