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.
This commit is contained in:
Christian Ditaputratama 2024-09-09 18:21:03 +07:00
parent 518d4b4335
commit c3f837e122
No known key found for this signature in database
GPG key ID: 31D3D06D77950979
7 changed files with 84 additions and 17 deletions

View file

@ -38,14 +38,14 @@ To build the executable binaries, you need:
- MySQL/MariaDB - MySQL/MariaDB
- [GeoIP Database][geoip_doc] (optional). Place it to `./assets/geoip`, - [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 ## Installation
### For initial server setup: ### For initial server setup:
1. Download [GeoIP Database][geoip_doc] and place it to `./assets/geoip`. 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. 2. Pepare your MySQL/MariaDB.
3. Copy `.env.example` to `.env` and edit it to match with server environment. 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 4. Build the binary with `make server` (or `make build` to build both

View file

@ -14,6 +14,7 @@ import (
"time" "time"
"github.com/ditatompel/xmr-remote-nodes/internal/config" "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/ditatompel/xmr-remote-nodes/internal/monero"
"github.com/spf13/cobra" "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 { 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{ jsonData, err := json.Marshal(monero.ProbeReport{
TookTime: tookTime, TookTime: tookTime,
Message: p.message, Message: p.message,

16
internal/ip/ip.go Normal file
View file

@ -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
}

46
internal/ip/ip_test.go Normal file
View file

@ -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)
}
})
}
}

View file

@ -12,7 +12,7 @@ import (
"time" "time"
"github.com/ditatompel/xmr-remote-nodes/internal/database" "github.com/ditatompel/xmr-remote-nodes/internal/database"
"github.com/ditatompel/xmr-remote-nodes/internal/ip"
"github.com/jmoiron/sqlx/types" "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") { if strings.HasSuffix(hostname, ".onion") {
is_tor = true is_tor = true
} }
ip := ""
ipv6_only := true ipAddr := ""
ipv6_only := false
if !is_tor { if !is_tor {
hostIps, err := net.LookupIP(hostname) hostIps, err := net.LookupIP(hostname)
@ -206,12 +206,7 @@ func (r *moneroRepo) Add(protocol string, hostname string, port uint) error {
return err return err
} }
for _, hostIp := range hostIps { ipv6_only = ip.IsIPv6Only(hostIps)
if hostIp.To4() != nil {
ipv6_only = false
break
}
}
hostIp := hostIps[0] hostIp := hostIps[0]
if hostIp.IsPrivate() { 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") return errors.New("IP address is loopback address")
} }
ip = hostIp.String() ipAddr = hostIp.String()
} }
row, err := r.db.Query(` row, err := r.db.Query(`
@ -276,7 +271,7 @@ func (r *moneroRepo) Add(protocol string, hostname string, port uint) error {
port, port,
is_tor, is_tor,
"", "",
ip, ipAddr,
0, 0,
0, 0,
time.Now().Unix(), time.Now().Unix(),

View file

@ -10,7 +10,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/ditatompel/xmr-remote-nodes/internal/geo" "github.com/ditatompel/xmr-remote-nodes/internal/ip/geo"
) )
type QueryLogs struct { type QueryLogs struct {
@ -308,7 +308,8 @@ func (r *moneroRepo) ProcessJob(report ProbeReport, proberId int64) error {
city = ?, city = ?,
last_checked = ?, last_checked = ?,
last_check_status = ?, last_check_status = ?,
cors_capable = ? cors_capable = ?,
ipv6_only = ?
WHERE WHERE
id = ?` id = ?`
_, err := r.db.Exec(update, _, err := r.db.Exec(update,
@ -330,6 +331,7 @@ func (r *moneroRepo) ProcessJob(report ProbeReport, proberId int64) error {
now.Unix(), now.Unix(),
statuses, statuses,
report.Node.CORSCapable, report.Node.CORSCapable,
report.Node.IPv6Only,
report.Node.ID) report.Node.ID)
if err != nil { if err != nil {
slog.Warn(err.Error()) slog.Warn(err.Error())
@ -341,10 +343,11 @@ func (r *moneroRepo) ProcessJob(report ProbeReport, proberId int64) error {
is_available = ?, is_available = ?,
uptime = ?, uptime = ?,
last_checked = ?, last_checked = ?,
last_check_status = ? last_check_status = ?,
ipv6_only = ?
WHERE WHERE
id = ?` 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()) slog.Warn(err.Error())
} }
} }