splitting the modules

This commit is contained in:
Gregory Bednov 2025-07-15 14:59:32 +03:00
commit 8705f87504
12 changed files with 402 additions and 396 deletions

176
yggdrasil/autopeering.go Normal file
View file

@ -0,0 +1,176 @@
package yggdrasil
import (
"context"
"io/fs"
"math/rand"
"net"
"net/url"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
git "github.com/go-git/go-git/v5"
)
type Peer struct {
URL url.URL
Online bool
Latency time.Duration
}
var PublicPeers string
const repoURL = "https://github.com/yggdrasil-network/public-peers"
func readPeersFile(path string) []url.URL {
data, err := os.ReadFile(path)
if err != nil {
return nil
}
var peers []url.URL
for line := range strings.SplitSeq(string(data), "\n") {
line = strings.TrimSpace(line)
if line != "" {
url, err := url.Parse(line)
if err != nil {
panic(err)
}
peers = append(peers, *url)
}
}
return peers
}
// GetPublicPeers fetches the public-peers repository and returns a list of peer
// connection strings. If fetching fails, it falls back to reading peers from
// peers.txt in the current working directory. It returns an empty slice if no
// peers can be retrieved.
func getPublicPeers() []url.URL {
tempDir, err := os.MkdirTemp("", "public-peers-*")
if err != nil {
return readPeersFile("peers.txt")
}
defer os.RemoveAll(tempDir)
_, err = git.PlainCloneContext(context.Background(), tempDir, false, &git.CloneOptions{URL: repoURL, Depth: 1})
if err != nil {
return readPeersFile("peers.txt")
}
var peers []url.URL
re := regexp.MustCompile(`(?m)^\s*(tcp|tls)://[^\s]+`)
filepath.WalkDir(tempDir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return nil
}
if d.IsDir() || !strings.HasSuffix(d.Name(), ".md") || d.Name() == "README.md" {
return nil
}
data, err := os.ReadFile(path)
if err != nil {
return nil
}
for _, m := range re.FindAllStringSubmatch(string(data), -1) {
url, err := url.Parse(strings.TrimSpace(m[1]))
if err != nil {
panic(err)
}
peers = append(peers, *url)
}
return nil
})
if len(peers) == 0 {
return readPeersFile("peers.txt")
}
return peers
}
// Get n online peers with best latency from a peer list
func GetClosestPeers(peerList []url.URL, n int) []url.URL {
var result []url.URL
onlinePeers := testPeers(peerList)
// Filter online peers
x := 0
for _, p := range onlinePeers {
if p.Online {
onlinePeers[x] = p
x++
}
}
onlinePeers = onlinePeers[:x]
sort.Slice(onlinePeers, func(i, j int) bool {
return onlinePeers[i].Latency < onlinePeers[j].Latency
})
for i := 0; i < len(onlinePeers); i++ {
if len(result) == n {
break
}
result = append(result, onlinePeers[i].URL)
}
return result
}
// Pick n random peers from a list
func RandomPick(peerList []url.URL, n int) []url.URL {
if len(peerList) <= n {
return peerList
}
var res []url.URL
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for _, i := range r.Perm(n) {
res = append(res, peerList[i])
}
return res
}
const defaultTimeout time.Duration = time.Duration(3) * time.Second
func testPeers(peers []url.URL) []Peer {
var res []Peer
results := make(chan Peer)
for _, p := range peers {
go testPeer(p, results)
}
for range peers {
res = append(res, <-results)
}
return res
}
func testPeer(peer url.URL, results chan Peer) {
p := Peer{peer, false, 0.0}
p.Online = false
t0 := time.Now()
var network string
if peer.Scheme == "tcp" || peer.Scheme == "tls" {
network = "tcp"
} else { // skip, not supported yet
results <- p
return
}
conn, err := net.DialTimeout(network, peer.Host, defaultTimeout)
if err == nil {
t1 := time.Now()
conn.Close()
p.Online = true
p.Latency = t1.Sub(t0)
}
results <- p
}

