mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 10:41:03 +01:00
improve remote addr parsing as requested at: https://github.com/kataras/iris/issues/1453
Former-commit-id: e5fde988eda9bf582b04285a1c77ba123910a699
This commit is contained in:
parent
c5392ede6a
commit
6c6de6b85d
|
@ -184,6 +184,8 @@ Other Improvements:
|
||||||
|
|
||||||
- Hero Handlers (and `app.ConfigureContainer().Handle`) do not have to require `iris.Context` just to call `ctx.Next()` anymore, this is done automatically now.
|
- Hero Handlers (and `app.ConfigureContainer().Handle`) do not have to require `iris.Context` just to call `ctx.Next()` anymore, this is done automatically now.
|
||||||
|
|
||||||
|
- Improve Remote Address parsing as requested at: https://github.com/kataras/iris/issues/1453. Add `Configuration.RemoteAddrPrivateSubnets` to exclude those addresses when fetched by `Configuration.RemoteAddrHeaders` through `context.RemoteAddr() string`.
|
||||||
|
|
||||||
New Context Methods:
|
New Context Methods:
|
||||||
|
|
||||||
- `context.UpsertCookie(*http.Cookie, cookieOptions ...context.CookieOption)` upserts a cookie, fixes [#1485](https://github.com/kataras/iris/issues/1485) too
|
- `context.UpsertCookie(*http.Cookie, cookieOptions ...context.CookieOption)` upserts a cookie, fixes [#1485](https://github.com/kataras/iris/issues/1485) too
|
||||||
|
|
|
@ -137,6 +137,21 @@ func main() {
|
||||||
## Builtin Configurators
|
## Builtin Configurators
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
|
||||||
|
// WithGlobalConfiguration will load the global yaml configuration file
|
||||||
|
// from the home directory and it will set/override the whole app's configuration
|
||||||
|
// to that file's contents. The global configuration file can be modified by user
|
||||||
|
// and be used by multiple iris instances.
|
||||||
|
//
|
||||||
|
// This is useful when we run multiple iris servers that share the same
|
||||||
|
// configuration, even with custom values at its "Other" field.
|
||||||
|
//
|
||||||
|
// Usage: `app.Configure(iris.WithGlobalConfiguration)` or `app.Run([iris.Runner], iris.WithGlobalConfiguration)`.
|
||||||
|
WithGlobalConfiguration
|
||||||
|
|
||||||
|
// variables for configurators don't need any receivers, functions
|
||||||
|
// for them that need (helps code editors to recognise as variables without parenthesis completion).
|
||||||
|
|
||||||
// WithoutServerError will cause to ignore the matched "errors"
|
// WithoutServerError will cause to ignore the matched "errors"
|
||||||
// from the main application's `Run` function.
|
// from the main application's `Run` function.
|
||||||
//
|
//
|
||||||
|
@ -147,81 +162,128 @@ func main() {
|
||||||
// See `Configuration#IgnoreServerErrors []string` too.
|
// See `Configuration#IgnoreServerErrors []string` too.
|
||||||
//
|
//
|
||||||
// Example: https://github.com/kataras/iris/tree/master/_examples/http-listening/listen-addr/omit-server-errors
|
// Example: https://github.com/kataras/iris/tree/master/_examples/http-listening/listen-addr/omit-server-errors
|
||||||
func WithoutServerError(errors ...error) Configurator
|
WithoutServerError(errors ...error) Configurator
|
||||||
|
|
||||||
// WithoutStartupLog turns off the information send, once, to the terminal when the main server is open.
|
// WithoutStartupLog turns off the information send, once, to the terminal when the main server is open.
|
||||||
var WithoutStartupLog
|
WithoutStartupLog
|
||||||
|
|
||||||
// WithoutInterruptHandler disables the automatic graceful server shutdown
|
// WithoutInterruptHandler disables the automatic graceful server shutdown
|
||||||
// when control/cmd+C pressed.
|
// when control/cmd+C pressed.
|
||||||
var WithoutInterruptHandler
|
WithoutInterruptHandle
|
||||||
|
|
||||||
// WithoutPathCorrection disables the PathCorrection setting.
|
// WithoutPathCorrection disables the PathCorrection setting.
|
||||||
//
|
//
|
||||||
// See `Configuration`.
|
// See `Configuration`.
|
||||||
var WithoutPathCorrection
|
WithoutPathCorrectio
|
||||||
|
|
||||||
|
// WithoutPathCorrectionRedirection disables the PathCorrectionRedirection setting.
|
||||||
|
//
|
||||||
|
// See `Configuration`.
|
||||||
|
WithoutPathCorrectionRedirection
|
||||||
|
|
||||||
// WithoutBodyConsumptionOnUnmarshal disables BodyConsumptionOnUnmarshal setting.
|
// WithoutBodyConsumptionOnUnmarshal disables BodyConsumptionOnUnmarshal setting.
|
||||||
//
|
//
|
||||||
// See `Configuration`.
|
// See `Configuration`.
|
||||||
var WithoutBodyConsumptionOnUnmarshal
|
WithoutBodyConsumptionOnUnmarshal
|
||||||
|
|
||||||
// WithoutAutoFireStatusCode disables the AutoFireStatusCode setting.
|
// WithoutAutoFireStatusCode disables the AutoFireStatusCode setting.
|
||||||
//
|
//
|
||||||
// See `Configuration`.
|
// See `Configuration`.
|
||||||
var WithoutAutoFireStatusCode
|
WithoutAutoFireStatusCode
|
||||||
|
|
||||||
// WithPathEscape enanbles the PathEscape setting.
|
// WithPathEscape enables the PathEscape setting.
|
||||||
//
|
//
|
||||||
// See `Configuration`.
|
// See `Configuration`.
|
||||||
var WithPathEscape
|
WithPathEscape
|
||||||
|
|
||||||
// WithOptimizations can force the application to optimize for the best performance where is possible.
|
// WithOptimizations can force the application to optimize for the best performance where is possible.
|
||||||
//
|
//
|
||||||
// See `Configuration`.
|
// See `Configuration`.
|
||||||
var WithOptimizations
|
WithOptimizations
|
||||||
|
|
||||||
// WithFireMethodNotAllowed enanbles the FireMethodNotAllowed setting.
|
// WithFireMethodNotAllowed enables the FireMethodNotAllowed setting.
|
||||||
//
|
//
|
||||||
// See `Configuration`.
|
// See `Configuration`.
|
||||||
var WithFireMethodNotAllowed
|
WithFireMethodNotAllowed
|
||||||
|
|
||||||
// WithTimeFormat sets the TimeFormat setting.
|
// WithTimeFormat sets the TimeFormat setting.
|
||||||
//
|
//
|
||||||
// See `Configuration`.
|
// See `Configuration`.
|
||||||
func WithTimeFormat(timeformat string) Configurator
|
WithTimeFormat(timeformat string) Configurator
|
||||||
|
|
||||||
// WithCharset sets the Charset setting.
|
// WithCharset sets the Charset setting.
|
||||||
//
|
//
|
||||||
// See `Configuration`.
|
// See `Configuration`.
|
||||||
func WithCharset(charset string) Configurator
|
WithCharset(charset string) Configurator
|
||||||
|
|
||||||
|
// WithPostMaxMemory sets the maximum post data size
|
||||||
|
// that a client can send to the server, this differs
|
||||||
|
// from the overral request body size which can be modified
|
||||||
|
// by the `context#SetMaxRequestBodySize` or `iris#LimitRequestBodySize`.
|
||||||
|
//
|
||||||
|
// Defaults to 32MB or 32 << 20 if you prefer.
|
||||||
|
WithPostMaxMemory(limit int64) Configurator
|
||||||
|
|
||||||
// WithRemoteAddrHeader enables or adds a new or existing request header name
|
// WithRemoteAddrHeader enables or adds a new or existing request header name
|
||||||
// that can be used to validate the client's real IP.
|
// that can be used to validate the client's real IP.
|
||||||
//
|
//
|
||||||
// Existing values are:
|
// By-default no "X-" header is consired safe to be used for retrieving the
|
||||||
// "X-Real-Ip": false,
|
// client's IP address, because those headers can manually change by
|
||||||
// "X-Forwarded-For": false,
|
// the client. But sometimes are useful e.g., when behind a proxy
|
||||||
// "CF-Connecting-IP": false
|
// you want to enable the "X-Forwarded-For" or when cloudflare
|
||||||
|
// you want to enable the "CF-Connecting-IP", inneed you
|
||||||
|
// can allow the `ctx.RemoteAddr()` to use any header
|
||||||
|
// that the client may sent.
|
||||||
|
//
|
||||||
|
// Defaults to an empty map but an example usage is:
|
||||||
|
// WithRemoteAddrHeader("X-Forwarded-For")
|
||||||
//
|
//
|
||||||
// Look `context.RemoteAddr()` for more.
|
// Look `context.RemoteAddr()` for more.
|
||||||
func WithRemoteAddrHeader(headerName string) Configurator
|
WithRemoteAddrHeader(headerName string) Configurator
|
||||||
|
|
||||||
// WithoutRemoteAddrHeader disables an existing request header name
|
// WithoutRemoteAddrHeader disables an existing request header name
|
||||||
// that can be used to validate the client's real IP.
|
// that can be used to validate and parse the client's real IP.
|
||||||
//
|
//
|
||||||
// Existing values are:
|
//
|
||||||
// "X-Real-Ip": false,
|
// Keep note that RemoteAddrHeaders is already defaults to an empty map
|
||||||
// "X-Forwarded-For": false,
|
// so you don't have to call this Configurator if you didn't
|
||||||
// "CF-Connecting-IP": false
|
// add allowed headers via configuration or via `WithRemoteAddrHeader` before.
|
||||||
//
|
//
|
||||||
// Look `context.RemoteAddr()` for more.
|
// Look `context.RemoteAddr()` for more.
|
||||||
func WithoutRemoteAddrHeader(headerName string) Configurator
|
WithoutRemoteAddrHeader(headerName string) Configurator
|
||||||
|
|
||||||
|
// WithRemoteAddrPrivateSubnet adds a new private sub-net to be excluded from `context.RemoteAddr`.
|
||||||
|
// See `WithRemoteAddrHeader` too.
|
||||||
|
WithRemoteAddrPrivateSubnet(startIP, endIP string) Configurator
|
||||||
|
|
||||||
// WithOtherValue adds a value based on a key to the Other setting.
|
// WithOtherValue adds a value based on a key to the Other setting.
|
||||||
//
|
//
|
||||||
// See `Configuration`.
|
// See `Configuration.Other`.
|
||||||
func WithOtherValue(key string, val interface{}) Configurator
|
WithOtherValue(key string, val interface{}) Configurator
|
||||||
|
|
||||||
|
// WithSitemap enables the sitemap generator.
|
||||||
|
// Use the Route's `SetLastMod`, `SetChangeFreq` and `SetPriority` to modify
|
||||||
|
// the sitemap's URL child element properties.
|
||||||
|
//
|
||||||
|
// It accepts a "startURL" input argument which
|
||||||
|
// is the prefix for the registered routes that will be included in the sitemap.
|
||||||
|
//
|
||||||
|
// If more than 50,000 static routes are registered then sitemaps will be splitted and a sitemap index will be served in
|
||||||
|
// /sitemap.xml.
|
||||||
|
//
|
||||||
|
// If `Application.I18n.Load/LoadAssets` is called then the sitemap will contain translated links for each static route.
|
||||||
|
//
|
||||||
|
// If the result does not complete your needs you can take control
|
||||||
|
// and use the github.com/kataras/sitemap package to generate a customized one instead.
|
||||||
|
//
|
||||||
|
// Example: https://github.com/kataras/iris/tree/master/_examples/sitemap.
|
||||||
|
WithSitemap(startURL string) Configurator
|
||||||
|
|
||||||
|
// WithTunneling is the `iris.Configurator` for the `iris.Configuration.Tunneling` field.
|
||||||
|
// It's used to enable http tunneling for an Iris Application, per registered host
|
||||||
|
//
|
||||||
|
// Alternatively use the `iris.WithConfiguration(iris.Configuration{Tunneling: iris.TunnelingConfiguration{ ...}}}`.
|
||||||
|
WithTunneling
|
||||||
```
|
```
|
||||||
|
|
||||||
## Custom Configurator
|
## Custom Configurator
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
@ -350,6 +351,17 @@ func WithoutRemoteAddrHeader(headerName string) Configurator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithRemoteAddrPrivateSubnet adds a new private sub-net to be excluded from `context.RemoteAddr`.
|
||||||
|
// See `WithRemoteAddrHeader` too.
|
||||||
|
func WithRemoteAddrPrivateSubnet(startIP, endIP string) Configurator {
|
||||||
|
return func(app *Application) {
|
||||||
|
app.config.RemoteAddrPrivateSubnets = append(app.config.RemoteAddrPrivateSubnets, netutil.IPRange{
|
||||||
|
Start: net.IP(startIP),
|
||||||
|
End: net.IP(endIP),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithOtherValue adds a value based on a key to the Other setting.
|
// WithOtherValue adds a value based on a key to the Other setting.
|
||||||
//
|
//
|
||||||
// See `Configuration.Other`.
|
// See `Configuration.Other`.
|
||||||
|
@ -857,6 +869,19 @@ type Configuration struct {
|
||||||
// Look `context.RemoteAddr()` for more.
|
// Look `context.RemoteAddr()` for more.
|
||||||
RemoteAddrHeaders map[string]bool `json:"remoteAddrHeaders,omitempty" yaml:"RemoteAddrHeaders" toml:"RemoteAddrHeaders"`
|
RemoteAddrHeaders map[string]bool `json:"remoteAddrHeaders,omitempty" yaml:"RemoteAddrHeaders" toml:"RemoteAddrHeaders"`
|
||||||
|
|
||||||
|
// RemoteAddrPrivateSubnets defines the private sub-networks.
|
||||||
|
// They are used to be compared against
|
||||||
|
// IP Addresses fetched through `RemoteAddrHeaders` or `Request.RemoteAddr`.
|
||||||
|
// For details please navigate through: https://github.com/kataras/iris/issues/1453
|
||||||
|
// Defaults to an empty slice, usage:
|
||||||
|
//
|
||||||
|
// RemoteAddrPrivateSubnets {
|
||||||
|
// {Start: "10.0.0.0", End: "10.255.255.255"},
|
||||||
|
// {Start: "100.64.0.0", End: "100.127.255.255"},
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Look `context.RemoteAddr()` for more.
|
||||||
|
RemoteAddrPrivateSubnets []netutil.IPRange `json:"remoteAddrPrivateSubnets" yaml:"RemoteAddrPrivateSubnets" toml:"RemoteAddrPrivateSubnets"`
|
||||||
// Other are the custom, dynamic options, can be empty.
|
// Other are the custom, dynamic options, can be empty.
|
||||||
// This field used only by you to set any app's options you want.
|
// This field used only by you to set any app's options you want.
|
||||||
//
|
//
|
||||||
|
@ -993,6 +1018,22 @@ func (c Configuration) GetRemoteAddrHeaders() map[string]bool {
|
||||||
return c.RemoteAddrHeaders
|
return c.RemoteAddrHeaders
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRemoteAddrPrivateSubnets returns the configuration's private sub-networks.
|
||||||
|
// They are used to be compared against
|
||||||
|
// IP Addresses fetched through `RemoteAddrHeaders` or `Request.RemoteAddr`.
|
||||||
|
// For details please navigate through: https://github.com/kataras/iris/issues/1453
|
||||||
|
// Defaults to an empty slice, usage:
|
||||||
|
//
|
||||||
|
// RemoteAddrPrivateSubnets {
|
||||||
|
// {Start: "10.0.0.0", End: "10.255.255.255"},
|
||||||
|
// {Start: "100.64.0.0", End: "100.127.255.255"},
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Look `context.RemoteAddr()` for more.
|
||||||
|
func (c Configuration) GetRemoteAddrPrivateSubnets() []netutil.IPRange {
|
||||||
|
return c.RemoteAddrPrivateSubnets
|
||||||
|
}
|
||||||
|
|
||||||
// GetOther returns the Configuration#Other map.
|
// GetOther returns the Configuration#Other map.
|
||||||
func (c Configuration) GetOther() map[string]interface{} {
|
func (c Configuration) GetOther() map[string]interface{} {
|
||||||
return c.Other
|
return c.Other
|
||||||
|
@ -1087,6 +1128,10 @@ func WithConfiguration(c Configuration) Configurator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v := c.RemoteAddrPrivateSubnets; len(v) > 0 {
|
||||||
|
main.RemoteAddrPrivateSubnets = v
|
||||||
|
}
|
||||||
|
|
||||||
if v := c.Other; len(v) > 0 {
|
if v := c.Other; len(v) > 0 {
|
||||||
if main.Other == nil {
|
if main.Other == nil {
|
||||||
main.Other = make(map[string]interface{}, len(v))
|
main.Other = make(map[string]interface{}, len(v))
|
||||||
|
@ -1121,6 +1166,7 @@ func DefaultConfiguration() Configuration {
|
||||||
ViewLayoutContextKey: "iris.viewLayout",
|
ViewLayoutContextKey: "iris.viewLayout",
|
||||||
ViewDataContextKey: "iris.viewData",
|
ViewDataContextKey: "iris.viewData",
|
||||||
RemoteAddrHeaders: make(map[string]bool),
|
RemoteAddrHeaders: make(map[string]bool),
|
||||||
|
RemoteAddrPrivateSubnets: []netutil.IPRange{},
|
||||||
EnableOptimizations: false,
|
EnableOptimizations: false,
|
||||||
Other: make(map[string]interface{}),
|
Other: make(map[string]interface{}),
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,10 +112,13 @@ func createGlobalConfiguration(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigurationGlobal(t *testing.T) {
|
func TestConfigurationGlobal(t *testing.T) {
|
||||||
|
t.Cleanup(func() {
|
||||||
|
os.Remove(homeConfigurationFilename(".yml"))
|
||||||
|
})
|
||||||
|
|
||||||
createGlobalConfiguration(t)
|
createGlobalConfiguration(t)
|
||||||
|
|
||||||
testConfigurationGlobal(t, WithGlobalConfiguration)
|
testConfigurationGlobal(t, WithGlobalConfiguration)
|
||||||
// globalConfigurationKeyword = "~""
|
|
||||||
testConfigurationGlobal(t, WithConfiguration(YAML(globalConfigurationKeyword)))
|
testConfigurationGlobal(t, WithConfiguration(YAML(globalConfigurationKeyword)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
package context
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kataras/iris/v12/core/netutil"
|
||||||
|
)
|
||||||
|
|
||||||
// ConfigurationReadOnly can be implemented
|
// ConfigurationReadOnly can be implemented
|
||||||
// by Configuration, it's being used inside the Context.
|
// by Configuration, it's being used inside the Context.
|
||||||
// All methods that it contains should be "safe" to be called by the context
|
// All methods that it contains should be "safe" to be called by the context
|
||||||
|
@ -91,7 +95,19 @@ type ConfigurationReadOnly interface {
|
||||||
//
|
//
|
||||||
// Look `context.RemoteAddr()` for more.
|
// Look `context.RemoteAddr()` for more.
|
||||||
GetRemoteAddrHeaders() map[string]bool
|
GetRemoteAddrHeaders() map[string]bool
|
||||||
|
// GetRemoteAddrPrivateSubnets returns the configuration's private sub-networks.
|
||||||
|
// They are used to be compared against
|
||||||
|
// IP Addresses fetched through `RemoteAddrHeaders` or `Request.RemoteAddr`.
|
||||||
|
// For details please navigate through: https://github.com/kataras/iris/issues/1453
|
||||||
|
// Defaults to an empty slice, usage:
|
||||||
|
//
|
||||||
|
// RemoteAddrPrivateSubnets {
|
||||||
|
// {Start: "10.0.0.0", End: "10.255.255.255"},
|
||||||
|
// {Start: "100.64.0.0", End: "100.127.255.255"},
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Look `context.RemoteAddr()` for more.
|
||||||
|
GetRemoteAddrPrivateSubnets() []netutil.IPRange
|
||||||
// GetOther returns the configuration.Other map.
|
// GetOther returns the configuration.Other map.
|
||||||
GetOther() map[string]interface{}
|
GetOther() map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/kataras/iris/v12/core/memstore"
|
"github.com/kataras/iris/v12/core/memstore"
|
||||||
|
"github.com/kataras/iris/v12/core/netutil"
|
||||||
|
|
||||||
"github.com/Shopify/goreferrer"
|
"github.com/Shopify/goreferrer"
|
||||||
"github.com/fatih/structs"
|
"github.com/fatih/structs"
|
||||||
|
@ -358,7 +359,8 @@ type Context interface {
|
||||||
//
|
//
|
||||||
// Look `Configuration.RemoteAddrHeaders`,
|
// Look `Configuration.RemoteAddrHeaders`,
|
||||||
// `Configuration.WithRemoteAddrHeader(...)`,
|
// `Configuration.WithRemoteAddrHeader(...)`,
|
||||||
// `Configuration.WithoutRemoteAddrHeader(...)` for more.
|
// `Configuration.WithoutRemoteAddrHeader(...)`and
|
||||||
|
// `Configuration.RemoteAddrPrivateSubnets` for more.
|
||||||
RemoteAddr() string
|
RemoteAddr() string
|
||||||
// GetHeader returns the request header's value based on its name.
|
// GetHeader returns the request header's value based on its name.
|
||||||
GetHeader(name string) string
|
GetHeader(name string) string
|
||||||
|
@ -1753,25 +1755,20 @@ const xForwardedForHeaderKey = "X-Forwarded-For"
|
||||||
//
|
//
|
||||||
// Look `Configuration.RemoteAddrHeaders`,
|
// Look `Configuration.RemoteAddrHeaders`,
|
||||||
// `Configuration.WithRemoteAddrHeader(...)`,
|
// `Configuration.WithRemoteAddrHeader(...)`,
|
||||||
// `Configuration.WithoutRemoteAddrHeader(...)` for more.
|
// `Configuration.WithoutRemoteAddrHeader(...)` and
|
||||||
|
// `Configuration.RemoteAddrPrivateSubnets` for more.
|
||||||
func (ctx *context) RemoteAddr() string {
|
func (ctx *context) RemoteAddr() string {
|
||||||
remoteHeaders := ctx.Application().ConfigurationReadOnly().GetRemoteAddrHeaders()
|
remoteHeaders := ctx.Application().ConfigurationReadOnly().GetRemoteAddrHeaders()
|
||||||
|
privateSubnets := ctx.Application().ConfigurationReadOnly().GetRemoteAddrPrivateSubnets()
|
||||||
|
|
||||||
for headerName, enabled := range remoteHeaders {
|
for headerName, enabled := range remoteHeaders {
|
||||||
if enabled {
|
if !enabled {
|
||||||
headerValue := ctx.GetHeader(headerName)
|
continue
|
||||||
// exception needed for 'X-Forwarded-For' only , if enabled.
|
|
||||||
if headerName == xForwardedForHeaderKey {
|
|
||||||
idx := strings.IndexByte(headerValue, ',')
|
|
||||||
if idx >= 0 {
|
|
||||||
headerValue = headerValue[0:idx]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
realIP := strings.TrimSpace(headerValue)
|
ipAddresses := strings.Split(ctx.GetHeader(headerName), ",")
|
||||||
if realIP != "" {
|
if ip, ok := netutil.GetIPAddress(ipAddresses, privateSubnets); ok {
|
||||||
return realIP
|
return ip
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
59
core/netutil/ip.go
Normal file
59
core/netutil/ip.go
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
package netutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Based on:
|
||||||
|
https://husobee.github.io/golang/ip-address/2015/12/17/remote-ip-go.html requested at:
|
||||||
|
https://github.com/kataras/iris/issues/1453
|
||||||
|
*/
|
||||||
|
|
||||||
|
//IPRange is a structure that holds the start and end of a range of IP Addresses.
|
||||||
|
type IPRange struct {
|
||||||
|
Start net.IP `json:"start" yaml:"Start" toml"Start"`
|
||||||
|
End net.IP `json:"end" yaml:"End" toml"End"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPInRange reports whether a given IP Address is within a range given.
|
||||||
|
func IPInRange(r IPRange, ipAddress net.IP) bool {
|
||||||
|
return bytes.Compare(ipAddress, r.Start) >= 0 && bytes.Compare(ipAddress, r.End) < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPIsPrivateSubnet reports whether this "ipAddress" is in a private subnet.
|
||||||
|
func IPIsPrivateSubnet(ipAddress net.IP, privateRanges []IPRange) bool {
|
||||||
|
// IPv4 for now.
|
||||||
|
if ipCheck := ipAddress.To4(); ipCheck != nil {
|
||||||
|
// iterate over all our ranges.
|
||||||
|
for _, r := range privateRanges {
|
||||||
|
// check if this ip is in a private range.
|
||||||
|
if IPInRange(r, ipAddress) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIPAddress returns a valid public IP Address from a collection of IP Addresses
|
||||||
|
// and a range of private subnets.
|
||||||
|
//
|
||||||
|
// Reports whether a valid IP was found.
|
||||||
|
func GetIPAddress(ipAddresses []string, privateRanges []IPRange) (string, bool) {
|
||||||
|
// march from right to left until we get a public address
|
||||||
|
// that will be the address right before our proxy.
|
||||||
|
for i := len(ipAddresses) - 1; i >= 0; i-- {
|
||||||
|
ip := strings.TrimSpace(ipAddresses[i])
|
||||||
|
realIP := net.ParseIP(ip)
|
||||||
|
if !realIP.IsGlobalUnicast() || IPIsPrivateSubnet(realIP, privateRanges) {
|
||||||
|
// bad address, go to next
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return ip, true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
62
core/netutil/ip_test.go
Normal file
62
core/netutil/ip_test.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
package netutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIP(t *testing.T) {
|
||||||
|
privateRanges := []IPRange{
|
||||||
|
{
|
||||||
|
Start: net.ParseIP("10.0.0.0"),
|
||||||
|
End: net.ParseIP("10.255.255.255"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Start: net.ParseIP("100.64.0.0"),
|
||||||
|
End: net.ParseIP("100.127.255.255"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Start: net.ParseIP("172.16.0.0"),
|
||||||
|
End: net.ParseIP("172.31.255.255"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Start: net.ParseIP("192.0.0.0"),
|
||||||
|
End: net.ParseIP("192.0.0.255"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Start: net.ParseIP("192.168.0.0"),
|
||||||
|
End: net.ParseIP("192.168.255.255"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Start: net.ParseIP("198.18.0.0"),
|
||||||
|
End: net.ParseIP("198.19.255.255"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
addresses := []string{
|
||||||
|
"201.37.138.59",
|
||||||
|
"159.117.3.153",
|
||||||
|
"166.192.97.84",
|
||||||
|
"225.181.213.210",
|
||||||
|
"124.50.84.134",
|
||||||
|
"87.53.250.102",
|
||||||
|
"106.79.33.62",
|
||||||
|
"242.120.17.144",
|
||||||
|
"131.179.101.254",
|
||||||
|
"103.11.11.174",
|
||||||
|
"115.97.0.114",
|
||||||
|
"219.202.120.251",
|
||||||
|
"37.72.123.120",
|
||||||
|
"154.94.78.101",
|
||||||
|
"126.105.144.250",
|
||||||
|
}
|
||||||
|
|
||||||
|
got, ok := GetIPAddress(addresses, privateRanges)
|
||||||
|
if !ok {
|
||||||
|
t.Logf("expected addr to be matched")
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected := "126.105.144.250"; expected != got {
|
||||||
|
t.Logf("expected addr to be found: %s but got: %s", expected, got)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user