Merge 16857146c8961f809f1a6735e84556d5835eaec9 into 5f4f9643258dc2a65e684b63f12c8d543c936c67

This commit is contained in:
Shirshendu Bhowmick 2026-05-09 13:45:55 +05:30 committed by GitHub
commit 55382278e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 50 additions and 64 deletions

View File

@ -15,6 +15,7 @@ import (
"mime/multipart" "mime/multipart"
"net" "net"
"net/http" "net/http"
"net/netip"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
@ -991,7 +992,7 @@ func (c *Context) ClientIP() string {
var ( var (
trusted bool trusted bool
remoteIP net.IP remoteIP netip.Addr
) )
// If gin is listening a unix socket, always trust it. // If gin is listening a unix socket, always trust it.
localAddr, ok := c.Request.Context().Value(http.LocalAddrContextKey).(net.Addr) localAddr, ok := c.Request.Context().Value(http.LocalAddrContextKey).(net.Addr)
@ -1004,8 +1005,9 @@ func (c *Context) ClientIP() string {
// It also checks if the remoteIP is a trusted proxy or not. // It also checks if the remoteIP is a trusted proxy or not.
// In order to perform this validation, it will see if the IP is contained within at least one of the CIDR blocks // In order to perform this validation, it will see if the IP is contained within at least one of the CIDR blocks
// defined by Engine.SetTrustedProxies() // defined by Engine.SetTrustedProxies()
remoteIP = net.ParseIP(c.RemoteIP()) var err error
if remoteIP == nil { remoteIP, err = netip.ParseAddr(c.RemoteIP())
if err != nil {
return "" return ""
} }
trusted = c.engine.isTrustedProxy(remoteIP) trusted = c.engine.isTrustedProxy(remoteIP)
@ -1020,6 +1022,9 @@ func (c *Context) ClientIP() string {
} }
} }
} }
if !remoteIP.IsValid() {
return ""
}
return remoteIP.String() return remoteIP.String()
} }

View File

