From b5fe362183592b85ca5dc3c678f7aaf521804329 Mon Sep 17 00:00:00 2001 From: dcarrillo Date: Thu, 2 Jan 2025 20:13:41 +0100 Subject: [PATCH] Make geo database usage optional (#39) --- Dockerfile | 2 + Makefile | 11 ++- README.md | 40 ++++---- cmd/whatismyip.go | 14 ++- go.mod | 25 +++-- go.sum | 69 +++++++++----- integration-tests/integration_test.go | 4 +- internal/setting/app.go | 23 ++--- internal/setting/app_test.go | 88 +++++++++--------- models/geo.go | 127 ++++++++++++++++++-------- models/geo_test.go | 26 +++--- router/dns.go | 28 ++++-- router/generic.go | 92 ++++++++++--------- router/geo.go | 18 ++-- router/setup.go | 6 +- router/setup_test.go | 14 +-- router/templates_test.go | 30 +++--- server/dns.go | 2 +- server/quic.go | 4 +- server/server.go | 19 ++-- server/tcp.go | 2 +- server/tls.go | 2 +- service/geo.go | 60 +++++++++--- service/geo_test.go | 19 ++-- 24 files changed, 457 insertions(+), 268 deletions(-) diff --git a/Dockerfile b/Dockerfile index dbc228d..8fb5bf2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,8 @@ RUN --mount=type=cache,target=/go/pkg/mod/ apk --no-cache add make && make build FROM builder AS build-prod-app # hadolint ignore=DL3018 +RUN apk update && apk add --no-cache ca-certificates && update-ca-certificates +# hadolint ignore=DL3018 RUN --mount=type=cache,target=/go/pkg/mod/ apk --no-cache add make upx \ && make build \ && upx --best --lzma whatismyip diff --git a/Makefile b/Makefile index e10a1e6..f08e717 100644 --- a/Makefile +++ b/Makefile @@ -50,9 +50,10 @@ endif docker-run: docker-build-dev docker run --tty --interactive --rm \ - -v ${PWD}/test/GeoIP2-City-Test.mmdb:/tmp/GeoIP2-City-Test.mmdb:ro \ - -v ${PWD}/test/GeoLite2-ASN-Test.mmdb:/tmp/GeoLite2-ASN-Test.mmdb:ro -p 8080:8080 \ - ${DOCKER_URL}:${VERSION} \ - -geoip2-city /tmp/GeoIP2-City-Test.mmdb \ - -geoip2-asn /tmp/GeoLite2-ASN-Test.mmdb \ + --publish 8080:8080 \ + --volume ${PWD}/test/GeoIP2-City-Test.mmdb:/GeoIP2-City-Test.mmdb \ + --volume ${PWD}/test/GeoLite2-ASN-Test.mmdb:/GeoLite2-ASN-Test.mmdb \ + ${DOCKER_URL}:${VERSION} \ + -geoip2-city /GeoIP2-City-Test.mmdb \ + -geoip2-asn /GeoLite2-ASN-Test.mmdb \ -trusted-header X-Real-IP diff --git a/README.md b/README.md index f014611..ccd7291 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ - [Usage](#usage) - [Examples](#examples) - [Run a default TCP server](#run-a-default-tcp-server) - - [Run a TLS (HTTP/2) and enable What is my DNS](#run-a-tls-http2-and-enable-what-is-my-dns) + - [Run a default TCP server with geo information enabled](#run-a-default-tco-server-with-geo-information-enabled) + - [Run a TLS (HTTP/2) and enable "what is my DNS" with geo information](#run-a-tls-http2-and-enable-what-is-my-dns-with-geo-information) - [Run an HTTP/3 server](#run-an-http3-server) - [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) @@ -22,7 +23,8 @@ - [Run a container locally using test databases](#run-a-container-locally-using-test-databases) - [From Docker Hub](#from-docker-hub) -> [!NOTE] +> [!NOTE] +> Since version 3.0.0, geodb database is not mandatory, not adding the flags will disable the geo feature. > Since version 2.3.0, the application includes an optional client [DNS discovery](#dns-discovery) Just another "what is my IP address" service, including geolocation, TCP open port checking, and headers information. Written in go with high performance in mind, @@ -124,42 +126,48 @@ Golang >= 1.22 is required. ```text Usage of whatismyip: -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-http3 - Enable HTTP/3 protocol. HTTP/3 requires --tls-bind set, as HTTP/3 starts as a TLS connection that then gets upgraded to UDP. The UDP port is the same as the one used for the TLS server. + Enable HTTP/3 protocol. HTTP/3 requires --tls-bind set, as HTTP/3 starts as a TLS connection that then gets upgraded to UDP. The UDP port is the same as the one used for the TLS server. -enable-secure-headers - Add sane security-related headers to every response + Add sane security-related headers to every response -geoip2-asn string - Path to GeoIP2 ASN database + Path to GeoIP2 ASN database. Enables ASN information. (--geoip2-city becomes mandatory) -geoip2-city string - Path to GeoIP2 city database + Path to GeoIP2 city database. Enables geo information (--geoip2-asn becomes mandatory) -resolver string - Path to the resolver configuration. It actually enables the resolver for DNS client discovery. + Path to the resolver configuration. It actually enables the resolver for DNS client discovery. -template string - Path to template file + Path to the template file -tls-bind string - Listening address for TLS (see https://pkg.go.dev/net?#Listen) + Listening address for TLS (see https://pkg.go.dev/net?#Listen) -tls-crt string - When using TLS, path to certificate file + When using TLS, path to certificate file -tls-key string - When using TLS, path to private key file + When using TLS, path to private key file -trusted-header string - 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 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 + Trusted request header for remote client port (e.g. X-Real-Port). When this parameter is set -trusted-header becomes mandatory -version - Output version information and exit + Output version information and exit ``` ## Examples ### Run a default TCP server +```bash +./whatismyip +``` + +### Run a default TCP server with geo information enabled + ```bash ./whatismyip -geoip2-city ./test/GeoIP2-City-Test.mmdb -geoip2-asn ./test/GeoLite2-ASN-Test.mmdb ``` -### Run a TLS (HTTP/2) and enable What is my DNS +### Run a TLS (HTTP/2) and enable "what is my DNS" with geo information ```bash ./whatismyip -geoip2-city ./test/GeoIP2-City-Test.mmdb -geoip2-asn ./test/GeoLite2-ASN-Test.mmdb \ diff --git a/cmd/whatismyip.go b/cmd/whatismyip.go index 1db79ab..1cf3b3a 100644 --- a/cmd/whatismyip.go +++ b/cmd/whatismyip.go @@ -13,6 +13,7 @@ import ( "github.com/dcarrillo/whatismyip/internal/setting" "github.com/dcarrillo/whatismyip/resolver" "github.com/dcarrillo/whatismyip/server" + "github.com/dcarrillo/whatismyip/service" "github.com/gin-contrib/secure" "github.com/patrickmn/go-cache" @@ -41,11 +42,20 @@ func main() { engine.Use(router.GetDNSDiscoveryHandler(store, setting.App.Resolver.Domain, setting.App.Resolver.RedirectPort)) } + var geoSvc *service.Geo + if setting.App.GeodbPath.City != "" || setting.App.GeodbPath.ASN != "" { + var err error + geoSvc, err = service.NewGeo(context.Background(), setting.App.GeodbPath.City, setting.App.GeodbPath.ASN) + if err != nil { + panic(err) + } + } + router.SetupTemplate(engine) - router.Setup(engine) + router.Setup(engine, geoSvc) servers = slices.Concat(servers, setupHTTPServers(context.Background(), engine.Handler())) - whatismyip := server.Setup(servers) + whatismyip := server.Setup(servers, geoSvc) whatismyip.Run() } diff --git a/go.mod b/go.mod index 31ced5b..c2fec0f 100644 --- a/go.mod +++ b/go.mod @@ -4,14 +4,14 @@ go 1.23 require ( github.com/docker/docker v26.1.5+incompatible - github.com/gin-contrib/secure v1.1.0 + github.com/gin-contrib/secure v1.1.1 github.com/gin-gonic/gin v1.10.0 github.com/google/uuid v1.6.0 github.com/miekg/dns v1.1.62 github.com/oschwald/maxminddb-golang v1.13.1 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/quic-go/quic-go v0.48.2 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/testcontainers/testcontainers-go v0.31.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -19,11 +19,13 @@ require ( require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/BurntSushi/toml v1.4.0 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Microsoft/hcsshim v0.11.4 // indirect github.com/bytedance/sonic v1.12.6 // indirect github.com/bytedance/sonic/loader v0.2.1 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/chavacava/garif v0.1.0 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/containerd/containerd v1.7.15 // indirect @@ -33,9 +35,11 @@ require ( github.com/distribution/reference v0.5.0 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/fatih/structtag v1.2.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/gabriel-vasile/mimetype v1.4.7 // indirect - github.com/gin-contrib/sse v0.1.0 // indirect + github.com/gin-contrib/sse v1.0.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect @@ -47,14 +51,20 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect + github.com/hashicorp/go-version v1.7.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 // indirect + github.com/mgechev/revive v1.5.1 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect @@ -63,7 +73,8 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/morikuni/aec v1.0.0 // indirect - github.com/onsi/ginkgo/v2 v2.22.0 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/onsi/ginkgo/v2 v2.22.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect @@ -71,9 +82,11 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/quic-go/qpack v0.5.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/spf13/afero v1.11.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect @@ -96,5 +109,5 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect google.golang.org/grpc v1.59.0 // indirect - google.golang.org/protobuf v1.36.0 // indirect + google.golang.org/protobuf v1.36.1 // indirect ) diff --git a/go.sum b/go.sum index 847f59e..dfa0127 100644 --- a/go.sum +++ b/go.sum @@ -4,12 +4,12 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= -github.com/bytedance/sonic v1.12.5 h1:hoZxY8uW+mT+OpkcUWw4k0fDINtOcVavEsGfzwzFU/w= -github.com/bytedance/sonic v1.12.5/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= github.com/bytedance/sonic v1.12.6 h1:/isNmCUF2x3Sh8RAp/4mh4ZGkcFAX/hLrzrK3AvpRzk= github.com/bytedance/sonic v1.12.6/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= @@ -17,6 +17,8 @@ github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iC github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= +github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= @@ -41,14 +43,18 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA= github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU= -github.com/gin-contrib/secure v1.1.0 h1:wy/psCWbgUBDCLH13KgB/m06NHXb1jczSTRp+H2hK7E= -github.com/gin-contrib/secure v1.1.0/go.mod h1:LtEfyy326NRwgkUq8ac6npf845L0L9B8yfEaLcxMHIc= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-contrib/secure v1.1.1 h1:q1AGANrYRhJYYHZCF0VH/NVvP0uOSMXmXbsaqWRgIEQ= +github.com/gin-contrib/secure v1.1.1/go.mod h1:4IhY8OTLEAI3R7qZF1ya4y75WowL8MJ09i2Kunl83HE= +github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E= +github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -66,7 +72,6 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o= github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= @@ -86,12 +91,14 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= @@ -106,10 +113,22 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 h1:zpIH83+oKzcpryru8ceC6BxnoG8TBrhgAvRg8obzup0= +github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg= +github.com/mgechev/revive v1.5.1 h1:hE+QPeq0/wIzJwOphdVyUJ82njdd8Khp4fUIHGZHW3M= +github.com/mgechev/revive v1.5.1/go.mod h1:lC9AhkJIBs5zwx8wkudyHrU+IJkrEKmpCmGMnIJPk4o= github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= @@ -127,10 +146,12 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= -github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= -github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= -github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= +github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= @@ -139,7 +160,6 @@ github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5 github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -152,6 +172,9 @@ github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= @@ -162,17 +185,20 @@ github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/testcontainers/testcontainers-go v0.31.0 h1:W0VwIhcEVhRflwL9as3dhY6jXjVCA27AkmbnZ+UTh3U= github.com/testcontainers/testcontainers-go v0.31.0/go.mod h1:D2lAoA0zUFiSY+eAflqK5mcUx/A5hrrORaEQrd0SefI= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= @@ -212,8 +238,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU= -golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo= golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -224,8 +248,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= -golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -240,6 +262,7 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -270,10 +293,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= -google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= -google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ= -google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/integration-tests/integration_test.go b/integration-tests/integration_test.go index a73b3e7..0f0ee90 100644 --- a/integration-tests/integration_test.go +++ b/integration-tests/integration_test.go @@ -200,8 +200,10 @@ func TestContainerIntegration(t *testing.T) { resp, body, err = doQuicRequest(req) } else { client := &http.Client{} - resp, _ = client.Do(req) + resp, err = client.Do(req) + assert.NoError(t, err) body, err = io.ReadAll(resp.Body) + assert.NoError(t, err) if strings.Contains(tt.url, "https://") { assert.Equal(t, `h3=":8001"; ma=2592000`, resp.Header.Get("Alt-Svc")) } diff --git a/internal/setting/app.go b/internal/setting/app.go index d1119b4..08b4712 100644 --- a/internal/setting/app.go +++ b/internal/setting/app.go @@ -12,9 +12,10 @@ import ( "gopkg.in/yaml.v3" ) -type geodbPath struct { - City string - ASN string +type geodbConf struct { + City string + ASN string + Token *string } type serverSettings struct { ReadTimeout time.Duration @@ -30,7 +31,7 @@ type resolver struct { } type settings struct { - GeodbPath geodbPath + GeodbPath geodbConf TemplatePath string BindAddress string TLSAddress string @@ -63,8 +64,8 @@ func Setup(args []string) (output string, err error) { var resolverConf string flags.SetOutput(&buf) - flags.StringVar(&App.GeodbPath.City, "geoip2-city", "", "Path to GeoIP2 city database") - flags.StringVar(&App.GeodbPath.ASN, "geoip2-asn", "", "Path to GeoIP2 ASN database") + flags.StringVar(&App.GeodbPath.City, "geoip2-city", "", "Path to GeoIP2 city database. Enables geo information (--geoip2-asn becomes mandatory)") + flags.StringVar(&App.GeodbPath.ASN, "geoip2-asn", "", "Path to GeoIP2 ASN database. Enables ASN information. (--geoip2-city becomes mandatory)") flags.StringVar(&App.TemplatePath, "template", "", "Path to the template file") flags.StringVar( &resolverConf, @@ -120,12 +121,12 @@ func Setup(args []string) (output string, err error) { 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") + if (App.GeodbPath.City != "" && App.GeodbPath.ASN == "") || (App.GeodbPath.City == "" && App.GeodbPath.ASN != "") { + return "", fmt.Errorf("both --geoip2-city and --geoip2-asn are mandatory to enable geo information") } - if App.GeodbPath.City == "" || App.GeodbPath.ASN == "" { - return "", fmt.Errorf("geoip2-city and geoip2-asn parameters are mandatory") + if App.TrustedPortHeader != "" && App.TrustedHeader == "" { + return "", fmt.Errorf("truster-header is mandatory when truster-port-header is set") } if (App.TLSAddress != "") && (App.TLSCrtPath == "" || App.TLSKeyPath == "") { @@ -150,7 +151,7 @@ func Setup(args []string) (output string, err error) { var err error App.Resolver, err = readYAML(resolverConf) if err != nil { - return "", fmt.Errorf("error reading resolver configuration %s", err) + return "", fmt.Errorf("error reading resolver configuration %w", err) } } diff --git a/internal/setting/app_test.go b/internal/setting/app_test.go index 33dd863..f4c1e9f 100644 --- a/internal/setting/app_test.go +++ b/internal/setting/app_test.go @@ -12,44 +12,43 @@ import ( ) func TestParseMandatoryFlags(t *testing.T) { - var mandatoryFlags = []struct { + mandatoryFlags := []struct { args []string }{ - { - []string{}, - }, - { - []string{"-geoip2-city", "/city-path"}, - }, - { - []string{"-geoip2-asn", "/asn-path"}, - }, { []string{ - "-geoip2-city", "/city-path", "-geoip2-asn", "/asn-path", "-tls-bind", ":9000", + "-geoip2-city", "my-city-path", }, }, { []string{ - "-geoip2-city", "/city-path", "-geoip2-asn", "/asn-path", "-tls-bind", ":9000", - "-tls-crt", "/crt-path", + "-geoip2-asn", "my-asn-path", + }, + }, + + { + []string{ + "-tls-bind", ":9000", }, }, { []string{ - "-geoip2-city", "/city-path", "-geoip2-asn", "/asn-path", "-tls-bind", ":9000", - "-tls-key", "/key-path", + "-tls-bind", ":9000", "-tls-crt", "/crt-path", }, }, { []string{ - "-geoip2-city", "/city-path", "-geoip2-asn", "/asn-path", "-enable-http3", + "-tls-bind", ":9000", "-tls-key", "/key-path", }, }, { []string{ - "-geoip2-city", "/city-path", "-geoip2-asn", "/asn-path", "-bind", ":8000", - "-trusted-port-header", "port-header", + "-enable-http3", + }, + }, + { + []string{ + "-bind", ":8000", "-trusted-port-header", "port-header", }, }, } @@ -57,24 +56,20 @@ func TestParseMandatoryFlags(t *testing.T) { for _, tt := range mandatoryFlags { t.Run(strings.Join(tt.args, " "), func(t *testing.T) { _, err := Setup(tt.args) - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), "mandatory") }) } } func TestParseFlags(t *testing.T) { - var flags = []struct { + flags := []struct { args []string conf settings }{ { - []string{"-geoip2-city", "/city-path", "-geoip2-asn", "/asn-path"}, + []string{}, settings{ - GeodbPath: geodbPath{ - City: "/city-path", - ASN: "/asn-path", - }, BindAddress: ":8080", Server: serverSettings{ ReadTimeout: 10 * time.Second, @@ -85,7 +80,7 @@ func TestParseFlags(t *testing.T) { { []string{"-bind", ":8001", "-geoip2-city", "/city-path", "-geoip2-asn", "/asn-path"}, settings{ - GeodbPath: geodbPath{ + GeodbPath: geodbConf{ City: "/city-path", ASN: "/asn-path", }, @@ -102,7 +97,7 @@ func TestParseFlags(t *testing.T) { "-tls-crt", "/crt-path", "-tls-key", "/key-path", }, settings{ - GeodbPath: geodbPath{ + GeodbPath: geodbConf{ City: "/city-path", ASN: "/asn-path", }, @@ -122,7 +117,7 @@ func TestParseFlags(t *testing.T) { "-trusted-header", "header", "-trusted-port-header", "port-header", }, settings{ - GeodbPath: geodbPath{ + GeodbPath: geodbConf{ City: "/city-path", ASN: "/asn-path", }, @@ -141,7 +136,7 @@ func TestParseFlags(t *testing.T) { "-trusted-header", "header", "-enable-secure-headers", }, settings{ - GeodbPath: geodbPath{ + GeodbPath: geodbConf{ City: "/city-path", ASN: "/asn-path", }, @@ -166,7 +161,7 @@ func TestParseFlags(t *testing.T) { } func TestParseFlagsUsage(t *testing.T) { - var usageArgs = []string{"-help", "-h", "--help"} + usageArgs := []string{"-help", "-h", "--help"} for _, arg := range usageArgs { t.Run(arg, func(t *testing.T) { @@ -184,19 +179,28 @@ func TestParseFlagVersion(t *testing.T) { } func TestParseFlagTemplate(t *testing.T) { - flags := []string{ - "-geoip2-city", "/city-path", "-geoip2-asn", "/asn-path", - "-template", "/template-path", + testCases := []struct { + name string + flags []string + errMsg string + }{ + { + name: "Invalid template path", + flags: []string{"-template", "/template-path"}, + errMsg: "no such file or directory", + }, + { + name: "Template path is a directory", + flags: []string{"-template", "/"}, + errMsg: "must be a file", + }, } - _, err := Setup(flags) - assert.Error(t, err) - assert.Contains(t, err.Error(), "no such file or directory") - flags = []string{ - "-geoip2-city", "/city-path", "-geoip2-asn", "/asn-path", - "-template", "/", + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, err := Setup(tc.flags) + require.Error(t, err) + assert.Contains(t, err.Error(), tc.errMsg) + }) } - _, err = Setup(flags) - require.Error(t, err) - assert.Contains(t, err.Error(), "must be a file") } diff --git a/models/geo.go b/models/geo.go index 3af6614..4afb7ab 100644 --- a/models/geo.go +++ b/models/geo.go @@ -1,13 +1,13 @@ package models import ( + "fmt" "log" "net" "github.com/oschwald/maxminddb-golang" ) -// GeoRecord is the model for City database type GeoRecord struct { Country struct { ISOCode string `maxminddb:"iso_code"` @@ -26,52 +26,107 @@ type GeoRecord struct { } `maxminddb:"postal"` } -// ASNRecord is the model for ASN database type ASNRecord struct { AutonomousSystemNumber uint `maxminddb:"autonomous_system_number"` AutonomousSystemOrganization string `maxminddb:"autonomous_system_organization"` } -type geodb struct { - city *maxminddb.Reader - asn *maxminddb.Reader +type GeoDB struct { + cityPath string + asnPath string + City *maxminddb.Reader + ASN *maxminddb.Reader } -var db geodb +func Setup(cityPath string, asnPath string) (*GeoDB, error) { + city, asn, err := openDatabases(cityPath, asnPath) + if err != nil { + return nil, err + } -func openMMDB(path string) *maxminddb.Reader { + return &GeoDB{ + cityPath: cityPath, + asnPath: asnPath, + City: city, + ASN: asn, + }, nil +} + +func (db *GeoDB) CloseDBs() error { + var errs []error + + if db.City != nil { + if err := db.City.Close(); err != nil { + errs = append(errs, fmt.Errorf("closing city db: %w", err)) + } + } + + if db.ASN != nil { + if err := db.ASN.Close(); err != nil { + errs = append(errs, fmt.Errorf("closing ASN db: %w", err)) + } + } + + if len(errs) > 0 { + return fmt.Errorf("errors closing databases: %s", errs) + } + return nil +} + +func (db *GeoDB) Reload() error { + if err := db.CloseDBs(); err != nil { + return fmt.Errorf("closing existing connections: %w", err) + } + + city, asn, err := openDatabases(db.cityPath, db.asnPath) + if err != nil { + return fmt.Errorf("opening new connections: %w", err) + } + + db.City = city + db.ASN = asn + return nil +} + +func (db *GeoDB) LookupCity(ip net.IP) (*GeoRecord, error) { + record := &GeoRecord{} + err := db.City.Lookup(ip, record) + if err != nil { + return nil, err + } + + return record, nil +} + +func (db *GeoDB) LookupASN(ip net.IP) (*ASNRecord, error) { + record := &ASNRecord{} + err := db.ASN.Lookup(ip, record) + if err != nil { + return nil, err + } + return record, nil +} + +func openDatabases(cityPath, asnPath string) (*maxminddb.Reader, *maxminddb.Reader, error) { + city, err := openMMDB(cityPath) + if err != nil { + return nil, nil, err + } + + asn, err := openMMDB(asnPath) + if err != nil { + return nil, nil, err + } + + return city, asn, nil +} + +func openMMDB(path string) (*maxminddb.Reader, error) { db, err := maxminddb.Open(path) if err != nil { - log.Fatal(err) + return nil, err } log.Printf("Database %s has been loaded\n", path) - return db -} - -// Setup opens all Geolite2 databases -func Setup(cityPath string, asnPath string) { - db.city = openMMDB(cityPath) - db.asn = openMMDB(asnPath) -} - -// CloseDBs unmaps from memory and frees resources to the filesystem -func CloseDBs() { - log.Printf("Closing dbs...") - if err := db.city.Close(); err != nil { - log.Printf("Error closing city db: %s", err) - } - if err := db.asn.Close(); err != nil { - log.Printf("Error closing ASN db: %s", err) - } -} - -// LookUp an IP and get city data -func (record *GeoRecord) LookUp(ip net.IP) error { - return db.city.Lookup(ip, record) -} - -// LookUp an IP and get ASN data -func (record *ASNRecord) LookUp(ip net.IP) error { - return db.asn.Lookup(ip, record) + return db, nil } diff --git a/models/geo_test.go b/models/geo_test.go index 8b85456..7506f7e 100644 --- a/models/geo_test.go +++ b/models/geo_test.go @@ -1,10 +1,12 @@ package models import ( + "fmt" "net" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestModels(t *testing.T) { @@ -59,19 +61,21 @@ func TestModels(t *testing.T) { AutonomousSystemOrganization: "IP-Only", } - Setup("../test/GeoIP2-City-Test.mmdb", "../test/GeoLite2-ASN-Test.mmdb") - defer CloseDBs() + db, err := Setup("../test/GeoIP2-City-Test.mmdb", "../test/GeoLite2-ASN-Test.mmdb") + require.NoError(t, err, fmt.Sprintf("Error setting up db: %s", err)) + defer db.CloseDBs() + assert.NotNil(t, db.ASN) + assert.NotNil(t, db.City) - assert.NotNil(t, db.asn) - assert.NotNil(t, db.city) - - cityRecord := &GeoRecord{} - assert.Nil(t, cityRecord.LookUp(net.ParseIP("81.2.69.192"))) + cityRecord, err := db.LookupCity(net.ParseIP("81.2.69.192")) + require.NoError(t, err, fmt.Sprintf("Error looking up city: %s", err)) assert.Equal(t, expectedCity, cityRecord) - assert.Error(t, cityRecord.LookUp(net.ParseIP("error"))) + _, err = db.LookupCity(net.ParseIP("error")) + assert.Error(t, err) - asnRecord := &ASNRecord{} - assert.Nil(t, asnRecord.LookUp(net.ParseIP("82.99.17.64"))) + asnRecord, err := db.LookupASN(net.ParseIP("82.99.17.64")) + require.NoError(t, err, fmt.Sprintf("Error looking up asn: %s", err)) assert.Equal(t, expectedASN, asnRecord) - assert.Error(t, asnRecord.LookUp(net.ParseIP("error"))) + _, err = db.LookupASN(net.ParseIP("error")) + assert.Error(t, err) } diff --git a/router/dns.go b/router/dns.go index 3b4b3db..a3e5fdd 100644 --- a/router/dns.go +++ b/router/dns.go @@ -7,7 +7,6 @@ import ( "strings" validator "github.com/dcarrillo/whatismyip/internal/validator/uuid" - "github.com/dcarrillo/whatismyip/service" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/patrickmn/go-cache" @@ -16,10 +15,14 @@ import ( type DNSJSONResponse struct { DNS dnsData `json:"dns"` } +type dnsGeoData struct { + Country string `json:"country,omitempty"` + AsnOrganization string `json:"provider,omitempty"` +} + type dnsData struct { - IP string `json:"ip"` - Country string `json:"country"` - AsnOrganization string `json:"provider"` + IP string `json:"ip"` + dnsGeoData } // TODO @@ -67,12 +70,21 @@ func handleDNS(ctx *gin.Context, store *cache.Cache) { return } - geo := service.Geo{IP: ip} + geoResp := dnsGeoData{} + if geoSvc != nil { + cityRecord := geoSvc.LookUpCity(ip) + asnRecord := geoSvc.LookUpASN(ip) + + geoResp = dnsGeoData{ + Country: cityRecord.Country.Names["en"], + AsnOrganization: asnRecord.AutonomousSystemOrganization, + } + } + j := DNSJSONResponse{ DNS: dnsData{ - IP: ipStr, - Country: geo.LookUpCity().Country.Names["en"], - AsnOrganization: geo.LookUpASN().AutonomousSystemOrganization, + IP: ipStr, + dnsGeoData: geoResp, }, } diff --git a/router/generic.go b/router/generic.go index 4cb3647..e374a1c 100644 --- a/router/generic.go +++ b/router/generic.go @@ -7,25 +7,28 @@ import ( "github.com/dcarrillo/whatismyip/internal/httputils" "github.com/dcarrillo/whatismyip/internal/setting" - "github.com/dcarrillo/whatismyip/service" "github.com/gin-gonic/gin" ) +type GeoResponse struct { + Country string `json:"country,omitempty"` + CountryCode string `json:"country_code,omitempty"` + City string `json:"city,omitempty"` + Latitude float64 `json:"latitude,omitempty"` + Longitude float64 `json:"longitude,omitempty"` + PostalCode string `json:"postal_code,omitempty"` + TimeZone string `json:"time_zone,omitempty"` + ASN uint `json:"asn,omitempty"` + ASNOrganization string `json:"asn_organization,omitempty"` +} + type JSONResponse struct { - IP string `json:"ip"` - IPVersion byte `json:"ip_version"` - ClientPort string `json:"client_port"` - Country string `json:"country"` - CountryCode string `json:"country_code"` - City string `json:"city"` - Latitude float64 `json:"latitude"` - Longitude float64 `json:"longitude"` - PostalCode string `json:"postal_code"` - TimeZone string `json:"time_zone"` - ASN uint `json:"asn"` - ASNOrganization string `json:"asn_organization"` - Host string `json:"host"` - Headers http.Header `json:"headers"` + IP string `json:"ip"` + IPVersion byte `json:"ip_version"` + ClientPort string `json:"client_port"` + Host string `json:"host"` + Headers http.Header `json:"headers"` + GeoResponse } func getRoot(ctx *gin.Context) { @@ -66,16 +69,14 @@ func getClientPortAsString(ctx *gin.Context) { } func getAllAsString(ctx *gin.Context) { - output := "IP: " + ctx.ClientIP() + "\n" + ip := net.ParseIP(ctx.ClientIP()) + + output := "IP: " + ip.String() + "\n" output += "Client Port: " + getClientPort(ctx) + "\n" - r := service.Geo{IP: net.ParseIP(ctx.ClientIP())} - if record := r.LookUpCity(); record != nil { - output += geoCityRecordToString(record) + "\n" - } - - if record := r.LookUpASN(); record != nil { - output += geoASNRecordToString(record) + "\n" + if geoSvc != nil { + output += geoCityRecordToString(geoSvc.LookUpCity(ip)) + "\n" + output += geoASNRecordToString(geoSvc.LookUpASN(ip)) + "\n" } h := httputils.GetHeadersWithoutTrustedHeaders(ctx) @@ -90,28 +91,37 @@ func getJSON(ctx *gin.Context) { } func jsonOutput(ctx *gin.Context) JSONResponse { - ip := service.Geo{IP: net.ParseIP(ctx.ClientIP())} - asnRecord := ip.LookUpASN() - cityRecord := ip.LookUpCity() + ip := net.ParseIP(ctx.ClientIP()) + var version byte = 4 - if p := net.ParseIP(ctx.ClientIP()).To4(); p == nil { + if p := ip.To4(); p == nil { version = 6 } + geoResp := GeoResponse{} + if geoSvc != nil { + cityRecord := geoSvc.LookUpCity(ip) + asnRecord := geoSvc.LookUpASN(ip) + + geoResp = GeoResponse{ + Country: cityRecord.Country.Names["en"], + CountryCode: cityRecord.Country.ISOCode, + City: cityRecord.City.Names["en"], + Latitude: cityRecord.Location.Latitude, + Longitude: cityRecord.Location.Longitude, + PostalCode: cityRecord.Postal.Code, + TimeZone: cityRecord.Location.TimeZone, + ASN: asnRecord.AutonomousSystemNumber, + ASNOrganization: asnRecord.AutonomousSystemOrganization, + } + } + return JSONResponse{ - IP: ctx.ClientIP(), - IPVersion: version, - ClientPort: getClientPort(ctx), - Country: cityRecord.Country.Names["en"], - CountryCode: cityRecord.Country.ISOCode, - City: cityRecord.City.Names["en"], - Latitude: cityRecord.Location.Latitude, - Longitude: cityRecord.Location.Longitude, - PostalCode: cityRecord.Postal.Code, - TimeZone: cityRecord.Location.TimeZone, - ASN: asnRecord.AutonomousSystemNumber, - ASNOrganization: asnRecord.AutonomousSystemOrganization, - Host: ctx.Request.Host, - Headers: httputils.GetHeadersWithoutTrustedHeaders(ctx), + IP: ip.String(), + IPVersion: version, + ClientPort: getClientPort(ctx), + Host: ctx.Request.Host, + Headers: httputils.GetHeadersWithoutTrustedHeaders(ctx), + GeoResponse: geoResp, } } diff --git a/router/geo.go b/router/geo.go index 10b48a0..06ec020 100644 --- a/router/geo.go +++ b/router/geo.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/dcarrillo/whatismyip/models" - "github.com/dcarrillo/whatismyip/service" "github.com/gin-gonic/gin" ) @@ -83,10 +82,13 @@ var asnOutput = map[string]asnDataFormatter{ } func getGeoAsString(ctx *gin.Context) { - field := strings.ToLower(ctx.Params.ByName("field")) - ip := service.Geo{IP: net.ParseIP(ctx.ClientIP())} - record := ip.LookUpCity() + if geoSvc == nil { + ctx.String(http.StatusNotFound, http.StatusText(http.StatusNotFound)) + return + } + field := strings.ToLower(ctx.Params.ByName("field")) + record := geoSvc.LookUpCity(net.ParseIP(ctx.ClientIP())) if field == "" { ctx.String(http.StatusOK, geoCityRecordToString(record)) } else if g, ok := geoOutput[field]; ok { @@ -97,10 +99,12 @@ func getGeoAsString(ctx *gin.Context) { } func getASNAsString(ctx *gin.Context) { + if geoSvc == nil { + ctx.String(http.StatusNotFound, http.StatusText(http.StatusNotFound)) + return + } field := strings.ToLower(ctx.Params.ByName("field")) - ip := service.Geo{IP: net.ParseIP(ctx.ClientIP())} - record := ip.LookUpASN() - + record := geoSvc.LookUpASN(net.ParseIP(ctx.ClientIP())) if field == "" { ctx.String(http.StatusOK, geoASNRecordToString(record)) } else if g, ok := asnOutput[field]; ok { diff --git a/router/setup.go b/router/setup.go index 18e9a0a..8712103 100644 --- a/router/setup.go +++ b/router/setup.go @@ -5,9 +5,12 @@ import ( "log" "github.com/dcarrillo/whatismyip/internal/setting" + "github.com/dcarrillo/whatismyip/service" "github.com/gin-gonic/gin" ) +var geoSvc *service.Geo + func SetupTemplate(r *gin.Engine) { if setting.App.TemplatePath == "" { t, _ := template.New("home").Parse(home) @@ -18,7 +21,8 @@ func SetupTemplate(r *gin.Engine) { } } -func Setup(r *gin.Engine) { +func Setup(r *gin.Engine, geo *service.Geo) { + geoSvc = geo r.GET("/", getRoot) r.GET("/scan/tcp/:port", scanTCPPort) r.GET("/client-port", getClientPortAsString) diff --git a/router/setup_test.go b/router/setup_test.go index e296e7b..b463c4f 100644 --- a/router/setup_test.go +++ b/router/setup_test.go @@ -1,10 +1,11 @@ package router import ( + "context" "os" "testing" - "github.com/dcarrillo/whatismyip/models" + "github.com/dcarrillo/whatismyip/service" "github.com/gin-gonic/gin" ) @@ -34,9 +35,9 @@ var ( text: "text/plain; charset=utf-8", json: "application/json; charset=utf-8", } - 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": {}}` - jsonIPv6 = `{"asn":3352, "asn_organization":"TELEFONICA DE ESPANA", "city":"", "client_port":"1001", "country":"", "country_code":"", "host":"test", "ip":"2a02:9000::1", "ip_version":6, "latitude":0, "longitude":0, "postal_code":"", "time_zone":"", "headers": {}}` - jsonDNSIPv4 = `{"dns":{"ip":"81.2.69.192","country":"United Kingdom","provider":""}}` + 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,"time_zone":"Europe/London","host":"test", "headers": {}}` + jsonIPv6 = `{"asn":3352,"asn_organization":"TELEFONICA DE ESPANA","client_port":"1001","host":"test","ip":"2a02:9000::1","ip_version":6,"headers": {}}` + jsonDNSIPv4 = `{"dns":{"ip":"81.2.69.192","country":"United Kingdom"}}` plainDNSIPv4 = "81.2.69.192 (United Kingdom / )\n" ) @@ -49,9 +50,8 @@ const ( func TestMain(m *testing.M) { app = gin.Default() app.TrustedPlatform = trustedHeader - models.Setup("../test/GeoIP2-City-Test.mmdb", "../test/GeoLite2-ASN-Test.mmdb") - Setup(app) - defer models.CloseDBs() + svc, _ := service.NewGeo(context.Background(), "../test/GeoIP2-City-Test.mmdb", "../test/GeoLite2-ASN-Test.mmdb") + Setup(app, svc) os.Exit(m.Run()) } diff --git a/router/templates_test.go b/router/templates_test.go index 0a3e6a4..c8b5759 100644 --- a/router/templates_test.go +++ b/router/templates_test.go @@ -62,20 +62,22 @@ func TestDefaultTemplate(t *testing.T) { tmpl, _ := template.New("home").Parse(home) response := JSONResponse{ - IP: "127.0.0.1", - IPVersion: 4, - ClientPort: "1000", - Country: "A Country", - CountryCode: "XX", - City: "A City", - Latitude: 100, - Longitude: -100, - PostalCode: "00000", - TimeZone: "My/Timezone", - ASN: 0, - ASNOrganization: "My ISP", - Host: "localhost", - Headers: req.Header, + IP: "127.0.0.1", + IPVersion: 4, + ClientPort: "1000", + Host: "localhost", + Headers: req.Header, + GeoResponse: GeoResponse{ + Country: "A Country", + CountryCode: "XX", + City: "A City", + Latitude: 100, + Longitude: -100, + PostalCode: "00000", + TimeZone: "My/Timezone", + ASN: 0, + ASNOrganization: "My ISP", + }, } buf := &bytes.Buffer{} diff --git a/server/dns.go b/server/dns.go index a95000c..a79b102 100644 --- a/server/dns.go +++ b/server/dns.go @@ -41,7 +41,7 @@ func (d *DNS) Start() { } func (d *DNS) Stop() { - log.Printf("Stopping DNS server...") + log.Print("Stopping DNS server...") if err := d.server.Shutdown(); err != nil { log.Printf("DNS server forced to shutdown: %s", err) } diff --git a/server/quic.go b/server/quic.go index 1b7a4aa..a057264 100644 --- a/server/quic.go +++ b/server/quic.go @@ -48,8 +48,8 @@ func (q *Quic) Start() { } func (q *Quic) Stop() { - log.Printf("Stopping QUIC server...") + log.Print("Stopping QUIC server...") if err := q.server.Close(); err != nil { - log.Printf("QUIC server forced to shutdown") + log.Print("QUIC server forced to shutdown") } } diff --git a/server/server.go b/server/server.go index 0e444b1..d2a7b09 100644 --- a/server/server.go +++ b/server/server.go @@ -6,8 +6,7 @@ import ( "os/signal" "syscall" - "github.com/dcarrillo/whatismyip/internal/setting" - "github.com/dcarrillo/whatismyip/models" + "github.com/dcarrillo/whatismyip/service" ) type Server interface { @@ -17,18 +16,19 @@ type Server interface { type Manager struct { servers []Server + geoSvc *service.Geo } -func Setup(servers []Server) *Manager { +func Setup(servers []Server, geoSvc *service.Geo) *Manager { return &Manager{ servers: servers, + geoSvc: geoSvc, } } func (m *Manager) Run() { m.start() - models.Setup(setting.App.GeodbPath.City, setting.App.GeodbPath.ASN) signalChan := make(chan os.Signal, len(m.servers)) signal.Notify(signalChan, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) var s os.Signal @@ -37,13 +37,16 @@ func (m *Manager) Run() { if s == syscall.SIGHUP { m.stop() - models.CloseDBs() - models.Setup(setting.App.GeodbPath.City, setting.App.GeodbPath.ASN) + if m.geoSvc != nil { + m.geoSvc.Reload() + } m.start() } else { - log.Printf("Shutting down...") + log.Print("Shutting down...") + if m.geoSvc != nil { + m.geoSvc.Shutdown() + } m.stop() - models.CloseDBs() break } } diff --git a/server/tcp.go b/server/tcp.go index f805471..6df3125 100644 --- a/server/tcp.go +++ b/server/tcp.go @@ -39,7 +39,7 @@ func (t *TCP) Start() { } func (t *TCP) Stop() { - log.Printf("Stopping TCP server...") + log.Print("Stopping TCP server...") if err := t.server.Shutdown(t.ctx); err != nil { log.Printf("TCP server forced to shutdown: %s", err) } diff --git a/server/tls.go b/server/tls.go index 47a931f..15ef776 100644 --- a/server/tls.go +++ b/server/tls.go @@ -40,7 +40,7 @@ func (t *TLS) Start() { } func (t *TLS) Stop() { - log.Printf("Stopping TLS server...") + log.Print("Stopping TLS server...") if err := t.server.Shutdown(t.ctx); err != nil { log.Printf("TLS server forced to shutdown: %s", err) } diff --git a/service/geo.go b/service/geo.go index c8a5df0..af2ab4f 100644 --- a/service/geo.go +++ b/service/geo.go @@ -1,37 +1,73 @@ package service import ( + "context" "log" "net" + "sync" "github.com/dcarrillo/whatismyip/models" ) -// Geo defines a base type for lookups type Geo struct { - IP net.IP + ctx context.Context + cancel context.CancelFunc + db *models.GeoDB + mu sync.RWMutex } -// LookUpCity queries the database for city data related to the given IP -func (g *Geo) LookUpCity() *models.GeoRecord { - record := &models.GeoRecord{} - err := record.LookUp(g.IP) +func NewGeo(ctx context.Context, cityPath string, asnPath string) (*Geo, error) { + ctx, cancel := context.WithCancel(ctx) + + db, err := models.Setup(cityPath, asnPath) if err != nil { - log.Println(err) + cancel() + return nil, err + } + + geo := &Geo{ + ctx: ctx, + cancel: cancel, + db: db, + } + + return geo, nil +} + +func (g *Geo) LookUpCity(ip net.IP) *models.GeoRecord { + record, err := g.db.LookupCity(ip) + if err != nil { + log.Print(err) return nil } return record } -// LookUpASN queries the database for ASN data related to the given IP -func (g *Geo) LookUpASN() *models.ASNRecord { - record := &models.ASNRecord{} - err := record.LookUp(g.IP) +func (g *Geo) LookUpASN(ip net.IP) *models.ASNRecord { + record, err := g.db.LookupASN(ip) if err != nil { - log.Println(err) + log.Print(err) return nil } return record } + +func (g *Geo) Shutdown() { + g.cancel() + g.db.CloseDBs() +} + +func (g *Geo) Reload() { + if err := g.ctx.Err(); err != nil { + log.Printf("Skipping reload, service is shutting down: %v", err) + return + } + + g.mu.Lock() + defer g.mu.Unlock() + + g.db.Reload() + log.Print("Geo database reloaded") +} diff --git a/service/geo_test.go b/service/geo_test.go index 5451c60..25508cb 100644 --- a/service/geo_test.go +++ b/service/geo_test.go @@ -1,36 +1,33 @@ package service import ( + "context" "net" "os" "testing" - "github.com/dcarrillo/whatismyip/models" "github.com/stretchr/testify/assert" ) +var geoSvc *Geo + func TestMain(m *testing.M) { - models.Setup("../test/GeoIP2-City-Test.mmdb", "../test/GeoLite2-ASN-Test.mmdb") - defer models.CloseDBs() + geoSvc, _ = NewGeo(context.Background(), "../test/GeoIP2-City-Test.mmdb", "../test/GeoLite2-ASN-Test.mmdb") os.Exit(m.Run()) } func TestCityLookup(t *testing.T) { - ip := Geo{IP: net.ParseIP("error")} - c := ip.LookUpCity() + c := geoSvc.LookUpCity(net.ParseIP("error")) assert.Nil(t, c) - ip = Geo{IP: net.ParseIP("1.1.1.1")} - c = ip.LookUpCity() + c = geoSvc.LookUpCity(net.ParseIP("1.1.1.1")) assert.NotNil(t, c) } func TestASNLookup(t *testing.T) { - ip := Geo{IP: net.ParseIP("error")} - a := ip.LookUpASN() + a := geoSvc.LookUpASN(net.ParseIP("error")) assert.Nil(t, a) - ip = Geo{IP: net.ParseIP("1.1.1.1")} - a = ip.LookUpASN() + a = geoSvc.LookUpASN(net.ParseIP("1.1.1.1")) assert.NotNil(t, a) }