nutils/server.go
2026-09-02 12:53:50 -03:00

138 lines
2.8 KiB
Go

package main
import (
"fmt"
"net"
"net/http"
"strings"
)
type Service struct {
Name string
Subdomains map[string]bool
Directories map[string]bool
FilterV4 bool
FilterV6 bool
}
func BuildServices(cfg *Config) map[string]*Service {
services := make(map[string]*Service)
for name, sc := range cfg.Services {
if !sc.Enabled {
continue
}
svc := &Service{
Name: name,
Subdomains: make(map[string]bool),
Directories: make(map[string]bool),
}
for _, s := range sc.Subdomains {
svc.Subdomains[strings.ToLower(s)] = true
}
for _, d := range sc.Directories {
svc.Directories[d] = true
}
switch name {
case "ip4":
svc.FilterV4 = true
case "ip6":
svc.FilterV6 = true
}
services[name] = svc
}
return services
}
func extractIP(r *http.Request) net.IP {
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
parts := strings.Split(xff, ",")
if ip := net.ParseIP(strings.TrimSpace(parts[0])); ip != nil {
return ip
}
}
if xri := r.Header.Get("X-Real-IP"); xri != "" {
if ip := net.ParseIP(strings.TrimSpace(xri)); ip != nil {
return ip
}
}
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
return net.ParseIP(r.RemoteAddr)
}
return net.ParseIP(host)
}
func matchService(host, path string, services map[string]*Service) *Service {
hostname := host
if i := strings.IndexByte(host, ':'); i != -1 {
hostname = host[:i]
}
for _, svc := range services {
if svc.Subdomains[hostname] {
return svc
}
}
parts := strings.Split(hostname, ".")
if len(parts) >= 2 {
label := parts[len(parts)-2]
for _, svc := range services {
if svc.Subdomains[label] {
return svc
}
}
}
for _, svc := range services {
if svc.Directories[path] {
return svc
}
}
return nil
}
func Handle(services map[string]*Service, geo *Geo) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
svc := matchService(r.Host, r.URL.Path, services)
if svc == nil {
http.NotFound(w, r)
return
}
ip := extractIP(r)
if ip == nil {
http.Error(w, "cannot determine client IP", 500)
return
}
isV4 := ip.To4() != nil
if (svc.FilterV4 && !isV4) || (svc.FilterV6 && isV4) {
http.Error(w, fmt.Sprintf("this endpoint requires %s", map[bool]string{true: "IPv6", false: "IPv4"}[svc.FilterV6]), 400)
return
}
info := geo.Lookup(ip)
fmt.Fprintf(w, "%s\n", ip)
fmt.Fprintf(w, "%s\n", info.ReverseDNS)
fmt.Fprintf(w, "proxy=NO, type=UNKNOWN, tor=NO\n")
continent, country, city := info.Continent, info.Country, info.City
if continent == "" {
continent = "Unknown"
}
if country == "" {
country = "Unknown"
}
if city == "" {
city = "Unknown"
}
fmt.Fprintf(w, "%s, %s, %s\n", continent, country, city)
if info.Org != "" {
fmt.Fprintf(w, "%s / %s\n", info.Org, info.ASN)
} else {
fmt.Fprintf(w, "Unknown\n")
}
}
}