feat: Added IPv6 nodes support (alpha) #84

This commit accept IPv6 nodes submission.

When user submit new public node, the server will check IP addresses
from given hostname. If host IP addresses doesn't have IPv4, it will
be recorded as "IPv6 only" node.

Probers that support IPv6 may add `IPV6_CAPABLE=true` to the `.env`
file.

Please note that this feature still experimental and may not being
merged to the main branch.
This commit is contained in:
Christian Ditaputratama 2024-09-06 00:08:59 +07:00
parent 187a7bc84a
commit 518d4b4335
No known key found for this signature in database
GPG key ID: 31D3D06D77950979
8 changed files with 67 additions and 21 deletions

View file

@ -8,6 +8,7 @@ SERVER_ENDPOINT="http://127.0.0.1:18901"
API_KEY= API_KEY=
ACCEPT_TOR=false ACCEPT_TOR=false
TOR_SOCKS="127.0.0.1:9050" TOR_SOCKS="127.0.0.1:9050"
IPV6_CAPABLE=false
# Server Config # Server Config
# ############# # #############

View file

@ -36,20 +36,22 @@ func (err errProber) Error() string {
} }
type proberClient struct { type proberClient struct {
endpoint string // server endpoint endpoint string // server endpoint
apiKey string // prober api key apiKey string // prober api key
acceptTor bool // accept tor acceptTor bool // accept tor
torSOCKS string // IP:Port of tor socks torSOCKS string // IP:Port of tor socks
message string // message to include when reporting back to server acceptIPv6 bool // accept ipv6
message string // message to include when reporting back to server
} }
func newProber() *proberClient { func newProber() *proberClient {
cfg := config.AppCfg() cfg := config.AppCfg()
return &proberClient{ return &proberClient{
endpoint: cfg.ServerEndpoint, endpoint: cfg.ServerEndpoint,
apiKey: cfg.APIKey, apiKey: cfg.APIKey,
acceptTor: cfg.AcceptTor, acceptTor: cfg.AcceptTor,
torSOCKS: cfg.TorSOCKS, torSOCKS: cfg.TorSOCKS,
acceptIPv6: cfg.IPv6Capable,
} }
} }
@ -85,6 +87,10 @@ func (p *proberClient) SetAcceptTor(acceptTor bool) {
p.acceptTor = acceptTor p.acceptTor = acceptTor
} }
func (p *proberClient) SetAcceptIPv6(acceptIPv6 bool) {
p.acceptIPv6 = acceptIPv6
}
// Fetch a new job from the server, fetches node info, and sends it to the server // Fetch a new job from the server, fetches node info, and sends it to the server
func (p *proberClient) Run() error { func (p *proberClient) Run() error {
if err := p.validateConfig(); err != nil { if err := p.validateConfig(); err != nil {
@ -121,20 +127,26 @@ func (p *proberClient) validateConfig() error {
// Get monero node info to fetch from the server // Get monero node info to fetch from the server
func (p *proberClient) fetchJob() (monero.Node, error) { func (p *proberClient) fetchJob() (monero.Node, error) {
queryParams := "" acceptTor := 0
if p.acceptTor { if p.acceptTor {
queryParams = "?accept_tor=1" acceptTor = 1
}
acceptIPv6 := 0
if p.acceptIPv6 {
acceptIPv6 = 1
} }
var node monero.Node var node monero.Node
uri := fmt.Sprintf("%s/api/v1/job%s", p.endpoint, queryParams) uri := fmt.Sprintf("%s/api/v1/job?accept_tor=%d&accept_ipv6=%d", p.endpoint, acceptTor, acceptIPv6)
slog.Info(fmt.Sprintf("[PROBE] Getting node from %s", uri)) slog.Info(fmt.Sprintf("[PROBE] Getting node from %s", uri))
req, err := http.NewRequest(http.MethodGet, uri, nil) req, err := http.NewRequest(http.MethodGet, uri, nil)
if err != nil { if err != nil {
return node, err return node, err
} }
req.Header.Add(monero.ProberAPIKey, p.apiKey) req.Header.Add(monero.ProberAPIKey, p.apiKey)
req.Header.Set("User-Agent", RPCUserAgent) req.Header.Set("User-Agent", RPCUserAgent)

View file

@ -42,7 +42,7 @@
<section id="form-add-monero-node"> <section id="form-add-monero-node">
<div class="section-container text-center"> <div class="section-container text-center">
<p>Enter your Monero node information below (IPv4 host only):</p> <p>Enter your Monero node information below (IPv6 host check is experimental):</p>
<form <form
class="mx-auto w-full max-w-3xl py-2" class="mx-auto w-full max-w-3xl py-2"

View file

@ -24,6 +24,7 @@ type App struct {
APIKey string APIKey string
AcceptTor bool AcceptTor bool
TorSOCKS string TorSOCKS string
IPv6Capable bool
} }
func init() { func init() {
@ -65,4 +66,5 @@ func LoadApp() {
app.APIKey = os.Getenv("API_KEY") app.APIKey = os.Getenv("API_KEY")
app.AcceptTor, _ = strconv.ParseBool(os.Getenv("ACCEPT_TOR")) app.AcceptTor, _ = strconv.ParseBool(os.Getenv("ACCEPT_TOR"))
app.TorSOCKS = os.Getenv("TOR_SOCKS") app.TorSOCKS = os.Getenv("TOR_SOCKS")
app.IPv6Capable, _ = strconv.ParseBool(os.Getenv("IPV6_CAPABLE"))
} }

View file

@ -7,7 +7,7 @@ import (
type migrateFn func(*DB) error type migrateFn func(*DB) error
var dbMigrate = [...]migrateFn{v1, v2} var dbMigrate = [...]migrateFn{v1, v2, v3}
func MigrateDb(db *DB) error { func MigrateDb(db *DB) error {
version := getSchemaVersion(db) version := getSchemaVersion(db)
@ -256,3 +256,19 @@ func v2(db *DB) error {
return nil return nil
} }
func v3(db *DB) error {
slog.Debug("[DB] Migrating database schema version 3")
// table: tbl_node
slog.Debug("[DB] Adding ipv6_only column to tbl_node")
_, err := db.Exec(`
ALTER TABLE tbl_node
ADD ipv6_only TINYINT(1) UNSIGNED NOT NULL DEFAULT '0'
AFTER cors_capable;`)
if err != nil {
return err
}
return nil
}

View file

@ -171,9 +171,10 @@ func Countries(c *fiber.Ctx) error {
// This handler should protected by `CheckProber` middleware. // This handler should protected by `CheckProber` middleware.
func GiveJob(c *fiber.Ctx) error { func GiveJob(c *fiber.Ctx) error {
acceptTor := c.QueryInt("accept_tor", 0) acceptTor := c.QueryInt("accept_tor", 0)
acceptIPv6 := c.QueryInt("accept_ipv6", 0)
moneroRepo := monero.New() moneroRepo := monero.New()
node, err := moneroRepo.GiveJob(acceptTor) node, err := moneroRepo.GiveJob(acceptTor, acceptIPv6)
if err != nil { if err != nil {
return c.JSON(fiber.Map{ return c.JSON(fiber.Map{
"status": "error", "status": "error",

View file

@ -54,6 +54,7 @@ type Node struct {
FailedCount uint `json:"failed_count,omitempty" db:"failed_count"` FailedCount uint `json:"failed_count,omitempty" db:"failed_count"`
LastCheckStatus types.JSONText `json:"last_check_statuses" db:"last_check_status"` LastCheckStatus types.JSONText `json:"last_check_statuses" db:"last_check_status"`
CORSCapable bool `json:"cors" db:"cors_capable"` CORSCapable bool `json:"cors" db:"cors_capable"`
IPv6Only bool `json:"ipv6_only" db:"ipv6_only"`
} }
// Get node from database by id // Get node from database by id
@ -197,16 +198,22 @@ func (r *moneroRepo) Add(protocol string, hostname string, port uint) error {
} }
ip := "" ip := ""
ipv6_only := true
if !is_tor { if !is_tor {
hostIps, err := net.LookupIP(hostname) hostIps, err := net.LookupIP(hostname)
if err != nil { if err != nil {
return err return err
} }
hostIp := hostIps[0].To4() for _, hostIp := range hostIps {
if hostIp == nil { if hostIp.To4() != nil {
return errors.New("Host IP is not IPv4") ipv6_only = false
break
}
} }
hostIp := hostIps[0]
if hostIp.IsPrivate() { if hostIp.IsPrivate() {
return errors.New("IP address is private") return errors.New("IP address is private")
} }
@ -248,7 +255,8 @@ func (r *moneroRepo) Add(protocol string, hostname string, port uint) error {
lon, lon,
date_entered, date_entered,
last_checked, last_checked,
last_check_status last_check_status,
ipv6_only
) VALUES ( ) VALUES (
?, ?,
?, ?,
@ -260,6 +268,7 @@ func (r *moneroRepo) Add(protocol string, hostname string, port uint) error {
?, ?,
?, ?,
?, ?,
?,
? ?
)`, )`,
protocol, protocol,
@ -272,7 +281,8 @@ func (r *moneroRepo) Add(protocol string, hostname string, port uint) error {
0, 0,
time.Now().Unix(), time.Now().Unix(),
0, 0,
string(statusDb)) string(statusDb),
ipv6_only)
if err != nil { if err != nil {
return err return err
} }

View file

@ -108,7 +108,7 @@ func (r *moneroRepo) Logs(q QueryLogs) (FetchLogs, error) {
} }
// GiveJob returns node that should be probed for the next time // GiveJob returns node that should be probed for the next time
func (r *moneroRepo) GiveJob(acceptTor int) (Node, error) { func (r *moneroRepo) GiveJob(acceptTor, acceptIPv6 int) (Node, error) {
args := []interface{}{} args := []interface{}{}
wq := []string{} wq := []string{}
where := "" where := ""
@ -117,6 +117,10 @@ func (r *moneroRepo) GiveJob(acceptTor int) (Node, error) {
wq = append(wq, "is_tor = ?") wq = append(wq, "is_tor = ?")
args = append(args, 0) args = append(args, 0)
} }
if acceptIPv6 != 1 {
wq = append(wq, "ipv6_only = ?")
args = append(args, 0)
}
if len(wq) > 0 { if len(wq) > 0 {
where = "WHERE " + strings.Join(wq, " AND ") where = "WHERE " + strings.Join(wq, " AND ")