97
yggdrasil/keys.go Normal file
View file

@ -0,0 +1,97 @@
package yggdrasil
import (
"crypto/ed25519"
"encoding/hex"
"fmt"
"os"
"strings"
"github.com/gologme/log"
"github.com/spf13/viper"
yggConfig "github.com/yggdrasil-network/yggdrasil-go/src/config"
"github.com/yggdrasil-network/yggdrasil-go/src/core"
)
func GeneratePrivateKey() yggConfig.KeyBytes {
return yggConfig.GenerateConfig().PrivateKey
}
func GetPublicKey(keyPath string) (ed25519.PublicKey, error) {
data, err := os.ReadFile(keyPath)
if err != nil {
return ed25519.PublicKey{}, err
}
decoded, err := hex.DecodeString(strings.TrimSpace(string(data)))
if err != nil {
return ed25519.PublicKey{}, err
}
if len(decoded) != ed25519.PrivateKeySize {
return ed25519.PublicKey{}, fmt.Errorf("invalid private key size: %d", len(decoded))
}
privateKey := ed25519.PrivateKey(decoded)
return privateKey.Public().(ed25519.PublicKey), nil
}
func GetYggdrasilAddress(config *viper.Viper) string {
//var remoteTcp types.TCPRemoteMappings
ygg := config.Sub("yggdrasil")
if ygg == nil {
return ""
}
//laddr := config.Sub("p2p").GetString("laddr")
//remoteTcp.Set(laddr)
cfg := yggConfig.GenerateConfig()
cfg.AdminListen = ygg.GetString("admin_listen")
cfg.Listen = ygg.GetStringSlice("listen")
cfg.Peers = ygg.GetStringSlice("peers")
cfg.AllowedPublicKeys = ygg.GetStringSlice("allowed-public-keys")
cfg.PrivateKeyPath = ygg.GetString("private-key-file")
logger := log.Default()
n := &node{}
// Setup the Yggdrasil node itself.
{
options := []core.SetupOption{
core.NodeInfo(cfg.NodeInfo),
core.NodeInfoPrivacy(cfg.NodeInfoPrivacy),
}
for _, addr := range cfg.Listen {
options = append(options, core.ListenAddress(addr))
}
for _, peer := range cfg.Peers {
options = append(options, core.Peer{URI: peer})
}
for intf, peers := range cfg.InterfacePeers {
for _, peer := range peers {
options = append(options, core.Peer{URI: peer, SourceInterface: intf})
}
}
for _, allowed := range cfg.AllowedPublicKeys {
k, err := hex.DecodeString(allowed)
if err != nil {
panic(err)
}
options = append(options, core.AllowedPublicKey(k[:]))
}
var err error
if n.core, err = core.New(cfg.Certificate, logger, options...); err != nil {
panic(err)
}
address := n.core.Address()
n.core.Stop()
return address.String()
}
}

View file

