45 Commits
1.0.9 ... 2.2.0

Author SHA1 Message Date
f8e27bef56 Add endopoint to check is a given port is open on the client (#22) 2023-12-31 12:52:08 +01:00
2bbeeb34c5 Bump github.com/containerd/containerd from 1.7.7 to 1.7.11 (#20)
Bumps [github.com/containerd/containerd](https://github.com/containerd/containerd) from 1.7.7 to 1.7.11.
- [Release notes](https://github.com/containerd/containerd/releases)
- [Changelog](https://github.com/containerd/containerd/blob/main/RELEASES.md)
- [Commits](https://github.com/containerd/containerd/compare/v1.7.7...v1.7.11)

---
updated-dependencies:
- dependency-name: github.com/containerd/containerd
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-20 20:08:35 +01:00
0090b794ee Bump golang.org/x/crypto from 0.14.0 to 0.17.0 (#19)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.14.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.14.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-19 09:10:37 +01:00
93f561d6ef Bump github.com/docker/docker from 20.10.24+incompatible to 24.0.7+incompatible (#18)
* Bump github.com/docker/docker

Bumps [github.com/docker/docker](https://github.com/docker/docker) from 20.10.24+incompatible to 24.0.7+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v20.10.24...v24.0.7)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update dependencies

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Daniel Carrillo <daniel.carrillo@gmail.com>
2023-10-30 19:17:40 +01:00
9da6d2fec5 Bump google.golang.org/grpc from 1.53.0 to 1.56.3 (#17)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.53.0 to 1.56.3.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.53.0...v1.56.3)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-26 09:25:00 +02:00
8e3d731719 [ci] Remove unused lines from codeql-analysis.yml 2023-10-12 12:52:44 +02:00
d5b244dc5f Bump go version to 1.21.3 (it fix HTTP/2 Stream Resets issue) 2023-10-12 12:14:49 +02:00
d767afd658 Update dependencies 2023-10-11 19:39:33 +02:00
f4fd79737e Bump golang version to 1.21 2023-08-25 18:34:21 +02:00
2ab6b29ed5 Update dependencies 2023-08-12 18:38:01 +02:00
55e6cd4816 Update test to latest quic version 2023-07-22 13:20:17 +02:00
a490d5f10e Update dependencies 2023-07-22 13:14:33 +02:00
994a12da5a Bump google.golang.org/grpc from 1.48.0 to 1.53.0 (#15)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.48.0 to 1.53.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.48.0...v1.53.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-06 08:07:54 +02:00
91deff4a14 chore: bump Go action to v4 (#13) 2023-06-02 17:23:48 +02:00
81c3a4fbb0 Bump github.com/gin-gonic/gin from 1.9.0 to 1.9.1 (#14)
Bumps [github.com/gin-gonic/gin](https://github.com/gin-gonic/gin) from 1.9.0 to 1.9.1.
- [Release notes](https://github.com/gin-gonic/gin/releases)
- [Changelog](https://github.com/gin-gonic/gin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gin-gonic/gin/compare/v1.9.0...v1.9.1)

---
updated-dependencies:
- dependency-name: github.com/gin-gonic/gin
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-02 17:13:53 +02:00
5b85eef7eb Bump github.com/docker/distribution (#12)
Bumps [github.com/docker/distribution](https://github.com/docker/distribution) from 2.8.1+incompatible to 2.8.2+incompatible.
- [Release notes](https://github.com/docker/distribution/releases)
- [Commits](https://github.com/docker/distribution/compare/v2.8.1...v2.8.2)

---
updated-dependencies:
- dependency-name: github.com/docker/distribution
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-12 17:00:06 +02:00
c54cf5a456 Add upx compression to Dockerfile 2023-04-29 18:49:21 +02:00
7dfa0a2e6d Update dependencies 2023-04-27 18:21:27 +02:00
68ef680439 Bump github.com/docker/docker (#11)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 20.10.17+incompatible to 20.10.24+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v20.10.17...v20.10.24)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-05 19:50:22 +02:00
bd42f712ea Bump github.com/opencontainers/runc from 1.1.3 to 1.1.5 (#10)
Bumps [github.com/opencontainers/runc](https://github.com/opencontainers/runc) from 1.1.3 to 1.1.5.
- [Release notes](https://github.com/opencontainers/runc/releases)
- [Changelog](https://github.com/opencontainers/runc/blob/v1.1.5/CHANGELOG.md)
- [Commits](https://github.com/opencontainers/runc/compare/v1.1.3...v1.1.5)

---
updated-dependencies:
- dependency-name: github.com/opencontainers/runc
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-29 18:23:38 +02:00
0b31633309 Remove useless function 2023-03-21 20:19:11 +01:00
b5fa3be506 Move logs about stopping servers to the proper place 2023-03-20 19:27:17 +01:00
8783db018b Use pointers to proper server initializing handling 2023-03-20 17:43:06 +01:00
e60d1ae5b7 Initial server handling refactor (#9) 2023-03-20 16:36:55 +01:00
84a767ade0 chore: Fix README.md typos 2023-03-18 21:14:08 +01:00
19c72f94a5 Add experimental support for HTTP/3 (#8)
* Wait for service in integration tests instead of watching for a string

* Add http3 experimental support
2023-03-18 21:06:51 +01:00
de78dcdf52 Bump dependencies due to minor security issues 2023-03-16 19:59:39 +01:00
eb200ddd81 Fix minor linting issues 2023-03-16 19:59:04 +01:00
5c4ac4a3ee [skip ci] bump action-gh-release to v1 2023-03-10 20:03:32 +01:00
ee328892d6 Bump gin to v1.9.0 2023-03-10 19:30:03 +01:00
04d983d671 Remove redundant ifs 2023-03-10 19:27:10 +01:00
202518d547 Merge branch 'main' of github.com:dcarrillo/whatismyip 2023-02-16 19:27:07 +01:00
52d14fe78f Update dependencies 2023-02-16 19:26:53 +01:00
8aadc4fbb6 Bump golang to 1.20 2023-02-16 19:24:16 +01:00
b6391d8fd1 Bump github.com/containerd/containerd from 1.6.12 to 1.6.18 (#6)
Bumps [github.com/containerd/containerd](https://github.com/containerd/containerd) from 1.6.12 to 1.6.18.
2023-02-16 19:21:04 +01:00
35fac1bd57 chore: update linters and go action 2023-01-26 20:47:03 +01:00
b13a30c354 chore: disable go cache 2023-01-26 20:43:13 +01:00
5982683cdd chore: use cache in CI and bump checkout action to v3 2023-01-26 20:38:31 +01:00
1a986a029f Update gin to v1.8.2 (GO-2022-1144) and containerd (CVE-2022-27664) 2022-12-26 19:30:02 +01:00
ed0ddccab5 Update golang.org/x/net (CVE-2022-32149) 2022-11-05 13:37:05 +01:00
20ae50c115 Update golang.org/x/net (CVE-2022-27664) 2022-09-13 20:38:24 +02:00
9763ed0e29 Update to golang 1.19 2022-08-11 20:02:27 +02:00
88691a5149 Update README.md
Add CodeQL badge
2022-07-03 11:57:58 +02:00
6b7fc0bc6a Bump gin and testify to latest versions 2022-07-03 11:39:51 +02:00
c5a659ff64 Create codeql-analysis.yml 2022-07-03 11:29:23 +02:00
19 changed files with 847 additions and 1406 deletions

52
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@ -0,0 +1,52 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "main" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "main" ]
schedule:
- cron: '21 21 * * 0'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: install go
uses: actions/setup-go@v3
with:
go-version-file: go.mod
cache: true
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: go
- run: |
echo "Build"
make build
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

View File

@ -11,19 +11,19 @@ on:
jobs: jobs:
tests: tests:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
make: ["lint", "test"]
steps: steps:
- uses: actions/checkout@v2.4.0 - uses: actions/checkout@v3
- name: install go - name: install go
uses: actions/setup-go@v2 uses: actions/setup-go@v4
with: with:
go-version: "^1.18" go-version-file: go.mod
- name: Lint - name: ${{ matrix.make }}
run: make lint run: make ${{ matrix.make }}
- name: Tests
run: make test
deploy: deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -33,9 +33,14 @@ jobs:
matrix: matrix:
goosarch: [linux-amd64] goosarch: [linux-amd64]
steps: steps:
- uses: actions/checkout@v2.4.0 - uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
- name: install go
uses: actions/setup-go@v3
with:
go-version-file: go.mod
cache: true
- name: Set env - name: Set env
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
@ -56,7 +61,7 @@ jobs:
sha256sum whatismyip-$RELEASE_VERSION-${{matrix.goosarch}}.tar.gz > whatismyip-$RELEASE_VERSION-${{matrix.goosarch}}.tar.gz.sha256 sha256sum whatismyip-$RELEASE_VERSION-${{matrix.goosarch}}.tar.gz > whatismyip-$RELEASE_VERSION-${{matrix.goosarch}}.tar.gz.sha256
- name: Release - name: Release
uses: softprops/action-gh-release@v0.1.14 uses: softprops/action-gh-release@v1
with: with:
body_path: changelog.txt body_path: changelog.txt
files: | files: |

View File

@ -1,4 +1,4 @@
FROM golang:1.18-alpine as builder FROM golang:1.21-alpine as builder
ARG ARG_VERSION ARG ARG_VERSION
ENV VERSION $ARG_VERSION ENV VERSION $ARG_VERSION
@ -7,7 +7,8 @@ WORKDIR /app
COPY . . COPY . .
RUN apk add make git && make build VERSION=$VERSION RUN apk add make git upx && make build VERSION=$VERSION \
&& upx --best --lzma whatismyip
# Build final image # Build final image
FROM scratch FROM scratch

View File

@ -7,29 +7,24 @@ test: unit-test integration-test
.PHONY: unit-test .PHONY: unit-test
unit-test: unit-test:
go test -race -short -cover ./... go test -count=1 -race -short -cover ./...
.PHONY: integration-test .PHONY: integration-test
integration-test: integration-test:
go test ./integration-tests -v go test -count=1 -v ./integration-tests
.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.2; \ curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin; \
fi fi
@command $(GOPATH)/shadow > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ @command $(GOPATH)/shadow > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@v0.1.10; \ go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest; \
fi
@command $(GOPATH)/golines > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go install github.com/segmentio/golines@latest; \
fi fi
.PHONY: lint .PHONY: lint
lint: install-tools lint: install-tools
gofmt -l . && test -z $$(gofmt -l .) gofmt -l . && test -z $$(gofmt -l .)
golines -l . && test -z $$(golines -l .)
golangci-lint run golangci-lint run
shadow ./... shadow ./...

View File

@ -1,6 +1,7 @@
# What is my IP address # What is my IP address
[![CI](https://github.com/dcarrillo/whatismyip/workflows/CI/badge.svg)](https://github.com/dcarrillo/whatismyip/actions) [![CI](https://github.com/dcarrillo/whatismyip/workflows/CI/badge.svg)](https://github.com/dcarrillo/whatismyip/actions)
[![CodeQL](https://github.com/dcarrillo/whatismyip/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/dcarrillo/whatismyip/actions/workflows/codeql-analysis.yml)
[![Go Report Card](https://goreportcard.com/badge/github.com/dcarrillo/whatismyip)](https://goreportcard.com/report/github.com/dcarrillo/whatismyip) [![Go Report Card](https://goreportcard.com/badge/github.com/dcarrillo/whatismyip)](https://goreportcard.com/report/github.com/dcarrillo/whatismyip)
[![GitHub release](https://img.shields.io/github/release/dcarrillo/whatismyip.svg)](https://github.com/dcarrillo/whatismyip/releases/) [![GitHub release](https://img.shields.io/github/release/dcarrillo/whatismyip.svg)](https://github.com/dcarrillo/whatismyip/releases/)
[![License Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](./LICENSE) [![License Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](./LICENSE)
@ -13,13 +14,15 @@
- [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 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) - [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)
- [From Docker Hub](#from-docker-hub) - [From Docker Hub](#from-docker-hub)
Just another "what is my IP address" service, including geolocation and headers information, written in go with high performance in mind, it uses [gin](https://github.com/gin-gonic/gin) which uses [httprouter](https://github.com/julienschmidt/httprouter) a lightweight high performance HTTP multiplexer. 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,
it uses [gin](https://github.com/gin-gonic/gin) which uses [httprouter](https://github.com/julienschmidt/httprouter) a lightweight high performance HTTP multiplexer.
Take a look at [ifconfig.es](https://ifconfig.es) a live site using `whatismyip` Take a look at [ifconfig.es](https://ifconfig.es) a live site using `whatismyip`
@ -36,11 +39,13 @@ curl -6 ifconfig.es
## Features ## Features
- TLS and HTTP/2. - TLS and HTTP/2.
- Experimental HTTP/3 support. HTTP/3 requires a TLS server running (`-tls-bind`), 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.
- 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. - 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.
- Checking TCP open ports.
- High performance. - High performance.
- Self-contained server what can reload GeoLite2 databases and/or SSL certificates without stop/start. The `hup` signal is honored. - Self-contained server that can reload GeoLite2 databases and/or SSL certificates without stop/start. The `hup` signal is honored.
- HTML templates for the landing page. - HTML templates for the landing page.
- Text plain and JSON output. - Text plain and JSON output.
@ -63,10 +68,11 @@ curl -6 ifconfig.es
- https://ifconfig.es/all - https://ifconfig.es/all
- https://ifconfig.es/headers - https://ifconfig.es/headers
- https://ifconfig.es/<header_name> - https://ifconfig.es/<header_name>
- https://ifconfig.es/scan/tcp/<port_number>
## Build ## Build
Golang >= 1.17 is required. Previous versions may work. Golang >= 1.19 is required.
`make build` `make build`
@ -76,6 +82,8 @@ Golang >= 1.17 is required. Previous versions may work.
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-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-secure-headers -enable-secure-headers
Add sane security-related headers to every response Add sane security-related headers to every response
-geoip2-asn string -geoip2-asn string
@ -113,6 +121,13 @@ 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 an HTTP/3 server
```bash
./whatismyip -geoip2-city ./test/GeoIP2-City-Test.mmdb -geoip2-asn ./test/GeoLite2-ASN-Test.mmdb \
-bind "" -tls-bind :8081 -tls-crt ./test/server.pem -tls-key ./test/server.key -enable-http3
```
### 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
```bash ```bash
@ -122,11 +137,11 @@ Usage of whatismyip:
## Download ## Download
Download latest version from https://github.com/dcarrillo/whatismyip/releases Download the latest version from https://github.com/dcarrillo/whatismyip/releases
## Docker ## Docker
An ultra-light (~10MB) image is available at [docker hub](https://hub.docker.com/r/dcarrillo/whatismyip). An ultra-light (~4MB) image is available on [docker hub](https://hub.docker.com/r/dcarrillo/whatismyip). Since version `2.1.2`, the binary is compressed using [upx](https://github.com/upx/upx).
### Run a container locally using test databases ### Run a container locally using test databases

View File

@ -2,142 +2,45 @@ package main
import ( import (
"context" "context"
"errors"
"flag" "flag"
"fmt" "fmt"
"log"
"net/http"
"os" "os"
"os/signal"
"syscall"
"github.com/dcarrillo/whatismyip/internal/httputils" "github.com/dcarrillo/whatismyip/internal/httputils"
"github.com/dcarrillo/whatismyip/internal/setting" "github.com/dcarrillo/whatismyip/internal/setting"
"github.com/dcarrillo/whatismyip/server"
"github.com/gin-contrib/secure" "github.com/gin-contrib/secure"
"github.com/dcarrillo/whatismyip/models" "github.com/dcarrillo/whatismyip/models"
"github.com/dcarrillo/whatismyip/router" "github.com/dcarrillo/whatismyip/router"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
var (
tcpServer *http.Server
tlsServer *http.Server
engine *gin.Engine
)
func main() { func main() {
o, err := setting.Setup(os.Args[1:]) o, err := setting.Setup(os.Args[1:])
if err == flag.ErrHelp || err == setting.ErrVersion { if err == flag.ErrHelp || err == setting.ErrVersion {
fmt.Print(o) fmt.Print(o)
os.Exit(0) os.Exit(0)
} else if err != nil { } else if err != nil {
fmt.Print(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
models.Setup(setting.App.GeodbPath.City, setting.App.GeodbPath.ASN) models.Setup(setting.App.GeodbPath.City, setting.App.GeodbPath.ASN)
setupEngine() engine := setupEngine()
router.SetupTemplate(engine) router.SetupTemplate(engine)
router.Setup(engine) router.Setup(engine)
if setting.App.BindAddress != "" { whatismyip := server.Setup(context.Background(), engine.Handler())
runTCPServer() whatismyip.Run()
}
if setting.App.TLSAddress != "" {
runTLSServer()
}
runHandler()
} }
func runHandler() { func setupEngine() *gin.Engine {
signalChan := make(chan os.Signal, 3)
signal.Notify(signalChan, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
ctx := context.Background()
var s os.Signal
for {
s = <-signalChan
if s == syscall.SIGHUP {
models.CloseDBs()
models.Setup(setting.App.GeodbPath.City, setting.App.GeodbPath.ASN)
router.SetupTemplate(engine)
if setting.App.BindAddress != "" {
if err := tcpServer.Shutdown(ctx); err != nil {
log.Printf("TCP server forced to shutdown: %s", err)
}
runTCPServer()
}
if setting.App.TLSAddress != "" {
if err := tlsServer.Shutdown(ctx); err != nil {
log.Printf("TLS server forced to shutdown: %s", err)
}
runTLSServer()
}
} else {
log.Printf("Shutting down...")
if setting.App.BindAddress != "" {
if err := tcpServer.Shutdown(ctx); err != nil {
log.Printf("TCP server forced to shutdown: %s", err)
}
}
if setting.App.TLSAddress != "" {
if err := tlsServer.Shutdown(ctx); err != nil {
log.Printf("TLS server forced to shutdown: %s", err)
}
}
models.CloseDBs()
break
}
}
}
func runTCPServer() {
tcpServer = &http.Server{
Addr: setting.App.BindAddress,
Handler: engine,
ReadTimeout: setting.App.Server.ReadTimeout,
WriteTimeout: setting.App.Server.WriteTimeout,
}
go func() {
log.Printf("Starting TCP server listening on %s", setting.App.BindAddress)
if err := tcpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatal(err)
}
log.Printf("Stopping TCP server...")
}()
}
func runTLSServer() {
tlsServer = &http.Server{
Addr: setting.App.TLSAddress,
Handler: engine,
ReadTimeout: setting.App.Server.ReadTimeout,
WriteTimeout: setting.App.Server.WriteTimeout,
}
go func() {
log.Printf("Starting TLS server listening on %s", setting.App.TLSAddress)
if err := tlsServer.ListenAndServeTLS(setting.App.TLSCrtPath, setting.App.TLSKeyPath); err != nil &&
!errors.Is(err, http.ErrServerClosed) {
log.Fatal(err)
}
log.Printf("Stopping TLS server...")
}()
}
func setupEngine() {
gin.DisableConsoleColor() gin.DisableConsoleColor()
if os.Getenv(gin.EnvGinMode) == "" { if os.Getenv(gin.EnvGinMode) == "" {
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
} }
engine = gin.New() engine := gin.New()
engine.Use(gin.LoggerWithFormatter(httputils.GetLogFormatter)) engine.Use(gin.LoggerWithFormatter(httputils.GetLogFormatter))
engine.Use(gin.Recovery()) engine.Use(gin.Recovery())
if setting.App.EnableSecureHeaders { if setting.App.EnableSecureHeaders {
@ -149,4 +52,6 @@ func setupEngine() {
} }
_ = engine.SetTrustedProxies(nil) _ = engine.SetTrustedProxies(nil)
engine.TrustedPlatform = setting.App.TrustedHeader engine.TrustedPlatform = setting.App.TrustedHeader
return engine
} }

136
go.mod
View File

@ -1,90 +1,90 @@
module github.com/dcarrillo/whatismyip module github.com/dcarrillo/whatismyip
go 1.18 go 1.21.3
require ( require (
github.com/gin-contrib/secure v0.0.1 github.com/gin-contrib/secure v0.0.1
github.com/gin-gonic/gin v1.8.0 github.com/gin-gonic/gin v1.9.1
github.com/oschwald/maxminddb-golang v1.9.0 github.com/oschwald/maxminddb-golang v1.12.0
github.com/stretchr/testify v1.7.1 github.com/quic-go/quic-go v0.40.1
github.com/testcontainers/testcontainers-go v0.13.0 github.com/stretchr/testify v1.8.4
github.com/testcontainers/testcontainers-go v0.27.0
golang.org/x/net v0.19.0
) )
require ( require (
dario.cat/mergo v1.0.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Microsoft/go-winio v0.5.2 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/Microsoft/hcsshim v0.9.3 // indirect github.com/Microsoft/hcsshim v0.11.4 // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/bytedance/sonic v1.10.2 // indirect
github.com/checkpoint-restore/go-criu/v5 v5.3.0 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cilium/ebpf v0.7.0 // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/containerd/cgroups v1.0.4 // indirect github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/containerd/console v1.0.3 // indirect github.com/containerd/containerd v1.7.11 // indirect
github.com/containerd/containerd v1.6.4 // indirect github.com/containerd/log v0.1.0 // indirect
github.com/containerd/continuity v0.2.2 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect
github.com/containerd/fifo v1.0.0 // indirect
github.com/containerd/ttrpc v1.1.0 // indirect
github.com/containerd/typeurl v1.0.2 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/docker v20.10.16+incompatible // indirect github.com/docker/docker v24.0.7+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/docker/go-units v0.5.0 // indirect
github.com/docker/go-units v0.4.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/validator/v10 v10.11.0 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/goccy/go-json v0.9.7 // indirect github.com/go-playground/validator/v10 v10.16.0 // indirect
github.com/godbus/dbus/v5 v5.0.6 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gogo/googleapis v1.4.0 // indirect github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42 // indirect
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.11.13 // indirect github.com/klauspost/compress v1.16.5 // indirect
github.com/leodido/go-urn v1.2.1 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/magiconair/properties v1.8.6 // indirect github.com/kr/pretty v0.3.1 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect github.com/leodido/go-urn v1.2.4 // indirect
github.com/moby/locker v1.0.1 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/moby/sys/mount v0.3.2 // indirect github.com/magiconair/properties v1.8.7 // indirect
github.com/moby/sys/mountinfo v0.6.1 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/moby/sys/signal v0.6.0 // indirect github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect github.com/moby/sys/sequential v0.5.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
github.com/mrunalp/fileutils v0.5.0 // indirect github.com/onsi/ginkgo/v2 v2.13.2 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
github.com/opencontainers/runc v1.1.2 // indirect github.com/opencontainers/runc v1.1.9 // indirect
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect
github.com/opencontainers/selinux v1.10.1 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.0.1 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921 // indirect github.com/quic-go/qpack v0.4.0 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/stretchr/objx v0.2.0 // indirect github.com/shirou/gopsutil/v3 v3.23.11 // indirect
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect github.com/sirupsen/logrus v1.9.3 // indirect
github.com/urfave/cli v1.22.2 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5 // indirect github.com/tklauser/numcpus v0.6.1 // indirect
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
go.opencensus.io v0.23.0 // indirect github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect
golang.org/x/net v0.0.0-20220531201128-c960675eff93 // indirect go.uber.org/mock v0.4.0 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/arch v0.6.0 // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect golang.org/x/crypto v0.17.0 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/exp v0.0.0-20231226003508-02704c960a9b // indirect
google.golang.org/genproto v0.0.0-20220602131408-e326c6e8e9c8 // indirect golang.org/x/mod v0.14.0 // indirect
google.golang.org/grpc v1.47.0 // indirect golang.org/x/sys v0.15.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect golang.org/x/text v0.14.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect golang.org/x/tools v0.16.1 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
google.golang.org/grpc v1.59.0 // indirect
google.golang.org/protobuf v1.32.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
gotest.tools/v3 v3.5.1 // indirect
) )

1320
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -4,14 +4,17 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"io/ioutil" "fmt"
"log" "io"
"net/http" "net/http"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings"
"testing" "testing"
"github.com/dcarrillo/whatismyip/router" "github.com/dcarrillo/whatismyip/router"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait" "github.com/testcontainers/testcontainers-go/wait"
@ -27,70 +30,178 @@ func buildContainer() testcontainers.ContainerRequest {
Dockerfile: "Dockerfile", Dockerfile: "Dockerfile",
}, },
Cmd: []string{ Cmd: []string{
"-geoip2-city", "/tmp/GeoIP2-City-Test.mmdb", "-geoip2-city", "/GeoIP2-City-Test.mmdb",
"-geoip2-asn", "/tmp/GeoLite2-ASN-Test.mmdb", "-geoip2-asn", "/GeoLite2-ASN-Test.mmdb",
"-bind", ":8000", "-bind", ":8000",
"-tls-bind", ":8001", "-tls-bind", ":8001",
"-tls-crt", "/tmp/server.pem", "-tls-crt", "/server.pem",
"-tls-key", "/tmp/server.key", "-tls-key", "/server.key",
"-trusted-header", "X-Real-IP", "-trusted-header", "X-Real-IP",
"-enable-secure-headers", "-enable-secure-headers",
"-enable-http3",
},
ExposedPorts: []string{"8000:8000", "8001:8001", "8001:8001/udp"},
WaitingFor: wait.ForHTTP("/geo").
WithTLS(true, &tls.Config{InsecureSkipVerify: true}).
WithPort("8001"),
Files: []testcontainers.ContainerFile{
{
HostFilePath: filepath.Join(dir, "/../test/GeoIP2-City-Test.mmdb"),
ContainerFilePath: "/GeoIP2-City-Test.mmdb",
FileMode: 0644,
},
{
HostFilePath: filepath.Join(dir, "/../test/GeoLite2-ASN-Test.mmdb"),
ContainerFilePath: "/GeoLite2-ASN-Test.mmdb",
FileMode: 0644,
},
{
HostFilePath: filepath.Join(dir, "/../test/server.pem"),
ContainerFilePath: "/server.pem",
FileMode: 0644,
},
{
HostFilePath: filepath.Join(dir, "/../test/server.key"),
ContainerFilePath: "/server.key",
FileMode: 0644,
},
}, },
ExposedPorts: []string{"8000:8000", "8001:8001"},
WaitingFor: wait.ForLog("Starting TLS server listening on :8001"),
Mounts: testcontainers.Mounts(
testcontainers.BindMount(
filepath.Join(dir, "/../test/GeoIP2-City-Test.mmdb"),
"/tmp/GeoIP2-City-Test.mmdb",
),
testcontainers.BindMount(
filepath.Join(dir, "/../test/GeoLite2-ASN-Test.mmdb"),
"/tmp/GeoLite2-ASN-Test.mmdb",
),
testcontainers.BindMount(filepath.Join(dir, "/../test/server.pem"), "/tmp/server.pem"),
testcontainers.BindMount(filepath.Join(dir, "/../test/server.key"), "/tmp/server.key"),
),
} }
return req return req
} }
func initContainer(t assert.TestingT, request testcontainers.ContainerRequest) func() {
ctx := context.Background()
container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: request,
Started: true,
})
assert.NoError(t, err)
return func() {
assert.NoError(t, container.Terminate(ctx))
}
}
func TestContainerIntegration(t *testing.T) { func TestContainerIntegration(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("Skiping integration tests") t.Skip("Skiping integration tests")
} }
ctx := context.Background() t.Cleanup(initContainer(t, buildContainer()))
container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: buildContainer(),
Started: true,
})
if err != nil {
log.Fatal(err)
}
defer func() {
err := container.Terminate(ctx)
if err != nil {
log.Fatal(err)
}
}()
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
for _, url := range []string{"http://localhost:8000", "https://localhost:8001"} { tests := []struct {
client := &http.Client{} name string
req, _ := http.NewRequest("GET", url, nil) url string
req.Header.Set("Accept", "application/json") quic bool
resp, _ := client.Do(req) }{
assert.Equal(t, 200, resp.StatusCode) {
name: "RequestOverHTTP",
body, err := ioutil.ReadAll(resp.Body) url: "http://localhost:8000",
if err != nil { quic: false,
log.Fatal(err) },
{
name: "RequestOverHTTPs",
url: "https://localhost:8001",
quic: false,
},
{
name: "RequestOverUDPWithQuic",
url: "https://localhost:8001",
quic: true,
},
} }
testsPortScan := []struct {
name string
port int
want bool
}{
{
name: "RequestOpenPortScan",
port: 8000,
want: true,
},
{
name: "RequestClosedPortScan",
port: 65533,
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req, err := http.NewRequest("GET", tt.url, nil)
assert.NoError(t, err)
req.Header.Set("Accept", "application/json")
var resp *http.Response
var body []byte
if tt.quic {
resp, body, err = doQuicRequest(req)
} else {
client := &http.Client{}
resp, _ = client.Do(req)
body, err = io.ReadAll(resp.Body)
if strings.Contains(tt.url, "https://") {
assert.Equal(t, `h3=":8001"; ma=2592000`, resp.Header.Get("Alt-Svc"))
}
}
assert.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode)
assert.NoError(t, json.Unmarshal(body, &router.JSONResponse{})) assert.NoError(t, json.Unmarshal(body, &router.JSONResponse{}))
assert.Equal(t, "DENY", resp.Header.Get("X-Frame-Options")) assert.Equal(t, "DENY", resp.Header.Get("X-Frame-Options"))
assert.Equal(t, "nosniff", resp.Header.Get("X-Content-Type-Options")) assert.Equal(t, "nosniff", resp.Header.Get("X-Content-Type-Options"))
assert.Equal(t, "1; mode=block", resp.Header.Get("X-Xss-Protection")) assert.Equal(t, "1; mode=block", resp.Header.Get("X-Xss-Protection"))
})
}
for _, tt := range testsPortScan {
t.Run(tt.name, func(t *testing.T) {
req, err := http.NewRequest("GET", fmt.Sprintf("http://localhost:8000/scan/tcp/%d", tt.port), nil)
assert.NoError(t, err)
req.Header.Set("Accept", "application/json")
req.Header.Set("X-Real-IP", "127.0.0.1")
client := &http.Client{}
resp, err := client.Do(req)
assert.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode)
body, err := io.ReadAll(resp.Body)
assert.NoError(t, err)
j := router.JSONScanResponse{}
assert.NoError(t, json.Unmarshal(body, &j))
assert.Equal(t, tt.want, j.Reachable)
})
} }
} }
func doQuicRequest(req *http.Request) (*http.Response, []byte, error) {
roundTripper := &http3.RoundTripper{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
QuicConfig: &quic.Config{},
}
defer roundTripper.Close()
client := &http.Client{
Transport: roundTripper,
}
resp, err := client.Do(req)
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
return resp, body, nil
}

View File

@ -29,6 +29,7 @@ type settings struct {
TrustedHeader string TrustedHeader string
TrustedPortHeader string TrustedPortHeader string
EnableSecureHeaders bool EnableSecureHeaders bool
EnableHTTP3 bool
Server serverSettings Server serverSettings
version bool version bool
} }
@ -89,6 +90,12 @@ func Setup(args []string) (output string, err error) {
false, false,
"Add sane security-related headers to every response", "Add sane security-related headers to every response",
) )
flags.BoolVar(
&App.EnableHTTP3,
"enable-http3",
false,
"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.",
)
err = flags.Parse(args) err = flags.Parse(args)
if err != nil { if err != nil {
@ -100,24 +107,28 @@ func Setup(args []string) (output string, err error) {
} }
if App.TrustedPortHeader != "" && App.TrustedHeader == "" { if App.TrustedPortHeader != "" && App.TrustedHeader == "" {
return "", fmt.Errorf("truster-header is mandatory when truster-port-header is set\n") return "", fmt.Errorf("truster-header is mandatory when truster-port-header is set")
} }
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\n") return "", fmt.Errorf("geoip2-city and geoip2-asn parameters are mandatory")
} }
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\n") return "", fmt.Errorf("in order to use TLS, the -tls-crt and -tls-key flags are mandatory")
}
if App.EnableHTTP3 && App.TLSAddress == "" {
return "", fmt.Errorf("in order to use HTTP3, the -tls-bind is mandatory")
} }
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\n", App.TemplatePath) return "", fmt.Errorf("%s no such file or directory", App.TemplatePath)
} }
if info.IsDir() { if info.IsDir() {
return "", fmt.Errorf("%s must be a file\n", App.TemplatePath) return "", fmt.Errorf("%s must be a file", App.TemplatePath)
} }
} }

View File

@ -41,6 +41,11 @@ func TestParseMandatoryFlags(t *testing.T) {
"-tls-key", "/key-path", "-tls-key", "/key-path",
}, },
}, },
{
[]string{
"-geoip2-city", "/city-path", "-geoip2-asn", "/asn-path", "-enable-http3",
},
},
{ {
[]string{ []string{
"-geoip2-city", "/city-path", "-geoip2-asn", "/asn-path", "-bind", ":8000", "-geoip2-city", "/city-path", "-geoip2-asn", "/asn-path", "-bind", ":8000",

View File

@ -68,18 +68,10 @@ func CloseDBs() {
// LookUp an IP and get city data // LookUp an IP and get city data
func (record *GeoRecord) LookUp(ip net.IP) error { func (record *GeoRecord) LookUp(ip net.IP) error {
if err := db.city.Lookup(ip, record); err != nil { return db.city.Lookup(ip, record)
return err
}
return nil
} }
// LookUp an IP and get ASN data // LookUp an IP and get ASN data
func (record *ASNRecord) LookUp(ip net.IP) error { func (record *ASNRecord) LookUp(ip net.IP) error {
if err := db.asn.Lookup(ip, record); err != nil { return db.asn.Lookup(ip, record)
return err
}
return nil
} }

54
router/port_scanner.go Normal file
View File

@ -0,0 +1,54 @@
package router
import (
"fmt"
"net"
"net/http"
"strconv"
"github.com/dcarrillo/whatismyip/service"
"github.com/gin-gonic/gin"
)
type JSONScanResponse struct {
IP string `json:"ip"`
Port int `json:"port"`
Reachable bool `json:"reachable"`
Reason string `json:"reason"`
}
func scanTCPPort(ctx *gin.Context) {
port, err := strconv.Atoi(ctx.Params.ByName("port"))
if err == nil && (port < 1 || port > 65535) {
err = fmt.Errorf("%d is not a valid port number", port)
}
if err != nil {
ctx.JSON(http.StatusBadRequest, JSONScanResponse{
Reason: err.Error(),
})
return
}
add := net.TCPAddr{
IP: net.ParseIP(ctx.ClientIP()),
Port: port,
}
scan := service.PortScanner{
Address: &add,
}
isOpen, err := scan.IsPortOpen()
reason := ""
if err != nil {
reason = err.Error()
}
response := JSONScanResponse{
IP: ctx.ClientIP(),
Port: port,
Reachable: isOpen,
Reason: reason,
}
ctx.JSON(http.StatusOK, response)
}

View File

@ -22,6 +22,7 @@ func SetupTemplate(r *gin.Engine) {
// Setup defines the endpoints // Setup defines the endpoints
func Setup(r *gin.Engine) { func Setup(r *gin.Engine) {
r.GET("/", getRoot) r.GET("/", getRoot)
r.GET("/scan/tcp/:port", scanTCPPort)
r.GET("/client-port", getClientPortAsString) r.GET("/client-port", getClientPortAsString)
r.GET("/geo", getGeoAsString) r.GET("/geo", getGeoAsString)
r.GET("/geo/:field", getGeoAsString) r.GET("/geo/:field", getGeoAsString)

54
server/quic_server.go Normal file
View File

@ -0,0 +1,54 @@
package server
import (
"context"
"log"
"net/http"
"github.com/dcarrillo/whatismyip/internal/setting"
"github.com/quic-go/quic-go/http3"
)
type QuicServer struct {
server *http3.Server
tlsServer *TLSServer
ctx context.Context
}
func NewQuicServer(ctx context.Context, tlsServer *TLSServer) *QuicServer {
return &QuicServer{
tlsServer: tlsServer,
ctx: ctx,
}
}
func (q *QuicServer) Start() {
q.server = &http3.Server{
Addr: setting.App.TLSAddress,
Handler: q.tlsServer.server.Handler,
}
parentHandler := q.tlsServer.server.Handler
q.tlsServer.server.Handler = http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if err := q.server.SetQuicHeaders(rw.Header()); err != nil {
log.Fatal(err)
}
parentHandler.ServeHTTP(rw, req)
})
log.Printf("Starting QUIC server listening on %s (udp)", setting.App.TLSAddress)
go func() {
if err := q.server.ListenAndServeTLS(setting.App.TLSCrtPath, setting.App.TLSKeyPath); err != nil &&
err.Error() != "quic: Server closed" {
log.Fatal(err)
}
}()
}
func (q *QuicServer) Stop() {
log.Printf("Stopping QUIC server...")
if err := q.server.Close(); err != nil {
log.Printf("QUIC server forced to shutdown")
}
}

96
server/server.go Normal file
View File

@ -0,0 +1,96 @@
package server
import (
"log"
"net/http"
"os"
"os/signal"
"syscall"
"github.com/dcarrillo/whatismyip/internal/setting"
"github.com/dcarrillo/whatismyip/models"
"golang.org/x/net/context"
)
type Server interface {
Start()
Stop()
}
type Factory struct {
tcpServer *TCPServer
tlsServer *TLSServer
quicServer *QuicServer
}
func Setup(ctx context.Context, handler http.Handler) *Factory {
var tcpServer *TCPServer
var tlsServer *TLSServer
var quicServer *QuicServer
if setting.App.BindAddress != "" {
tcpServer = NewTCPServer(ctx, &handler)
}
if setting.App.TLSAddress != "" {
tlsServer = NewTLSServer(ctx, &handler)
if setting.App.EnableHTTP3 {
quicServer = NewQuicServer(ctx, tlsServer)
}
}
return &Factory{
tcpServer: tcpServer,
tlsServer: tlsServer,
quicServer: quicServer,
}
}
func (f *Factory) Run() {
f.start()
signalChan := make(chan os.Signal, 3)
signal.Notify(signalChan, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
var s os.Signal
for {
s = <-signalChan
if s == syscall.SIGHUP {
f.stop()
models.CloseDBs()
models.Setup(setting.App.GeodbPath.City, setting.App.GeodbPath.ASN)
f.start()
} else {
log.Printf("Shutting down...")
f.stop()
models.CloseDBs()
break
}
}
}
func (f *Factory) start() {
if f.tcpServer != nil {
f.tcpServer.Start()
}
if f.tlsServer != nil {
f.tlsServer.Start()
if f.quicServer != nil {
f.quicServer.Start()
}
}
}
func (f *Factory) stop() {
if f.tcpServer != nil {
f.tcpServer.Stop()
}
if f.tlsServer != nil {
if f.quicServer != nil {
f.quicServer.Stop()
}
f.tlsServer.Stop()
}
}

46
server/tcp_server.go Normal file
View File

@ -0,0 +1,46 @@
package server
import (
"context"
"errors"
"log"
"net/http"
"github.com/dcarrillo/whatismyip/internal/setting"
)
type TCPServer struct {
server *http.Server
handler *http.Handler
ctx context.Context
}
func NewTCPServer(ctx context.Context, handler *http.Handler) *TCPServer {
return &TCPServer{
handler: handler,
ctx: ctx,
}
}
func (t *TCPServer) Start() {
t.server = &http.Server{
Addr: setting.App.BindAddress,
Handler: *t.handler,
ReadTimeout: setting.App.Server.ReadTimeout,
WriteTimeout: setting.App.Server.WriteTimeout,
}
log.Printf("Starting TCP server listening on %s", setting.App.BindAddress)
go func() {
if err := t.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatal(err)
}
}()
}
func (t *TCPServer) Stop() {
log.Printf("Stopping TCP server...")
if err := t.server.Shutdown(t.ctx); err != nil {
log.Printf("TCP server forced to shutdown: %s", err)
}
}

47
server/tls_server.go Normal file
View File

@ -0,0 +1,47 @@
package server
import (
"context"
"errors"
"log"
"net/http"
"github.com/dcarrillo/whatismyip/internal/setting"
)
type TLSServer struct {
server *http.Server
handler *http.Handler
ctx context.Context
}
func NewTLSServer(ctx context.Context, handler *http.Handler) *TLSServer {
return &TLSServer{
handler: handler,
ctx: ctx,
}
}
func (t *TLSServer) Start() {
t.server = &http.Server{
Addr: setting.App.TLSAddress,
Handler: *t.handler,
ReadTimeout: setting.App.Server.ReadTimeout,
WriteTimeout: setting.App.Server.WriteTimeout,
}
log.Printf("Starting TLS server listening on %s", setting.App.TLSAddress)
go func() {
if err := t.server.ListenAndServeTLS(setting.App.TLSCrtPath, setting.App.TLSKeyPath); err != nil &&
!errors.Is(err, http.ErrServerClosed) {
log.Fatal(err)
}
}()
}
func (t *TLSServer) Stop() {
log.Printf("Stopping TLS server...")
if err := t.server.Shutdown(t.ctx); err != nil {
log.Printf("TLS server forced to shutdown: %s", err)
}
}

21
service/port_scanner.go Normal file
View File

@ -0,0 +1,21 @@
package service
import (
"net"
"time"
)
type PortScanner struct {
Address net.Addr
}
func (p *PortScanner) IsPortOpen() (bool, error) {
conn, err := net.DialTimeout(p.Address.Network(), p.Address.String(), 3*time.Second)
if err != nil {
return false, err
}
if conn != nil {
defer conn.Close()
}
return true, nil
}