ip-checker/main.go

190 lines
4.2 KiB
Go
Raw Normal View History

2024-08-26 22:01:24 +00:00
package main
import (
"fmt"
"html/template"
"log"
2024-10-19 08:04:28 +00:00
"net"
2024-08-26 22:01:24 +00:00
"net/http"
"os"
"strings"
2024-10-19 08:04:28 +00:00
"sync"
"github.com/oschwald/geoip2-golang"
)
var (
2024-10-20 08:56:23 +00:00
ASNDB *geoip2.Reader
CityDB *geoip2.Reader
2024-10-19 08:04:28 +00:00
tmpl *template.Template
config ServerConfig
ipCache sync.Map
2024-08-26 22:01:24 +00:00
)
type ServerConfig struct {
2024-10-20 08:56:23 +00:00
Listen string
CityDB string
ASNDB string
2024-08-26 22:01:24 +00:00
}
func main() {
2024-10-19 08:04:28 +00:00
config = ServerConfig{
2024-10-20 08:56:23 +00:00
Listen: getEnvOr("IP_CHECKER_LISTEN", ":8080"),
CityDB: os.Getenv("IP_CHECKER_CITY_DB"),
ASNDB: os.Getenv("IP_CHECKER_ASN_DB"),
2024-10-19 08:04:28 +00:00
}
2024-10-20 08:56:23 +00:00
if config.CityDB != "" {
2024-10-19 08:04:28 +00:00
var err error
2024-10-20 08:56:23 +00:00
CityDB, err = geoip2.Open(config.CityDB)
2024-10-19 08:04:28 +00:00
if err != nil {
2024-10-20 08:56:23 +00:00
log.Fatalf("Error opening GeoIP City database: %v", err)
2024-10-19 08:04:28 +00:00
}
2024-10-20 08:56:23 +00:00
defer CityDB.Close()
}
if config.ASNDB != "" {
var err error
ASNDB, err = geoip2.Open(config.ASNDB)
if err != nil {
log.Fatalf("Error opening GeoIP ASN database: %v", err)
}
defer ASNDB.Close()
2024-08-26 22:01:24 +00:00
}
2024-10-19 08:04:28 +00:00
tmpl = template.Must(template.ParseFiles("assets/index.html"))
2024-08-26 22:01:24 +00:00
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./assets/static"))))
2024-10-19 08:04:28 +00:00
http.HandleFunc("/{$}", handleRequest)
2024-08-26 22:01:24 +00:00
2024-10-19 04:19:03 +00:00
http.HandleFunc("/robots.txt", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `User-Agent: *
Disallow: /harming/humans
Disallow: /ignoring/human/orders
Disallow: /harm/to/self
`)
})
2024-10-20 08:56:23 +00:00
log.Printf("Starting server on %s", config.Listen)
log.Fatal(http.ListenAndServe(config.Listen, nil))
2024-08-26 22:01:24 +00:00
}
2024-10-19 08:04:28 +00:00
func getIPCountry(ip string) string {
parsedIP := net.ParseIP(ip)
2024-10-20 08:56:23 +00:00
// check for reserved ip
if parsedIP.IsPrivate() {
return "private IP"
}
if parsedIP.IsLoopback() {
return "loopback IP"
}
if parsedIP == nil {
log.Printf("Invalid IP address: %s", ip)
return "invalid IP"
}
2024-10-20 08:56:23 +00:00
// check if DB exists
// FIXME:
if config.CityDB == "" && config.ASNDB == "" {
log.Printf("GeoIP database not set. Returning 'unknown'")
2024-10-19 08:04:28 +00:00
return "unknown"
}
2024-10-20 08:56:23 +00:00
// cache
if cached, found := ipCache.Load(ip); found {
return cached.(string)
2024-10-19 08:04:28 +00:00
}
2024-10-20 08:56:23 +00:00
// parse DB
cityRecord, err := CityDB.City(parsedIP)
2024-10-19 08:04:28 +00:00
if err != nil {
2024-10-20 08:56:23 +00:00
log.Printf("GeoIP City lookup failed for IP: %s, error: %v", ip, err)
return "City record lookup failed"
}
2024-10-20 08:56:23 +00:00
ASNRecord, err := ASNDB.ASN(parsedIP)
if err != nil {
log.Printf("GeoIP ASN lookup failed for IP: %s, error: %v", ip, err)
return "ASN record lookup failed"
}
// parse text
country, ok := cityRecord.Country.Names["en"]
if !ok {
log.Printf("Country name not found in GeoIP data for IP: %s", ip)
return "unknown"
2024-10-19 08:04:28 +00:00
}
2024-10-20 08:56:23 +00:00
region, ok := cityRecord.Subdivisions[0].Names["en"]
if !ok {
log.Printf("Region name not found in GeoIP data for IP: %s", ip)
return "unknown"
}
ASN := ASNRecord.AutonomousSystemOrganization
if ASN == "" {
log.Printf("ASN name not found in GeoIP data for IP: %s", ip)
return "unknown"
}
2024-10-19 08:04:28 +00:00
2024-10-20 08:56:23 +00:00
result := region + ", " + country + ", " + ASN
ipCache.Store(ip, result)
return result
2024-10-19 08:04:28 +00:00
}
func handleRequest(w http.ResponseWriter, r *http.Request) {
2024-10-19 05:28:17 +00:00
sourceIP := func() string {
if ip := r.Header.Get("X-Forwarded-For"); ip != "" {
2024-10-19 08:18:59 +00:00
return strings.Split(ip, ",")[0]
2024-10-19 05:28:17 +00:00
}
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
errorText := fmt.Sprintf("Unable to parse remote address: %v", err)
log.Printf(errorText)
return errorText
}
return host
2024-10-19 05:28:17 +00:00
}()
2024-08-26 22:01:24 +00:00
isHeadless := strings.HasPrefix(r.Header.Get("User-Agent"), "curl/")
log.Printf("r.URL.Path: %s, sourceIP: %s, isHeadless: %t, User-Agent: %s", r.URL.Path, sourceIP, isHeadless, r.Header.Get("User-Agent"))
2024-10-19 04:19:03 +00:00
if isHeadless {
fmt.Fprintf(w, "%s\n", sourceIP)
return
2024-10-19 04:19:03 +00:00
} else {
csp := []string{
"default-src 'none'",
"img-src 'self'",
"script-src-elem 'self'",
"style-src-elem 'self' fonts.googleapis.com",
"font-src fonts.gstatic.com",
"connect-src api.ip.sb api-v3.speedtest.cn api.ipapi.is",
2024-08-26 22:01:24 +00:00
}
2024-10-19 04:19:03 +00:00
w.Header().Set("Content-Security-Policy", strings.Join(csp, "; "))
2024-08-26 22:01:24 +00:00
2024-10-19 04:19:03 +00:00
data := map[string]string{
2024-10-19 08:04:28 +00:00
"sourceIP": sourceIP,
"sourceCountry": getIPCountry(sourceIP),
2024-10-19 04:19:03 +00:00
}
2024-08-26 22:01:24 +00:00
2024-10-19 04:19:03 +00:00
err := tmpl.Execute(w, data)
if err != nil {
http.Error(w, "Unable to load template", http.StatusInternalServerError)
log.Printf("Template execution error: %v", err)
return
}
2024-08-26 22:01:24 +00:00
}
}
func getEnvOr(key, defaultValue string) string {
if value, exists := os.LookupEnv(key); exists {
return value
}
return defaultValue
}