Files
rose_cat707 0d84b07f68
CI / docker (push) Successful in 1m58s
feat: Wormhole 内网穿透与反向代理网关
Go + Vue 管理台:Agent 隧道、域名反代、IP 安全、访客验证与监控大盘。
Gitea Actions CI 多架构镜像;纯 Go SQLite、无 CGO Docker 构建。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 17:52:43 +08:00

153 lines
3.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package proxy
import (
"encoding/json"
"net"
"net/http"
"strings"
"github.com/wormhole/wormhole/internal/auth"
"github.com/wormhole/wormhole/internal/store"
)
const (
// IPForwardReplace 覆盖 X-Forwarded-For / X-Real-IP 为访客真实 IP。
IPForwardReplace = "replace"
// IPForwardAppend 按反向代理规范追加 X-Forwarded-ForNginx proxy_add_x_forwarded_for)。
IPForwardAppend = "append"
)
func normalizeIPForwardMode(mode string) string {
switch mode {
case IPForwardReplace, IPForwardAppend:
return mode
case "real": // 兼容旧值
return IPForwardReplace
case "proxy": // 兼容旧值
return IPForwardAppend
default:
return IPForwardReplace
}
}
func parseHeaderMap(raw string) map[string]string {
raw = strings.TrimSpace(raw)
if raw == "" || raw == "{}" {
return nil
}
var m map[string]string
if err := json.Unmarshal([]byte(raw), &m); err != nil {
return nil
}
return m
}
func mergeHeaderMaps(base, override map[string]string) map[string]string {
out := make(map[string]string)
for k, v := range base {
out[k] = v
}
for k, v := range override {
out[k] = v
}
if len(out) == 0 {
return nil
}
return out
}
func effectiveIPMode(resourceMode, clientMode string) string {
if resourceMode != "" {
return normalizeIPForwardMode(resourceMode)
}
if clientMode != "" {
return normalizeIPForwardMode(clientMode)
}
return IPForwardReplace
}
func resolveProxyConfig(st *store.Store, clientID uint, resourceMode, resourceHeaders string) (mode string, headers map[string]string) {
client, _ := st.GetClientByID(clientID)
clientMode := ""
clientHeaders := ""
if client != nil {
clientMode = client.IPForwardMode
clientHeaders = client.CustomHeaders
}
mode = effectiveIPMode(resourceMode, clientMode)
headers = mergeHeaderMaps(parseHeaderMap(clientHeaders), parseHeaderMap(resourceHeaders))
return mode, headers
}
func hostOnly(addr string) string {
if addr == "" {
return ""
}
host, _, err := net.SplitHostPort(addr)
if err != nil {
return strings.Trim(addr, "[]")
}
return host
}
func directClientIP(r *http.Request) string {
return hostOnly(r.RemoteAddr)
}
func forwardIPForHTTP(r *http.Request, mode string) string {
mode = normalizeIPForwardMode(mode)
if mode == IPForwardAppend {
return directClientIP(r)
}
return auth.ExtractRemoteIP(r)
}
func forwardIPForTCP(clientAddr string, _ string) string {
return hostOnly(clientAddr)
}
func isForbiddenHeader(name string) bool {
switch strings.ToLower(name) {
case "connection", "transfer-encoding", "keep-alive", "proxy-connection",
"te", "trailer", "upgrade", "content-length", "host":
return true
default:
return false
}
}
func applyOutboundHeaders(req *http.Request, r *http.Request, mode string, custom map[string]string) {
mode = normalizeIPForwardMode(mode)
req.Header.Del("X-Forwarded-For")
req.Header.Del("X-Real-IP")
req.Header.Del("Forwarded")
switch mode {
case IPForwardAppend:
clientIP := directClientIP(r)
if clientIP == "" {
break
}
existing := strings.TrimSpace(r.Header.Get("X-Forwarded-For"))
xff := clientIP
if existing != "" {
xff = existing + ", " + clientIP
}
req.Header.Set("X-Forwarded-For", xff)
req.Header.Set("X-Real-IP", clientIP)
default:
visitorIP := auth.ExtractRemoteIP(r)
if visitorIP != "" {
req.Header.Set("X-Forwarded-For", visitorIP)
req.Header.Set("X-Real-IP", visitorIP)
}
}
for k, v := range custom {
if k == "" || isForbiddenHeader(k) {
continue
}
req.Header.Set(k, v)
}
}