Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/build-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ jobs:
name: Building, ${{ matrix.TARGET }}
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Setup Go
uses: actions/setup-go@v2
uses: actions/setup-go@v5
with:
go-version: 1.20.12
go-version: 1.24.1

- name: Update sources
if: matrix.TARGET == 'linux/aarch64' || matrix.TARGET == 'windows/x64'
Expand Down
5 changes: 0 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
# DNSSeeder

**This project is currently under active development and is in Beta
state.**

[![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](https://choosealicense.com/licenses/isc/)

DNSSeeder exposes a list of known peers to any new peer joining the
Expand All @@ -23,8 +20,6 @@ of a random selection of the reliable nodes it knows about.

It is written in Go (golang).

This project is currently under active development and is in Beta state.

## Requirements

Latest version of [Go](http://golang.org)
Expand Down
39 changes: 39 additions & 0 deletions checkversion/checkversion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package checkversion

import (
"github.com/hashicorp/go-version"
"github.com/pkg/errors"
_ "net/http/pprof"
"regexp"
"strings"
)

var versionRegexp = regexp.MustCompile(`\b(\d+\.\d+\.\d+)\b`)

func CheckVersion(minUaVer string, userAgent string) error {
if minUaVer == "" {
return nil
}
minVer, err := version.NewVersion(minUaVer)
if err != nil {
return err
}
trimmed := strings.TrimPrefix(userAgent, "/")
end := strings.Index(trimmed, "/")
if end == -1 {
return errors.New("Invalid userAgent format")
}
firstGroup := trimmed[:end]
matches := versionRegexp.FindStringSubmatch(firstGroup)
if len(matches) < 2 {
return errors.New("No valid version found in userAgent")
}
clientVer, err := version.NewVersion(matches[1])
if err != nil {
return err
}
if clientVer.LessThan(minVer) {
return errors.New("UserAgent version is below minimum required")
}
return nil
}
41 changes: 41 additions & 0 deletions checkversion/checkversion_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package checkversion

import (
"testing"
)

func TestCheckVersion(t *testing.T) {
tests := []struct {
minUaVer string
userAgent string
shouldFail bool
}{
{"0.3.14", "/spectred:0.3.14/spectred:0.3.14/", false},
{"0.3.14", "/spectred:0.3.14/spectred:0.0.0/", false},
{"0.3.14", "/spectred:0.3.14/spectred:0.3.14(spectre-desktop_0.3.14)/", false},
{"0.3.14", "/spectred:0.3.19/spectred:0.3.19/", false},
{"0.3.14", "/spectred:1.1.0/", false},

{"0.3.57", "/spectred:0.3.14/spectred:0.3.14/", true},
{"0.3.57", "/spectred:0.3.14/spectred:0.0.0/", true},
{"0.3.57", "/spectred:0.3.14/spectred:0.3.14(spectre-desktop_0.3.14)/", true},
{"0.3.57", "/spectred:0.3.57/spectred:0.3.57/", false},
{"0.3.57", "/spectred:1.1.0/", false},

{"1.0.0", "/spectred:0.3.14/spectred:0.3.14/", true},
{"1.0.0", "/spectred:0.3.14/spectred:0.0.0/", true},
{"1.0.0", "/spectred:0.3.14/spectred:0.3.14(spectre-desktop_0.3.14)/", true},
{"1.0.0", "/spectred:0.18.9/spectred:0.18.9/", true},
{"1.0.0", "/spectred:1.1.0/", false},
}

for _, tt := range tests {
err := CheckVersion(tt.minUaVer, tt.userAgent)
if tt.shouldFail && err == nil {
t.Errorf("Expected failure for %q with %q, but got nil", tt.minUaVer, tt.userAgent)
}
if !tt.shouldFail && err != nil {
t.Errorf("Unexpected error for %q with %q: %v", tt.minUaVer, tt.userAgent, err)
}
}
}
32 changes: 31 additions & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import (

"github.com/spectre-project/spectred/infrastructure/config"

"github.com/spectre-project/dnsseeder/version"
"github.com/pkg/errors"
"github.com/spectre-project/dnsseeder/version"

"github.com/jessevdk/go-flags"
"github.com/spectre-project/spectred/util"
Expand All @@ -28,6 +28,7 @@ const (
defaultListenPort = "5354"
defaultGrpcListenPort = "3737"
defaultLogLevel = "info"
defaultThreads = 8
)

var (
Expand Down Expand Up @@ -55,8 +56,12 @@ type ConfigFlags struct {
Seeder string `short:"s" long:"default-seeder" description:"IP address of a working node, optionally with a port specifier"`
Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
GRPCListen string `long:"grpclisten" description:"Listen gRPC requests on address:port"`
MinProtoVer uint8 `short:"v" long:"minprotocolversion" description:"Minimum p2p protocol version for nodes."`
MinUaVer string `long:"minuseragentversion" description:"Minimum user agent version for nodes. Prefer minprotocolversion if possible."`
NetSuffix uint16 `long:"netsuffix" description:"Testnet network suffix number"`
NoLogFiles bool `long:"nologfiles" description:"Disable logging to file"`
LogLevel string `long:"loglevel" description:"Loglevel for stdout (console). Default: info"`
Threads uint8 `long:"threads" description:"Number of threads to use for polling."`
config.NetworkFlags
}

Expand Down Expand Up @@ -104,6 +109,7 @@ func loadConfig() (*ConfigFlags, error) {
Listen: normalizeAddress("localhost", defaultListenPort),
GRPCListen: normalizeAddress("localhost", defaultGrpcListenPort),
LogLevel: defaultLogLevel,
Threads: defaultThreads,
}

preCfg := activeConfig
Expand Down Expand Up @@ -171,6 +177,23 @@ func loadConfig() (*ConfigFlags, error) {
return nil, err
}

if activeConfig.NetSuffix != 0 {
if !activeConfig.Testnet {
return nil, errors.New("Net suffix can only be used with testnet")
}

switch activeConfig.NetSuffix {
case 8:
activeConfig.NetParams().DefaultPort = "18211"
activeConfig.NetParams().Name = "spectre-testnet-8"
case 11:
activeConfig.NetParams().DefaultPort = "18311"
activeConfig.NetParams().Name = "spectre-testnet-11"
default:
return nil, errors.New("Only explicit testnet suffixes are 8 and 11")
}
}

activeConfig.AppDir = cleanAndExpandPath(activeConfig.AppDir)
// Append the network type to the app directory so it is "namespaced"
// per network.
Expand All @@ -196,6 +219,13 @@ func loadConfig() (*ConfigFlags, error) {

initLog(activeConfig.NoLogFiles, activeConfig.LogLevel, appLogFile, appErrLogFile)

if activeConfig.Threads < 1 || activeConfig.Threads > 32 {
str := "threads must be between 1 and 32"
err := errors.Errorf(str)
fmt.Fprintln(os.Stderr, err)
return nil, err
}

return activeConfig, nil
}

Expand Down
4 changes: 2 additions & 2 deletions dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import (
"github.com/spectre-project/spectred/domain/consensus/model/externalapi"
"github.com/spectre-project/spectred/domain/consensus/utils/subnetworks"

"github.com/spectre-project/spectred/infrastructure/network/dnsseed"
"github.com/pkg/errors"
"github.com/spectre-project/spectred/infrastructure/network/dnsseed"

"github.com/miekg/dns"
)
Expand Down Expand Up @@ -141,7 +141,7 @@ func (d *DNSServer) validateDNSRequest(addr *net.UDPAddr, b []byte) (dnsMsg *dns
domainName = strings.ToLower(dnsMsg.Question[0].Name)
ff := strings.LastIndex(domainName, d.hostname)
if ff < 0 {
str := fmt.Sprintf("invalid name: %s", dnsMsg.Question[0].Name)
str := fmt.Sprintf("%s: invalid name: %s", addr, dnsMsg.Question[0].Name)
log.Infof("%s", str)
return nil, "", "", errors.Errorf("%s", str)
}
Expand Down
77 changes: 51 additions & 26 deletions dnsseed.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package main

import (
"fmt"
"github.com/spectre-project/dnsseeder/checkversion"
"net"
"os"
"strconv"
Expand All @@ -14,9 +15,9 @@ import (
"sync/atomic"
"time"

"github.com/spectre-project/dnsseeder/netadapter"
"github.com/spectre-project/spectred/app/protocol/common"
"github.com/spectre-project/spectred/infrastructure/config"
"github.com/spectre-project/spectred/infrastructure/network/netadapter/standalone"

"github.com/pkg/errors"

Expand Down Expand Up @@ -53,9 +54,9 @@ func hostLookup(host string) ([]net.IP, error) {
func creep() {
defer wg.Done()

netAdapter, err := standalone.NewMinimalNetAdapter(&config.Config{Flags: &config.Flags{NetworkFlags: ActiveConfig().NetworkFlags}})
if err != nil {
panic(errors.Wrap(err, "Could not start net adapter"))
var netAdapters []*netadapter.DnsseedNetAdapter
for i := uint8(0); i < ActiveConfig().Threads; i++ {
netAdapters = append(netAdapters, newNetAdapter())
}

var knownPeers []*appmessage.NetAddress
Expand Down Expand Up @@ -84,8 +85,8 @@ func creep() {

amgr.AddAddresses(knownPeers)
for _, peer := range knownPeers {
amgr.Good(peer.IP, nil)
amgr.Attempt(peer.IP)
amgr.Attempt(peer)
amgr.Good(peer, nil, nil)
}
}

Expand All @@ -101,8 +102,8 @@ func creep() {
peers = amgr.Addresses()
}
if len(peers) == 0 {
log.Infof("No stale addresses -- sleeping for 10 minutes")
for i := 0; i < 600; i++ {
log.Infof("No stale addresses -- sleeping for 1 minute")
for i := 0; i < 60; i++ {
time.Sleep(time.Second)
if atomic.LoadInt32(&systemShutdown) != 0 {
log.Infof("Creep thread shutdown")
Expand All @@ -112,18 +113,19 @@ func creep() {
continue
}

for _, addr := range peers {
for i, addr := range peers {
if atomic.LoadInt32(&systemShutdown) != 0 {
log.Infof("Waiting creep threads to terminate")
wgCreep.Wait()
log.Infof("Creep thread shutdown")
return
}
wgCreep.Add(1)
i := i
go func(addr *appmessage.NetAddress) {
defer wgCreep.Done()

err := pollPeer(netAdapter, addr)
err := pollPeer(netAdapters[i%len(netAdapters)], addr)
if err != nil {
log.Debugf(err.Error())
if defaultSeeder != nil && addr == defaultSeeder {
Expand All @@ -136,16 +138,24 @@ func creep() {
}
}

func pollPeer(netAdapter *standalone.MinimalNetAdapter, addr *appmessage.NetAddress) error {
defer amgr.Attempt(addr.IP)
func pollPeer(netAdapter *netadapter.DnsseedNetAdapter, addr *appmessage.NetAddress) error {
amgr.Attempt(addr)

peerAddress := net.JoinHostPort(addr.IP.String(), strconv.Itoa(int(addr.Port)))
routes, err := netAdapter.Connect(peerAddress)

log.Debugf("Polling peer %s", peerAddress)
routes, msgVersion, err := netAdapter.Connect(peerAddress)
if err != nil {
return errors.Wrapf(err, "could not connect to %s", peerAddress)
}
defer routes.Disconnect()

// Abort before collecting peers for nodes below minimum protocol
if ActiveConfig().MinProtoVer > 0 && msgVersion.ProtocolVersion < uint32(ActiveConfig().MinProtoVer) {
return errors.Errorf("Peer %s (%s) protocol version %d is below minimum: %d",
peerAddress, msgVersion.UserAgent, msgVersion.ProtocolVersion, ActiveConfig().MinProtoVer)
}

msgRequestAddresses := appmessage.NewMsgRequestAddresses(true, nil)
err = routes.OutgoingRoute.Enqueue(msgRequestAddresses)
if err != nil {
Expand All @@ -159,14 +169,29 @@ func pollPeer(netAdapter *standalone.MinimalNetAdapter, addr *appmessage.NetAddr
msgAddresses := message.(*appmessage.MsgAddresses)

added := amgr.AddAddresses(msgAddresses.AddressList)
log.Infof("Peer %s sent %d addresses, %d new",
peerAddress, len(msgAddresses.AddressList), added)

amgr.Good(addr.IP, nil)

log.Infof("Peer %s (%s) sent %d addresses, %d new",
peerAddress, msgVersion.UserAgent, len(msgAddresses.AddressList), added)

// Abort after collecting peers for nodes below minimum user agent version
if ActiveConfig().MinUaVer != "" {
err = checkversion.CheckVersion(ActiveConfig().MinUaVer, msgVersion.UserAgent)
if err != nil {
return errors.Wrapf(err, "Peer %s version %s doesn't satisfy minimum: %s",
peerAddress, msgVersion.UserAgent, ActiveConfig().MinUaVer)
}
}
amgr.Good(addr, &msgVersion.UserAgent, nil)
return nil
}

func newNetAdapter() *netadapter.DnsseedNetAdapter {
netAdapter, err := netadapter.NewDnsseedNetAdapter(&config.Config{Flags: &config.Flags{NetworkFlags: ActiveConfig().NetworkFlags}})
if err != nil {
panic(errors.Wrap(err, "Could not start net adapter"))
}
return netAdapter
}

func main() {
defer panics.HandlePanic(log, "main", nil)
interrupt := signal.InterruptListener()
Expand Down Expand Up @@ -200,29 +225,29 @@ func main() {
if len(cfg.Seeder) != 0 {
// Prepare the seeder address, supporting either a simple IP with default network port
// or a full IP:port format
seederIp := cfg.Seeder
seederIP := cfg.Seeder
seederPort := peersDefaultPort

// Try to split seeder host and port
foundIp, foundPort, err := net.SplitHostPort(cfg.Seeder)
if (err == nil) {
seederIp = foundIp
foundIP, foundPort, err := net.SplitHostPort(cfg.Seeder)
if err == nil {
seederIP = foundIP
seederPort, err = strconv.Atoi(foundPort)
if err != nil {
log.Errorf("Invalid seeder port: %s", foundPort)
return
}
}

ip := net.ParseIP(seederIp)
ip := net.ParseIP(seederIP)
if ip == nil {
hostAddrs, err := net.LookupHost(seederIp)
hostAddrs, err := net.LookupHost(seederIP)
if err != nil {
log.Warnf("Failed to resolve seed host: %v, %v, ignoring", seederIp, err)
log.Warnf("Failed to resolve seed host: %v, %v, ignoring", seederIP, err)
} else {
ip = net.ParseIP(hostAddrs[0])
if ip == nil {
log.Warnf("Failed to resolve seed host: %v, ignoring", seederIp)
log.Warnf("Failed to resolve seed host: %v, ignoring", seederIP)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -- multistage docker build: stage #1: build stage
FROM golang:1.18-alpine AS build
FROM golang:1.24.1-alpine AS build

RUN mkdir -p /go/src/github.com/spectre-project/dnsseeder

Expand Down
Loading