ip.go

74 lines
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
package router

import (
	"net"
	"net/http"
	"net/netip"
	"strings"
	"sync"
	"time"
)

// ClientIP extracts the client IP from a request, respecting X-Forwarded-For.
func ClientIP(r *http.Request) string {
	if fwd := r.Header.Get("X-Forwarded-For"); fwd != "" {
		if i := strings.IndexByte(fwd, ','); i > 0 {
			return strings.TrimSpace(fwd[:i])
		}
		return fwd
	}
	return r.RemoteAddr
}

// clientAddr parses the client IP (honoring X-Forwarded-For) into a netip.Addr.
// ClientIP may hand back a bare IP (from XFF) or "host:port" (from RemoteAddr);
// both are normalized here. Returns the zero Addr if it can't be parsed.
func clientAddr(r *http.Request) netip.Addr {
	ip := ClientIP(r)
	if h, _, err := net.SplitHostPort(ip); err == nil {
		ip = h
	}
	addr, err := netip.ParseAddr(strings.TrimSpace(ip))
	if err != nil {
		return netip.Addr{}
	}
	return addr.Unmap()
}

func stripPort(host string) string {
	if h, _, err := net.SplitHostPort(host); err == nil {
		return h
	}
	return host
}

// --- Server IP detection ---

var (
	serverIPOnce sync.Once
	serverIPVal  string
)

// ServerIP returns this server's public IP (cached after first call).
func ServerIP() string {
	serverIPOnce.Do(func() {
		client := &http.Client{Timeout: 3 * time.Second}
		for _, url := range []string{
			"http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address",
			"https://ifconfig.me",
		} {
			resp, err := client.Get(url)
			if err != nil {
				continue
			}
			body := make([]byte, 64)
			n, _ := resp.Body.Read(body)
			resp.Body.Close()
			if ip := strings.TrimSpace(string(body[:n])); ip != "" {
				serverIPVal = ip
				return
			}
		}
	})
	return serverIPVal
}