@ -2,17 +2,12 @@ package yggdrasil
import (
"context"
"crypto/ed25519"
"encoding/hex"
"errors"
"fmt"
"lbc/autopeering"
ppp "lbc/persistentpeersparser"
"net"
"os"
"os/signal"
"regexp"
"runtime"
"strings"
"syscall"
@ -45,88 +40,6 @@ type TCPRemoteListenerMapping struct {
Mapped *net.TCPListener
}
func GeneratePrivateKey() yggConfig.KeyBytes {
return yggConfig.GenerateConfig().PrivateKey
}
func GetPublicKey(keyPath string) (ed25519.PublicKey, error) {
data, err := os.ReadFile(keyPath)
if err != nil {
return ed25519.PublicKey{}, err
}
decoded, err := hex.DecodeString(strings.TrimSpace(string(data)))
if err != nil {
return ed25519.PublicKey{}, err
}
if len(decoded) != ed25519.PrivateKeySize {
return ed25519.PublicKey{}, fmt.Errorf("invalid private key size: %d", len(decoded))
}
privateKey := ed25519.PrivateKey(decoded)
return privateKey.Public().(ed25519.PublicKey), nil
}
func GetYggdrasilAddress(config *viper.Viper) string {
//var remoteTcp types.TCPRemoteMappings
ygg := config.Sub("yggdrasil")
if ygg == nil {
return ""
}
//laddr := config.Sub("p2p").GetString("laddr")
//remoteTcp.Set(laddr)
cfg := yggConfig.GenerateConfig()
cfg.AdminListen = ygg.GetString("admin_listen")
cfg.Listen = ygg.GetStringSlice("listen")
cfg.Peers = ygg.GetStringSlice("peers")
cfg.AllowedPublicKeys = ygg.GetStringSlice("allowed-public-keys")
cfg.PrivateKeyPath = ygg.GetString("private-key-file")
logger := log.Default()
n := &node{}
// Setup the Yggdrasil node itself.
{
options := []core.SetupOption{
core.NodeInfo(cfg.NodeInfo),
core.NodeInfoPrivacy(cfg.NodeInfoPrivacy),
}
for _, addr := range cfg.Listen {
options = append(options, core.ListenAddress(addr))
}
for _, peer := range cfg.Peers {
options = append(options, core.Peer{URI: peer})
}
for intf, peers := range cfg.InterfacePeers {
for _, peer := range peers {
options = append(options, core.Peer{URI: peer, SourceInterface: intf})
}
}
for _, allowed := range cfg.AllowedPublicKeys {
k, err := hex.DecodeString(allowed)
if err != nil {
panic(err)
}
options = append(options, core.AllowedPublicKey(k[:]))
}
var err error
if n.core, err = core.New(cfg.Certificate, logger, options...); err != nil {
panic(err)
}
address := n.core.Address()
n.core.Stop()
return address.String()
}
}
// The main function is responsible for configuring and starting Yggdrasil.
func Yggdrasil(config *viper.Viper, ch chan string) {
var remoteTcp types.TCPRemoteMappings
@ -157,9 +70,9 @@ func Yggdrasil(config *viper.Viper, ch chan string) {
ch <- remoteTcp[0].Mapped.String()
peers := p2p.GetString("persistent_peers")
parsed, err := ppp.ParseEntries(peers)
parsed, err := ParseEntries(peers)
if err != nil {
parsed = []ppp.ParsedEntry{}
parsed = []ParsedEntry{}
ch <- ""
log.Warnln("Warning: persistent peers has an error")
}
@ -169,7 +82,7 @@ func Yggdrasil(config *viper.Viper, ch chan string) {
cfg.AdminListen = ygg.GetString("admin_listen")
cfg.Listen = ygg.GetStringSlice("listen")
if ygg.GetString("peers") == "auto" {
publicPeers := autopeering.GetPublicPeers()
publicPeers := getPublicPeers()
var urlsAsStrings []string
for _, u := range publicPeers {
urlsAsStrings = append(urlsAsStrings, u.String())
@ -372,23 +285,4 @@ func Yggdrasil(config *viper.Viper, ch chan string) {
n.core.Stop()
}
// Helper to detect if socket address is in use
// https://stackoverflow.com/a/52152912
func isErrorAddressAlreadyInUse(err error) bool {
var eOsSyscall *os.SyscallError
if !errors.As(err, &eOsSyscall) {
return false
}
var errErrno syscall.Errno // doesn't need a "*" (ptr) because it's already a ptr (uintptr)
if !errors.As(eOsSyscall, &errErrno) {
return false
}
if errors.Is(errErrno, syscall.EADDRINUSE) {
return true
}
const WSAEADDRINUSE = 10048
if runtime.GOOS == "windows" && errErrno == WSAEADDRINUSE {
return true
}
return false
}

119
yggdrasil/peers.txt Normal file
View file

@ -0,0 +1,119 @@
tcp://ipv6.campina-grande.paraiba.brazil.yggdrasil.iasylum.net:41000
tcp://ipv4.campina-grande.paraiba.brazil.yggdrasil.iasylum.net:40000
tls://ipv6.campina-grande.paraiba.brazil.yggdrasil.iasylum.net:51000
tls://ipv4.campina-grande.paraiba.brazil.yggdrasil.iasylum.net:50000
tls://192.99.145.61:58226
tls://[2607:5300:201:3100::50a1]:58226
tcp://kusoneko.moe:9002
tls://ca1.servers.devices.cwinfo.net:58226
tcp://[2a05:9403::8b]:7743
tcp://195.123.245.146:7743
tls://95.216.5.243:18836
tls://[2a01:4f9:2a:60c::2]:18836
tls://[2a01:4f9:c010:664d::1]:61995
tls://aurora.devices.waren.io:18836
tls://fi1.servers.devices.cwinfo.net:61995
tls://65.21.57.122:61995
tls://[2001:41d0:2:c44a:51:255:223:60]:54232
tcp://62.210.85.80:39565
tls://51.255.223.60:54232
tcp://51.15.204.214:12345
tls://51.15.204.214:54321
tcp://[2001:470:1f13:e56::64]:39565
tcp://s2.i2pd.xyz:39565
tls://[2001:41d0:304:200::ace3]:23108
tls://62.210.85.80:39575
tls://[2001:470:1f13:e56::64]:39575
tls://152.228.216.112:23108
tls://s2.i2pd.xyz:39575
tls://cloudberry.fr1.servers.devices.cwinfo.net:54232
tls://fr2.servers.devices.cwinfo.net:23108
tls://163.172.31.60:12221?key=060f2d49c6a1a2066357ea06e58f5cff8c76a5c0cc513ceb2dab75c900fe183b&sni=jorropo.net
tls://jorropo.net:12221?key=060f2d49c6a1a2066357ea06e58f5cff8c76a5c0cc513ceb2dab75c900fe183b&sni=jorropo.net
tcp://94.130.203.208:5999
tls://yggdrasil.su:62586
tcp://ygg.mkg20001.io:80
tls://vpn.ltha.de:443?key=0000006149970f245e6cec43664bce203f2514b60a153e194f31e2b229a1339d
tls://de-fsn-1.peer.v4.yggdrasil.chaz6.com:4444
tcp://p2p-node.de:1337?key=000000d80a2d7b3126ea65c8c08fc751088c491a5cdd47eff11c86fa1e4644ae
tcp://phrl42.ydns.eu:8842
tcp://yggdrasil.su:62486
tls://ygg.mkg20001.io:443
tls://p2p-node.de:1338?key=000000d80a2d7b3126ea65c8c08fc751088c491a5cdd47eff11c86fa1e4644ae
tcp://ygg1.mk16.de:1337?key=0000000087ee9949eeab56bd430ee8f324cad55abf3993ed9b9be63ce693e18a
tls://ygg1.mk16.de:1338?key=0000000087ee9949eeab56bd430ee8f324cad55abf3993ed9b9be63ce693e18a
tls://minecast.xyz:3785
tls://45.147.198.155:6010
tcp://ygg-nl.incognet.io:8883
tcp://vpn.itrus.su:7991
tls://ygg-nl.incognet.io:8884
tls://109.107.173.235:9111
tls://94.103.82.150:8080
tls://aaoth.xyz:25565
tcp://aaoth.xyz:7777
tls://[2001:41d0:601:1100::cf2]:11129
tls://54.37.137.221:11129
tls://pl1.servers.devices.cwinfo.net:11129
tcp://185.165.169.234:8880
tls://185.165.169.234:8443
tcp://188.225.9.167:18226
tcp://92.124.136.131:30111
tls://188.225.9.167:18227
tcp://ygg.tomasgl.ru:61933?key=c5e0c28a600c2118e986196a0bbcbda4934d8e9278ceabea48838dc5d8fae576
tls://[2a01:d0:ffff:4353::2]:6010
tls://avevad.com:1337
tcp://itcom.multed.com:7991
tls://ygg.tomasgl.ru:61944?key=c5e0c28a600c2118e986196a0bbcbda4934d8e9278ceabea48838dc5d8fae576
tcp://srv.itrus.su:7991
tcp://box.paulll.cc:13337
tcp://yggno.de:18226
tls://yggno.de:18227
tcp://ekb.itrus.su:7991
tls://box.paulll.cc:13338
tls://yggpvs.duckdns.org:8443
tcp://158.101.229.219:17002
tcp://[2603:c023:8001:1600:35e0:acde:2c6e:b27f]:17002
tls://[2603:c023:8001:1600:35e0:acde:2c6e:b27f]:17001
tls://158.101.229.219:17001
tcp://sin.yuetau.net:6642
tls://sin.yuetau.net:6643
tcp://y.zbin.eu:7743
tcp://[2a04:5b81:2010:5000:27d3:6343:a821:eb1c]:2000
tls://[2a04:5b81:2010:5000:27d3:6343:a821:eb1c]:2001
tls://[2a07:e01:105:444:c634:6bff:feb5:6e28]:7040
tls://185.130.44.194:7040
tls://ygg.ace.ctrl-c.liu.se:9999?key=5636b3af4738c3998284c4805d91209cab38921159c66a6f359f3f692af1c908
tcp://ygg.ace.ctrl-c.liu.se:9998?key=5636b3af4738c3998284c4805d91209cab38921159c66a6f359f3f692af1c908
tcp://212.154.86.134:8800
tls://212.154.86.134:4433
tcp://ip6-antalya.ddns.net:8800
tls://ip6-antalya.ddns.net:4433
tcp://193.111.114.28:8080
tls://193.111.114.28:1443
tls://91.224.254.114:18001
tcp://78.27.153.163:33165
tls://78.27.153.163:3784
tls://78.27.153.163:179
tls://78.27.153.163:3785
tls://78.27.153.163:33166
tls://51.38.64.12:28395
tls://185.175.90.87:43006
tls://[2a10:4740:40:0:2222:3f9c:b7cf:1]:43006
tls://uk1.servers.devices.cwinfo.net:28395
tcp://curiosity.tdjs.tech:30003
tcp://50.236.201.218:56088
tcp://longseason.1200bps.xyz:13121
tcp://149.28.123.138:8008
tcp://lancis.iscute.moe:49273
tcp://zabugor.itrus.su:7991
tls://108.175.10.127:61216
tls://longseason.1200bps.xyz:13122
tls://167.160.89.98:7040
tcp://tasty.chowder.land:9002
tls://44.234.134.124:443
tls://[2605:9f80:2000:64::2]:7040
tls://bazari.sh:3725
tls://tasty.chowder.land:9001
tls://5.161.114.182:443
tls://5.161.139.99:443
tls://lancis.iscute.moe:49274

View file

@ -0,0 +1,64 @@
package yggdrasil
import (
"fmt"
"regexp"
"strconv"
"strings"
)
type ParsedEntry struct {
ID string
Proto string
Address string
Port *int // nil если не указан
}
var entryPattern = regexp.MustCompile(`^([a-fA-F0-9]+)@((?:[a-zA-Z]+://)?(?:\[[^\]]+\]|[^:]+))(?:[:](\d+))?$`)
func ParseEntries(input string) ([]ParsedEntry, error) {
entries := strings.Split(input, ",")
var result []ParsedEntry
for _, entry := range entries {
entry = strings.TrimSpace(entry)
if entry == "" {
continue
}
matches := entryPattern.FindStringSubmatch(entry)
if matches == nil {
return nil, fmt.Errorf("invalid entry: %s", entry)
}
id := matches[1]
rawAddr := matches[2]
portStr := matches[3]
proto := ""
address := rawAddr
if strings.Contains(rawAddr, "://") {
split := strings.SplitN(rawAddr, "://", 2)
proto = split[0]
address = split[1]
}
var port *int
if portStr != "" {
p, err := strconv.Atoi(portStr)
if err != nil {
return nil, fmt.Errorf("invalid port in entry: %s", entry)
}
port = &p
}
result = append(result, ParsedEntry{
ID: id,
Proto: proto,
Address: address,
Port: port,
})
}
return result, nil
}