0.3.49
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/ponzischeme89/memby/server/internal/config"
|
||||
)
|
||||
|
||||
// clientIP is where a request came from and how that was worked out. Via is one of
|
||||
// "forwarded", "real-ip", "socket" or "none". It is for operational logging and the
|
||||
// admin console's directory only — never authentication or access control.
|
||||
type clientIP struct {
|
||||
Addr string
|
||||
Via string
|
||||
}
|
||||
|
||||
// String is the address, or "unknown" when none could be determined, matching what the
|
||||
// login history and the trailer log previously recorded.
|
||||
func (c clientIP) String() string {
|
||||
if c.Addr == "" {
|
||||
return "unknown"
|
||||
}
|
||||
return c.Addr
|
||||
}
|
||||
|
||||
// resolveClientIP works out the originating address of an incoming request. It believes
|
||||
// X-Forwarded-For and X-Real-IP only when the immediate peer is a configured trusted
|
||||
// proxy, so a client that reaches the gateway directly cannot spoof its address with a
|
||||
// header.
|
||||
func (s *Server) resolveClientIP(r *http.Request) clientIP {
|
||||
trusted := s.cfg.TrustedProxies
|
||||
if trusted == nil {
|
||||
trusted = config.DefaultTrustedProxyRanges()
|
||||
}
|
||||
return clientIPFrom(r.RemoteAddr, r.Header, trusted)
|
||||
}
|
||||
|
||||
func clientIPFrom(remoteAddr string, header http.Header, trusted []netip.Prefix) clientIP {
|
||||
socket := parseHostAddr(remoteAddr)
|
||||
if !socket.IsValid() {
|
||||
return clientIP{Via: "none"}
|
||||
}
|
||||
fromSocket := clientIP{Addr: socket.String(), Via: "socket"}
|
||||
if !prefixesContain(trusted, socket) {
|
||||
// The peer is not a known proxy, so nothing it forwarded is believed.
|
||||
return fromSocket
|
||||
}
|
||||
|
||||
// X-Forwarded-For grows by one entry per hop, so the rightmost is the nearest
|
||||
// proxy. Walking right to left, the first entry that is not itself a trusted proxy
|
||||
// is the originating client.
|
||||
forwarded := forwardedChain(header)
|
||||
for i := len(forwarded) - 1; i >= 0; i-- {
|
||||
if !prefixesContain(trusted, forwarded[i]) {
|
||||
return clientIP{Addr: forwarded[i].String(), Via: "forwarded"}
|
||||
}
|
||||
}
|
||||
|
||||
// Every forwarded hop was itself trusted. If there were any, the leftmost is the
|
||||
// genuine origin — a direct LAN client behind the household proxy. Otherwise fall
|
||||
// back to a single X-Real-IP, then to the socket.
|
||||
if len(forwarded) > 0 {
|
||||
return clientIP{Addr: forwarded[0].String(), Via: "forwarded"}
|
||||
}
|
||||
if realIP := parseAddr(header.Get("X-Real-IP")); realIP.IsValid() {
|
||||
return clientIP{Addr: realIP.String(), Via: "real-ip"}
|
||||
}
|
||||
return fromSocket
|
||||
}
|
||||
|
||||
func parseHostAddr(remoteAddr string) netip.Addr {
|
||||
remoteAddr = strings.TrimSpace(remoteAddr)
|
||||
if host, _, err := net.SplitHostPort(remoteAddr); err == nil {
|
||||
remoteAddr = host
|
||||
}
|
||||
return parseAddr(remoteAddr)
|
||||
}
|
||||
|
||||
func parseAddr(value string) netip.Addr {
|
||||
addr, err := netip.ParseAddr(strings.TrimSpace(value))
|
||||
if err != nil {
|
||||
return netip.Addr{}
|
||||
}
|
||||
return addr.Unmap()
|
||||
}
|
||||
|
||||
func forwardedChain(header http.Header) []netip.Addr {
|
||||
var out []netip.Addr
|
||||
for _, value := range header.Values("X-Forwarded-For") {
|
||||
for _, part := range strings.Split(value, ",") {
|
||||
if addr := parseAddr(part); addr.IsValid() {
|
||||
out = append(out, addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func prefixesContain(prefixes []netip.Prefix, addr netip.Addr) bool {
|
||||
for _, prefix := range prefixes {
|
||||
if prefix.Contains(addr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user