diff options
| author | sergei <sergei@em-sysadmin.xyz> | 2026-04-14 06:23:55 +0400 |
|---|---|---|
| committer | sergei <sergei@em-sysadmin.xyz> | 2026-04-14 06:23:55 +0400 |
| commit | 3d51aa455006903345f554a2dd90034993796114 (patch) | |
| tree | 62a7be2faf047f5eb7886feebc3b815556f03d7f /internal/api/middleware.go | |
| download | vpnem-3d51aa455006903345f554a2dd90034993796114.tar.gz vpnem-3d51aa455006903345f554a2dd90034993796114.tar.bz2 vpnem-3d51aa455006903345f554a2dd90034993796114.zip | |
- Multi-protocol VPS nodes (VLESS-REALITY + Hysteria2 + SOCKS5)
- Smart load balancing via recommendation API
- Windows/Linux client (Go + Wails + sing-box)
- Server API with RealIP detection and connection tracking
- Auto-deployment via vpnui control plane
- Silent Windows installer with UAC elevation
- Load-based server recommendation (no sticky sessions)
- Best Server one-click connection workflow
Diffstat (limited to 'internal/api/middleware.go')
| -rw-r--r-- | internal/api/middleware.go | 70 |
1 files changed, 70 insertions, 0 deletions
diff --git a/internal/api/middleware.go b/internal/api/middleware.go new file mode 100644 index 0000000..76885ac --- /dev/null +++ b/internal/api/middleware.go @@ -0,0 +1,70 @@ +package api + +import ( + "context" + "net" + "net/http" + "strings" +) + +// contextKey for real IP. +type contextKey string + +const ctxRealIP contextKey = "real_ip" + +// RealIP middleware extracts the client's real public IP. +// Priority: X-Forwarded-For (from Traefik) > X-Real-IP > RemoteAddr. +func RealIP(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ip := extractRealIP(r) + if ip != "" { + r = r.WithContext(context.WithValue(r.Context(), ctxRealIP, ip)) + } + next(w, r) + } +} + +// GetRealIP returns the client IP from context. +func GetRealIP(r *http.Request) string { + if ip, ok := r.Context().Value(ctxRealIP).(string); ok { + return ip + } + return "" +} + +func extractRealIP(r *http.Request) string { + // 1. X-Forwarded-For (Traefik, nginx, etc.) + if xff := r.Header.Get("X-Forwarded-For"); xff != "" { + // Can contain multiple IPs: client, proxy1, proxy2 + // First one is the original client + parts := strings.Split(xff, ",") + if len(parts) > 0 { + ip := strings.TrimSpace(parts[0]) + if isValidIP(ip) { + return ip + } + } + } + + // 2. X-Real-IP (some proxies use this) + if xri := r.Header.Get("X-Real-IP"); xri != "" { + ip := strings.TrimSpace(xri) + if isValidIP(ip) { + return ip + } + } + + // 3. RemoteAddr fallback (direct connection) + host, _, err := net.SplitHostPort(r.RemoteAddr) + if err == nil && isValidIP(host) { + return host + } + + return "" +} + +func isValidIP(ip string) bool { + // Accept both IPv4 and IPv6 + parsed := net.ParseIP(ip) + return parsed != nil +} |
