From c3f837e122b9e79a48ea91afbceae52612e1866a Mon Sep 17 00:00:00 2001 From: Christian Ditaputratama Date: Mon, 9 Sep 2024 18:21:03 +0700 Subject: [PATCH] feat: Check IP-stack info everytime prober send report #84 This commit add IsIPv6Only function inside `internal/ip` package and moving `geo` package from `internal/geo` to `internal/ip/geo`. Although it increases server resource usage, checking hostname to IP is required every time the prober sends a report so that the `ipv6_only` record in the database is not up-to-date. Previously, this feature did not exist. --- README.md | 4 +-- cmd/client/probe.go | 7 ++++ internal/{geo/ip.go => ip/geo/geoip.go} | 0 internal/ip/ip.go | 16 +++++++++ internal/ip/ip_test.go | 46 +++++++++++++++++++++++++ internal/monero/monero.go | 17 ++++----- internal/monero/report.go | 11 +++--- 7 files changed, 84 insertions(+), 17 deletions(-) rename internal/{geo/ip.go => ip/geo/geoip.go} (100%) create mode 100644 internal/ip/ip.go create mode 100644 internal/ip/ip_test.go diff --git a/README.md b/README.md index b0a521a..e12cb40 100644 --- a/README.md +++ b/README.md @@ -38,14 +38,14 @@ To build the executable binaries, you need: - MySQL/MariaDB - [GeoIP Database][geoip_doc] (optional). Place it to `./assets/geoip`, - see [./internal/geo/ip.go](./internal/geo/ip.go). + see [./internal/ip/geo/geoip.go](./internal/ip/geo/geoip.go). ## Installation ### For initial server setup: 1. Download [GeoIP Database][geoip_doc] and place it to `./assets/geoip`. - (see [./internal/geo/ip.go](./internal/geo/ip.go)). + (see [./internal/ip/geo/geoip.go](./internal/ip/geo/geoip.go)). 2. Pepare your MySQL/MariaDB. 3. Copy `.env.example` to `.env` and edit it to match with server environment. 4. Build the binary with `make server` (or `make build` to build both diff --git a/cmd/client/probe.go b/cmd/client/probe.go index c79d787..7dc3357 100644 --- a/cmd/client/probe.go +++ b/cmd/client/probe.go @@ -14,6 +14,7 @@ import ( "time" "github.com/ditatompel/xmr-remote-nodes/internal/config" + "github.com/ditatompel/xmr-remote-nodes/internal/ip" "github.com/ditatompel/xmr-remote-nodes/internal/monero" "github.com/spf13/cobra" @@ -334,6 +335,12 @@ func (p *proberClient) fetchFee(client http.Client, endpoint string) (uint, erro } func (p *proberClient) reportResult(node monero.Node, tookTime float64) error { + if !node.IsTor { + if hostIps, err := net.LookupIP(node.Hostname); err == nil { + node.IPv6Only = ip.IsIPv6Only(hostIps) + } + } + jsonData, err := json.Marshal(monero.ProbeReport{ TookTime: tookTime, Message: p.message, diff --git a/internal/geo/ip.go b/internal/ip/geo/geoip.go similarity index 100% rename from internal/geo/ip.go rename to internal/ip/geo/geoip.go diff --git a/internal/ip/ip.go b/internal/ip/ip.go new file mode 100644 index 0000000..410ba34 --- /dev/null +++ b/internal/ip/ip.go @@ -0,0 +1,16 @@ +// Package ip provides IP address related functions +package ip + +import ( + "net" +) + +// IsIPv6Only returns true if all given IPs are IPv6 +func IsIPv6Only(ips []net.IP) bool { + for _, ip := range ips { + if ip.To4() != nil { + return false + } + } + return true +} diff --git a/internal/ip/ip_test.go b/internal/ip/ip_test.go new file mode 100644 index 0000000..ae6730a --- /dev/null +++ b/internal/ip/ip_test.go @@ -0,0 +1,46 @@ +package ip + +import ( + "net" + "testing" +) + +// Single test: go test ./internal/ip -bench TestIsIPv6Only -benchmem -run=^$ -v +func TestIsIPv6Only(t *testing.T) { + tests := []struct { + name string + ips []net.IP + want bool + }{ + { + name: "IPv4", + ips: []net.IP{ + net.ParseIP("1.1.1.1"), + }, + want: false, + }, + { + name: "IPv6", + ips: []net.IP{ + net.ParseIP("2606:4700::6810:85e5"), + }, + want: true, + }, + { + name: "IPv6 and IPv4", + ips: []net.IP{ + net.ParseIP("1.1.1.1"), + net.ParseIP("2606:4700::6810:84e5"), + }, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsIPv6Only(tt.ips); got != tt.want { + t.Errorf("IsIPv6Only() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/monero/monero.go b/internal/monero/monero.go index 02609a1..85150dc 100644 --- a/internal/monero/monero.go +++ b/internal/monero/monero.go @@ -12,7 +12,7 @@ import ( "time" "github.com/ditatompel/xmr-remote-nodes/internal/database" - + "github.com/ditatompel/xmr-remote-nodes/internal/ip" "github.com/jmoiron/sqlx/types" ) @@ -196,9 +196,9 @@ func (r *moneroRepo) Add(protocol string, hostname string, port uint) error { if strings.HasSuffix(hostname, ".onion") { is_tor = true } - ip := "" - ipv6_only := true + ipAddr := "" + ipv6_only := false if !is_tor { hostIps, err := net.LookupIP(hostname) @@ -206,12 +206,7 @@ func (r *moneroRepo) Add(protocol string, hostname string, port uint) error { return err } - for _, hostIp := range hostIps { - if hostIp.To4() != nil { - ipv6_only = false - break - } - } + ipv6_only = ip.IsIPv6Only(hostIps) hostIp := hostIps[0] if hostIp.IsPrivate() { @@ -221,7 +216,7 @@ func (r *moneroRepo) Add(protocol string, hostname string, port uint) error { return errors.New("IP address is loopback address") } - ip = hostIp.String() + ipAddr = hostIp.String() } row, err := r.db.Query(` @@ -276,7 +271,7 @@ func (r *moneroRepo) Add(protocol string, hostname string, port uint) error { port, is_tor, "", - ip, + ipAddr, 0, 0, time.Now().Unix(), diff --git a/internal/monero/report.go b/internal/monero/report.go index 80cb152..1aaa834 100644 --- a/internal/monero/report.go +++ b/internal/monero/report.go @@ -10,7 +10,7 @@ import ( "strings" "time" - "github.com/ditatompel/xmr-remote-nodes/internal/geo" + "github.com/ditatompel/xmr-remote-nodes/internal/ip/geo" ) type QueryLogs struct { @@ -308,7 +308,8 @@ func (r *moneroRepo) ProcessJob(report ProbeReport, proberId int64) error { city = ?, last_checked = ?, last_check_status = ?, - cors_capable = ? + cors_capable = ?, + ipv6_only = ? WHERE id = ?` _, err := r.db.Exec(update, @@ -330,6 +331,7 @@ func (r *moneroRepo) ProcessJob(report ProbeReport, proberId int64) error { now.Unix(), statuses, report.Node.CORSCapable, + report.Node.IPv6Only, report.Node.ID) if err != nil { slog.Warn(err.Error()) @@ -341,10 +343,11 @@ func (r *moneroRepo) ProcessJob(report ProbeReport, proberId int64) error { is_available = ?, uptime = ?, last_checked = ?, - last_check_status = ? + last_check_status = ?, + ipv6_only = ? WHERE id = ?` - if _, err := r.db.Exec(u, 0, report.Node.Uptime, now.Unix(), statuses, report.Node.ID); err != nil { + if _, err := r.db.Exec(u, 0, report.Node.Uptime, now.Unix(), statuses, report.Node.IPv6Only, report.Node.ID); err != nil { slog.Warn(err.Error()) } }