mirror of
https://github.com/dcarrillo/whatismyip.git
synced 2026-07-23 21:35:47 +00:00
refactor: dependency injection instead of package globals (#57)
* refactor: export Settings type, Setup returns value * refactor: resolver.Setup takes explicit Settings struct * refactor: GetHeadersWithoutTrustedHeaders takes explicit header params * refactor: server constructors take narrow config * refactor: Router struct with handler methods, remove geoSvc global * refactor: wire DI through main, remove setting.App references * refactor: remove App global, use returned Settings * refactor: update router tests for DI * chore: fix lint issues — rename ServerTimeouts to Timeouts, fix shadowed variables
This commit is contained in:
+49
-34
@@ -15,17 +15,17 @@ import (
|
||||
"github.com/dcarrillo/whatismyip/internal/metrics"
|
||||
"github.com/dcarrillo/whatismyip/internal/setting"
|
||||
"github.com/dcarrillo/whatismyip/resolver"
|
||||
"github.com/dcarrillo/whatismyip/router"
|
||||
"github.com/dcarrillo/whatismyip/server"
|
||||
"github.com/dcarrillo/whatismyip/service"
|
||||
"github.com/gin-contrib/secure"
|
||||
"github.com/patrickmn/go-cache"
|
||||
|
||||
"github.com/dcarrillo/whatismyip/router"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
o, err := setting.Setup(os.Args[1:])
|
||||
cfg, o, err := setting.Setup(os.Args[1:])
|
||||
if err != nil {
|
||||
if errors.Is(err, flag.ErrHelp) || errors.Is(err, setting.ErrVersion) {
|
||||
fmt.Print(o)
|
||||
@@ -35,33 +35,44 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
servers := []server.Server{}
|
||||
engine := setupEngine()
|
||||
|
||||
if setting.App.Resolver.Domain != "" {
|
||||
store := cache.New(1*time.Minute, 10*time.Minute)
|
||||
var dnsEngine *resolver.Resolver
|
||||
if dnsEngine, err = resolver.Setup(store); err != nil {
|
||||
log.Fatalf("Invalid resolver configuration: %s", err)
|
||||
}
|
||||
nameServer := server.NewDNSServer(context.Background(), dnsEngine.Handler())
|
||||
servers = append(servers, nameServer)
|
||||
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 != "" {
|
||||
if geoSvc, err = service.NewGeo(context.Background(), setting.App.GeodbPath.City, setting.App.GeodbPath.ASN); err != nil {
|
||||
if cfg.GeodbPath.City != "" || cfg.GeodbPath.ASN != "" {
|
||||
if geoSvc, err = service.NewGeo(context.Background(), cfg.GeodbPath.City, cfg.GeodbPath.ASN); err != nil {
|
||||
log.Fatalf("Failed to load geo databases: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
router.SetupTemplate(engine)
|
||||
router.Setup(engine, geoSvc)
|
||||
servers = slices.Concat(servers, setupHTTPServers(context.Background(), engine.Handler()))
|
||||
servers := []server.Server{}
|
||||
engine := setupEngine(cfg)
|
||||
|
||||
if setting.App.PrometheusAddress != "" {
|
||||
prometheusServer := server.NewPrometheusServer(context.Background())
|
||||
if cfg.Resolver.Domain != "" {
|
||||
store := cache.New(1*time.Minute, 10*time.Minute)
|
||||
var dnsEngine *resolver.Resolver
|
||||
if dnsEngine, err = resolver.Setup(store, resolver.Settings{
|
||||
Domain: cfg.Resolver.Domain,
|
||||
ResourceRecords: cfg.Resolver.ResourceRecords,
|
||||
RedirectPort: cfg.Resolver.RedirectPort,
|
||||
IPv4: cfg.Resolver.Ipv4,
|
||||
IPv6: cfg.Resolver.Ipv6,
|
||||
}); err != nil {
|
||||
log.Fatalf("Invalid resolver configuration: %s", err)
|
||||
}
|
||||
nameServer := server.NewDNSServer(context.Background(), dnsEngine.Handler())
|
||||
servers = append(servers, nameServer)
|
||||
engine.Use(router.GetDNSDiscoveryHandler(store, geoSvc, cfg.Resolver.Domain, cfg.Resolver.RedirectPort))
|
||||
}
|
||||
|
||||
rt := router.NewRouter(geoSvc, cfg.TrustedHeader, cfg.TrustedPortHeader, cfg.TemplatePath, cfg.DisableTCPScan)
|
||||
router.SetupTemplate(engine, cfg.TemplatePath)
|
||||
router.Setup(engine, rt)
|
||||
servers = slices.Concat(servers, setupHTTPServers(context.Background(), engine.Handler(), cfg))
|
||||
|
||||
if cfg.PrometheusAddress != "" {
|
||||
prometheusServer := server.NewPrometheusServer(context.Background(), cfg.PrometheusAddress,
|
||||
server.Timeouts{
|
||||
ReadTimeout: cfg.Server.ReadTimeout,
|
||||
WriteTimeout: cfg.Server.WriteTimeout,
|
||||
})
|
||||
servers = append(servers, prometheusServer)
|
||||
}
|
||||
|
||||
@@ -69,18 +80,18 @@ func main() {
|
||||
whatismyip.Run()
|
||||
}
|
||||
|
||||
func setupEngine() *gin.Engine {
|
||||
func setupEngine(cfg setting.Settings) *gin.Engine {
|
||||
gin.DisableConsoleColor()
|
||||
if os.Getenv(gin.EnvGinMode) == "" {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
engine := gin.New()
|
||||
engine.Use(gin.LoggerWithFormatter(httputils.GetLogFormatter), gin.Recovery())
|
||||
if setting.App.PrometheusAddress != "" {
|
||||
if cfg.PrometheusAddress != "" {
|
||||
metrics.Enable()
|
||||
engine.Use(metrics.GinMiddleware())
|
||||
}
|
||||
if setting.App.EnableSecureHeaders {
|
||||
if cfg.EnableSecureHeaders {
|
||||
engine.Use(secure.New(secure.Config{
|
||||
BrowserXssFilter: true,
|
||||
ContentTypeNosniff: true,
|
||||
@@ -88,24 +99,28 @@ func setupEngine() *gin.Engine {
|
||||
}))
|
||||
}
|
||||
_ = engine.SetTrustedProxies(nil)
|
||||
engine.TrustedPlatform = setting.App.TrustedHeader
|
||||
engine.TrustedPlatform = cfg.TrustedHeader
|
||||
|
||||
return engine
|
||||
}
|
||||
|
||||
func setupHTTPServers(ctx context.Context, handler http.Handler) []server.Server {
|
||||
func setupHTTPServers(ctx context.Context, handler http.Handler, cfg setting.Settings) []server.Server {
|
||||
var servers []server.Server
|
||||
timeouts := server.Timeouts{
|
||||
ReadTimeout: cfg.Server.ReadTimeout,
|
||||
WriteTimeout: cfg.Server.WriteTimeout,
|
||||
}
|
||||
|
||||
if setting.App.BindAddress != "" {
|
||||
tcpServer := server.NewTCPServer(ctx, handler)
|
||||
if cfg.BindAddress != "" {
|
||||
tcpServer := server.NewTCPServer(ctx, handler, cfg.BindAddress, timeouts)
|
||||
servers = append(servers, tcpServer)
|
||||
}
|
||||
|
||||
if setting.App.TLSAddress != "" {
|
||||
tlsServer := server.NewTLSServer(ctx, handler)
|
||||
if cfg.TLSAddress != "" {
|
||||
tlsServer := server.NewTLSServer(ctx, handler, cfg.TLSAddress, cfg.TLSCrtPath, cfg.TLSKeyPath, timeouts)
|
||||
servers = append(servers, tlsServer)
|
||||
if setting.App.EnableHTTP3 {
|
||||
quicServer := server.NewQuicServer(ctx, tlsServer)
|
||||
if cfg.EnableHTTP3 {
|
||||
quicServer := server.NewQuicServer(ctx, tlsServer, cfg.TLSAddress, cfg.TLSCrtPath, cfg.TLSKeyPath)
|
||||
servers = append(servers, quicServer)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/dcarrillo/whatismyip/internal/setting"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -30,10 +29,10 @@ func HeadersToSortedString(headers http.Header) string {
|
||||
}
|
||||
|
||||
// GetHeadersWithoutTrustedHeaders returns a copy of the request headers with the trusted headers removed
|
||||
func GetHeadersWithoutTrustedHeaders(ctx *gin.Context) http.Header {
|
||||
func GetHeadersWithoutTrustedHeaders(ctx *gin.Context, trustedHeader, trustedPortHeader string) http.Header {
|
||||
h := ctx.Request.Header.Clone()
|
||||
|
||||
for _, k := range []string{setting.App.TrustedHeader, setting.App.TrustedPortHeader} {
|
||||
for _, k := range []string{trustedHeader, trustedPortHeader} {
|
||||
delete(h, textproto.CanonicalMIMEHeaderKey(k))
|
||||
}
|
||||
|
||||
|
||||
+39
-42
@@ -29,7 +29,7 @@ type resolver struct {
|
||||
Ipv6 []string `yaml:"ipv6,omitempty"`
|
||||
}
|
||||
|
||||
type settings struct {
|
||||
type Settings struct {
|
||||
GeodbPath geodbConf
|
||||
TemplatePath string
|
||||
BindAddress string
|
||||
@@ -51,75 +51,72 @@ const defaultAddress = ":8080"
|
||||
|
||||
var ErrVersion = errors.New("setting: version requested")
|
||||
|
||||
var App = settings{
|
||||
// hard-coded for the time being
|
||||
Server: serverSettings{
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
},
|
||||
}
|
||||
|
||||
func Setup(args []string) (output string, err error) {
|
||||
func Setup(args []string) (cfg Settings, output string, err error) {
|
||||
flags := flag.NewFlagSet("whatismyip", flag.ContinueOnError)
|
||||
var buf bytes.Buffer
|
||||
var resolverConf string
|
||||
flags.SetOutput(&buf)
|
||||
|
||||
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")
|
||||
cfg.Server = serverSettings{
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
flags.StringVar(&cfg.GeodbPath.City, "geoip2-city", "", "Path to GeoIP2 city database. Enables geo information (--geoip2-asn becomes mandatory)")
|
||||
flags.StringVar(&cfg.GeodbPath.ASN, "geoip2-asn", "", "Path to GeoIP2 ASN database. Enables ASN information. (--geoip2-city becomes mandatory)")
|
||||
flags.StringVar(&cfg.TemplatePath, "template", "", "Path to the template file")
|
||||
flags.StringVar(
|
||||
&resolverConf,
|
||||
"resolver",
|
||||
"",
|
||||
"Path to the resolver configuration. It actually enables the resolver for DNS client discovery.")
|
||||
flags.StringVar(
|
||||
&App.BindAddress,
|
||||
&cfg.BindAddress,
|
||||
"bind",
|
||||
defaultAddress,
|
||||
"Listening address (see https://pkg.go.dev/net?#Listen)",
|
||||
)
|
||||
flags.StringVar(
|
||||
&App.TLSAddress,
|
||||
&cfg.TLSAddress,
|
||||
"tls-bind",
|
||||
"",
|
||||
"Listening address for TLS (see https://pkg.go.dev/net?#Listen)",
|
||||
)
|
||||
flags.StringVar(&App.TLSCrtPath, "tls-crt", "", "When using TLS, path to certificate file")
|
||||
flags.StringVar(&App.TLSKeyPath, "tls-key", "", "When using TLS, path to private key file")
|
||||
flags.StringVar(&cfg.TLSCrtPath, "tls-crt", "", "When using TLS, path to certificate file")
|
||||
flags.StringVar(&cfg.TLSKeyPath, "tls-key", "", "When using TLS, path to private key file")
|
||||
flags.StringVar(
|
||||
&App.PrometheusAddress,
|
||||
&cfg.PrometheusAddress,
|
||||
"metrics-bind",
|
||||
"",
|
||||
"Listening address for Prometheus metrics endpoint (see https://pkg.go.dev/net?#Listen). It enables the metrics available at the given address/port via the /metrics endpoint.",
|
||||
)
|
||||
flags.StringVar(
|
||||
&App.TrustedHeader,
|
||||
&cfg.TrustedHeader,
|
||||
"trusted-header",
|
||||
"",
|
||||
"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'",
|
||||
)
|
||||
flags.StringVar(
|
||||
&App.TrustedPortHeader,
|
||||
&cfg.TrustedPortHeader,
|
||||
"trusted-port-header",
|
||||
"",
|
||||
"Trusted request header for remote client port (e.g. X-Real-Port). When this parameter is set -trusted-header becomes mandatory",
|
||||
)
|
||||
flags.BoolVar(&App.version, "version", false, "Output version information and exit")
|
||||
flags.BoolVar(&cfg.version, "version", false, "Output version information and exit")
|
||||
flags.BoolVar(
|
||||
&App.EnableSecureHeaders,
|
||||
&cfg.EnableSecureHeaders,
|
||||
"enable-secure-headers",
|
||||
false,
|
||||
"Add sane security-related headers to every response",
|
||||
)
|
||||
flags.BoolVar(
|
||||
&App.EnableHTTP3,
|
||||
&cfg.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.",
|
||||
)
|
||||
flags.BoolVar(
|
||||
&App.DisableTCPScan,
|
||||
&cfg.DisableTCPScan,
|
||||
"disable-scan",
|
||||
false,
|
||||
"Disable TCP port scanning functionality",
|
||||
@@ -127,48 +124,48 @@ func Setup(args []string) (output string, err error) {
|
||||
|
||||
err = flags.Parse(args)
|
||||
if err != nil {
|
||||
return buf.String(), err
|
||||
return cfg, buf.String(), err
|
||||
}
|
||||
|
||||
if App.version {
|
||||
return fmt.Sprintf("whatismyip version %s", core.Version), ErrVersion
|
||||
if cfg.version {
|
||||
return cfg, fmt.Sprintf("whatismyip version %s", core.Version), ErrVersion
|
||||
}
|
||||
|
||||
if (App.GeodbPath.City != "" && App.GeodbPath.ASN == "") || (App.GeodbPath.City == "" && App.GeodbPath.ASN != "") {
|
||||
return "", errors.New("both --geoip2-city and --geoip2-asn are mandatory to enable geo information")
|
||||
if (cfg.GeodbPath.City != "" && cfg.GeodbPath.ASN == "") || (cfg.GeodbPath.City == "" && cfg.GeodbPath.ASN != "") {
|
||||
return cfg, "", errors.New("both --geoip2-city and --geoip2-asn are mandatory to enable geo information")
|
||||
}
|
||||
|
||||
if App.TrustedPortHeader != "" && App.TrustedHeader == "" {
|
||||
return "", errors.New("trusted-header is mandatory when trusted-port-header is set")
|
||||
if cfg.TrustedPortHeader != "" && cfg.TrustedHeader == "" {
|
||||
return cfg, "", errors.New("trusted-header is mandatory when trusted-port-header is set")
|
||||
}
|
||||
|
||||
if (App.TLSAddress != "") && (App.TLSCrtPath == "" || App.TLSKeyPath == "") {
|
||||
return "", errors.New("in order to use TLS, the -tls-crt and -tls-key flags are mandatory")
|
||||
if (cfg.TLSAddress != "") && (cfg.TLSCrtPath == "" || cfg.TLSKeyPath == "") {
|
||||
return cfg, "", errors.New("in order to use TLS, the -tls-crt and -tls-key flags are mandatory")
|
||||
}
|
||||
|
||||
if App.EnableHTTP3 && App.TLSAddress == "" {
|
||||
return "", errors.New("in order to use HTTP3, the -tls-bind is mandatory")
|
||||
if cfg.EnableHTTP3 && cfg.TLSAddress == "" {
|
||||
return cfg, "", errors.New("in order to use HTTP3, the -tls-bind is mandatory")
|
||||
}
|
||||
|
||||
if App.TemplatePath != "" {
|
||||
info, err := os.Stat(App.TemplatePath)
|
||||
if cfg.TemplatePath != "" {
|
||||
info, err := os.Stat(cfg.TemplatePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("template path: %w", err)
|
||||
return cfg, "", fmt.Errorf("template path: %w", err)
|
||||
}
|
||||
if info.IsDir() {
|
||||
return "", fmt.Errorf("%s must be a file", App.TemplatePath)
|
||||
return cfg, "", fmt.Errorf("%s must be a file", cfg.TemplatePath)
|
||||
}
|
||||
}
|
||||
|
||||
if resolverConf != "" {
|
||||
var err error
|
||||
App.Resolver, err = readYAML(resolverConf)
|
||||
cfg.Resolver, err = readYAML(resolverConf)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("reading resolver configuration: %w", err)
|
||||
return cfg, "", fmt.Errorf("reading resolver configuration: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
return cfg, buf.String(), nil
|
||||
}
|
||||
|
||||
func readYAML(path string) (resolver resolver, err error) {
|
||||
|
||||
@@ -55,7 +55,7 @@ func TestParseMandatoryFlags(t *testing.T) {
|
||||
|
||||
for _, tt := range mandatoryFlags {
|
||||
t.Run(strings.Join(tt.args, " "), func(t *testing.T) {
|
||||
_, err := Setup(tt.args)
|
||||
_, _, err := Setup(tt.args)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "mandatory")
|
||||
})
|
||||
@@ -65,11 +65,11 @@ func TestParseMandatoryFlags(t *testing.T) {
|
||||
func TestParseFlags(t *testing.T) {
|
||||
flags := []struct {
|
||||
args []string
|
||||
conf settings
|
||||
conf Settings
|
||||
}{
|
||||
{
|
||||
[]string{},
|
||||
settings{
|
||||
Settings{
|
||||
BindAddress: ":8080",
|
||||
Server: serverSettings{
|
||||
ReadTimeout: 10 * time.Second,
|
||||
@@ -79,7 +79,7 @@ func TestParseFlags(t *testing.T) {
|
||||
},
|
||||
{
|
||||
[]string{"-disable-scan"},
|
||||
settings{
|
||||
Settings{
|
||||
BindAddress: ":8080",
|
||||
Server: serverSettings{
|
||||
ReadTimeout: 10 * time.Second,
|
||||
@@ -90,7 +90,7 @@ func TestParseFlags(t *testing.T) {
|
||||
},
|
||||
{
|
||||
[]string{"-bind", ":8001", "-geoip2-city", "/city-path", "-geoip2-asn", "/asn-path"},
|
||||
settings{
|
||||
Settings{
|
||||
GeodbPath: geodbConf{
|
||||
City: "/city-path",
|
||||
ASN: "/asn-path",
|
||||
@@ -107,7 +107,7 @@ func TestParseFlags(t *testing.T) {
|
||||
"-geoip2-city", "/city-path", "-geoip2-asn", "/asn-path", "-tls-bind", ":9000",
|
||||
"-tls-crt", "/crt-path", "-tls-key", "/key-path",
|
||||
},
|
||||
settings{
|
||||
Settings{
|
||||
GeodbPath: geodbConf{
|
||||
City: "/city-path",
|
||||
ASN: "/asn-path",
|
||||
@@ -127,7 +127,7 @@ func TestParseFlags(t *testing.T) {
|
||||
"-geoip2-city", "/city-path", "-geoip2-asn", "/asn-path",
|
||||
"-trusted-header", "header", "-trusted-port-header", "port-header",
|
||||
},
|
||||
settings{
|
||||
Settings{
|
||||
GeodbPath: geodbConf{
|
||||
City: "/city-path",
|
||||
ASN: "/asn-path",
|
||||
@@ -146,7 +146,7 @@ func TestParseFlags(t *testing.T) {
|
||||
"-geoip2-city", "/city-path", "-geoip2-asn", "/asn-path",
|
||||
"-trusted-header", "header", "-enable-secure-headers",
|
||||
},
|
||||
settings{
|
||||
Settings{
|
||||
GeodbPath: geodbConf{
|
||||
City: "/city-path",
|
||||
ASN: "/asn-path",
|
||||
@@ -164,9 +164,9 @@ func TestParseFlags(t *testing.T) {
|
||||
|
||||
for _, tt := range flags {
|
||||
t.Run(strings.Join(tt.args, " "), func(t *testing.T) {
|
||||
_, err := Setup(tt.args)
|
||||
cfg, _, err := Setup(tt.args)
|
||||
require.Nil(t, err)
|
||||
assert.True(t, reflect.DeepEqual(App, tt.conf))
|
||||
assert.True(t, reflect.DeepEqual(cfg, tt.conf))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -176,7 +176,7 @@ func TestParseFlagsUsage(t *testing.T) {
|
||||
|
||||
for _, arg := range usageArgs {
|
||||
t.Run(arg, func(t *testing.T) {
|
||||
output, err := Setup([]string{arg})
|
||||
_, output, err := Setup([]string{arg})
|
||||
assert.ErrorIs(t, err, flag.ErrHelp)
|
||||
assert.Contains(t, output, "Usage of")
|
||||
})
|
||||
@@ -184,7 +184,7 @@ func TestParseFlagsUsage(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseFlagVersion(t *testing.T) {
|
||||
output, err := Setup([]string{"-version"})
|
||||
_, output, err := Setup([]string{"-version"})
|
||||
assert.ErrorIs(t, err, ErrVersion)
|
||||
assert.Contains(t, output, "whatismyip version")
|
||||
}
|
||||
@@ -209,7 +209,7 @@ func TestParseFlagTemplate(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := Setup(tc.flags)
|
||||
_, _, err := Setup(tc.flags)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tc.errMsg)
|
||||
})
|
||||
|
||||
+14
-7
@@ -7,12 +7,19 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/dcarrillo/whatismyip/internal/metrics"
|
||||
"github.com/dcarrillo/whatismyip/internal/setting"
|
||||
"github.com/dcarrillo/whatismyip/internal/validator/uuid"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
type Settings struct {
|
||||
Domain string
|
||||
ResourceRecords []string
|
||||
RedirectPort string
|
||||
IPv4 []string
|
||||
IPv6 []string
|
||||
}
|
||||
|
||||
type Resolver struct {
|
||||
handler *dns.ServeMux
|
||||
store *cache.Cache
|
||||
@@ -29,11 +36,11 @@ func ensureDotSuffix(s string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
func Setup(store *cache.Cache) (*Resolver, error) {
|
||||
domain := ensureDotSuffix(setting.App.Resolver.Domain)
|
||||
func Setup(store *cache.Cache, cfg Settings) (*Resolver, error) {
|
||||
domain := ensureDotSuffix(cfg.Domain)
|
||||
|
||||
rr := make([]dns.RR, 0, len(setting.App.Resolver.ResourceRecords))
|
||||
for _, res := range setting.App.Resolver.ResourceRecords {
|
||||
rr := make([]dns.RR, 0, len(cfg.ResourceRecords))
|
||||
for _, res := range cfg.ResourceRecords {
|
||||
record, err := dns.NewRR(domain + " " + res)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing resource record %q: %w", res, err)
|
||||
@@ -41,11 +48,11 @@ func Setup(store *cache.Cache) (*Resolver, error) {
|
||||
rr = append(rr, record)
|
||||
}
|
||||
|
||||
ipv4, err := parseIPs(setting.App.Resolver.Ipv4)
|
||||
ipv4, err := parseIPs(cfg.IPv4)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing ipv4 addresses: %w", err)
|
||||
}
|
||||
ipv6, err := parseIPs(setting.App.Resolver.Ipv6)
|
||||
ipv6, err := parseIPs(cfg.IPv6)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing ipv6 addresses: %w", err)
|
||||
}
|
||||
|
||||
+4
-3
@@ -7,6 +7,7 @@ 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"
|
||||
@@ -27,7 +28,7 @@ type dnsData struct {
|
||||
|
||||
// TODO
|
||||
// Implement a proper vhost manager instead of using a middleware
|
||||
func GetDNSDiscoveryHandler(store *cache.Cache, domain string, redirectPort string) gin.HandlerFunc {
|
||||
func GetDNSDiscoveryHandler(store *cache.Cache, geoSvc *service.Geo, domain string, redirectPort string) gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
host := hostWithoutPort(ctx.Request.Host)
|
||||
if host != domain && !strings.HasSuffix(host, "."+domain) {
|
||||
@@ -41,7 +42,7 @@ func GetDNSDiscoveryHandler(store *cache.Cache, domain string, redirectPort stri
|
||||
return
|
||||
}
|
||||
|
||||
handleDNS(ctx, store)
|
||||
handleDNS(ctx, store, geoSvc)
|
||||
ctx.Abort()
|
||||
}
|
||||
}
|
||||
@@ -53,7 +54,7 @@ func hostWithoutPort(host string) string {
|
||||
return host
|
||||
}
|
||||
|
||||
func handleDNS(ctx *gin.Context, store *cache.Cache) {
|
||||
func handleDNS(ctx *gin.Context, store *cache.Cache, geoSvc *service.Geo) {
|
||||
d := strings.Split(ctx.Request.Host, ".")[0]
|
||||
if !validator.IsValid(d) {
|
||||
ctx.String(http.StatusNotFound, http.StatusText(http.StatusNotFound))
|
||||
|
||||
+3
-3
@@ -16,7 +16,7 @@ import (
|
||||
|
||||
func TestGetDNSDiscoveryHandler(t *testing.T) {
|
||||
store := cache.New(cache.NoExpiration, cache.NoExpiration)
|
||||
handler := GetDNSDiscoveryHandler(store, domain, "")
|
||||
handler := GetDNSDiscoveryHandler(store, rt.geo, domain, "")
|
||||
|
||||
t.Run("calls next if host does not have domain suffix", func(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "/", nil)
|
||||
@@ -106,7 +106,7 @@ func TestHandleDNS(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = req
|
||||
handleDNS(c, store)
|
||||
handleDNS(c, store, rt.geo)
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
})
|
||||
}
|
||||
@@ -144,7 +144,7 @@ func TestAcceptDNSRequest(t *testing.T) {
|
||||
c.Request = req
|
||||
|
||||
store.Set(u, testIP.ipv4, cache.DefaultExpiration)
|
||||
handleDNS(c, store)
|
||||
handleDNS(c, store, rt.geo)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, tt.want, w.Body.String())
|
||||
|
||||
+25
-26
@@ -6,7 +6,6 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/dcarrillo/whatismyip/internal/httputils"
|
||||
"github.com/dcarrillo/whatismyip/internal/setting"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -31,31 +30,31 @@ type JSONResponse struct {
|
||||
GeoResponse
|
||||
}
|
||||
|
||||
func getRoot(ctx *gin.Context) {
|
||||
func (rt *Router) getRoot(ctx *gin.Context) {
|
||||
switch ctx.NegotiateFormat(gin.MIMEPlain, gin.MIMEHTML, gin.MIMEJSON) {
|
||||
case gin.MIMEHTML:
|
||||
name := "home"
|
||||
if setting.App.TemplatePath != "" {
|
||||
name = filepath.Base(setting.App.TemplatePath)
|
||||
if rt.templatePath != "" {
|
||||
name = filepath.Base(rt.templatePath)
|
||||
}
|
||||
ctx.HTML(http.StatusOK, name, jsonOutput(ctx))
|
||||
ctx.HTML(http.StatusOK, name, rt.jsonOutput(ctx))
|
||||
case gin.MIMEJSON:
|
||||
getJSON(ctx)
|
||||
rt.getJSON(ctx)
|
||||
default:
|
||||
ctx.String(http.StatusOK, ctx.ClientIP()+"\n")
|
||||
}
|
||||
}
|
||||
|
||||
func getClientPort(ctx *gin.Context) string {
|
||||
func (rt *Router) getClientPort(ctx *gin.Context) string {
|
||||
var port string
|
||||
if setting.App.TrustedPortHeader == "" {
|
||||
if setting.App.TrustedHeader != "" {
|
||||
if rt.trustedPortHeader == "" {
|
||||
if rt.trustedHeader != "" {
|
||||
port = "unknown"
|
||||
} else {
|
||||
_, port, _ = net.SplitHostPort(ctx.Request.RemoteAddr)
|
||||
}
|
||||
} else {
|
||||
port = ctx.GetHeader(setting.App.TrustedPortHeader)
|
||||
port = ctx.GetHeader(rt.trustedPortHeader)
|
||||
if port == "" {
|
||||
port = "unknown"
|
||||
}
|
||||
@@ -64,37 +63,37 @@ func getClientPort(ctx *gin.Context) string {
|
||||
return port
|
||||
}
|
||||
|
||||
func getClientPortAsString(ctx *gin.Context) {
|
||||
ctx.String(http.StatusOK, getClientPort(ctx)+"\n")
|
||||
func (rt *Router) getClientPortAsString(ctx *gin.Context) {
|
||||
ctx.String(http.StatusOK, rt.getClientPort(ctx)+"\n")
|
||||
}
|
||||
|
||||
func getAllAsString(ctx *gin.Context) {
|
||||
func (rt *Router) getAllAsString(ctx *gin.Context) {
|
||||
ip := net.ParseIP(ctx.ClientIP())
|
||||
|
||||
output := "IP: " + ip.String() + "\n"
|
||||
output += "Client Port: " + getClientPort(ctx) + "\n"
|
||||
output += "Client Port: " + rt.getClientPort(ctx) + "\n"
|
||||
|
||||
if geoSvc != nil {
|
||||
if cityRecord := geoSvc.LookUpCity(ip); cityRecord != nil {
|
||||
if rt.geo != nil {
|
||||
if cityRecord := rt.geo.LookUpCity(ip); cityRecord != nil {
|
||||
output += geoCityRecordToString(cityRecord) + "\n"
|
||||
}
|
||||
if asnRecord := geoSvc.LookUpASN(ip); asnRecord != nil {
|
||||
if asnRecord := rt.geo.LookUpASN(ip); asnRecord != nil {
|
||||
output += geoASNRecordToString(asnRecord) + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
h := httputils.GetHeadersWithoutTrustedHeaders(ctx)
|
||||
h := httputils.GetHeadersWithoutTrustedHeaders(ctx, rt.trustedHeader, rt.trustedPortHeader)
|
||||
h.Set("Host", ctx.Request.Host)
|
||||
output += httputils.HeadersToSortedString(h)
|
||||
|
||||
ctx.String(http.StatusOK, output)
|
||||
}
|
||||
|
||||
func getJSON(ctx *gin.Context) {
|
||||
ctx.JSON(http.StatusOK, jsonOutput(ctx))
|
||||
func (rt *Router) getJSON(ctx *gin.Context) {
|
||||
ctx.JSON(http.StatusOK, rt.jsonOutput(ctx))
|
||||
}
|
||||
|
||||
func jsonOutput(ctx *gin.Context) JSONResponse {
|
||||
func (rt *Router) jsonOutput(ctx *gin.Context) JSONResponse {
|
||||
ip := net.ParseIP(ctx.ClientIP())
|
||||
|
||||
var version byte = 4
|
||||
@@ -103,8 +102,8 @@ func jsonOutput(ctx *gin.Context) JSONResponse {
|
||||
}
|
||||
|
||||
geoResp := GeoResponse{}
|
||||
if geoSvc != nil {
|
||||
if cityRecord := geoSvc.LookUpCity(ip); cityRecord != nil {
|
||||
if rt.geo != nil {
|
||||
if cityRecord := rt.geo.LookUpCity(ip); cityRecord != nil {
|
||||
geoResp.Country = cityRecord.Country.Names["en"]
|
||||
geoResp.CountryCode = cityRecord.Country.ISOCode
|
||||
geoResp.City = cityRecord.City.Names["en"]
|
||||
@@ -113,7 +112,7 @@ func jsonOutput(ctx *gin.Context) JSONResponse {
|
||||
geoResp.PostalCode = cityRecord.Postal.Code
|
||||
geoResp.TimeZone = cityRecord.Location.TimeZone
|
||||
}
|
||||
if asnRecord := geoSvc.LookUpASN(ip); asnRecord != nil {
|
||||
if asnRecord := rt.geo.LookUpASN(ip); asnRecord != nil {
|
||||
geoResp.ASN = asnRecord.AutonomousSystemNumber
|
||||
geoResp.ASNOrganization = asnRecord.AutonomousSystemOrganization
|
||||
}
|
||||
@@ -122,9 +121,9 @@ func jsonOutput(ctx *gin.Context) JSONResponse {
|
||||
return JSONResponse{
|
||||
IP: ip.String(),
|
||||
IPVersion: version,
|
||||
ClientPort: getClientPort(ctx),
|
||||
ClientPort: rt.getClientPort(ctx),
|
||||
Host: ctx.Request.Host,
|
||||
Headers: httputils.GetHeadersWithoutTrustedHeaders(ctx),
|
||||
Headers: httputils.GetHeadersWithoutTrustedHeaders(ctx, rt.trustedHeader, rt.trustedPortHeader),
|
||||
GeoResponse: geoResp,
|
||||
}
|
||||
}
|
||||
|
||||
+29
-41
@@ -1,12 +1,14 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/dcarrillo/whatismyip/internal/setting"
|
||||
"github.com/dcarrillo/whatismyip/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -99,8 +101,9 @@ func TestHost(t *testing.T) {
|
||||
|
||||
func TestClientPort(t *testing.T) {
|
||||
type args struct {
|
||||
params []string
|
||||
headers map[string][]string
|
||||
trustedHeader string
|
||||
trustedPortHeader string
|
||||
headers map[string][]string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -114,35 +117,23 @@ func TestClientPort(t *testing.T) {
|
||||
{
|
||||
name: "Trusted header only set",
|
||||
args: args{
|
||||
params: []string{
|
||||
"-geoip2-city", "city",
|
||||
"-geoip2-asn", "asn",
|
||||
"-trusted-header", trustedHeader,
|
||||
},
|
||||
trustedHeader: trustedHeader,
|
||||
},
|
||||
expected: "unknown\n",
|
||||
},
|
||||
{
|
||||
name: "Trusted and port header set but not included in headers",
|
||||
args: args{
|
||||
params: []string{
|
||||
"-geoip2-city", "city",
|
||||
"-geoip2-asn", "asn",
|
||||
"-trusted-header", trustedHeader,
|
||||
"-trusted-port-header", trustedPortHeader,
|
||||
},
|
||||
trustedHeader: trustedHeader,
|
||||
trustedPortHeader: trustedPortHeader,
|
||||
},
|
||||
expected: "unknown\n",
|
||||
},
|
||||
{
|
||||
name: "Trusted and port header set and included in headers",
|
||||
args: args{
|
||||
params: []string{
|
||||
"-geoip2-city", "city",
|
||||
"-geoip2-asn", "asn",
|
||||
"-trusted-header", trustedHeader,
|
||||
"-trusted-port-header", trustedPortHeader,
|
||||
},
|
||||
trustedHeader: trustedHeader,
|
||||
trustedPortHeader: trustedPortHeader,
|
||||
headers: map[string][]string{
|
||||
trustedHeader: {testIP.ipv4},
|
||||
trustedPortHeader: {"1001"},
|
||||
@@ -153,19 +144,22 @@ func TestClientPort(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
_, _ = setting.Setup(tt.args.params)
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
engine := gin.Default()
|
||||
engine.TrustedPlatform = tt.args.trustedHeader
|
||||
r := NewRouter(nil, tt.args.trustedHeader, tt.args.trustedPortHeader, "", false)
|
||||
Setup(engine, r)
|
||||
|
||||
req, _ := http.NewRequest("GET", "/client-port", nil)
|
||||
req.RemoteAddr = net.JoinHostPort(testIP.ipv4, "1000")
|
||||
req.Header = tt.args.headers
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
app.ServeHTTP(w, req)
|
||||
engine.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, contentType.text, w.Header().Get("Content-Type"))
|
||||
assert.Equal(t, tt.expected, w.Body.String())
|
||||
t.Log(w.Header())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -181,14 +175,11 @@ func TestNotFound(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestJSON(t *testing.T) {
|
||||
_, _ = setting.Setup(
|
||||
[]string{
|
||||
"-geoip2-city", "city",
|
||||
"-geoip2-asn", "asn",
|
||||
"-trusted-header", trustedHeader,
|
||||
"-trusted-port-header", trustedPortHeader,
|
||||
},
|
||||
)
|
||||
svc, _ := service.NewGeo(context.Background(), "../test/GeoIP2-City-Test.mmdb", "../test/GeoLite2-ASN-Test.mmdb")
|
||||
engine := gin.Default()
|
||||
engine.TrustedPlatform = trustedHeader
|
||||
r := NewRouter(svc, trustedHeader, trustedPortHeader, "", false)
|
||||
Setup(engine, r)
|
||||
|
||||
type args struct {
|
||||
ip string
|
||||
@@ -222,7 +213,7 @@ func TestJSON(t *testing.T) {
|
||||
req.Header.Set(trustedPortHeader, "1001")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
app.ServeHTTP(w, req)
|
||||
engine.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, contentType.json, w.Header().Get("Content-Type"))
|
||||
@@ -248,14 +239,11 @@ ASN Organization:
|
||||
Header1: one
|
||||
Host: test
|
||||
`
|
||||
_, _ = setting.Setup(
|
||||
[]string{
|
||||
"-geoip2-city", "city",
|
||||
"-geoip2-asn", "asn",
|
||||
"-trusted-header", trustedHeader,
|
||||
"-trusted-port-header", trustedPortHeader,
|
||||
},
|
||||
)
|
||||
svc, _ := service.NewGeo(context.Background(), "../test/GeoIP2-City-Test.mmdb", "../test/GeoLite2-ASN-Test.mmdb")
|
||||
engine := gin.Default()
|
||||
engine.TrustedPlatform = trustedHeader
|
||||
r := NewRouter(svc, trustedHeader, trustedPortHeader, "", false)
|
||||
Setup(engine, r)
|
||||
|
||||
req, _ := http.NewRequest("GET", "/all", nil)
|
||||
req.RemoteAddr = net.JoinHostPort(testIP.ipv4, "1000")
|
||||
@@ -265,7 +253,7 @@ Host: test
|
||||
req.Header.Set("Header1", "one")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
app.ServeHTTP(w, req)
|
||||
engine.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, contentType.text, w.Header().Get("Content-Type"))
|
||||
|
||||
+6
-6
@@ -81,13 +81,13 @@ var asnOutput = map[string]asnDataFormatter{
|
||||
},
|
||||
}
|
||||
|
||||
func getGeoAsString(ctx *gin.Context) {
|
||||
if geoSvc == nil {
|
||||
func (rt *Router) getGeoAsString(ctx *gin.Context) {
|
||||
if rt.geo == nil {
|
||||
ctx.String(http.StatusNotFound, http.StatusText(http.StatusNotFound))
|
||||
return
|
||||
}
|
||||
|
||||
record := geoSvc.LookUpCity(net.ParseIP(ctx.ClientIP()))
|
||||
record := rt.geo.LookUpCity(net.ParseIP(ctx.ClientIP()))
|
||||
if record == nil {
|
||||
ctx.String(http.StatusNotFound, http.StatusText(http.StatusNotFound))
|
||||
return
|
||||
@@ -107,13 +107,13 @@ func getGeoAsString(ctx *gin.Context) {
|
||||
ctx.String(http.StatusOK, g.format(record))
|
||||
}
|
||||
|
||||
func getASNAsString(ctx *gin.Context) {
|
||||
if geoSvc == nil {
|
||||
func (rt *Router) getASNAsString(ctx *gin.Context) {
|
||||
if rt.geo == nil {
|
||||
ctx.String(http.StatusNotFound, http.StatusText(http.StatusNotFound))
|
||||
return
|
||||
}
|
||||
|
||||
record := geoSvc.LookUpASN(net.ParseIP(ctx.ClientIP()))
|
||||
record := rt.geo.LookUpASN(net.ParseIP(ctx.ClientIP()))
|
||||
if record == nil {
|
||||
ctx.String(http.StatusNotFound, http.StatusText(http.StatusNotFound))
|
||||
return
|
||||
|
||||
+4
-5
@@ -9,15 +9,14 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func getHeadersAsSortedString(ctx *gin.Context) {
|
||||
h := httputils.GetHeadersWithoutTrustedHeaders(ctx)
|
||||
func (rt *Router) getHeadersAsSortedString(ctx *gin.Context) {
|
||||
h := httputils.GetHeadersWithoutTrustedHeaders(ctx, rt.trustedHeader, rt.trustedPortHeader)
|
||||
h.Set("Host", ctx.Request.Host)
|
||||
|
||||
ctx.String(http.StatusOK, httputils.HeadersToSortedString(h))
|
||||
}
|
||||
|
||||
func getHeaderAsString(ctx *gin.Context) {
|
||||
headers := httputils.GetHeadersWithoutTrustedHeaders(ctx)
|
||||
func (rt *Router) getHeaderAsString(ctx *gin.Context) {
|
||||
headers := httputils.GetHeadersWithoutTrustedHeaders(ctx, rt.trustedHeader, rt.trustedPortHeader)
|
||||
|
||||
h := ctx.Params.ByName("header")
|
||||
if v := headers.Get(ctx.Params.ByName("header")); v != "" {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/dcarrillo/whatismyip/internal/setting"
|
||||
"github.com/dcarrillo/whatismyip/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -27,12 +29,11 @@ Header2: value22
|
||||
Header3: value3
|
||||
Host:
|
||||
`
|
||||
_, _ = setting.Setup([]string{
|
||||
"-geoip2-city", "city",
|
||||
"-geoip2-asn", "asn",
|
||||
"-trusted-header", trustedHeader,
|
||||
"-trusted-port-header", trustedPortHeader,
|
||||
})
|
||||
svc, _ := service.NewGeo(context.Background(), "../test/GeoIP2-City-Test.mmdb", "../test/GeoLite2-ASN-Test.mmdb")
|
||||
engine := gin.Default()
|
||||
engine.TrustedPlatform = trustedHeader
|
||||
r := NewRouter(svc, trustedHeader, trustedPortHeader, "", false)
|
||||
Setup(engine, r)
|
||||
req, _ := http.NewRequest("GET", "/headers", nil)
|
||||
req.Header = map[string][]string{
|
||||
"Header1": {"value1"},
|
||||
@@ -43,7 +44,7 @@ Host:
|
||||
req.Header.Set(trustedPortHeader, "1025")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
app.ServeHTTP(w, req)
|
||||
engine.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, contentType.text, w.Header().Get("Content-Type"))
|
||||
|
||||
@@ -17,7 +17,7 @@ type JSONScanResponse struct {
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
func scanTCPPort(ctx *gin.Context) {
|
||||
func (rt *Router) 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)
|
||||
|
||||
+34
-20
@@ -4,35 +4,49 @@ import (
|
||||
"html/template"
|
||||
"log"
|
||||
|
||||
"github.com/dcarrillo/whatismyip/internal/setting"
|
||||
"github.com/dcarrillo/whatismyip/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var geoSvc *service.Geo
|
||||
type Router struct {
|
||||
geo *service.Geo
|
||||
trustedHeader string
|
||||
trustedPortHeader string
|
||||
templatePath string
|
||||
disableScan bool
|
||||
}
|
||||
|
||||
func SetupTemplate(r *gin.Engine) {
|
||||
if setting.App.TemplatePath == "" {
|
||||
func NewRouter(geo *service.Geo, trustedHeader, trustedPortHeader, templatePath string, disableScan bool) *Router {
|
||||
return &Router{
|
||||
geo: geo,
|
||||
trustedHeader: trustedHeader,
|
||||
trustedPortHeader: trustedPortHeader,
|
||||
templatePath: templatePath,
|
||||
disableScan: disableScan,
|
||||
}
|
||||
}
|
||||
|
||||
func SetupTemplate(r *gin.Engine, templatePath string) {
|
||||
if templatePath == "" {
|
||||
r.SetHTMLTemplate(template.Must(template.New("home").Parse(home)))
|
||||
} else {
|
||||
log.Printf("Template %s has been loaded", setting.App.TemplatePath)
|
||||
r.LoadHTMLFiles(setting.App.TemplatePath)
|
||||
log.Printf("Template %s has been loaded", templatePath)
|
||||
r.LoadHTMLFiles(templatePath)
|
||||
}
|
||||
}
|
||||
|
||||
func Setup(r *gin.Engine, geo *service.Geo) {
|
||||
geoSvc = geo
|
||||
r.GET("/", getRoot)
|
||||
if !setting.App.DisableTCPScan {
|
||||
r.GET("/scan/tcp/:port", scanTCPPort)
|
||||
func Setup(r *gin.Engine, rt *Router) {
|
||||
r.GET("/", rt.getRoot)
|
||||
if !rt.disableScan {
|
||||
r.GET("/scan/tcp/:port", rt.scanTCPPort)
|
||||
}
|
||||
r.GET("/client-port", getClientPortAsString)
|
||||
r.GET("/geo", getGeoAsString)
|
||||
r.GET("/geo/:field", getGeoAsString)
|
||||
r.GET("/asn", getASNAsString)
|
||||
r.GET("/asn/:field", getASNAsString)
|
||||
r.GET("/headers", getHeadersAsSortedString)
|
||||
r.GET("/all", getAllAsString)
|
||||
r.GET("/json", getJSON)
|
||||
r.GET("/:header", getHeaderAsString)
|
||||
r.GET("/client-port", rt.getClientPortAsString)
|
||||
r.GET("/geo", rt.getGeoAsString)
|
||||
r.GET("/geo/:field", rt.getGeoAsString)
|
||||
r.GET("/asn", rt.getASNAsString)
|
||||
r.GET("/asn/:field", rt.getASNAsString)
|
||||
r.GET("/headers", rt.getHeadersAsSortedString)
|
||||
r.GET("/all", rt.getAllAsString)
|
||||
r.GET("/json", rt.getJSON)
|
||||
r.GET("/:header", rt.getHeaderAsString)
|
||||
}
|
||||
|
||||
@@ -47,11 +47,14 @@ const (
|
||||
domain = "dns.example.com"
|
||||
)
|
||||
|
||||
var rt *Router
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
app = gin.Default()
|
||||
app.TrustedPlatform = trustedHeader
|
||||
svc, _ := service.NewGeo(context.Background(), "../test/GeoIP2-City-Test.mmdb", "../test/GeoLite2-ASN-Test.mmdb")
|
||||
Setup(app, svc)
|
||||
rt = NewRouter(svc, trustedHeader, "", "", false)
|
||||
Setup(app, rt)
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
+12
-9
@@ -6,18 +6,21 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/dcarrillo/whatismyip/internal/setting"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
type Prometheus struct {
|
||||
server *http.Server
|
||||
ctx context.Context
|
||||
server *http.Server
|
||||
ctx context.Context
|
||||
addr string
|
||||
timeouts Timeouts
|
||||
}
|
||||
|
||||
func NewPrometheusServer(ctx context.Context) *Prometheus {
|
||||
func NewPrometheusServer(ctx context.Context, addr string, timeouts Timeouts) *Prometheus {
|
||||
return &Prometheus{
|
||||
ctx: ctx,
|
||||
ctx: ctx,
|
||||
addr: addr,
|
||||
timeouts: timeouts,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,13 +29,13 @@ func (p *Prometheus) Start() {
|
||||
mux.Handle("/metrics", promhttp.Handler())
|
||||
|
||||
p.server = &http.Server{
|
||||
Addr: setting.App.PrometheusAddress,
|
||||
Addr: p.addr,
|
||||
Handler: mux,
|
||||
ReadTimeout: setting.App.Server.ReadTimeout,
|
||||
WriteTimeout: setting.App.Server.WriteTimeout,
|
||||
ReadTimeout: p.timeouts.ReadTimeout,
|
||||
WriteTimeout: p.timeouts.WriteTimeout,
|
||||
}
|
||||
|
||||
log.Printf("Starting Prometheus server listening on %s", setting.App.PrometheusAddress)
|
||||
log.Printf("Starting Prometheus server listening on %s", p.addr)
|
||||
go func() {
|
||||
if err := p.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatal(err)
|
||||
|
||||
+10
-5
@@ -6,7 +6,6 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/dcarrillo/whatismyip/internal/setting"
|
||||
"github.com/quic-go/quic-go/http3"
|
||||
)
|
||||
|
||||
@@ -14,18 +13,24 @@ type Quic struct {
|
||||
server *http3.Server
|
||||
tlsServer *TLS
|
||||
ctx context.Context
|
||||
addr string
|
||||
crtPath string
|
||||
keyPath string
|
||||
}
|
||||
|
||||
func NewQuicServer(ctx context.Context, tlsServer *TLS) *Quic {
|
||||
func NewQuicServer(ctx context.Context, tlsServer *TLS, addr, crt, key string) *Quic {
|
||||
return &Quic{
|
||||
tlsServer: tlsServer,
|
||||
ctx: ctx,
|
||||
addr: addr,
|
||||
crtPath: crt,
|
||||
keyPath: key,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Quic) Start() {
|
||||
q.server = &http3.Server{
|
||||
Addr: setting.App.TLSAddress,
|
||||
Addr: q.addr,
|
||||
Handler: q.tlsServer.server.Handler,
|
||||
}
|
||||
|
||||
@@ -38,9 +43,9 @@ func (q *Quic) Start() {
|
||||
parentHandler.ServeHTTP(rw, req)
|
||||
})
|
||||
|
||||
log.Printf("Starting QUIC server listening on %s (udp)", setting.App.TLSAddress)
|
||||
log.Printf("Starting QUIC server listening on %s (udp)", q.addr)
|
||||
go func() {
|
||||
if err := q.server.ListenAndServeTLS(setting.App.TLSCrtPath, setting.App.TLSKeyPath); err != nil &&
|
||||
if err := q.server.ListenAndServeTLS(q.crtPath, q.keyPath); err != nil &&
|
||||
!errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
+21
-13
@@ -5,32 +5,40 @@ import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/dcarrillo/whatismyip/internal/setting"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TCP struct {
|
||||
server *http.Server
|
||||
handler http.Handler
|
||||
ctx context.Context
|
||||
type Timeouts struct {
|
||||
ReadTimeout time.Duration
|
||||
WriteTimeout time.Duration
|
||||
}
|
||||
|
||||
func NewTCPServer(ctx context.Context, handler http.Handler) *TCP {
|
||||
type TCP struct {
|
||||
server *http.Server
|
||||
handler http.Handler
|
||||
ctx context.Context
|
||||
addr string
|
||||
timeouts Timeouts
|
||||
}
|
||||
|
||||
func NewTCPServer(ctx context.Context, handler http.Handler, addr string, timeouts Timeouts) *TCP {
|
||||
return &TCP{
|
||||
handler: handler,
|
||||
ctx: ctx,
|
||||
handler: handler,
|
||||
ctx: ctx,
|
||||
addr: addr,
|
||||
timeouts: timeouts,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TCP) Start() {
|
||||
t.server = &http.Server{
|
||||
Addr: setting.App.BindAddress,
|
||||
Addr: t.addr,
|
||||
Handler: t.handler,
|
||||
ReadTimeout: setting.App.Server.ReadTimeout,
|
||||
WriteTimeout: setting.App.Server.WriteTimeout,
|
||||
ReadTimeout: t.timeouts.ReadTimeout,
|
||||
WriteTimeout: t.timeouts.WriteTimeout,
|
||||
}
|
||||
|
||||
log.Printf("Starting TCP server listening on %s", setting.App.BindAddress)
|
||||
log.Printf("Starting TCP server listening on %s", t.addr)
|
||||
go func() {
|
||||
if err := t.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatal(err)
|
||||
|
||||
+19
-13
@@ -6,37 +6,43 @@ import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/dcarrillo/whatismyip/internal/setting"
|
||||
)
|
||||
|
||||
type TLS struct {
|
||||
server *http.Server
|
||||
handler http.Handler
|
||||
ctx context.Context
|
||||
server *http.Server
|
||||
handler http.Handler
|
||||
ctx context.Context
|
||||
addr string
|
||||
crtPath string
|
||||
keyPath string
|
||||
timeouts Timeouts
|
||||
}
|
||||
|
||||
func NewTLSServer(ctx context.Context, handler http.Handler) *TLS {
|
||||
func NewTLSServer(ctx context.Context, handler http.Handler, addr, crt, key string, timeouts Timeouts) *TLS {
|
||||
return &TLS{
|
||||
handler: handler,
|
||||
ctx: ctx,
|
||||
handler: handler,
|
||||
ctx: ctx,
|
||||
addr: addr,
|
||||
crtPath: crt,
|
||||
keyPath: key,
|
||||
timeouts: timeouts,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TLS) Start() {
|
||||
t.server = &http.Server{
|
||||
Addr: setting.App.TLSAddress,
|
||||
Addr: t.addr,
|
||||
Handler: t.handler,
|
||||
ReadTimeout: setting.App.Server.ReadTimeout,
|
||||
WriteTimeout: setting.App.Server.WriteTimeout,
|
||||
ReadTimeout: t.timeouts.ReadTimeout,
|
||||
WriteTimeout: t.timeouts.WriteTimeout,
|
||||
TLSConfig: &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
},
|
||||
}
|
||||
|
||||
log.Printf("Starting TLS server listening on %s", setting.App.TLSAddress)
|
||||
log.Printf("Starting TLS server listening on %s", t.addr)
|
||||
go func() {
|
||||
if err := t.server.ListenAndServeTLS(setting.App.TLSCrtPath, setting.App.TLSKeyPath); err != nil &&
|
||||
if err := t.server.ListenAndServeTLS(t.crtPath, t.keyPath); err != nil &&
|
||||
!errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user