mirror of
				https://github.com/dcarrillo/whatismyip.git
				synced 2025-11-03 22:49:08 +00:00 
			
		
		
		
	Add feature to get the right client port when using a trusted proxy
This commit is contained in:
		
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							@@ -16,7 +16,7 @@ integration-test:
 | 
				
			|||||||
.PHONY: install-tools
 | 
					.PHONY: install-tools
 | 
				
			||||||
install-tools:
 | 
					install-tools:
 | 
				
			||||||
	@command golangci-lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
 | 
						@command golangci-lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
 | 
				
			||||||
		curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin v1.45.0; \
 | 
							curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin v1.45.2; \
 | 
				
			||||||
	fi
 | 
						fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@command $(GOPATH)/shadow > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
 | 
						@command $(GOPATH)/shadow > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										19
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								README.md
									
									
									
									
									
								
							@@ -13,7 +13,7 @@
 | 
				
			|||||||
  - [Examples](#examples)
 | 
					  - [Examples](#examples)
 | 
				
			||||||
    - [Run a default TCP server](#run-a-default-tcp-server)
 | 
					    - [Run a default TCP server](#run-a-default-tcp-server)
 | 
				
			||||||
    - [Run a TLS (HTTP/2) server only](#run-a-tls-http2-server-only)
 | 
					    - [Run a TLS (HTTP/2) server only](#run-a-tls-http2-server-only)
 | 
				
			||||||
    - [Run a default TCP server with a custom template and trust a custom header set by an upstream proxy](#run-a-default-tcp-server-with-a-custom-template-and-trust-a-custom-header-set-by-an-upstream-proxy)
 | 
					    - [Run a default TCP server with a custom template and trust a pair of custom headers set by an upstream proxy](#run-a-default-tcp-server-with-a-custom-template-and-trust-a-pair-of-custom-headers-set-by-an-upstream-proxy)
 | 
				
			||||||
  - [Download](#download)
 | 
					  - [Download](#download)
 | 
				
			||||||
  - [Docker](#docker)
 | 
					  - [Docker](#docker)
 | 
				
			||||||
    - [Run a container locally using test databases](#run-a-container-locally-using-test-databases)
 | 
					    - [Run a container locally using test databases](#run-a-container-locally-using-test-databases)
 | 
				
			||||||
@@ -36,7 +36,7 @@ curl -6 ifconfig.es
 | 
				
			|||||||
## Features
 | 
					## Features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- TLS and HTTP/2.
 | 
					- TLS and HTTP/2.
 | 
				
			||||||
- Can run behind a proxy by trusting a custom header (usually `X-Real-IP`) to figure out the source IP address.
 | 
					- Can run behind a proxy by trusting a custom header (usually `X-Real-IP`) to figure out the source IP address. It also supports a custom header to resolve the client port, if the proxy can only add a header for the IP (for example a fixed header from CDNs) the client port is shown as unknown.
 | 
				
			||||||
- IPv4 and IPv6.
 | 
					- IPv4 and IPv6.
 | 
				
			||||||
- Geolocation info including ASN. This feature is possible thanks to [maxmind](https://dev.maxmind.com/geoip/geolite2-free-geolocation-data?lang=en) GeoLite2 databases. In order to use these databases, a license key is needed. Please visit Maxmind site for further instructions and get a free license.
 | 
					- Geolocation info including ASN. This feature is possible thanks to [maxmind](https://dev.maxmind.com/geoip/geolite2-free-geolocation-data?lang=en) GeoLite2 databases. In order to use these databases, a license key is needed. Please visit Maxmind site for further instructions and get a free license.
 | 
				
			||||||
- High performance.
 | 
					- High performance.
 | 
				
			||||||
@@ -47,6 +47,7 @@ curl -6 ifconfig.es
 | 
				
			|||||||
## Endpoints
 | 
					## Endpoints
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- https://ifconfig.es/
 | 
					- https://ifconfig.es/
 | 
				
			||||||
 | 
					- https://ifconfig.es/client-port
 | 
				
			||||||
- https://ifconfig.es/json (this is the same as `curl -H "Accept: application/json" https://ifconfig.es/`)
 | 
					- https://ifconfig.es/json (this is the same as `curl -H "Accept: application/json" https://ifconfig.es/`)
 | 
				
			||||||
- https://ifconfig.es/geo
 | 
					- https://ifconfig.es/geo
 | 
				
			||||||
  - https://ifconfig.es/geo/city
 | 
					  - https://ifconfig.es/geo/city
 | 
				
			||||||
@@ -72,9 +73,11 @@ Golang >= 1.17 is required. Previous versions may work.
 | 
				
			|||||||
## Usage
 | 
					## Usage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```text
 | 
					```text
 | 
				
			||||||
Usage of ./whatismyip:
 | 
					Usage of whatismyip:
 | 
				
			||||||
  -bind string
 | 
					  -bind string
 | 
				
			||||||
        Listening address (see https://pkg.go.dev/net?#Listen) (default ":8080")
 | 
					        Listening address (see https://pkg.go.dev/net?#Listen) (default ":8080")
 | 
				
			||||||
 | 
					  -enable-secure-headers
 | 
				
			||||||
 | 
					        Add sane security-related headers to every response
 | 
				
			||||||
  -geoip2-asn string
 | 
					  -geoip2-asn string
 | 
				
			||||||
        Path to GeoIP2 ASN database
 | 
					        Path to GeoIP2 ASN database
 | 
				
			||||||
  -geoip2-city string
 | 
					  -geoip2-city string
 | 
				
			||||||
@@ -88,7 +91,9 @@ Usage of ./whatismyip:
 | 
				
			|||||||
  -tls-key string
 | 
					  -tls-key string
 | 
				
			||||||
        When using TLS, path to private key file
 | 
					        When using TLS, path to private key file
 | 
				
			||||||
  -trusted-header string
 | 
					  -trusted-header string
 | 
				
			||||||
        Trusted request header for remote IP (e.g. X-Real-IP)
 | 
					        Trusted request header for remote IP (e.g. X-Real-IP). When using this feature if -trusted-port-header is not set the client port is shown as 'unknown'
 | 
				
			||||||
 | 
					  -trusted-port-header string
 | 
				
			||||||
 | 
					        Trusted request header for remote client port (e.g. X-Real-Port). When this parameter is set -trusted-header becomes mandatory
 | 
				
			||||||
  -version
 | 
					  -version
 | 
				
			||||||
        Output version information and exit
 | 
					        Output version information and exit
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
@@ -108,11 +113,11 @@ Usage of ./whatismyip:
 | 
				
			|||||||
             -bind "" -tls-bind :8081 -tls-crt ./test/server.pem -tls-key ./test/server.key
 | 
					             -bind "" -tls-bind :8081 -tls-crt ./test/server.pem -tls-key ./test/server.key
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Run a default TCP server with a custom template and trust a custom header set by an upstream proxy
 | 
					### Run a default TCP server with a custom template and trust a pair of custom headers set by an upstream proxy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
./whatismyip -geoip2-city ./test/GeoIP2-City-Test.mmdb -geoip2-asn ./test/GeoLite2-ASN-Test.mmdb \
 | 
					./whatismyip -geoip2-city ./test/GeoIP2-City-Test.mmdb -geoip2-asn ./test/GeoLite2-ASN-Test.mmdb \
 | 
				
			||||||
             -trusted-header X-Real-IP -template mytemplate.tmpl
 | 
					             -trusted-header X-Real-IP -trusted-port-header X-Real-Port -template mytemplate.tmpl
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Download
 | 
					## Download
 | 
				
			||||||
@@ -121,7 +126,7 @@ Download latest version from https://github.com/dcarrillo/whatismyip/releases
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## Docker
 | 
					## Docker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
An ultra-light (~9MB) image is available.
 | 
					An ultra-light (~10MB) image is available at [docker hub](https://hub.docker.com/r/dcarrillo/whatismyip).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Run a container locally using test databases
 | 
					### Run a container locally using test databases
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,6 +27,7 @@ type settings struct {
 | 
				
			|||||||
	TLSCrtPath          string
 | 
						TLSCrtPath          string
 | 
				
			||||||
	TLSKeyPath          string
 | 
						TLSKeyPath          string
 | 
				
			||||||
	TrustedHeader       string
 | 
						TrustedHeader       string
 | 
				
			||||||
 | 
						TrustedPortHeader   string
 | 
				
			||||||
	EnableSecureHeaders bool
 | 
						EnableSecureHeaders bool
 | 
				
			||||||
	Server              serverSettings
 | 
						Server              serverSettings
 | 
				
			||||||
	version             bool
 | 
						version             bool
 | 
				
			||||||
@@ -74,6 +75,11 @@ func Setup(args []string) (output string, err error) {
 | 
				
			|||||||
		"",
 | 
							"",
 | 
				
			||||||
		"Trusted request header for remote IP (e.g. X-Real-IP)",
 | 
							"Trusted request header for remote IP (e.g. X-Real-IP)",
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
						flags.StringVar(&App.TrustedPortHeader,
 | 
				
			||||||
 | 
							"trusted-port-header",
 | 
				
			||||||
 | 
							"",
 | 
				
			||||||
 | 
							"Trusted request header for remote client port (e.g. X-Real-Port)",
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
	flags.BoolVar(&App.version, "version", false, "Output version information and exit")
 | 
						flags.BoolVar(&App.version, "version", false, "Output version information and exit")
 | 
				
			||||||
	flags.BoolVar(
 | 
						flags.BoolVar(
 | 
				
			||||||
		&App.EnableSecureHeaders,
 | 
							&App.EnableSecureHeaders,
 | 
				
			||||||
@@ -91,21 +97,25 @@ func Setup(args []string) (output string, err error) {
 | 
				
			|||||||
		return fmt.Sprintf("whatismyip version %s", core.Version), ErrVersion
 | 
							return fmt.Sprintf("whatismyip version %s", core.Version), ErrVersion
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if App.TrustedPortHeader != "" && App.TrustedHeader == "" {
 | 
				
			||||||
 | 
							return "", fmt.Errorf("truster-header is mandatory when truster-port-header is set\n")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if App.GeodbPath.City == "" || App.GeodbPath.ASN == "" {
 | 
						if App.GeodbPath.City == "" || App.GeodbPath.ASN == "" {
 | 
				
			||||||
		return "", fmt.Errorf("geoip2-city and geoip2-asn parameters are mandatory")
 | 
							return "", fmt.Errorf("geoip2-city and geoip2-asn parameters are mandatory\n")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (App.TLSAddress != "") && (App.TLSCrtPath == "" || App.TLSKeyPath == "") {
 | 
						if (App.TLSAddress != "") && (App.TLSCrtPath == "" || App.TLSKeyPath == "") {
 | 
				
			||||||
		return "", fmt.Errorf("In order to use TLS -tls-crt and -tls-key flags are mandatory")
 | 
							return "", fmt.Errorf("In order to use TLS -tls-crt and -tls-key flags are mandatory\n")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if App.TemplatePath != "" {
 | 
						if App.TemplatePath != "" {
 | 
				
			||||||
		info, err := os.Stat(App.TemplatePath)
 | 
							info, err := os.Stat(App.TemplatePath)
 | 
				
			||||||
		if os.IsNotExist(err) {
 | 
							if os.IsNotExist(err) {
 | 
				
			||||||
			return "", fmt.Errorf("%s no such file or directory", App.TemplatePath)
 | 
								return "", fmt.Errorf("%s no such file or directory\n", App.TemplatePath)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if info.IsDir() {
 | 
							if info.IsDir() {
 | 
				
			||||||
			return "", fmt.Errorf("%s must be a file", App.TemplatePath)
 | 
								return "", fmt.Errorf("%s must be a file\n", App.TemplatePath)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,51 +8,51 @@ import (
 | 
				
			|||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/require"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestParseMandatoryFlags(t *testing.T) {
 | 
					func TestParseMandatoryFlags(t *testing.T) {
 | 
				
			||||||
	var mandatoryFlags = []struct {
 | 
						var mandatoryFlags = []struct {
 | 
				
			||||||
		args []string
 | 
							args []string
 | 
				
			||||||
		conf settings
 | 
					 | 
				
			||||||
	}{
 | 
						}{
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			[]string{},
 | 
								[]string{},
 | 
				
			||||||
			settings{},
 | 
					 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			[]string{"-geoip2-city", "/city-path"},
 | 
								[]string{"-geoip2-city", "/city-path"},
 | 
				
			||||||
			settings{},
 | 
					 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			[]string{"-geoip2-asn", "/asn-path"},
 | 
								[]string{"-geoip2-asn", "/asn-path"},
 | 
				
			||||||
			settings{},
 | 
					 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			[]string{
 | 
								[]string{
 | 
				
			||||||
				"-geoip2-city", "/city-path", "-geoip2-asn", "/asn-path", "-tls-bind", ":9000",
 | 
									"-geoip2-city", "/city-path", "-geoip2-asn", "/asn-path", "-tls-bind", ":9000",
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			settings{},
 | 
					 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			[]string{
 | 
								[]string{
 | 
				
			||||||
				"-geoip2-city", "/city-path", "-geoip2-asn", "/asn-path", "-tls-bind", ":9000",
 | 
									"-geoip2-city", "/city-path", "-geoip2-asn", "/asn-path", "-tls-bind", ":9000",
 | 
				
			||||||
				"-tls-crt", "/crt-path",
 | 
									"-tls-crt", "/crt-path",
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			settings{},
 | 
					 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			[]string{
 | 
								[]string{
 | 
				
			||||||
				"-geoip2-city", "/city-path", "-geoip2-asn", "/asn-path", "-tls-bind", ":9000",
 | 
									"-geoip2-city", "/city-path", "-geoip2-asn", "/asn-path", "-tls-bind", ":9000",
 | 
				
			||||||
				"-tls-key", "/key-path",
 | 
									"-tls-key", "/key-path",
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			settings{},
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								[]string{
 | 
				
			||||||
 | 
									"-geoip2-city", "/city-path", "-geoip2-asn", "/asn-path", "-bind", ":8000",
 | 
				
			||||||
 | 
									"-trusted-port-header", "port-header",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, tt := range mandatoryFlags {
 | 
						for _, tt := range mandatoryFlags {
 | 
				
			||||||
		t.Run(strings.Join(tt.args, " "), func(t *testing.T) {
 | 
							t.Run(strings.Join(tt.args, " "), func(t *testing.T) {
 | 
				
			||||||
			_, err := Setup(tt.args)
 | 
								_, err := Setup(tt.args)
 | 
				
			||||||
			assert.NotNil(t, err)
 | 
								require.NotNil(t, err)
 | 
				
			||||||
			assert.Contains(t, err.Error(), "mandatory")
 | 
								assert.Contains(t, err.Error(), "mandatory")
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -70,13 +70,7 @@ func TestParseFlags(t *testing.T) {
 | 
				
			|||||||
					City: "/city-path",
 | 
										City: "/city-path",
 | 
				
			||||||
					ASN:  "/asn-path",
 | 
										ASN:  "/asn-path",
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
				TemplatePath:        "",
 | 
					 | 
				
			||||||
				BindAddress: ":8080",
 | 
									BindAddress: ":8080",
 | 
				
			||||||
				TLSAddress:          "",
 | 
					 | 
				
			||||||
				TLSCrtPath:          "",
 | 
					 | 
				
			||||||
				TLSKeyPath:          "",
 | 
					 | 
				
			||||||
				TrustedHeader:       "",
 | 
					 | 
				
			||||||
				EnableSecureHeaders: false,
 | 
					 | 
				
			||||||
				Server: serverSettings{
 | 
									Server: serverSettings{
 | 
				
			||||||
					ReadTimeout:  10 * time.Second,
 | 
										ReadTimeout:  10 * time.Second,
 | 
				
			||||||
					WriteTimeout: 10 * time.Second,
 | 
										WriteTimeout: 10 * time.Second,
 | 
				
			||||||
@@ -90,13 +84,7 @@ func TestParseFlags(t *testing.T) {
 | 
				
			|||||||
					City: "/city-path",
 | 
										City: "/city-path",
 | 
				
			||||||
					ASN:  "/asn-path",
 | 
										ASN:  "/asn-path",
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
				TemplatePath:        "",
 | 
					 | 
				
			||||||
				BindAddress: ":8001",
 | 
									BindAddress: ":8001",
 | 
				
			||||||
				TLSAddress:          "",
 | 
					 | 
				
			||||||
				TLSCrtPath:          "",
 | 
					 | 
				
			||||||
				TLSKeyPath:          "",
 | 
					 | 
				
			||||||
				TrustedHeader:       "",
 | 
					 | 
				
			||||||
				EnableSecureHeaders: false,
 | 
					 | 
				
			||||||
				Server: serverSettings{
 | 
									Server: serverSettings{
 | 
				
			||||||
					ReadTimeout:  10 * time.Second,
 | 
										ReadTimeout:  10 * time.Second,
 | 
				
			||||||
					WriteTimeout: 10 * time.Second,
 | 
										WriteTimeout: 10 * time.Second,
 | 
				
			||||||
@@ -113,13 +101,29 @@ func TestParseFlags(t *testing.T) {
 | 
				
			|||||||
					City: "/city-path",
 | 
										City: "/city-path",
 | 
				
			||||||
					ASN:  "/asn-path",
 | 
										ASN:  "/asn-path",
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
				TemplatePath:        "",
 | 
					 | 
				
			||||||
				BindAddress: ":8080",
 | 
									BindAddress: ":8080",
 | 
				
			||||||
				TLSAddress:  ":9000",
 | 
									TLSAddress:  ":9000",
 | 
				
			||||||
				TLSCrtPath:  "/crt-path",
 | 
									TLSCrtPath:  "/crt-path",
 | 
				
			||||||
				TLSKeyPath:  "/key-path",
 | 
									TLSKeyPath:  "/key-path",
 | 
				
			||||||
				TrustedHeader:       "",
 | 
									Server: serverSettings{
 | 
				
			||||||
				EnableSecureHeaders: false,
 | 
										ReadTimeout:  10 * time.Second,
 | 
				
			||||||
 | 
										WriteTimeout: 10 * time.Second,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								[]string{
 | 
				
			||||||
 | 
									"-geoip2-city", "/city-path", "-geoip2-asn", "/asn-path",
 | 
				
			||||||
 | 
									"-trusted-header", "header", "-trusted-port-header", "port-header",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								settings{
 | 
				
			||||||
 | 
									GeodbPath: geodbPath{
 | 
				
			||||||
 | 
										City: "/city-path",
 | 
				
			||||||
 | 
										ASN:  "/asn-path",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									BindAddress:       ":8080",
 | 
				
			||||||
 | 
									TrustedHeader:     "header",
 | 
				
			||||||
 | 
									TrustedPortHeader: "port-header",
 | 
				
			||||||
				Server: serverSettings{
 | 
									Server: serverSettings{
 | 
				
			||||||
					ReadTimeout:  10 * time.Second,
 | 
										ReadTimeout:  10 * time.Second,
 | 
				
			||||||
					WriteTimeout: 10 * time.Second,
 | 
										WriteTimeout: 10 * time.Second,
 | 
				
			||||||
@@ -136,11 +140,7 @@ func TestParseFlags(t *testing.T) {
 | 
				
			|||||||
					City: "/city-path",
 | 
										City: "/city-path",
 | 
				
			||||||
					ASN:  "/asn-path",
 | 
										ASN:  "/asn-path",
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
				TemplatePath:        "",
 | 
					 | 
				
			||||||
				BindAddress:         ":8080",
 | 
									BindAddress:         ":8080",
 | 
				
			||||||
				TLSAddress:          "",
 | 
					 | 
				
			||||||
				TLSCrtPath:          "",
 | 
					 | 
				
			||||||
				TLSKeyPath:          "",
 | 
					 | 
				
			||||||
				TrustedHeader:       "header",
 | 
									TrustedHeader:       "header",
 | 
				
			||||||
				EnableSecureHeaders: true,
 | 
									EnableSecureHeaders: true,
 | 
				
			||||||
				Server: serverSettings{
 | 
									Server: serverSettings{
 | 
				
			||||||
@@ -154,7 +154,7 @@ func TestParseFlags(t *testing.T) {
 | 
				
			|||||||
	for _, tt := range flags {
 | 
						for _, tt := range flags {
 | 
				
			||||||
		t.Run(strings.Join(tt.args, " "), func(t *testing.T) {
 | 
							t.Run(strings.Join(tt.args, " "), func(t *testing.T) {
 | 
				
			||||||
			_, err := Setup(tt.args)
 | 
								_, err := Setup(tt.args)
 | 
				
			||||||
			assert.Nil(t, err)
 | 
								require.Nil(t, err)
 | 
				
			||||||
			assert.True(t, reflect.DeepEqual(App, tt.conf))
 | 
								assert.True(t, reflect.DeepEqual(App, tt.conf))
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -192,6 +192,6 @@ func TestParseFlagTemplate(t *testing.T) {
 | 
				
			|||||||
		"-template", "/",
 | 
							"-template", "/",
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	_, err = Setup(flags)
 | 
						_, err = Setup(flags)
 | 
				
			||||||
	assert.Error(t, err)
 | 
						require.Error(t, err)
 | 
				
			||||||
	assert.Contains(t, err.Error(), "must be a file")
 | 
						assert.Contains(t, err.Error(), "must be a file")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -44,15 +44,31 @@ func getRoot(ctx *gin.Context) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getClientPort(ctx *gin.Context) string {
 | 
				
			||||||
 | 
						var port string
 | 
				
			||||||
 | 
						if setting.App.TrustedPortHeader == "" {
 | 
				
			||||||
 | 
							if setting.App.TrustedHeader != "" {
 | 
				
			||||||
 | 
								port = "unknown"
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								_, port, _ = net.SplitHostPort(ctx.Request.RemoteAddr)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							port = ctx.GetHeader(setting.App.TrustedPortHeader)
 | 
				
			||||||
 | 
							if port == "" {
 | 
				
			||||||
 | 
								port = "unknown"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return port
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getClientPortAsString(ctx *gin.Context) {
 | 
					func getClientPortAsString(ctx *gin.Context) {
 | 
				
			||||||
	_, port, _ := net.SplitHostPort(ctx.Request.RemoteAddr)
 | 
						ctx.String(http.StatusOK, getClientPort(ctx)+"\n")
 | 
				
			||||||
	ctx.String(http.StatusOK, port+"\n")
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getAllAsString(ctx *gin.Context) {
 | 
					func getAllAsString(ctx *gin.Context) {
 | 
				
			||||||
	output := "IP: " + ctx.ClientIP() + "\n"
 | 
						output := "IP: " + ctx.ClientIP() + "\n"
 | 
				
			||||||
	_, port, _ := net.SplitHostPort(ctx.Request.RemoteAddr)
 | 
						output += "Client Port: " + getClientPort(ctx) + "\n"
 | 
				
			||||||
	output += "Client Port: " + port + "\n"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	r := service.Geo{IP: net.ParseIP(ctx.ClientIP())}
 | 
						r := service.Geo{IP: net.ParseIP(ctx.ClientIP())}
 | 
				
			||||||
	if record := r.LookUpCity(); record != nil {
 | 
						if record := r.LookUpCity(); record != nil {
 | 
				
			||||||
@@ -83,11 +99,10 @@ func jsonOutput(ctx *gin.Context) JSONResponse {
 | 
				
			|||||||
		version = 6
 | 
							version = 6
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, port, _ := net.SplitHostPort(ctx.Request.RemoteAddr)
 | 
					 | 
				
			||||||
	return JSONResponse{
 | 
						return JSONResponse{
 | 
				
			||||||
		IP:              ctx.ClientIP(),
 | 
							IP:              ctx.ClientIP(),
 | 
				
			||||||
		IPVersion:       version,
 | 
							IPVersion:       version,
 | 
				
			||||||
		ClientPort:      port,
 | 
							ClientPort:      getClientPort(ctx),
 | 
				
			||||||
		Country:         cityRecord.Country.Names["en"],
 | 
							Country:         cityRecord.Country.Names["en"],
 | 
				
			||||||
		CountryCode:     cityRecord.Country.ISOCode,
 | 
							CountryCode:     cityRecord.Country.ISOCode,
 | 
				
			||||||
		City:            cityRecord.City.Names["en"],
 | 
							City:            cityRecord.City.Names["en"],
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ import (
 | 
				
			|||||||
	"net/http/httptest"
 | 
						"net/http/httptest"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/dcarrillo/whatismyip/internal/setting"
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -97,16 +98,78 @@ func TestHost(t *testing.T) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestClientPort(t *testing.T) {
 | 
					func TestClientPort(t *testing.T) {
 | 
				
			||||||
 | 
						type args struct {
 | 
				
			||||||
 | 
							params  []string
 | 
				
			||||||
 | 
							headers map[string][]string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						type expected struct {
 | 
				
			||||||
 | 
							body string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name     string
 | 
				
			||||||
 | 
							args     args
 | 
				
			||||||
 | 
							expected string
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:     "No trusted headers set",
 | 
				
			||||||
 | 
								expected: "1000\n",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "Trusted header only set",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									params: []string{
 | 
				
			||||||
 | 
										"-geoip2-city", "city",
 | 
				
			||||||
 | 
										"-geoip2-asn", "asn",
 | 
				
			||||||
 | 
										"-trusted-header", trustedHeader,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: "unknown\n",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "Trusted and port header set but not included in headers",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									params: []string{
 | 
				
			||||||
 | 
										"-geoip2-city", "city",
 | 
				
			||||||
 | 
										"-geoip2-asn", "asn",
 | 
				
			||||||
 | 
										"-trusted-header", trustedHeader,
 | 
				
			||||||
 | 
										"-trusted-port-header", trustedPortHeader,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: "unknown\n",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "Trusted and port header set and included in headers",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									params: []string{
 | 
				
			||||||
 | 
										"-geoip2-city", "city",
 | 
				
			||||||
 | 
										"-geoip2-asn", "asn",
 | 
				
			||||||
 | 
										"-trusted-header", trustedHeader,
 | 
				
			||||||
 | 
										"-trusted-port-header", trustedPortHeader,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									headers: map[string][]string{
 | 
				
			||||||
 | 
										trustedHeader:     {testIP.ipv4},
 | 
				
			||||||
 | 
										trustedPortHeader: {"1001"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: "1001\n",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tt := range tests {
 | 
				
			||||||
 | 
							_, _ = setting.Setup(tt.args.params)
 | 
				
			||||||
 | 
							t.Run(tt.name, func(t *testing.T) {
 | 
				
			||||||
			req, _ := http.NewRequest("GET", "/client-port", nil)
 | 
								req, _ := http.NewRequest("GET", "/client-port", nil)
 | 
				
			||||||
			req.RemoteAddr = net.JoinHostPort(testIP.ipv4, "1000")
 | 
								req.RemoteAddr = net.JoinHostPort(testIP.ipv4, "1000")
 | 
				
			||||||
	req.Header.Set(trustedHeader, testIP.ipv4)
 | 
								req.Header = tt.args.headers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			w := httptest.NewRecorder()
 | 
								w := httptest.NewRecorder()
 | 
				
			||||||
			app.ServeHTTP(w, req)
 | 
								app.ServeHTTP(w, req)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			assert.Equal(t, 200, w.Code)
 | 
								assert.Equal(t, 200, w.Code)
 | 
				
			||||||
			assert.Equal(t, contentType.text, w.Header().Get("Content-Type"))
 | 
								assert.Equal(t, contentType.text, w.Header().Get("Content-Type"))
 | 
				
			||||||
	assert.Equal(t, "1000\n", w.Body.String())
 | 
								assert.Equal(t, tt.expected, w.Body.String())
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestNotFound(t *testing.T) {
 | 
					func TestNotFound(t *testing.T) {
 | 
				
			||||||
@@ -120,6 +183,15 @@ func TestNotFound(t *testing.T) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestJSON(t *testing.T) {
 | 
					func TestJSON(t *testing.T) {
 | 
				
			||||||
 | 
						_, _ = setting.Setup(
 | 
				
			||||||
 | 
							[]string{
 | 
				
			||||||
 | 
								"-geoip2-city", "city",
 | 
				
			||||||
 | 
								"-geoip2-asn", "asn",
 | 
				
			||||||
 | 
								"-trusted-header", trustedHeader,
 | 
				
			||||||
 | 
								"-trusted-port-header", trustedPortHeader,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	type args struct {
 | 
						type args struct {
 | 
				
			||||||
		ip string
 | 
							ip string
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -149,6 +221,7 @@ func TestJSON(t *testing.T) {
 | 
				
			|||||||
			req.RemoteAddr = net.JoinHostPort(tt.args.ip, "1000")
 | 
								req.RemoteAddr = net.JoinHostPort(tt.args.ip, "1000")
 | 
				
			||||||
			req.Host = "test"
 | 
								req.Host = "test"
 | 
				
			||||||
			req.Header.Set(trustedHeader, tt.args.ip)
 | 
								req.Header.Set(trustedHeader, tt.args.ip)
 | 
				
			||||||
 | 
								req.Header.Set(trustedPortHeader, "1001")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			w := httptest.NewRecorder()
 | 
								w := httptest.NewRecorder()
 | 
				
			||||||
			app.ServeHTTP(w, req)
 | 
								app.ServeHTTP(w, req)
 | 
				
			||||||
@@ -162,7 +235,7 @@ func TestJSON(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func TestAll(t *testing.T) {
 | 
					func TestAll(t *testing.T) {
 | 
				
			||||||
	expected := `IP: 81.2.69.192
 | 
						expected := `IP: 81.2.69.192
 | 
				
			||||||
Client Port: 1000
 | 
					Client Port: 1001
 | 
				
			||||||
City: London
 | 
					City: London
 | 
				
			||||||
Country: United Kingdom
 | 
					Country: United Kingdom
 | 
				
			||||||
Country Code: GB
 | 
					Country Code: GB
 | 
				
			||||||
@@ -177,12 +250,22 @@ ASN Organization:
 | 
				
			|||||||
Header1: one
 | 
					Header1: one
 | 
				
			||||||
Host: test
 | 
					Host: test
 | 
				
			||||||
X-Real-Ip: 81.2.69.192
 | 
					X-Real-Ip: 81.2.69.192
 | 
				
			||||||
 | 
					X-Real-Port: 1001
 | 
				
			||||||
`
 | 
					`
 | 
				
			||||||
 | 
						_, _ = setting.Setup(
 | 
				
			||||||
 | 
							[]string{
 | 
				
			||||||
 | 
								"-geoip2-city", "city",
 | 
				
			||||||
 | 
								"-geoip2-asn", "asn",
 | 
				
			||||||
 | 
								"-trusted-header", trustedHeader,
 | 
				
			||||||
 | 
								"-trusted-port-header", trustedPortHeader,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	req, _ := http.NewRequest("GET", "/all", nil)
 | 
						req, _ := http.NewRequest("GET", "/all", nil)
 | 
				
			||||||
	req.RemoteAddr = net.JoinHostPort(testIP.ipv4, "1000")
 | 
						req.RemoteAddr = net.JoinHostPort(testIP.ipv4, "1000")
 | 
				
			||||||
	req.Host = "test"
 | 
						req.Host = "test"
 | 
				
			||||||
	req.Header.Set(trustedHeader, testIP.ipv4)
 | 
						req.Header.Set(trustedHeader, testIP.ipv4)
 | 
				
			||||||
 | 
						req.Header.Set(trustedPortHeader, "1001")
 | 
				
			||||||
	req.Header.Set("Header1", "one")
 | 
						req.Header.Set("Header1", "one")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	w := httptest.NewRecorder()
 | 
						w := httptest.NewRecorder()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,11 +34,12 @@ var (
 | 
				
			|||||||
		text: "text/plain; charset=utf-8",
 | 
							text: "text/plain; charset=utf-8",
 | 
				
			||||||
		json: "application/json; charset=utf-8",
 | 
							json: "application/json; charset=utf-8",
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	jsonIPv4 = `{"client_port":"1000","ip":"81.2.69.192","ip_version":4,"country":"United Kingdom","country_code":"GB","city":"London","latitude":51.5142,"longitude":-0.0931,"postal_code":"","time_zone":"Europe/London","asn":0,"asn_organization":"","host":"test","headers":{"X-Real-Ip":["81.2.69.192"]}}`
 | 
						jsonIPv4 = `{"client_port":"1001","ip":"81.2.69.192","ip_version":4,"country":"United Kingdom","country_code":"GB","city":"London","latitude":51.5142,"longitude":-0.0931,"postal_code":"","time_zone":"Europe/London","asn":0,"asn_organization":"","host":"test","headers":{"X-Real-Ip":["81.2.69.192"], "X-Real-Port":["1001"]}}`
 | 
				
			||||||
	jsonIPv6 = `{"asn":3352, "asn_organization":"TELEFONICA DE ESPANA", "city":"", "client_port":"1000", "country":"", "country_code":"", "headers":{"X-Real-Ip":["2a02:9000::1"]}, "host":"test", "ip":"2a02:9000::1", "ip_version":6, "latitude":0, "longitude":0, "postal_code":"", "time_zone":""}`
 | 
						jsonIPv6 = `{"asn":3352, "asn_organization":"TELEFONICA DE ESPANA", "city":"", "client_port":"1001", "country":"", "country_code":"", "headers":{"X-Real-Ip":["2a02:9000::1"], "X-Real-Port":["1001"]}, "host":"test", "ip":"2a02:9000::1", "ip_version":6, "latitude":0, "longitude":0, "postal_code":"", "time_zone":""}`
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const trustedHeader = "X-Real-IP"
 | 
					const trustedHeader = "X-Real-IP"
 | 
				
			||||||
 | 
					const trustedPortHeader = "X-Real-Port"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestMain(m *testing.M) {
 | 
					func TestMain(m *testing.M) {
 | 
				
			||||||
	app = gin.Default()
 | 
						app = gin.Default()
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user