@ -16,6 +16,7 @@ import (
"net" "net"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/netip"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
@ -1983,6 +1984,12 @@ func TestContextClientIP(t *testing.T) {
c.Request.RemoteAddr = addr.String() c.Request.RemoteAddr = addr.String()
assert.Equal(t, "20.20.20.20", c.ClientIP()) assert.Equal(t, "20.20.20.20", c.ClientIP())
// unix address with no valid forwarded header: remoteIP stays zero, must return ""
c.Request.Header.Del("X-Forwarded-For")
c.Request.Header.Del("X-Real-IP")
assert.Empty(t, c.ClientIP())
resetContextForClientIPTests(c)
// reset // reset
c.Request = c.Request.WithContext(context.Background()) c.Request = c.Request.WithContext(context.Background())
resetContextForClientIPTests(c) resetContextForClientIPTests(c)
@ -3128,9 +3135,9 @@ func TestRemoteIPFail(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request, _ = http.NewRequest(http.MethodPost, "/", nil)
c.Request.RemoteAddr = "[:::]:80" c.Request.RemoteAddr = "[:::]:80"
ip := net.ParseIP(c.RemoteIP()) ip, err := netip.ParseAddr(c.RemoteIP())
trust := c.engine.isTrustedProxy(ip) trust := c.engine.isTrustedProxy(ip)
assert.Nil(t, ip) require.Error(t, err)
assert.False(t, trust) assert.False(t, trust)
} }

67
gin.go
View File

@ -9,6 +9,7 @@ import (
"html/template" "html/template"
"net" "net"
"net/http" "net/http"
"net/netip"
"os" "os"
"path" "path"
"strings" "strings"
@ -36,15 +37,9 @@ var (
var defaultPlatform string var defaultPlatform string
var defaultTrustedCIDRs = []*net.IPNet{ var defaultTrustedCIDRs = []netip.Prefix{
{ // 0.0.0.0/0 (IPv4) netip.MustParsePrefix("0.0.0.0/0"), // IPv4
IP: net.IP{0x0, 0x0, 0x0, 0x0}, netip.MustParsePrefix("::/0"), // IPv6
Mask: net.IPMask{0x0, 0x0, 0x0, 0x0},
},
{ // ::/0 (IPv6)
IP: net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
Mask: net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
},
} }
// HandlerFunc defines the handler used by gin middleware as return value. // HandlerFunc defines the handler used by gin middleware as return value.
@ -185,7 +180,7 @@ type Engine struct {
maxParams uint16 maxParams uint16
maxSections uint16 maxSections uint16
trustedProxies []string trustedProxies []string
trustedCIDRs []*net.IPNet trustedCIDRs []netip.Prefix
} }
var _ IRouter = (*Engine)(nil) var _ IRouter = (*Engine)(nil)
@ -411,33 +406,33 @@ func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
return routes return routes
} }
func (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) { func (engine *Engine) prepareTrustedCIDRs() ([]netip.Prefix, error) {
if engine.trustedProxies == nil { if engine.trustedProxies == nil {
return nil, nil return nil, nil
} }
cidr := make([]*net.IPNet, 0, len(engine.trustedProxies)) cidrs := make([]netip.Prefix, 0, len(engine.trustedProxies))
for _, trustedProxy := range engine.trustedProxies { for _, trustedProxy := range engine.trustedProxies {
if !strings.Contains(trustedProxy, "/") { if !strings.Contains(trustedProxy, "/") {
ip := parseIP(trustedProxy) addr, err := netip.ParseAddr(trustedProxy)
if ip == nil { if err != nil {
return cidr, &net.ParseError{Type: "IP address", Text: trustedProxy} return cidrs, &net.ParseError{Type: "IP address", Text: trustedProxy}
} }
addr = addr.Unmap()
switch len(ip) { bits := 128
case net.IPv4len: if addr.Is4() {
trustedProxy += "/32" bits = 32
case net.IPv6len:
trustedProxy += "/128"
} }
cidrs = append(cidrs, netip.PrefixFrom(addr, bits))
continue
} }
_, cidrNet, err := net.ParseCIDR(trustedProxy) prefix, err := netip.ParsePrefix(trustedProxy)
if err != nil { if err != nil {
return cidr, err return cidrs, &net.ParseError{Type: "CIDR address", Text: trustedProxy}
} }
cidr = append(cidr, cidrNet) cidrs = append(cidrs, prefix.Masked())
} }
return cidr, nil return cidrs, nil
} }
// SetTrustedProxies set a list of network origins (IPv4 addresses, // SetTrustedProxies set a list of network origins (IPv4 addresses,
@ -455,7 +450,7 @@ func (engine *Engine) SetTrustedProxies(trustedProxies []string) error {
// isUnsafeTrustedProxies checks if Engine.trustedCIDRs contains all IPs, it's not safe if it has (returns true) // isUnsafeTrustedProxies checks if Engine.trustedCIDRs contains all IPs, it's not safe if it has (returns true)
func (engine *Engine) isUnsafeTrustedProxies() bool { func (engine *Engine) isUnsafeTrustedProxies() bool {
return engine.isTrustedProxy(net.ParseIP("0.0.0.0")) || engine.isTrustedProxy(net.ParseIP("::")) return engine.isTrustedProxy(netip.MustParseAddr("0.0.0.0")) || engine.isTrustedProxy(netip.MustParseAddr("::"))
} }
// parseTrustedProxies parse Engine.trustedProxies to Engine.trustedCIDRs // parseTrustedProxies parse Engine.trustedProxies to Engine.trustedCIDRs
@ -466,7 +461,7 @@ func (engine *Engine) parseTrustedProxies() error {
} }
// isTrustedProxy will check whether the IP address is included in the trusted list according to Engine.trustedCIDRs // isTrustedProxy will check whether the IP address is included in the trusted list according to Engine.trustedCIDRs
func (engine *Engine) isTrustedProxy(ip net.IP) bool { func (engine *Engine) isTrustedProxy(ip netip.Addr) bool {
if engine.trustedCIDRs == nil { if engine.trustedCIDRs == nil {
return false return false
} }
@ -486,8 +481,8 @@ func (engine *Engine) validateHeader(header string) (clientIP string, valid bool
items := strings.Split(header, ",") items := strings.Split(header, ",")
for i := len(items) - 1; i >= 0; i-- { for i := len(items) - 1; i >= 0; i-- {
ipStr := strings.TrimSpace(items[i]) ipStr := strings.TrimSpace(items[i])
ip := net.ParseIP(ipStr) ip, err := netip.ParseAddr(ipStr)
if ip == nil { if err != nil {
break break
} }
@ -520,20 +515,6 @@ func (engine *Engine) updateRouteTrees() {
} }
} }
// parseIP parse a string representation of an IP and returns a net.IP with the
// minimum byte representation or nil if input is invalid.
func parseIP(ip string) net.IP {
parsedIP := net.ParseIP(ip)
if ipv4 := parsedIP.To4(); ipv4 != nil {
// return ip in a 4-byte representation
return ipv4
}
// return ip in a 16-byte representation or nil
return parsedIP
}
// Run attaches the router to a http.Server and starts listening and serving HTTP requests. // Run attaches the router to a http.Server and starts listening and serving HTTP requests.
// It is a shortcut for http.ListenAndServe(addr, router) // It is a shortcut for http.ListenAndServe(addr, router)
// Note: this method will block the calling goroutine indefinitely unless an error happens. // Note: this method will block the calling goroutine indefinitely unless an error happens.

View File

@ -12,6 +12,7 @@ import (
"net" "net"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/netip"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
@ -869,7 +870,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) {
// valid ipv4 cidr // valid ipv4 cidr
{ {
expectedTrustedCIDRs := []*net.IPNet{parseCIDR("0.0.0.0/0")} expectedTrustedCIDRs := []netip.Prefix{netip.MustParsePrefix("0.0.0.0/0")}
err := r.SetTrustedProxies([]string{"0.0.0.0/0"}) err := r.SetTrustedProxies([]string{"0.0.0.0/0"})
require.NoError(t, err) require.NoError(t, err)
@ -885,7 +886,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) {
// valid ipv4 address // valid ipv4 address
{ {
expectedTrustedCIDRs := []*net.IPNet{parseCIDR("192.168.1.33/32")} expectedTrustedCIDRs := []netip.Prefix{netip.MustParsePrefix("192.168.1.33/32")}
err := r.SetTrustedProxies([]string{"192.168.1.33"}) err := r.SetTrustedProxies([]string{"192.168.1.33"})
@ -902,7 +903,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) {
// valid ipv6 address // valid ipv6 address
{ {
expectedTrustedCIDRs := []*net.IPNet{parseCIDR("2002:0000:0000:1234:abcd:ffff:c0a8:0101/128")} expectedTrustedCIDRs := []netip.Prefix{netip.MustParsePrefix("2002:0000:0000:1234:abcd:ffff:c0a8:0101/128")}
err := r.SetTrustedProxies([]string{"2002:0000:0000:1234:abcd:ffff:c0a8:0101"}) err := r.SetTrustedProxies([]string{"2002:0000:0000:1234:abcd:ffff:c0a8:0101"})
require.NoError(t, err) require.NoError(t, err)
@ -918,7 +919,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) {
// valid ipv6 cidr // valid ipv6 cidr
{ {
expectedTrustedCIDRs := []*net.IPNet{parseCIDR("::/0")} expectedTrustedCIDRs := []netip.Prefix{netip.MustParsePrefix("::/0")}
err := r.SetTrustedProxies([]string{"::/0"}) err := r.SetTrustedProxies([]string{"::/0"})
require.NoError(t, err) require.NoError(t, err)
@ -934,10 +935,10 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) {
// valid combination // valid combination
{ {
expectedTrustedCIDRs := []*net.IPNet{ expectedTrustedCIDRs := []netip.Prefix{
parseCIDR("::/0"), netip.MustParsePrefix("::/0"),
parseCIDR("192.168.0.0/16"), netip.MustParsePrefix("192.168.0.0/16"),
parseCIDR("172.16.0.1/32"), netip.MustParsePrefix("172.16.0.1/32"),
} }
err := r.SetTrustedProxies([]string{ err := r.SetTrustedProxies([]string{
"::/0", "::/0",
@ -969,14 +970,6 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) {
} }
} }
func parseCIDR(cidr string) *net.IPNet {
_, parsedCIDR, err := net.ParseCIDR(cidr)
if err != nil {
fmt.Println(err)
}
return parsedCIDR
}
func assertRoutePresent(t *testing.T, gotRoutes RoutesInfo, wantRoute RouteInfo) { func assertRoutePresent(t *testing.T, gotRoutes RoutesInfo, wantRoute RouteInfo) {
for _, gotRoute := range gotRoutes { for _, gotRoute := range gotRoutes {
if gotRoute.Path == wantRoute.Path && gotRoute.Method == wantRoute.Method { if gotRoute.Path == wantRoute.Path && gotRoute.Method == wantRoute.Method {