801
// the headers at all, then they will be added with the values
802
// that we can glean from the request.
803
func (h Handler) addForwardedHeaders(req *http.Request) error {
804
- // Parse the remote IP, ignore the error as non-fatal,
805
- // but the remote IP is required to continue, so we
806
- // just return early. This should probably never happen
807
- // though, unless some other module manipulated the request's
808
- // remote address and used an invalid value.
809
- clientIP, _, err := net.SplitHostPort(req.RemoteAddr)
810
- if err != nil {
811
- // Remove the `X-Forwarded-*` headers to avoid upstreams
812
- // potentially trusting a header that came from the client
813
- req.Header.Del("X-Forwarded-For")
814
- req.Header.Del("X-Forwarded-Proto")
815
- req.Header.Del("X-Forwarded-Host")
816
- return nil
817
- }
818
-
819
- // Client IP may contain a zone if IPv6, so we need
820
- // to pull that out before parsing the IP
821
- clientIP, _, _ = strings.Cut(clientIP, "%")
822
- ipAddr, err := netip.ParseAddr(clientIP)
823
-
804
// Check if the client is a trusted proxy
805
trusted := caddyhttp.GetVar(req.Context(), caddyhttp.TrustedProxyVarKey).(bool)
806
827
- // If ParseAddr fails (e.g. non-IP network like SCION), we cannot check
828
- // if it is a trusted proxy by IP range. In this case, we ignore the
829
- // error and treat the connection as untrusted (or retain existing status).
830
- if err == nil {
831
- for _, ipRange := range h.trustedProxies {
832
- if ipRange.Contains(ipAddr) {
833
- trusted = true
834
- break
807
+ var clientIP string
808
+
809
+ if req.RemoteAddr == "@" {
810
+ // For Unix socket connections, RemoteAddr is "@" which cannot
811
+ // be parsed as host:port. If untrusted, strip forwarded headers
812
+ // for security. If trusted, there is no peer IP to append to
813
+ // X-Forwarded-For, so clientIP stays empty.
814
+ if !trusted {
815
+ req.Header.Del("X-Forwarded-For")
816
+ req.Header.Del("X-Forwarded-Proto")
817
+ req.Header.Del("X-Forwarded-Host")
818
+ return nil
819
+ }
820
+ } else {
821
+ // Parse the remote IP, ignore the error as non-fatal,
822
+ // but the remote IP is required to continue, so we
823
+ // just return early. This should probably never happen
824
+ // though, unless some other module manipulated the request's
825
+ // remote address and used an invalid value.
826
+ var err error
827
+ clientIP, _, err = net.SplitHostPort(req.RemoteAddr)
828
+ if err != nil {
829
+ // Remove the `X-Forwarded-*` headers to avoid upstreams
830
+ // potentially trusting a header that came from the client
831
+ req.Header.Del("X-Forwarded-For")
832
+ req.Header.Del("X-Forwarded-Proto")
833
+ req.Header.Del("X-Forwarded-Host")
834
+ return nil
835
+ }
836
+
837
+ // Client IP may contain a zone if IPv6, so we need
838
+ // to pull that out before parsing the IP
839
+ clientIP, _, _ = strings.Cut(clientIP, "%")
840
+ ipAddr, err := netip.ParseAddr(clientIP)
841
+
842
+ // If ParseAddr fails (e.g. non-IP network like SCION), we cannot check
843
+ // if it is a trusted proxy by IP range. In this case, we ignore the
844
+ // error and treat the connection as untrusted (or retain existing status).
845
+ if err == nil {
846
+ for _, ipRange := range h.trustedProxies {
847
+ if ipRange.Contains(ipAddr) {
848
+ trusted = true
849
+ break
850
+ }
851
}
852
}
853
}
855
// If we aren't the first proxy, and the proxy is trusted,
856
// retain prior X-Forwarded-For information as a comma+space
857
// separated list and fold multiple headers into one.
842
- clientXFF := clientIP
858
prior, ok, omit := allHeaderValues(req.Header, "X-Forwarded-For")
844
- if trusted && ok && prior != "" {
845
- clientXFF = prior + ", " + clientXFF
846
- }
859
if !omit {
848
- req.Header.Set("X-Forwarded-For", clientXFF)
860
+ if trusted && ok && prior != "" {
861
+ if clientIP != "" {
862
+ req.Header.Set("X-Forwarded-For", prior+", "+clientIP)
863
+ } else {
864
+ req.Header.Set("X-Forwarded-For", prior)
865
+ }
866
+ } else if clientIP != "" {
867
+ req.Header.Set("X-Forwarded-For", clientIP)
868
+ }
869
}
870
871
// Set X-Forwarded-Proto; many backend apps expect this,