From 2d677200222494d132f959166e1e2eb47fa07247 Mon Sep 17 00:00:00 2001 From: wenjie Date: Fri, 24 Apr 2026 10:29:16 +0800 Subject: [PATCH] fix: preserve loopback access for host configuration - Keep internal loopback HTTP APIs reachable when the server is bound to a specific non-loopback host by adding a dedicated 127.0.0.1 listener. - Move listener address helpers into utils and normalize HTTPS redirect hosts so IPv6 request hosts are not double-bracketed. --- server/main.go | 36 +++++++++++++++++++++--------- server/middleware/loopback_http.go | 33 ++++++++++++++++----------- server/utils/listener.go | 28 +++++++++++++++++++++++ 3 files changed, 73 insertions(+), 24 deletions(-) create mode 100644 server/utils/listener.go diff --git a/server/main.go b/server/main.go index b1940db..7832878 100644 --- a/server/main.go +++ b/server/main.go @@ -1,9 +1,7 @@ package main import ( - "fmt" "log" - "net" "os" "os/signal" "strconv" @@ -73,18 +71,33 @@ func run() { router.Init(r) - httpAddr := listenAddr(conf.Host, strconv.Itoa(conf.Port.Http)) + httpAddr := utils.ListenAddr(conf.Host, strconv.Itoa(conf.Port.Http)) + loopbackHTTPAddr := utils.ListenAddr("127.0.0.1", strconv.Itoa(conf.Port.Http)) + needsLoopbackHTTP := utils.NeedsDedicatedLoopbackListener(conf.Host) if conf.Proto == "https" { httpsPortStr := strconv.Itoa(conf.Port.Https) go func() { - err := r.RunTLS(listenAddr(conf.Host, httpsPortStr), conf.Cert.Crt, conf.Cert.Key) + err := r.RunTLS(utils.ListenAddr(conf.Host, httpsPortStr), conf.Cert.Crt, conf.Cert.Key) if err != nil { panic("start https server failed") } }() + if needsLoopbackHTTP { + go func() { + if err := middleware.ListenAndServeLoopbackHTTPRedirect( + loopbackHTTPAddr, + httpsPortStr, + r, + router.LoopbackHTTPAllowedPaths()..., + ); err != nil { + panic("start loopback http server failed") + } + }() + } + if err := middleware.ListenAndServeLoopbackHTTPRedirect( httpAddr, httpsPortStr, @@ -94,6 +107,14 @@ func run() { panic("start http server failed") } } else { + if needsLoopbackHTTP { + go func() { + if err := r.Run(loopbackHTTPAddr); err != nil { + panic("start loopback http server failed") + } + }() + } + if err := r.Run(httpAddr); err != nil { panic("start http server failed") } @@ -103,10 +124,3 @@ func run() { func dispose() { common.GetKvmVision().Close() } - -func listenAddr(host string, port string) string { - if host == "" { - return fmt.Sprintf(":%s", port) - } - return net.JoinHostPort(host, port) -} diff --git a/server/middleware/loopback_http.go b/server/middleware/loopback_http.go index 48dc55d..453c64a 100644 --- a/server/middleware/loopback_http.go +++ b/server/middleware/loopback_http.go @@ -34,22 +34,29 @@ func ListenAndServeLoopbackHTTPRedirect( return } - host := req.Host - if h, _, err := net.SplitHostPort(host); err == nil { - host = h - } - - if strings.Contains(host, ":") { - host = "[" + host + "]" - } - if httpsPort != "443" { - host += ":" + httpsPort - } - - http.Redirect(w, req, "https://"+host+req.URL.RequestURI(), http.StatusTemporaryRedirect) + http.Redirect(w, req, "https://"+redirectHost(req.Host, httpsPort)+req.URL.RequestURI(), http.StatusTemporaryRedirect) })) } +func redirectHost(requestHost string, httpsPort string) string { + host := requestHost + if h, _, err := net.SplitHostPort(requestHost); err == nil { + host = h + } else if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") { + host = strings.TrimPrefix(strings.TrimSuffix(host, "]"), "[") + } + + if httpsPort != "443" { + return net.JoinHostPort(host, httpsPort) + } + + if strings.Contains(host, ":") && !strings.HasPrefix(host, "[") && !strings.HasSuffix(host, "]") { + return "[" + host + "]" + } + + return host +} + func allowByLoopbackInternalToken(req *http.Request) bool { return req != nil && isLoopbackRemote(req.RemoteAddr) && hasValidLoopbackHTTPToken(req) } diff --git a/server/utils/listener.go b/server/utils/listener.go new file mode 100644 index 0000000..0483ee0 --- /dev/null +++ b/server/utils/listener.go @@ -0,0 +1,28 @@ +package utils + +import ( + "fmt" + "net" + "strings" +) + +func ListenAddr(host string, port string) string { + if host == "" { + return fmt.Sprintf(":%s", port) + } + return net.JoinHostPort(host, port) +} + +func NeedsDedicatedLoopbackListener(host string) bool { + host = strings.TrimSpace(strings.Trim(host, "[]")) + if host == "" { + return false + } + + ip := net.ParseIP(host) + if ip == nil { + return !strings.EqualFold(host, "localhost") + } + + return !ip.IsLoopback() && !ip.IsUnspecified() +}