mirror of
https://github.com/yggdrasil-network/yggdrasil-go.git
synced 2024-11-23 10:05:17 +00:00
Yggdrasil 0.5 RC1 (merge future
into develop
)
Merge `future` into `develop`
This commit is contained in:
commit
e110dd46fd
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19
|
||||
go-version: 1.21
|
||||
- uses: actions/checkout@v3
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
@ -51,7 +51,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
goversion: ["1.17", "1.18", "1.19", "1.20"]
|
||||
goversion: ["1.20", "1.21"]
|
||||
|
||||
name: Build & Test (Linux, Go ${{ matrix.goversion }})
|
||||
needs: [lint]
|
||||
@ -75,7 +75,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
goversion: ["1.17", "1.18", "1.19", "1.20"]
|
||||
goversion: ["1.20", "1.21"]
|
||||
|
||||
name: Build & Test (Windows, Go ${{ matrix.goversion }})
|
||||
needs: [lint]
|
||||
@ -99,7 +99,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
goversion: ["1.17", "1.18", "1.19", "1.20"]
|
||||
goversion: ["1.20", "1.21"]
|
||||
|
||||
name: Build & Test (macOS, Go ${{ matrix.goversion }})
|
||||
needs: [lint]
|
||||
|
753
CHANGELOG.md
753
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,4 @@
|
||||
/*
|
||||
|
||||
This file generates crypto keys.
|
||||
It prints out a new set of keys each time if finds a "better" one.
|
||||
By default, "better" means a higher NodeID (-> higher IP address).
|
||||
@ -8,7 +7,6 @@ This is because the IP address format can compress leading 1s in the address, to
|
||||
If run with the "-sig" flag, it generates signing keys instead.
|
||||
A "better" signing key means one with a higher TreeID.
|
||||
This only matters if it's high enough to make you the root of the tree.
|
||||
|
||||
*/
|
||||
package main
|
||||
|
||||
|
@ -1,34 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
|
||||
"github.com/gologme/log"
|
||||
gsyslog "github.com/hashicorp/go-syslog"
|
||||
"github.com/hjson/hjson-go"
|
||||
"github.com/hjson/hjson-go/v4"
|
||||
"github.com/kardianos/minwinsvc"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc"
|
||||
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
||||
@ -44,122 +37,13 @@ type node struct {
|
||||
admin *admin.AdminSocket
|
||||
}
|
||||
|
||||
func readConfig(log *log.Logger, useconf bool, useconffile string, normaliseconf bool) *config.NodeConfig {
|
||||
// Use a configuration file. If -useconf, the configuration will be read
|
||||
// from stdin. If -useconffile, the configuration will be read from the
|
||||
// filesystem.
|
||||
var conf []byte
|
||||
var err error
|
||||
if useconffile != "" {
|
||||
// Read the file from the filesystem
|
||||
conf, err = os.ReadFile(useconffile)
|
||||
} else {
|
||||
// Read the file from stdin.
|
||||
conf, err = io.ReadAll(os.Stdin)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// If there's a byte order mark - which Windows 10 is now incredibly fond of
|
||||
// throwing everywhere when it's converting things into UTF-16 for the hell
|
||||
// of it - remove it and decode back down into UTF-8. This is necessary
|
||||
// because hjson doesn't know what to do with UTF-16 and will panic
|
||||
if bytes.Equal(conf[0:2], []byte{0xFF, 0xFE}) ||
|
||||
bytes.Equal(conf[0:2], []byte{0xFE, 0xFF}) {
|
||||
utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM)
|
||||
decoder := utf.NewDecoder()
|
||||
conf, err = decoder.Bytes(conf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
// Generate a new configuration - this gives us a set of sane defaults -
|
||||
// then parse the configuration we loaded above on top of it. The effect
|
||||
// of this is that any configuration item that is missing from the provided
|
||||
// configuration will use a sane default.
|
||||
cfg := defaults.GenerateConfig()
|
||||
var dat map[string]interface{}
|
||||
if err := hjson.Unmarshal(conf, &dat); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Sanitise the config
|
||||
confJson, err := json.Marshal(dat)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := json.Unmarshal(confJson, &cfg); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Overlay our newly mapped configuration onto the autoconf node config that
|
||||
// we generated above.
|
||||
if err = mapstructure.Decode(dat, &cfg); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
// Generates a new configuration and returns it in HJSON format. This is used
|
||||
// with -genconf.
|
||||
func doGenconf(isjson bool) string {
|
||||
cfg := defaults.GenerateConfig()
|
||||
var bs []byte
|
||||
var err error
|
||||
if isjson {
|
||||
bs, err = json.MarshalIndent(cfg, "", " ")
|
||||
} else {
|
||||
bs, err = hjson.Marshal(cfg)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return string(bs)
|
||||
}
|
||||
|
||||
func setLogLevel(loglevel string, logger *log.Logger) {
|
||||
levels := [...]string{"error", "warn", "info", "debug", "trace"}
|
||||
loglevel = strings.ToLower(loglevel)
|
||||
|
||||
contains := func() bool {
|
||||
for _, l := range levels {
|
||||
if l == loglevel {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if !contains() { // set default log level
|
||||
logger.Infoln("Loglevel parse failed. Set default level(info)")
|
||||
loglevel = "info"
|
||||
}
|
||||
|
||||
for _, l := range levels {
|
||||
logger.EnableLevel(l)
|
||||
if l == loglevel {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type yggArgs struct {
|
||||
genconf bool
|
||||
useconf bool
|
||||
normaliseconf bool
|
||||
confjson bool
|
||||
autoconf bool
|
||||
ver bool
|
||||
getaddr bool
|
||||
getsnet bool
|
||||
useconffile string
|
||||
logto string
|
||||
loglevel string
|
||||
}
|
||||
|
||||
func getArgs() yggArgs {
|
||||
// The main function is responsible for configuring and starting Yggdrasil.
|
||||
func main() {
|
||||
genconf := flag.Bool("genconf", false, "print a new config to stdout")
|
||||
useconf := flag.Bool("useconf", false, "read HJSON/JSON config from stdin")
|
||||
useconffile := flag.String("useconffile", "", "read HJSON/JSON config from specified file path")
|
||||
normaliseconf := flag.Bool("normaliseconf", false, "use in combination with either -useconf or -useconffile, outputs your configuration normalised")
|
||||
exportkey := flag.Bool("exportkey", false, "use in combination with either -useconf or -useconffile, outputs your private key in PEM format")
|
||||
confjson := flag.Bool("json", false, "print configuration from -genconf or -normaliseconf as JSON instead of HJSON")
|
||||
autoconf := flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)")
|
||||
ver := flag.Bool("version", false, "prints the version of this build")
|
||||
@ -168,34 +52,26 @@ func getArgs() yggArgs {
|
||||
getsnet := flag.Bool("subnet", false, "returns the IPv6 subnet as derived from the supplied configuration")
|
||||
loglevel := flag.String("loglevel", "info", "loglevel to enable")
|
||||
flag.Parse()
|
||||
return yggArgs{
|
||||
genconf: *genconf,
|
||||
useconf: *useconf,
|
||||
useconffile: *useconffile,
|
||||
normaliseconf: *normaliseconf,
|
||||
confjson: *confjson,
|
||||
autoconf: *autoconf,
|
||||
ver: *ver,
|
||||
logto: *logto,
|
||||
getaddr: *getaddr,
|
||||
getsnet: *getsnet,
|
||||
loglevel: *loglevel,
|
||||
}
|
||||
}
|
||||
|
||||
// The main function is responsible for configuring and starting Yggdrasil.
|
||||
func run(args yggArgs, ctx context.Context) {
|
||||
// Catch interrupts from the operating system to exit gracefully.
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
// Capture the service being stopped on Windows.
|
||||
minwinsvc.SetOnExit(cancel)
|
||||
|
||||
// Create a new logger that logs output to stdout.
|
||||
var logger *log.Logger
|
||||
switch args.logto {
|
||||
switch *logto {
|
||||
case "stdout":
|
||||
logger = log.New(os.Stdout, "", log.Flags())
|
||||
|
||||
case "syslog":
|
||||
if syslogger, err := gsyslog.NewLogger(gsyslog.LOG_NOTICE, "DAEMON", version.BuildName()); err == nil {
|
||||
logger = log.New(syslogger, "", log.Flags())
|
||||
logger = log.New(syslogger, "", log.Flags() &^ (log.Ldate | log.Ltime))
|
||||
}
|
||||
|
||||
default:
|
||||
if logfd, err := os.OpenFile(args.logto, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil {
|
||||
if logfd, err := os.OpenFile(*logto, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil {
|
||||
logger = log.New(logfd, "", log.Flags())
|
||||
}
|
||||
}
|
||||
@ -203,87 +79,100 @@ func run(args yggArgs, ctx context.Context) {
|
||||
logger = log.New(os.Stdout, "", log.Flags())
|
||||
logger.Warnln("Logging defaulting to stdout")
|
||||
}
|
||||
|
||||
if args.normaliseconf {
|
||||
if *normaliseconf {
|
||||
setLogLevel("error", logger)
|
||||
} else {
|
||||
setLogLevel(args.loglevel, logger)
|
||||
setLogLevel(*loglevel, logger)
|
||||
}
|
||||
|
||||
var cfg *config.NodeConfig
|
||||
cfg := config.GenerateConfig()
|
||||
var err error
|
||||
switch {
|
||||
case args.ver:
|
||||
case *ver:
|
||||
fmt.Println("Build name:", version.BuildName())
|
||||
fmt.Println("Build version:", version.BuildVersion())
|
||||
return
|
||||
case args.autoconf:
|
||||
|
||||
case *autoconf:
|
||||
// Use an autoconf-generated config, this will give us random keys and
|
||||
// port numbers, and will use an automatically selected TUN interface.
|
||||
cfg = defaults.GenerateConfig()
|
||||
case args.useconffile != "" || args.useconf:
|
||||
// Read the configuration from either stdin or from the filesystem
|
||||
cfg = readConfig(logger, args.useconf, args.useconffile, args.normaliseconf)
|
||||
// If the -normaliseconf option was specified then remarshal the above
|
||||
// configuration and print it back to stdout. This lets the user update
|
||||
// their configuration file with newly mapped names (like above) or to
|
||||
// convert from plain JSON to commented HJSON.
|
||||
if args.normaliseconf {
|
||||
var bs []byte
|
||||
if args.confjson {
|
||||
bs, err = json.MarshalIndent(cfg, "", " ")
|
||||
} else {
|
||||
bs, err = hjson.Marshal(cfg)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(bs))
|
||||
return
|
||||
|
||||
case *useconf:
|
||||
if _, err := cfg.ReadFrom(os.Stdin); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case args.genconf:
|
||||
// Generate a new configuration and print it to stdout.
|
||||
fmt.Println(doGenconf(args.confjson))
|
||||
|
||||
case *useconffile != "":
|
||||
f, err := os.Open(*useconffile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if _, err := cfg.ReadFrom(f); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_ = f.Close()
|
||||
|
||||
case *genconf:
|
||||
var bs []byte
|
||||
if *confjson {
|
||||
bs, err = json.MarshalIndent(cfg, "", " ")
|
||||
} else {
|
||||
bs, err = hjson.Marshal(cfg)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(bs))
|
||||
return
|
||||
|
||||
default:
|
||||
// No flags were provided, therefore print the list of flags to stdout.
|
||||
fmt.Println("Usage:")
|
||||
flag.PrintDefaults()
|
||||
|
||||
if args.getaddr || args.getsnet {
|
||||
if *getaddr || *getsnet {
|
||||
fmt.Println("\nError: You need to specify some config data using -useconf or -useconffile.")
|
||||
}
|
||||
}
|
||||
// Have we got a working configuration? If we don't then it probably means
|
||||
// that neither -autoconf, -useconf or -useconffile were set above. Stop
|
||||
// if we don't.
|
||||
if cfg == nil {
|
||||
return
|
||||
}
|
||||
// Have we been asked for the node address yet? If so, print it and then stop.
|
||||
getNodeKey := func() ed25519.PublicKey {
|
||||
if pubkey, err := hex.DecodeString(cfg.PrivateKey); err == nil {
|
||||
return ed25519.PrivateKey(pubkey).Public().(ed25519.PublicKey)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
privateKey := ed25519.PrivateKey(cfg.PrivateKey)
|
||||
publicKey := privateKey.Public().(ed25519.PublicKey)
|
||||
|
||||
switch {
|
||||
case args.getaddr:
|
||||
if key := getNodeKey(); key != nil {
|
||||
addr := address.AddrForKey(key)
|
||||
ip := net.IP(addr[:])
|
||||
fmt.Println(ip.String())
|
||||
}
|
||||
case *getaddr:
|
||||
addr := address.AddrForKey(publicKey)
|
||||
ip := net.IP(addr[:])
|
||||
fmt.Println(ip.String())
|
||||
return
|
||||
case args.getsnet:
|
||||
if key := getNodeKey(); key != nil {
|
||||
snet := address.SubnetForKey(key)
|
||||
ipnet := net.IPNet{
|
||||
IP: append(snet[:], 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
Mask: net.CIDRMask(len(snet)*8, 128),
|
||||
}
|
||||
fmt.Println(ipnet.String())
|
||||
|
||||
case *getsnet:
|
||||
snet := address.SubnetForKey(publicKey)
|
||||
ipnet := net.IPNet{
|
||||
IP: append(snet[:], 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
Mask: net.CIDRMask(len(snet)*8, 128),
|
||||
}
|
||||
fmt.Println(ipnet.String())
|
||||
return
|
||||
|
||||
case *normaliseconf:
|
||||
var bs []byte
|
||||
if *confjson {
|
||||
bs, err = json.MarshalIndent(cfg, "", " ")
|
||||
} else {
|
||||
bs, err = hjson.Marshal(cfg)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(bs))
|
||||
return
|
||||
|
||||
case *exportkey:
|
||||
pem, err := cfg.MarshalPEMPrivateKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(pem))
|
||||
return
|
||||
}
|
||||
|
||||
@ -291,10 +180,6 @@ func run(args yggArgs, ctx context.Context) {
|
||||
|
||||
// Setup the Yggdrasil node itself.
|
||||
{
|
||||
sk, err := hex.DecodeString(cfg.PrivateKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
options := []core.SetupOption{
|
||||
core.NodeInfo(cfg.NodeInfo),
|
||||
core.NodeInfoPrivacy(cfg.NodeInfoPrivacy),
|
||||
@ -317,7 +202,7 @@ func run(args yggArgs, ctx context.Context) {
|
||||
}
|
||||
options = append(options, core.AllowedPublicKey(k[:]))
|
||||
}
|
||||
if n.core, err = core.New(sk[:], logger, options...); err != nil {
|
||||
if n.core, err = core.New(cfg.Certificate, logger, options...); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@ -345,6 +230,7 @@ func run(args yggArgs, ctx context.Context) {
|
||||
Listen: intf.Listen,
|
||||
Port: intf.Port,
|
||||
Priority: uint8(intf.Priority),
|
||||
Password: intf.Password,
|
||||
})
|
||||
}
|
||||
if n.multicast, err = multicast.New(n.core, logger, options...); err != nil {
|
||||
@ -369,15 +255,6 @@ func run(args yggArgs, ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// Make some nice output that tells us what our IPv6 address and subnet are.
|
||||
// This is just logged to stdout for the user.
|
||||
address := n.core.Address()
|
||||
subnet := n.core.Subnet()
|
||||
public := n.core.GetSelf().Key
|
||||
logger.Infof("Your public key is %s", hex.EncodeToString(public[:]))
|
||||
logger.Infof("Your IPv6 address is %s", address.String())
|
||||
logger.Infof("Your IPv6 subnet is %s", subnet.String())
|
||||
|
||||
// Block until we are told to shut down.
|
||||
<-ctx.Done()
|
||||
|
||||
@ -388,21 +265,28 @@ func run(args yggArgs, ctx context.Context) {
|
||||
n.core.Stop()
|
||||
}
|
||||
|
||||
func main() {
|
||||
args := getArgs()
|
||||
func setLogLevel(loglevel string, logger *log.Logger) {
|
||||
levels := [...]string{"error", "warn", "info", "debug", "trace"}
|
||||
loglevel = strings.ToLower(loglevel)
|
||||
|
||||
// Catch interrupts from the operating system to exit gracefully.
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
contains := func() bool {
|
||||
for _, l := range levels {
|
||||
if l == loglevel {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Capture the service being stopped on Windows.
|
||||
minwinsvc.SetOnExit(cancel)
|
||||
if !contains() { // set default log level
|
||||
logger.Infoln("Loglevel parse failed. Set default level(info)")
|
||||
loglevel = "info"
|
||||
}
|
||||
|
||||
// Start the node, block and then wait for it to shut down.
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
run(args, ctx)
|
||||
}()
|
||||
wg.Wait()
|
||||
for _, l := range levels {
|
||||
logger.EnableLevel(l)
|
||||
if l == loglevel {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,10 +7,10 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/hjson/hjson-go"
|
||||
"github.com/hjson/hjson-go/v4"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||
)
|
||||
|
||||
type CmdLineEnv struct {
|
||||
@ -21,7 +21,7 @@ type CmdLineEnv struct {
|
||||
|
||||
func newCmdLineEnv() CmdLineEnv {
|
||||
var cmdLineEnv CmdLineEnv
|
||||
cmdLineEnv.endpoint = defaults.GetDefaults().DefaultAdminListen
|
||||
cmdLineEnv.endpoint = config.GetDefaults().DefaultAdminListen
|
||||
return cmdLineEnv
|
||||
}
|
||||
|
||||
@ -38,7 +38,6 @@ func (cmdLineEnv *CmdLineEnv) parseFlagsAndArgs() {
|
||||
fmt.Println("Examples:")
|
||||
fmt.Println(" - ", os.Args[0], "list")
|
||||
fmt.Println(" - ", os.Args[0], "getPeers")
|
||||
fmt.Println(" - ", os.Args[0], "-v getSelf")
|
||||
fmt.Println(" - ", os.Args[0], "setTunTap name=auto mtu=1500 tap_mode=false")
|
||||
fmt.Println(" - ", os.Args[0], "-endpoint=tcp://localhost:9001 getDHT")
|
||||
fmt.Println(" - ", os.Args[0], "-endpoint=unix:///var/run/ygg.sock getDHT")
|
||||
@ -58,31 +57,31 @@ func (cmdLineEnv *CmdLineEnv) parseFlagsAndArgs() {
|
||||
|
||||
func (cmdLineEnv *CmdLineEnv) setEndpoint(logger *log.Logger) {
|
||||
if cmdLineEnv.server == cmdLineEnv.endpoint {
|
||||
if config, err := os.ReadFile(defaults.GetDefaults().DefaultConfigFile); err == nil {
|
||||
if bytes.Equal(config[0:2], []byte{0xFF, 0xFE}) ||
|
||||
bytes.Equal(config[0:2], []byte{0xFE, 0xFF}) {
|
||||
if cfg, err := os.ReadFile(config.GetDefaults().DefaultConfigFile); err == nil {
|
||||
if bytes.Equal(cfg[0:2], []byte{0xFF, 0xFE}) ||
|
||||
bytes.Equal(cfg[0:2], []byte{0xFE, 0xFF}) {
|
||||
utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM)
|
||||
decoder := utf.NewDecoder()
|
||||
config, err = decoder.Bytes(config)
|
||||
cfg, err = decoder.Bytes(cfg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
var dat map[string]interface{}
|
||||
if err := hjson.Unmarshal(config, &dat); err != nil {
|
||||
if err := hjson.Unmarshal(cfg, &dat); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if ep, ok := dat["AdminListen"].(string); ok && (ep != "none" && ep != "") {
|
||||
cmdLineEnv.endpoint = ep
|
||||
logger.Println("Found platform default config file", defaults.GetDefaults().DefaultConfigFile)
|
||||
logger.Println("Found platform default config file", config.GetDefaults().DefaultConfigFile)
|
||||
logger.Println("Using endpoint", cmdLineEnv.endpoint, "from AdminListen")
|
||||
} else {
|
||||
logger.Println("Configuration file doesn't contain appropriate AdminListen option")
|
||||
logger.Println("Falling back to platform default", defaults.GetDefaults().DefaultAdminListen)
|
||||
logger.Println("Falling back to platform default", config.GetDefaults().DefaultAdminListen)
|
||||
}
|
||||
} else {
|
||||
logger.Println("Can't open config file from default location", defaults.GetDefaults().DefaultConfigFile)
|
||||
logger.Println("Falling back to platform default", defaults.GetDefaults().DefaultAdminListen)
|
||||
logger.Println("Can't open config file from default location", config.GetDefaults().DefaultConfigFile)
|
||||
logger.Println("Falling back to platform default", config.GetDefaults().DefaultAdminListen)
|
||||
}
|
||||
} else {
|
||||
cmdLineEnv.endpoint = cmdLineEnv.server
|
||||
|
@ -165,7 +165,7 @@ func run() int {
|
||||
table.Append([]string{"Build version:", resp.BuildVersion})
|
||||
table.Append([]string{"IPv6 address:", resp.IPAddress})
|
||||
table.Append([]string{"IPv6 subnet:", resp.Subnet})
|
||||
table.Append([]string{"Coordinates:", fmt.Sprintf("%v", resp.Coords)})
|
||||
table.Append([]string{"Routing table size:", fmt.Sprintf("%d", resp.RoutingEntries)})
|
||||
table.Append([]string{"Public key:", resp.PublicKey})
|
||||
table.Render()
|
||||
|
||||
@ -174,33 +174,49 @@ func run() int {
|
||||
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
table.SetHeader([]string{"Port", "Public Key", "IP Address", "Uptime", "RX", "TX", "Pr", "URI"})
|
||||
table.SetHeader([]string{"URI", "State", "Dir", "IP Address", "Uptime", "RX", "TX", "Pr", "Last Error"})
|
||||
for _, peer := range resp.Peers {
|
||||
state, lasterr, dir := "Up", "-", "Out"
|
||||
if !peer.Up {
|
||||
state, lasterr = "Down", fmt.Sprintf("%s ago: %s", peer.LastErrorTime.Round(time.Second), peer.LastError)
|
||||
}
|
||||
if peer.Inbound {
|
||||
dir = "In"
|
||||
}
|
||||
uri, err := url.Parse(peer.URI)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
uri.RawQuery = ""
|
||||
table.Append([]string{
|
||||
fmt.Sprintf("%d", peer.Port),
|
||||
peer.PublicKey,
|
||||
uri.String(),
|
||||
state,
|
||||
dir,
|
||||
peer.IPAddress,
|
||||
(time.Duration(peer.Uptime) * time.Second).String(),
|
||||
peer.RXBytes.String(),
|
||||
peer.TXBytes.String(),
|
||||
fmt.Sprintf("%d", peer.Priority),
|
||||
peer.Remote,
|
||||
lasterr,
|
||||
})
|
||||
}
|
||||
table.Render()
|
||||
|
||||
case "getdht":
|
||||
var resp admin.GetDHTResponse
|
||||
case "gettree":
|
||||
var resp admin.GetTreeResponse
|
||||
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
table.SetHeader([]string{"Public Key", "IP Address", "Port", "Rest"})
|
||||
for _, dht := range resp.DHT {
|
||||
//table.SetHeader([]string{"Public Key", "IP Address", "Port", "Rest"})
|
||||
table.SetHeader([]string{"Public Key", "IP Address", "Parent", "Sequence"})
|
||||
for _, tree := range resp.Tree {
|
||||
table.Append([]string{
|
||||
dht.PublicKey,
|
||||
dht.IPAddress,
|
||||
fmt.Sprintf("%d", dht.Port),
|
||||
fmt.Sprintf("%d", dht.Rest),
|
||||
tree.PublicKey,
|
||||
tree.IPAddress,
|
||||
tree.Parent,
|
||||
fmt.Sprintf("%d", tree.Sequence),
|
||||
//fmt.Sprintf("%d", dht.Port),
|
||||
//fmt.Sprintf("%d", dht.Rest),
|
||||
})
|
||||
}
|
||||
table.Render()
|
||||
@ -210,12 +226,13 @@ func run() int {
|
||||
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
table.SetHeader([]string{"Public Key", "IP Address", "Path"})
|
||||
table.SetHeader([]string{"Public Key", "IP Address", "Path", "Seq"})
|
||||
for _, p := range resp.Paths {
|
||||
table.Append([]string{
|
||||
p.PublicKey,
|
||||
p.IPAddress,
|
||||
fmt.Sprintf("%v", p.Path),
|
||||
fmt.Sprintf("%d", p.Sequence),
|
||||
})
|
||||
}
|
||||
table.Render()
|
||||
|
@ -1,7 +1,5 @@
|
||||
/*
|
||||
|
||||
This file generates crypto keys for [ansible-yggdrasil](https://github.com/jcgruenhage/ansible-yggdrasil/)
|
||||
|
||||
*/
|
||||
package main
|
||||
|
||||
|
@ -3,7 +3,6 @@ package mobile
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
|
||||
@ -12,7 +11,6 @@ import (
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/multicast"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/tun"
|
||||
@ -46,20 +44,16 @@ func (m *Yggdrasil) StartAutoconfigure() error {
|
||||
func (m *Yggdrasil) StartJSON(configjson []byte) error {
|
||||
setMemLimitIfPossible()
|
||||
|
||||
m.logger = log.New(m.log, "", 0)
|
||||
m.logger.EnableLevel("error")
|
||||
m.logger.EnableLevel("warn")
|
||||
m.logger.EnableLevel("info")
|
||||
m.config = defaults.GenerateConfig()
|
||||
if err := json.Unmarshal(configjson, &m.config); err != nil {
|
||||
logger := log.New(m.log, "", 0)
|
||||
logger.EnableLevel("error")
|
||||
logger.EnableLevel("warn")
|
||||
logger.EnableLevel("info")
|
||||
m.config = config.GenerateConfig()
|
||||
if err := m.config.UnmarshalHJSON(configjson); err != nil {
|
||||
return err
|
||||
}
|
||||
// Setup the Yggdrasil node itself.
|
||||
{
|
||||
sk, err := hex.DecodeString(m.config.PrivateKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
options := []core.SetupOption{}
|
||||
for _, peer := range m.config.Peers {
|
||||
options = append(options, core.Peer{URI: peer})
|
||||
@ -76,7 +70,8 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error {
|
||||
}
|
||||
options = append(options, core.AllowedPublicKey(k[:]))
|
||||
}
|
||||
m.core, err = core.New(sk[:], m.logger, options...)
|
||||
var err error
|
||||
m.core, err = core.New(m.config.Certificate, logger, options...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -93,6 +88,7 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error {
|
||||
Listen: intf.Listen,
|
||||
Port: intf.Port,
|
||||
Priority: uint8(intf.Priority),
|
||||
Password: intf.Password,
|
||||
})
|
||||
}
|
||||
m.multicast, err = multicast.New(m.core, m.logger, options...)
|
||||
@ -176,7 +172,7 @@ func (m *Yggdrasil) RetryPeersNow() {
|
||||
|
||||
// GenerateConfigJSON generates mobile-friendly configuration in JSON format
|
||||
func GenerateConfigJSON() []byte {
|
||||
nc := defaults.GenerateConfig()
|
||||
nc := config.GenerateConfig()
|
||||
nc.IfName = "none"
|
||||
if json, err := json.Marshal(nc); err == nil {
|
||||
return json
|
||||
@ -201,9 +197,9 @@ func (m *Yggdrasil) GetPublicKeyString() string {
|
||||
return hex.EncodeToString(m.core.GetSelf().Key)
|
||||
}
|
||||
|
||||
// GetCoordsString gets the node's coordinates
|
||||
func (m *Yggdrasil) GetCoordsString() string {
|
||||
return fmt.Sprintf("%v", m.core.GetSelf().Coords)
|
||||
// GetRoutingEntries gets the number of entries in the routing table
|
||||
func (m *Yggdrasil) GetRoutingEntries() int {
|
||||
return int(m.core.GetSelf().RoutingEntries)
|
||||
}
|
||||
|
||||
func (m *Yggdrasil) GetPeersJSON() (result string) {
|
||||
@ -229,8 +225,16 @@ func (m *Yggdrasil) GetPeersJSON() (result string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Yggdrasil) GetDHTJSON() (result string) {
|
||||
if res, err := json.Marshal(m.core.GetDHT()); err == nil {
|
||||
func (m *Yggdrasil) GetPathsJSON() (result string) {
|
||||
if res, err := json.Marshal(m.core.GetPaths()); err == nil {
|
||||
return string(res)
|
||||
} else {
|
||||
return "{}"
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Yggdrasil) GetTreeJSON() (result string) {
|
||||
if res, err := json.Marshal(m.core.GetTree()); err == nil {
|
||||
return string(res)
|
||||
} else {
|
||||
return "{}"
|
||||
|
@ -1,15 +1,27 @@
|
||||
package mobile
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/gologme/log"
|
||||
)
|
||||
|
||||
func TestStartYggdrasil(t *testing.T) {
|
||||
ygg := &Yggdrasil{}
|
||||
logger := log.New(os.Stdout, "", 0)
|
||||
logger.EnableLevel("error")
|
||||
logger.EnableLevel("warn")
|
||||
logger.EnableLevel("info")
|
||||
|
||||
ygg := &Yggdrasil{
|
||||
logger: logger,
|
||||
}
|
||||
if err := ygg.StartAutoconfigure(); err != nil {
|
||||
t.Fatalf("Failed to start Yggdrasil: %s", err)
|
||||
}
|
||||
t.Log("Address:", ygg.GetAddressString())
|
||||
t.Log("Subnet:", ygg.GetSubnetString())
|
||||
t.Log("Coords:", ygg.GetCoordsString())
|
||||
t.Log("Routing entries:", ygg.GetRoutingEntries())
|
||||
if err := ygg.Stop(); err != nil {
|
||||
t.Fatalf("Failed to stop Yggdrasil: %s", err)
|
||||
}
|
||||
|
49
go.mod
49
go.mod
@ -1,38 +1,47 @@
|
||||
module github.com/yggdrasil-network/yggdrasil-go
|
||||
|
||||
go 1.17
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/Arceliar/ironwood v0.0.0-20221115123222-ec61cea2f439
|
||||
github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979
|
||||
github.com/cheggaaa/pb/v3 v3.0.8
|
||||
github.com/gologme/log v1.2.0
|
||||
github.com/Arceliar/ironwood v0.0.0-20230805085300-86206813435f
|
||||
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d
|
||||
github.com/cheggaaa/pb/v3 v3.1.4
|
||||
github.com/gologme/log v1.3.0
|
||||
github.com/hashicorp/go-syslog v1.0.0
|
||||
github.com/hjson/hjson-go v3.1.0+incompatible
|
||||
github.com/hjson/hjson-go/v4 v4.3.0
|
||||
github.com/kardianos/minwinsvc v1.0.2
|
||||
github.com/mitchellh/mapstructure v1.4.1
|
||||
github.com/quic-go/quic-go v0.39.0
|
||||
github.com/vishvananda/netlink v1.1.0
|
||||
golang.org/x/mobile v0.0.0-20221110043201-43a038452099
|
||||
golang.org/x/net v0.7.0
|
||||
golang.org/x/sys v0.5.0
|
||||
golang.org/x/text v0.7.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a
|
||||
golang.zx2c4.com/wireguard/windows v0.4.12
|
||||
golang.org/x/crypto v0.14.0
|
||||
golang.org/x/mobile v0.0.0-20231006135142-2b44d11868fe
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/sys v0.13.0
|
||||
golang.org/x/text v0.13.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230223181233-21636207a675
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.5.0 // indirect
|
||||
github.com/bits-and-blooms/bloom/v3 v3.3.1 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.3.4 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20221012134737-56aed061732a // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
go.uber.org/mock v0.3.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
|
||||
golang.org/x/mod v0.13.0 // indirect
|
||||
golang.org/x/tools v0.14.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/VividCortex/ewma v1.2.0 // indirect
|
||||
github.com/fatih/color v1.12.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect
|
||||
)
|
||||
|
170
go.sum
170
go.sum
@ -1,121 +1,137 @@
|
||||
github.com/Arceliar/ironwood v0.0.0-20221115123222-ec61cea2f439 h1:eOW6/XIs06TnUn9GPCnfv71CQZw8edP3u3mH3lZt6iM=
|
||||
github.com/Arceliar/ironwood v0.0.0-20221115123222-ec61cea2f439/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk=
|
||||
github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ=
|
||||
github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
|
||||
github.com/Arceliar/ironwood v0.0.0-20230805085300-86206813435f h1:Fz0zG7ZyQQqk+ROnmHuGrIZO250Lx/YHmp9o48XE+Vw=
|
||||
github.com/Arceliar/ironwood v0.0.0-20230805085300-86206813435f/go.mod h1:5x7fWW0mshe9WQ1lvSMmmHBYC3BeHH9gpwW5tz7cbfw=
|
||||
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d h1:UK9fsWbWqwIQkMCz1CP+v5pGbsGoWAw6g4AyvMpm1EM=
|
||||
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d/go.mod h1:BCnxhRf47C/dy/e/D2pmB8NkB3dQVIrkD98b220rx5Q=
|
||||
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
|
||||
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
|
||||
github.com/cheggaaa/pb/v3 v3.0.8 h1:bC8oemdChbke2FHIIGy9mn4DPJ2caZYQnfbRqwmdCoA=
|
||||
github.com/cheggaaa/pb/v3 v3.0.8/go.mod h1:UICbiLec/XO6Hw6k+BHEtHeQFzzBH4i2/qk/ow1EJTA=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
|
||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/gologme/log v1.2.0 h1:Ya5Ip/KD6FX7uH0S31QO87nCCSucKtF44TLbTtO7V4c=
|
||||
github.com/gologme/log v1.2.0/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U=
|
||||
github.com/bits-and-blooms/bitset v1.3.1/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||
github.com/bits-and-blooms/bitset v1.5.0 h1:NpE8frKRLGHIcEzkR+gZhiioW1+WbYV6fKwD6ZIpQT8=
|
||||
github.com/bits-and-blooms/bitset v1.5.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||
github.com/bits-and-blooms/bloom/v3 v3.3.1 h1:K2+A19bXT8gJR5mU7y+1yW6hsKfNCjcP2uNfLFKncjQ=
|
||||
github.com/bits-and-blooms/bloom/v3 v3.3.1/go.mod h1:bhUUknWd5khVbTe4UgMCSiOOVJzr3tMoijSK3WwvW90=
|
||||
github.com/cheggaaa/pb/v3 v3.1.4 h1:DN8j4TVVdKu3WxVwcRKu0sG00IIU6FewoABZzXbRQeo=
|
||||
github.com/cheggaaa/pb/v3 v3.1.4/go.mod h1:6wVjILNBaXMs8c21qRiaUM8BR82erfgau1DQ4iUXmSA=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/gologme/log v1.3.0 h1:l781G4dE+pbigClDSDzSaaYKtiueHCILUa/qSDsmHAo=
|
||||
github.com/gologme/log v1.3.0/go.mod h1:yKT+DvIPdDdDoPtqFrFxheooyVmoqi0BAsw+erN3wA4=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hjson/hjson-go v3.1.0+incompatible h1:DY/9yE8ey8Zv22bY+mHV1uk2yRy0h8tKhZ77hEdi0Aw=
|
||||
github.com/hjson/hjson-go v3.1.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio=
|
||||
github.com/hjson/hjson-go/v4 v4.3.0 h1:dyrzJdqqFGhHt+FSrs5n9s6b0fPM8oSJdWo+oS3YnJw=
|
||||
github.com/hjson/hjson-go/v4 v4.3.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/kardianos/minwinsvc v1.0.2 h1:JmZKFJQrmTGa/WiW+vkJXKmfzdjabuEW4Tirj5lLdR0=
|
||||
github.com/kardianos/minwinsvc v1.0.2/go.mod h1:LUZNYhNmxujx2tR7FbdxqYJ9XDDoCd3MQcl1o//FWl4=
|
||||
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
|
||||
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
|
||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.4/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/quic-go/quic-go v0.39.0 h1:AgP40iThFMY0bj8jGxROhw3S0FMGa8ryqsmi9tBH3So=
|
||||
github.com/quic-go/quic-go v0.39.0/go.mod h1:T09QsDQWjLiQ74ZmacDfqZmhY/NLnw5BC40MANNNZ1Q=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
|
||||
github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
|
||||
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA=
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
|
||||
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20221012134737-56aed061732a h1:NmSIgad6KjE6VvHciPZuNRTKxGhlPfD6OA87W/PLkqg=
|
||||
golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20221110043201-43a038452099 h1:aIu0lKmfdgtn2uTj7JI2oN4TUrQvgB+wzTPO23bCKt8=
|
||||
golang.org/x/mobile v0.0.0-20221110043201-43a038452099/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/mobile v0.0.0-20231006135142-2b44d11868fe h1:lrXv4yHeD9FA8PSJATWowP1QvexpyAPWmPia+Kbzql8=
|
||||
golang.org/x/mobile v0.0.0-20231006135142-2b44d11868fe/go.mod h1:BrnXpEObnFxpaT75Jo9hsCazwOWcp7nVIa8NNuH5cuA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
|
||||
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210927181540-4e4d966f7476/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211011170408-caeb26a5c8c0/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
|
||||
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20211012062646-82d2aa87aa62/go.mod h1:id8Oh3eCCmpj9uVGWVjsUAl6UPX5ysMLzu6QxJU2UOU=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a h1:tTbyylK9/D3u/wEP26Vx7L700UpY48nhioJWZM1vhZw=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a/go.mod h1:id8Oh3eCCmpj9uVGWVjsUAl6UPX5ysMLzu6QxJU2UOU=
|
||||
golang.zx2c4.com/wireguard/windows v0.4.12 h1:CUmbdWKVNzTSsVb4yUAiEwL3KsabdJkEPdDjCHxBlhA=
|
||||
golang.zx2c4.com/wireguard/windows v0.4.12/go.mod h1:PW4y+d9oY83XU9rRwRwrJDwEMuhVjMxu2gfD1cfzS7w=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230223181233-21636207a675 h1:/J/RVnr7ng4fWPRH3xa4WtBJ1Jp+Auu4YNLmGiPv5QU=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230223181233-21636207a675/go.mod h1:whfbyDBt09xhCYQWtO2+3UVjlaq6/9hDZrjg2ZE6SyA=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
@ -3,13 +3,13 @@ package address
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"math/rand"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAddress_Address_IsValid(t *testing.T) {
|
||||
var address Address
|
||||
rand.Read(address[:])
|
||||
_, _ = rand.Read(address[:])
|
||||
|
||||
address[0] = 0
|
||||
|
||||
@ -32,7 +32,7 @@ func TestAddress_Address_IsValid(t *testing.T) {
|
||||
|
||||
func TestAddress_Subnet_IsValid(t *testing.T) {
|
||||
var subnet Subnet
|
||||
rand.Read(subnet[:])
|
||||
_, _ = rand.Read(subnet[:])
|
||||
|
||||
subnet[0] = 0
|
||||
|
||||
|
@ -1,5 +1,10 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type AddPeerRequest struct {
|
||||
Uri string `json:"uri"`
|
||||
Sintf string `json:"interface,omitempty"`
|
||||
@ -8,5 +13,9 @@ type AddPeerRequest struct {
|
||||
type AddPeerResponse struct{}
|
||||
|
||||
func (a *AdminSocket) addPeerHandler(req *AddPeerRequest, res *AddPeerResponse) error {
|
||||
return a.core.AddPeer(req.Uri, req.Sintf)
|
||||
u, err := url.Parse(req.Uri)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse peering URI: %w", err)
|
||||
}
|
||||
return a.core.AddPeer(u, req.Sintf)
|
||||
}
|
||||
|
@ -35,10 +35,10 @@ type AdminSocketRequest struct {
|
||||
}
|
||||
|
||||
type AdminSocketResponse struct {
|
||||
Status string `json:"status"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Request json.RawMessage `json:"request"`
|
||||
Response json.RawMessage `json:"response"`
|
||||
Status string `json:"status"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Request AdminSocketRequest `json:"request"`
|
||||
Response json.RawMessage `json:"response"`
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
@ -132,14 +132,14 @@ func (a *AdminSocket) SetupAdminHandlers() {
|
||||
},
|
||||
)
|
||||
_ = a.AddHandler(
|
||||
"getDHT", "Show known DHT entries", []string{},
|
||||
"getTree", "Show known Tree entries", []string{},
|
||||
func(in json.RawMessage) (interface{}, error) {
|
||||
req := &GetDHTRequest{}
|
||||
res := &GetDHTResponse{}
|
||||
req := &GetTreeRequest{}
|
||||
res := &GetTreeResponse{}
|
||||
if err := json.Unmarshal(in, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := a.getDHTHandler(req, res); err != nil {
|
||||
if err := a.getTreeHandler(req, res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
@ -309,18 +309,22 @@ func (a *AdminSocket) handleRequest(conn net.Conn) {
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != nil {
|
||||
a.log.Debugln("Admin socket error:", r)
|
||||
if err := encoder.Encode(&ErrorResponse{
|
||||
Error: "Check your syntax and input types",
|
||||
}); err != nil {
|
||||
a.log.Debugln("Admin socket JSON encode error:", err)
|
||||
/*
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != nil {
|
||||
fmt.Println("ERROR:", r)
|
||||
a.log.Debugln("Admin socket error:", r)
|
||||
if err := encoder.Encode(&ErrorResponse{
|
||||
Error: "Check your syntax and input types",
|
||||
}); err != nil {
|
||||
fmt.Println("ERROR 2:", err)
|
||||
a.log.Debugln("Admin socket JSON encode error:", err)
|
||||
}
|
||||
conn.Close()
|
||||
}
|
||||
conn.Close()
|
||||
}
|
||||
}()
|
||||
}()
|
||||
*/
|
||||
|
||||
for {
|
||||
var err error
|
||||
@ -335,6 +339,7 @@ func (a *AdminSocket) handleRequest(conn net.Conn) {
|
||||
if err = json.Unmarshal(buf, &req); err != nil {
|
||||
return fmt.Errorf("Failed to unmarshal request")
|
||||
}
|
||||
resp.Request = req
|
||||
if req.Name == "" {
|
||||
return fmt.Errorf("No request specified")
|
||||
}
|
||||
|
@ -1,41 +0,0 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||
)
|
||||
|
||||
type GetDHTRequest struct{}
|
||||
|
||||
type GetDHTResponse struct {
|
||||
DHT []DHTEntry `json:"dht"`
|
||||
}
|
||||
|
||||
type DHTEntry struct {
|
||||
IPAddress string `json:"address"`
|
||||
PublicKey string `json:"key"`
|
||||
Port uint64 `json:"port"`
|
||||
Rest uint64 `json:"rest"`
|
||||
}
|
||||
|
||||
func (a *AdminSocket) getDHTHandler(req *GetDHTRequest, res *GetDHTResponse) error {
|
||||
dht := a.core.GetDHT()
|
||||
res.DHT = make([]DHTEntry, 0, len(dht))
|
||||
for _, d := range dht {
|
||||
addr := address.AddrForKey(d.Key)
|
||||
res.DHT = append(res.DHT, DHTEntry{
|
||||
IPAddress: net.IP(addr[:]).String(),
|
||||
PublicKey: hex.EncodeToString(d.Key[:]),
|
||||
Port: d.Port,
|
||||
Rest: d.Rest,
|
||||
})
|
||||
}
|
||||
sort.SliceStable(res.DHT, func(i, j int) bool {
|
||||
return strings.Compare(res.DHT[i].PublicKey, res.DHT[j].PublicKey) < 0
|
||||
})
|
||||
return nil
|
||||
}
|
@ -20,6 +20,7 @@ type PathEntry struct {
|
||||
IPAddress string `json:"address"`
|
||||
PublicKey string `json:"key"`
|
||||
Path []uint64 `json:"path"`
|
||||
Sequence uint64 `json:"sequence"`
|
||||
}
|
||||
|
||||
func (a *AdminSocket) getPathsHandler(req *GetPathsRequest, res *GetPathsResponse) error {
|
||||
@ -31,6 +32,7 @@ func (a *AdminSocket) getPathsHandler(req *GetPathsRequest, res *GetPathsRespons
|
||||
IPAddress: net.IP(addr[:]).String(),
|
||||
PublicKey: hex.EncodeToString(p.Key),
|
||||
Path: p.Path,
|
||||
Sequence: p.Sequence,
|
||||
})
|
||||
}
|
||||
sort.SliceStable(res.Paths, func(i, j int) bool {
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"encoding/hex"
|
||||
"net"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||
)
|
||||
@ -16,33 +17,43 @@ type GetPeersResponse struct {
|
||||
}
|
||||
|
||||
type PeerEntry struct {
|
||||
IPAddress string `json:"address"`
|
||||
PublicKey string `json:"key"`
|
||||
Port uint64 `json:"port"`
|
||||
Priority uint64 `json:"priority"`
|
||||
Coords []uint64 `json:"coords"`
|
||||
Remote string `json:"remote"`
|
||||
RXBytes DataUnit `json:"bytes_recvd"`
|
||||
TXBytes DataUnit `json:"bytes_sent"`
|
||||
Uptime float64 `json:"uptime"`
|
||||
URI string `json:"remote,omitempty"`
|
||||
Up bool `json:"up"`
|
||||
Inbound bool `json:"inbound"`
|
||||
IPAddress string `json:"address,omitempty"`
|
||||
PublicKey string `json:"key"`
|
||||
Port uint64 `json:"port"`
|
||||
Priority uint64 `json:"priority"`
|
||||
RXBytes DataUnit `json:"bytes_recvd,omitempty"`
|
||||
TXBytes DataUnit `json:"bytes_sent,omitempty"`
|
||||
Uptime float64 `json:"uptime,omitempty"`
|
||||
LastError string `json:"last_error,omitempty"`
|
||||
LastErrorTime time.Duration `json:"last_error_time,omitempty"`
|
||||
}
|
||||
|
||||
func (a *AdminSocket) getPeersHandler(req *GetPeersRequest, res *GetPeersResponse) error {
|
||||
peers := a.core.GetPeers()
|
||||
res.Peers = make([]PeerEntry, 0, len(peers))
|
||||
for _, p := range peers {
|
||||
addr := address.AddrForKey(p.Key)
|
||||
res.Peers = append(res.Peers, PeerEntry{
|
||||
IPAddress: net.IP(addr[:]).String(),
|
||||
PublicKey: hex.EncodeToString(p.Key),
|
||||
Port: p.Port,
|
||||
Priority: uint64(p.Priority), // can't be uint8 thanks to gobind
|
||||
Coords: p.Coords,
|
||||
Remote: p.Remote,
|
||||
RXBytes: DataUnit(p.RXBytes),
|
||||
TXBytes: DataUnit(p.TXBytes),
|
||||
Uptime: p.Uptime.Seconds(),
|
||||
})
|
||||
peer := PeerEntry{
|
||||
Port: p.Port,
|
||||
Up: p.Up,
|
||||
Inbound: p.Inbound,
|
||||
Priority: uint64(p.Priority), // can't be uint8 thanks to gobind
|
||||
URI: p.URI,
|
||||
RXBytes: DataUnit(p.RXBytes),
|
||||
TXBytes: DataUnit(p.TXBytes),
|
||||
Uptime: p.Uptime.Seconds(),
|
||||
}
|
||||
if addr := address.AddrForKey(p.Key); addr != nil {
|
||||
peer.PublicKey = hex.EncodeToString(p.Key)
|
||||
peer.IPAddress = net.IP(addr[:]).String()
|
||||
}
|
||||
if p.LastError != nil {
|
||||
peer.LastError = p.LastError.Error()
|
||||
peer.LastErrorTime = time.Since(p.LastErrorTime)
|
||||
}
|
||||
res.Peers = append(res.Peers, peer)
|
||||
}
|
||||
sort.Slice(res.Peers, func(i, j int) bool {
|
||||
if res.Peers[i].Port == res.Peers[j].Port {
|
||||
|
@ -9,12 +9,12 @@ import (
|
||||
type GetSelfRequest struct{}
|
||||
|
||||
type GetSelfResponse struct {
|
||||
BuildName string `json:"build_name"`
|
||||
BuildVersion string `json:"build_version"`
|
||||
PublicKey string `json:"key"`
|
||||
IPAddress string `json:"address"`
|
||||
Coords []uint64 `json:"coords"`
|
||||
Subnet string `json:"subnet"`
|
||||
BuildName string `json:"build_name"`
|
||||
BuildVersion string `json:"build_version"`
|
||||
PublicKey string `json:"key"`
|
||||
IPAddress string `json:"address"`
|
||||
RoutingEntries uint64 `json:"routing_entries"`
|
||||
Subnet string `json:"subnet"`
|
||||
}
|
||||
|
||||
func (a *AdminSocket) getSelfHandler(req *GetSelfRequest, res *GetSelfResponse) error {
|
||||
@ -25,6 +25,6 @@ func (a *AdminSocket) getSelfHandler(req *GetSelfRequest, res *GetSelfResponse)
|
||||
res.PublicKey = hex.EncodeToString(self.Key[:])
|
||||
res.IPAddress = a.core.Address().String()
|
||||
res.Subnet = snet.String()
|
||||
res.Coords = self.Coords
|
||||
res.RoutingEntries = self.RoutingEntries
|
||||
return nil
|
||||
}
|
||||
|
45
src/admin/gettree.go
Normal file
45
src/admin/gettree.go
Normal file
@ -0,0 +1,45 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||
)
|
||||
|
||||
type GetTreeRequest struct{}
|
||||
|
||||
type GetTreeResponse struct {
|
||||
Tree []TreeEntry `json:"tree"`
|
||||
}
|
||||
|
||||
type TreeEntry struct {
|
||||
IPAddress string `json:"address"`
|
||||
PublicKey string `json:"key"`
|
||||
Parent string `json:"parent"`
|
||||
Sequence uint64 `json:"sequence"`
|
||||
//Port uint64 `json:"port"`
|
||||
//Rest uint64 `json:"rest"`
|
||||
}
|
||||
|
||||
func (a *AdminSocket) getTreeHandler(req *GetTreeRequest, res *GetTreeResponse) error {
|
||||
tree := a.core.GetTree()
|
||||
res.Tree = make([]TreeEntry, 0, len(tree))
|
||||
for _, d := range tree {
|
||||
addr := address.AddrForKey(d.Key)
|
||||
res.Tree = append(res.Tree, TreeEntry{
|
||||
IPAddress: net.IP(addr[:]).String(),
|
||||
PublicKey: hex.EncodeToString(d.Key[:]),
|
||||
Parent: hex.EncodeToString(d.Parent[:]),
|
||||
Sequence: d.Sequence,
|
||||
//Port: d.Port,
|
||||
//Rest: d.Rest,
|
||||
})
|
||||
}
|
||||
sort.SliceStable(res.Tree, func(i, j int) bool {
|
||||
return strings.Compare(res.Tree[i].PublicKey, res.Tree[j].PublicKey) < 0
|
||||
})
|
||||
return nil
|
||||
}
|
@ -17,22 +17,38 @@ configuration option that is not provided.
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/hjson/hjson-go/v4"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
)
|
||||
|
||||
// NodeConfig is the main configuration structure, containing configuration
|
||||
// options that are necessary for an Yggdrasil node to run. You will need to
|
||||
// supply one of these structs to the Yggdrasil core when starting a node.
|
||||
type NodeConfig struct {
|
||||
PrivateKey KeyBytes `comment:"Your private key. DO NOT share this with anyone!"`
|
||||
PrivateKeyPath string `json:",omitempty"`
|
||||
Certificate *tls.Certificate `json:"-"`
|
||||
Peers []string `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tls://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j. These connections\nwill obey the operating system routing table, therefore you should\nuse this section when you may connect via different interfaces."`
|
||||
InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ \"tls://a.b.c.d:e\" ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."`
|
||||
Listen []string `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntls://0.0.0.0:0 or tls://[::]:0 to listen on all interfaces."`
|
||||
AdminListen string `comment:"Listen address for admin connections. Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X. To disable\nthe admin socket, use the value \"none\" instead."`
|
||||
MulticastInterfaces []MulticastInterfaceConfig `comment:"Configuration for which interfaces multicast peer discovery should be\nenabled on. Each entry in the list should be a json object which may\ncontain Regex, Beacon, Listen, and Port. Regex is a regular expression\nwhich is matched against an interface name, and interfaces use the\nfirst configuration that they match gainst. Beacon configures whether\nor not the node should send link-local multicast beacons to advertise\ntheir presence, while listening for incoming connections on Port.\nListen controls whether or not the node listens for multicast beacons\nand opens outgoing connections."`
|
||||
AllowedPublicKeys []string `comment:"List of peer public keys to allow incoming peering connections\nfrom. If left empty/undefined then all connections will be allowed\nby default. This does not affect outgoing peerings, nor does it\naffect link-local peers discovered via multicast."`
|
||||
PublicKey string `comment:"Your public key. Your peers may ask you for this to put\ninto their AllowedPublicKeys configuration."`
|
||||
PrivateKey string `comment:"Your private key. DO NOT share this with anyone!"`
|
||||
IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."`
|
||||
IfMTU uint64 `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."`
|
||||
NodeInfoPrivacy bool `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."`
|
||||
@ -45,16 +61,199 @@ type MulticastInterfaceConfig struct {
|
||||
Listen bool
|
||||
Port uint16
|
||||
Priority uint64 // really uint8, but gobind won't export it
|
||||
Password string
|
||||
}
|
||||
|
||||
// NewSigningKeys replaces the signing keypair in the NodeConfig with a new
|
||||
// signing keypair. The signing keys are used by the switch to derive the
|
||||
// structure of the spanning tree.
|
||||
func (cfg *NodeConfig) NewKeys() {
|
||||
spub, spriv, err := ed25519.GenerateKey(nil)
|
||||
// Generates default configuration and returns a pointer to the resulting
|
||||
// NodeConfig. This is used when outputting the -genconf parameter and also when
|
||||
// using -autoconf.
|
||||
func GenerateConfig() *NodeConfig {
|
||||
// Get the defaults for the platform.
|
||||
defaults := GetDefaults()
|
||||
// Create a node configuration and populate it.
|
||||
cfg := new(NodeConfig)
|
||||
cfg.NewPrivateKey()
|
||||
cfg.Listen = []string{}
|
||||
cfg.AdminListen = defaults.DefaultAdminListen
|
||||
cfg.Peers = []string{}
|
||||
cfg.InterfacePeers = map[string][]string{}
|
||||
cfg.AllowedPublicKeys = []string{}
|
||||
cfg.MulticastInterfaces = defaults.DefaultMulticastInterfaces
|
||||
cfg.IfName = defaults.DefaultIfName
|
||||
cfg.IfMTU = defaults.DefaultIfMTU
|
||||
cfg.NodeInfoPrivacy = false
|
||||
if err := cfg.postprocessConfig(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (cfg *NodeConfig) ReadFrom(r io.Reader) (int64, error) {
|
||||
conf, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n := int64(len(conf))
|
||||
// If there's a byte order mark - which Windows 10 is now incredibly fond of
|
||||
// throwing everywhere when it's converting things into UTF-16 for the hell
|
||||
// of it - remove it and decode back down into UTF-8. This is necessary
|
||||
// because hjson doesn't know what to do with UTF-16 and will panic
|
||||
if bytes.Equal(conf[0:2], []byte{0xFF, 0xFE}) ||
|
||||
bytes.Equal(conf[0:2], []byte{0xFE, 0xFF}) {
|
||||
utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM)
|
||||
decoder := utf.NewDecoder()
|
||||
conf, err = decoder.Bytes(conf)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
// Generate a new configuration - this gives us a set of sane defaults -
|
||||
// then parse the configuration we loaded above on top of it. The effect
|
||||
// of this is that any configuration item that is missing from the provided
|
||||
// configuration will use a sane default.
|
||||
*cfg = *GenerateConfig()
|
||||
if err := cfg.UnmarshalHJSON(conf); err != nil {
|
||||
return n, err
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (cfg *NodeConfig) UnmarshalHJSON(b []byte) error {
|
||||
if err := hjson.Unmarshal(b, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
return cfg.postprocessConfig()
|
||||
}
|
||||
|
||||
func (cfg *NodeConfig) postprocessConfig() error {
|
||||
if cfg.PrivateKeyPath != "" {
|
||||
cfg.PrivateKey = nil
|
||||
f, err := os.ReadFile(cfg.PrivateKeyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cfg.UnmarshalPEMPrivateKey(f); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case cfg.Certificate == nil:
|
||||
// No self-signed certificate has been generated yet.
|
||||
fallthrough
|
||||
case !bytes.Equal(cfg.Certificate.PrivateKey.(ed25519.PrivateKey), cfg.PrivateKey):
|
||||
// A self-signed certificate was generated but the private
|
||||
// key has changed since then, possibly because a new config
|
||||
// was parsed.
|
||||
if err := cfg.GenerateSelfSignedCertificate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RFC5280 section 4.1.2.5
|
||||
var notAfterNeverExpires = time.Date(9999, time.December, 31, 23, 59, 59, 0, time.UTC)
|
||||
|
||||
func (cfg *NodeConfig) GenerateSelfSignedCertificate() error {
|
||||
key, err := cfg.MarshalPEMPrivateKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cert, err := cfg.MarshalPEMCertificate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tlsCert, err := tls.X509KeyPair(cert, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg.Certificate = &tlsCert
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *NodeConfig) MarshalPEMCertificate() ([]byte, error) {
|
||||
privateKey := ed25519.PrivateKey(cfg.PrivateKey)
|
||||
publicKey := privateKey.Public().(ed25519.PublicKey)
|
||||
|
||||
cert := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{
|
||||
CommonName: hex.EncodeToString(publicKey),
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: notAfterNeverExpires,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
certbytes, err := x509.CreateCertificate(rand.Reader, cert, cert, publicKey, privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block := &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: certbytes,
|
||||
}
|
||||
return pem.EncodeToMemory(block), nil
|
||||
}
|
||||
|
||||
func (cfg *NodeConfig) NewPrivateKey() {
|
||||
_, spriv, err := ed25519.GenerateKey(nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cfg.PublicKey = hex.EncodeToString(spub[:])
|
||||
cfg.PrivateKey = hex.EncodeToString(spriv[:])
|
||||
cfg.PrivateKey = KeyBytes(spriv)
|
||||
}
|
||||
|
||||
func (cfg *NodeConfig) MarshalPEMPrivateKey() ([]byte, error) {
|
||||
b, err := x509.MarshalPKCS8PrivateKey(ed25519.PrivateKey(cfg.PrivateKey))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal PKCS8 key: %w", err)
|
||||
}
|
||||
block := &pem.Block{
|
||||
Type: "PRIVATE KEY",
|
||||
Bytes: b,
|
||||
}
|
||||
return pem.EncodeToMemory(block), nil
|
||||
}
|
||||
|
||||
func (cfg *NodeConfig) UnmarshalPEMPrivateKey(b []byte) error {
|
||||
p, _ := pem.Decode(b)
|
||||
if p == nil {
|
||||
return fmt.Errorf("failed to parse PEM file")
|
||||
}
|
||||
if p.Type != "PRIVATE KEY" {
|
||||
return fmt.Errorf("unexpected PEM type %q", p.Type)
|
||||
}
|
||||
k, err := x509.ParsePKCS8PrivateKey(p.Bytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal PKCS8 key: %w", err)
|
||||
}
|
||||
key, ok := k.(ed25519.PrivateKey)
|
||||
if !ok {
|
||||
return fmt.Errorf("private key must be ed25519 key")
|
||||
}
|
||||
if len(key) != ed25519.PrivateKeySize {
|
||||
return fmt.Errorf("unexpected ed25519 private key length")
|
||||
}
|
||||
cfg.PrivateKey = KeyBytes(key)
|
||||
return nil
|
||||
}
|
||||
|
||||
type KeyBytes []byte
|
||||
|
||||
func (k KeyBytes) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(hex.EncodeToString(k))
|
||||
}
|
||||
|
||||
func (k *KeyBytes) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
var err error
|
||||
if err = json.Unmarshal(b, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
*k, err = hex.DecodeString(s)
|
||||
return err
|
||||
}
|
||||
|
@ -1,54 +1,54 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConfig_Keys(t *testing.T) {
|
||||
var nodeConfig NodeConfig
|
||||
nodeConfig.NewKeys()
|
||||
/*
|
||||
var nodeConfig NodeConfig
|
||||
nodeConfig.NewKeys()
|
||||
|
||||
publicKey1, err := hex.DecodeString(nodeConfig.PublicKey)
|
||||
publicKey1, err := hex.DecodeString(nodeConfig.PublicKey)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal("can not decode generated public key")
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal("can not decode generated public key")
|
||||
}
|
||||
|
||||
if len(publicKey1) == 0 {
|
||||
t.Fatal("empty public key generated")
|
||||
}
|
||||
if len(publicKey1) == 0 {
|
||||
t.Fatal("empty public key generated")
|
||||
}
|
||||
|
||||
privateKey1, err := hex.DecodeString(nodeConfig.PrivateKey)
|
||||
privateKey1, err := hex.DecodeString(nodeConfig.PrivateKey)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal("can not decode generated private key")
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal("can not decode generated private key")
|
||||
}
|
||||
|
||||
if len(privateKey1) == 0 {
|
||||
t.Fatal("empty private key generated")
|
||||
}
|
||||
if len(privateKey1) == 0 {
|
||||
t.Fatal("empty private key generated")
|
||||
}
|
||||
|
||||
nodeConfig.NewKeys()
|
||||
nodeConfig.NewKeys()
|
||||
|
||||
publicKey2, err := hex.DecodeString(nodeConfig.PublicKey)
|
||||
publicKey2, err := hex.DecodeString(nodeConfig.PublicKey)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal("can not decode generated public key")
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal("can not decode generated public key")
|
||||
}
|
||||
|
||||
if bytes.Equal(publicKey2, publicKey1) {
|
||||
t.Fatal("same public key generated")
|
||||
}
|
||||
if bytes.Equal(publicKey2, publicKey1) {
|
||||
t.Fatal("same public key generated")
|
||||
}
|
||||
|
||||
privateKey2, err := hex.DecodeString(nodeConfig.PrivateKey)
|
||||
privateKey2, err := hex.DecodeString(nodeConfig.PrivateKey)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal("can not decode generated private key")
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal("can not decode generated private key")
|
||||
}
|
||||
|
||||
if bytes.Equal(privateKey2, privateKey1) {
|
||||
t.Fatal("same private key generated")
|
||||
}
|
||||
if bytes.Equal(privateKey2, privateKey1) {
|
||||
t.Fatal("same private key generated")
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
34
src/config/defaults.go
Normal file
34
src/config/defaults.go
Normal file
@ -0,0 +1,34 @@
|
||||
package config
|
||||
|
||||
var defaultConfig = "" // LDFLAGS='-X github.com/yggdrasil-network/yggdrasil-go/src/config.defaultConfig=/path/to/config
|
||||
var defaultAdminListen = "" // LDFLAGS='-X github.com/yggdrasil-network/yggdrasil-go/src/config.defaultAdminListen=unix://path/to/sock'
|
||||
|
||||
// Defines which parameters are expected by default for configuration on a
|
||||
// specific platform. These values are populated in the relevant defaults_*.go
|
||||
// for the platform being targeted. They must be set.
|
||||
type platformDefaultParameters struct {
|
||||
// Admin socket
|
||||
DefaultAdminListen string
|
||||
|
||||
// Configuration (used for yggdrasilctl)
|
||||
DefaultConfigFile string
|
||||
|
||||
// Multicast interfaces
|
||||
DefaultMulticastInterfaces []MulticastInterfaceConfig
|
||||
|
||||
// TUN
|
||||
MaximumIfMTU uint64
|
||||
DefaultIfMTU uint64
|
||||
DefaultIfName string
|
||||
}
|
||||
|
||||
func GetDefaults() platformDefaultParameters {
|
||||
defaults := getDefaults()
|
||||
if defaultConfig != "" {
|
||||
defaults.DefaultConfigFile = defaultConfig
|
||||
}
|
||||
if defaultAdminListen != "" {
|
||||
defaults.DefaultAdminListen = defaultAdminListen
|
||||
}
|
||||
return defaults
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package defaults
|
||||
package config
|
||||
|
||||
// Sane defaults for the macOS/Darwin platform. The "default" options may be
|
||||
// may be replaced by the running configuration.
|
@ -1,7 +1,7 @@
|
||||
//go:build freebsd
|
||||
// +build freebsd
|
||||
|
||||
package defaults
|
||||
package config
|
||||
|
||||
// Sane defaults for the BSD platforms. The "default" options may be
|
||||
// may be replaced by the running configuration.
|
@ -1,7 +1,7 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package defaults
|
||||
package config
|
||||
|
||||
// Sane defaults for the Linux platform. The "default" options may be
|
||||
// may be replaced by the running configuration.
|
@ -1,7 +1,7 @@
|
||||
//go:build openbsd
|
||||
// +build openbsd
|
||||
|
||||
package defaults
|
||||
package config
|
||||
|
||||
// Sane defaults for the BSD platforms. The "default" options may be
|
||||
// may be replaced by the running configuration.
|
@ -1,7 +1,7 @@
|
||||
//go:build !linux && !darwin && !windows && !openbsd && !freebsd
|
||||
// +build !linux,!darwin,!windows,!openbsd,!freebsd
|
||||
|
||||
package defaults
|
||||
package config
|
||||
|
||||
// Sane defaults for the other platforms. The "default" options may be
|
||||
// may be replaced by the running configuration.
|
@ -1,7 +1,7 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package defaults
|
||||
package config
|
||||
|
||||
// Sane defaults for the Windows platform. The "default" options may be
|
||||
// may be replaced by the running configuration.
|
202
src/core/api.go
202
src/core/api.go
@ -10,36 +10,44 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Arceliar/phony"
|
||||
|
||||
"github.com/Arceliar/ironwood/network"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||
)
|
||||
|
||||
type SelfInfo struct {
|
||||
Key ed25519.PublicKey
|
||||
Root ed25519.PublicKey
|
||||
Coords []uint64
|
||||
Key ed25519.PublicKey
|
||||
RoutingEntries uint64
|
||||
}
|
||||
|
||||
type PeerInfo struct {
|
||||
Key ed25519.PublicKey
|
||||
Root ed25519.PublicKey
|
||||
Coords []uint64
|
||||
Port uint64
|
||||
Priority uint8
|
||||
Remote string
|
||||
RXBytes uint64
|
||||
TXBytes uint64
|
||||
Uptime time.Duration
|
||||
URI string
|
||||
Up bool
|
||||
Inbound bool
|
||||
LastError error
|
||||
LastErrorTime time.Time
|
||||
Key ed25519.PublicKey
|
||||
Root ed25519.PublicKey
|
||||
Coords []uint64
|
||||
Port uint64
|
||||
Priority uint8
|
||||
RXBytes uint64
|
||||
TXBytes uint64
|
||||
Uptime time.Duration
|
||||
}
|
||||
|
||||
type DHTEntryInfo struct {
|
||||
Key ed25519.PublicKey
|
||||
Port uint64
|
||||
Rest uint64
|
||||
type TreeEntryInfo struct {
|
||||
Key ed25519.PublicKey
|
||||
Parent ed25519.PublicKey
|
||||
Sequence uint64
|
||||
//Port uint64
|
||||
//Rest uint64
|
||||
}
|
||||
|
||||
type PathEntryInfo struct {
|
||||
Key ed25519.PublicKey
|
||||
Path []uint64
|
||||
Key ed25519.PublicKey
|
||||
Path []uint64
|
||||
Sequence uint64
|
||||
}
|
||||
|
||||
type SessionInfo struct {
|
||||
@ -53,55 +61,59 @@ func (c *Core) GetSelf() SelfInfo {
|
||||
var self SelfInfo
|
||||
s := c.PacketConn.PacketConn.Debug.GetSelf()
|
||||
self.Key = s.Key
|
||||
self.Root = s.Root
|
||||
self.Coords = s.Coords
|
||||
self.RoutingEntries = s.RoutingEntries
|
||||
return self
|
||||
}
|
||||
|
||||
func (c *Core) GetPeers() []PeerInfo {
|
||||
var peers []PeerInfo
|
||||
names := make(map[net.Conn]string)
|
||||
peers := []PeerInfo{}
|
||||
conns := map[net.Conn]network.DebugPeerInfo{}
|
||||
iwpeers := c.PacketConn.PacketConn.Debug.GetPeers()
|
||||
for _, p := range iwpeers {
|
||||
conns[p.Conn] = p
|
||||
}
|
||||
|
||||
phony.Block(&c.links, func() {
|
||||
for _, info := range c.links._links {
|
||||
if info == nil {
|
||||
continue
|
||||
for info, state := range c.links._links {
|
||||
var peerinfo PeerInfo
|
||||
var conn net.Conn
|
||||
peerinfo.URI = info.uri
|
||||
peerinfo.LastError = state._err
|
||||
peerinfo.LastErrorTime = state._errtime
|
||||
if c := state._conn; c != nil {
|
||||
conn = c
|
||||
peerinfo.Up = true
|
||||
peerinfo.Inbound = state.linkType == linkTypeIncoming
|
||||
peerinfo.RXBytes = atomic.LoadUint64(&c.rx)
|
||||
peerinfo.TXBytes = atomic.LoadUint64(&c.tx)
|
||||
peerinfo.Uptime = time.Since(c.up)
|
||||
}
|
||||
names[info.conn] = info.lname
|
||||
if p, ok := conns[conn]; ok {
|
||||
peerinfo.Key = p.Key
|
||||
peerinfo.Root = p.Root
|
||||
peerinfo.Port = p.Port
|
||||
peerinfo.Priority = p.Priority
|
||||
}
|
||||
peers = append(peers, peerinfo)
|
||||
}
|
||||
})
|
||||
ps := c.PacketConn.PacketConn.Debug.GetPeers()
|
||||
for _, p := range ps {
|
||||
var info PeerInfo
|
||||
info.Key = p.Key
|
||||
info.Root = p.Root
|
||||
info.Coords = p.Coords
|
||||
info.Port = p.Port
|
||||
info.Priority = p.Priority
|
||||
info.Remote = p.Conn.RemoteAddr().String()
|
||||
if name := names[p.Conn]; name != "" {
|
||||
info.Remote = name
|
||||
}
|
||||
if linkconn, ok := p.Conn.(*linkConn); ok {
|
||||
info.RXBytes = atomic.LoadUint64(&linkconn.rx)
|
||||
info.TXBytes = atomic.LoadUint64(&linkconn.tx)
|
||||
info.Uptime = time.Since(linkconn.up)
|
||||
}
|
||||
peers = append(peers, info)
|
||||
}
|
||||
|
||||
return peers
|
||||
}
|
||||
|
||||
func (c *Core) GetDHT() []DHTEntryInfo {
|
||||
var dhts []DHTEntryInfo
|
||||
ds := c.PacketConn.PacketConn.Debug.GetDHT()
|
||||
for _, d := range ds {
|
||||
var info DHTEntryInfo
|
||||
info.Key = d.Key
|
||||
info.Port = d.Port
|
||||
info.Rest = d.Rest
|
||||
dhts = append(dhts, info)
|
||||
func (c *Core) GetTree() []TreeEntryInfo {
|
||||
var trees []TreeEntryInfo
|
||||
ts := c.PacketConn.PacketConn.Debug.GetTree()
|
||||
for _, t := range ts {
|
||||
var info TreeEntryInfo
|
||||
info.Key = t.Key
|
||||
info.Parent = t.Parent
|
||||
info.Sequence = t.Sequence
|
||||
//info.Port = d.Port
|
||||
//info.Rest = d.Rest
|
||||
trees = append(trees, info)
|
||||
}
|
||||
return dhts
|
||||
return trees
|
||||
}
|
||||
|
||||
func (c *Core) GetPaths() []PathEntryInfo {
|
||||
@ -110,6 +122,7 @@ func (c *Core) GetPaths() []PathEntryInfo {
|
||||
for _, p := range ps {
|
||||
var info PathEntryInfo
|
||||
info.Key = p.Key
|
||||
info.Sequence = p.Sequence
|
||||
info.Path = p.Path
|
||||
paths = append(paths, info)
|
||||
}
|
||||
@ -134,16 +147,7 @@ func (c *Core) GetSessions() []SessionInfo {
|
||||
// parsed from a string of the form e.g. "tcp://a.b.c.d:e". In the case of a
|
||||
// link-local address, the interface should be provided as the second argument.
|
||||
func (c *Core) Listen(u *url.URL, sintf string) (*Listener, error) {
|
||||
switch u.Scheme {
|
||||
case "tcp":
|
||||
return c.links.tcp.listen(u, sintf)
|
||||
case "tls":
|
||||
return c.links.tls.listen(u, sintf)
|
||||
case "unix":
|
||||
return c.links.unix.listen(u, sintf)
|
||||
default:
|
||||
return nil, fmt.Errorf("unrecognised scheme %q", u.Scheme)
|
||||
}
|
||||
return c.links.listen(u, sintf)
|
||||
}
|
||||
|
||||
// Address gets the IPv6 address of the Yggdrasil node. This is always a /128
|
||||
@ -182,49 +186,34 @@ func (c *Core) SetLogger(log Logger) {
|
||||
//
|
||||
// This adds the peer to the peer list, so that they will be called again if the
|
||||
// connection drops.
|
||||
func (c *Core) AddPeer(uri string, sourceInterface string) error {
|
||||
var known bool
|
||||
phony.Block(c, func() {
|
||||
_, known = c.config._peers[Peer{uri, sourceInterface}]
|
||||
})
|
||||
if known {
|
||||
return fmt.Errorf("peer already configured")
|
||||
}
|
||||
u, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := c.links.call(u, sourceInterface, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
phony.Block(c, func() {
|
||||
c.config._peers[Peer{uri, sourceInterface}] = &info
|
||||
})
|
||||
return nil
|
||||
func (c *Core) AddPeer(u *url.URL, sintf string) error {
|
||||
return c.links.add(u, sintf, linkTypePersistent)
|
||||
}
|
||||
|
||||
// RemovePeer removes a peer. The peer should be specified in URI format, see AddPeer.
|
||||
// The peer is not disconnected immediately.
|
||||
func (c *Core) RemovePeer(uri string, sourceInterface string) error {
|
||||
var err error
|
||||
phony.Block(c, func() {
|
||||
peer := Peer{uri, sourceInterface}
|
||||
linkInfo, ok := c.config._peers[peer]
|
||||
if !ok {
|
||||
err = fmt.Errorf("peer not configured")
|
||||
return
|
||||
}
|
||||
if ok && linkInfo != nil {
|
||||
c.links.Act(nil, func() {
|
||||
if link := c.links._links[*linkInfo]; link != nil {
|
||||
_ = link.close()
|
||||
}
|
||||
})
|
||||
}
|
||||
delete(c.config._peers, peer)
|
||||
})
|
||||
return err
|
||||
return fmt.Errorf("not implemented yet")
|
||||
/*
|
||||
var err error
|
||||
phony.Block(c, func() {
|
||||
peer := Peer{uri, sourceInterface}
|
||||
linkInfo, ok := c.config._peers[peer]
|
||||
if !ok {
|
||||
err = fmt.Errorf("peer not configured")
|
||||
return
|
||||
}
|
||||
if ok && linkInfo != nil {
|
||||
c.links.Act(nil, func() {
|
||||
if link := c.links._links[*linkInfo]; link != nil {
|
||||
_ = link.conn.Close()
|
||||
}
|
||||
})
|
||||
}
|
||||
delete(c.config._peers, peer)
|
||||
})
|
||||
return err
|
||||
*/
|
||||
}
|
||||
|
||||
// CallPeer calls a peer once. This should be specified in the peer URI format,
|
||||
@ -236,8 +225,7 @@ func (c *Core) RemovePeer(uri string, sourceInterface string) error {
|
||||
// This does not add the peer to the peer list, so if the connection drops, the
|
||||
// peer will not be called again automatically.
|
||||
func (c *Core) CallPeer(u *url.URL, sintf string) error {
|
||||
_, err := c.links.call(u, sintf, nil)
|
||||
return err
|
||||
return c.links.add(u, sintf, linkTypeEphemeral)
|
||||
}
|
||||
|
||||
func (c *Core) PublicKey() ed25519.PublicKey {
|
||||
@ -274,8 +262,8 @@ func (c *Core) SetAdmin(a AddHandler) error {
|
||||
return err
|
||||
}
|
||||
if err := a.AddHandler(
|
||||
"debug_remoteGetDHT", "Debug use only", []string{"key"},
|
||||
c.proto.getDHTHandler,
|
||||
"debug_remoteGetTree", "Debug use only", []string{"key"},
|
||||
c.proto.getTreeHandler,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
139
src/core/core.go
139
src/core/core.go
@ -3,6 +3,8 @@ package core
|
||||
import (
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
@ -10,10 +12,12 @@ import (
|
||||
"time"
|
||||
|
||||
iwe "github.com/Arceliar/ironwood/encrypted"
|
||||
iwn "github.com/Arceliar/ironwood/network"
|
||||
iwt "github.com/Arceliar/ironwood/types"
|
||||
"github.com/Arceliar/phony"
|
||||
"github.com/gologme/log"
|
||||
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/version"
|
||||
)
|
||||
|
||||
@ -34,49 +38,91 @@ type Core struct {
|
||||
log Logger
|
||||
addPeerTimer *time.Timer
|
||||
config struct {
|
||||
_peers map[Peer]*linkInfo // configurable after startup
|
||||
tls *tls.Config // immutable after startup
|
||||
//_peers map[Peer]*linkInfo // configurable after startup
|
||||
_listeners map[ListenAddress]struct{} // configurable after startup
|
||||
nodeinfo NodeInfo // immutable after startup
|
||||
nodeinfoPrivacy NodeInfoPrivacy // immutable after startup
|
||||
_allowedPublicKeys map[[32]byte]struct{} // configurable after startup
|
||||
}
|
||||
pathNotify func(ed25519.PublicKey)
|
||||
}
|
||||
|
||||
func New(secret ed25519.PrivateKey, logger Logger, opts ...SetupOption) (*Core, error) {
|
||||
func New(cert *tls.Certificate, logger Logger, opts ...SetupOption) (*Core, error) {
|
||||
c := &Core{
|
||||
log: logger,
|
||||
}
|
||||
c.ctx, c.cancel = context.WithCancel(context.Background())
|
||||
if c.log == nil {
|
||||
c.log = log.New(io.Discard, "", 0)
|
||||
}
|
||||
|
||||
if name := version.BuildName(); name != "unknown" {
|
||||
c.log.Infoln("Build name:", name)
|
||||
}
|
||||
if version := version.BuildVersion(); version != "unknown" {
|
||||
c.log.Infoln("Build version:", version)
|
||||
}
|
||||
c.ctx, c.cancel = context.WithCancel(context.Background())
|
||||
// Take a copy of the private key so that it is in our own memory space.
|
||||
if len(secret) != ed25519.PrivateKeySize {
|
||||
return nil, fmt.Errorf("private key is incorrect length")
|
||||
}
|
||||
c.secret = make(ed25519.PrivateKey, ed25519.PrivateKeySize)
|
||||
copy(c.secret, secret)
|
||||
c.public = secret.Public().(ed25519.PublicKey)
|
||||
|
||||
var err error
|
||||
if c.PacketConn, err = iwe.NewPacketConn(c.secret); err != nil {
|
||||
return nil, fmt.Errorf("error creating encryption: %w", err)
|
||||
}
|
||||
c.config._peers = map[Peer]*linkInfo{}
|
||||
c.config._listeners = map[ListenAddress]struct{}{}
|
||||
c.config._allowedPublicKeys = map[[32]byte]struct{}{}
|
||||
for _, opt := range opts {
|
||||
c._applyOption(opt)
|
||||
switch opt.(type) {
|
||||
case Peer, ListenAddress:
|
||||
// We can't do peers yet as the links aren't set up.
|
||||
continue
|
||||
default:
|
||||
if err = c._applyOption(opt); err != nil {
|
||||
return nil, fmt.Errorf("failed to apply configuration option %T: %w", opt, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if c.log == nil {
|
||||
c.log = log.New(io.Discard, "", 0)
|
||||
if cert == nil || cert.PrivateKey == nil {
|
||||
return nil, fmt.Errorf("no private key supplied")
|
||||
}
|
||||
var ok bool
|
||||
if c.secret, ok = cert.PrivateKey.(ed25519.PrivateKey); !ok {
|
||||
return nil, fmt.Errorf("private key must be ed25519")
|
||||
}
|
||||
if len(c.secret) != ed25519.PrivateKeySize {
|
||||
return nil, fmt.Errorf("private key is incorrect length")
|
||||
}
|
||||
c.public = c.secret.Public().(ed25519.PublicKey)
|
||||
|
||||
if c.config.tls, err = c.generateTLSConfig(cert); err != nil {
|
||||
return nil, fmt.Errorf("error generating TLS config: %w", err)
|
||||
}
|
||||
keyXform := func(key ed25519.PublicKey) ed25519.PublicKey {
|
||||
return address.SubnetForKey(key).GetKey()
|
||||
}
|
||||
if c.PacketConn, err = iwe.NewPacketConn(
|
||||
c.secret,
|
||||
iwn.WithBloomTransform(keyXform),
|
||||
iwn.WithPeerMaxMessageSize(65535*2),
|
||||
iwn.WithPathNotify(c.doPathNotify),
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("error creating encryption: %w", err)
|
||||
}
|
||||
address, subnet := c.Address(), c.Subnet()
|
||||
c.log.Infof("Your public key is %s", hex.EncodeToString(c.public))
|
||||
c.log.Infof("Your IPv6 address is %s", address.String())
|
||||
c.log.Infof("Your IPv6 subnet is %s", subnet.String())
|
||||
c.proto.init(c)
|
||||
if err := c.links.init(c); err != nil {
|
||||
return nil, fmt.Errorf("error initialising links: %w", err)
|
||||
}
|
||||
for _, opt := range opts {
|
||||
switch opt.(type) {
|
||||
case Peer, ListenAddress:
|
||||
// Now do the peers and listeners.
|
||||
if err = c._applyOption(opt); err != nil {
|
||||
return nil, fmt.Errorf("failed to apply configuration option %T: %w", opt, err)
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
if err := c.proto.nodeinfo.setNodeInfo(c.config.nodeinfo, bool(c.config.nodeinfoPrivacy)); err != nil {
|
||||
return nil, fmt.Errorf("error setting node info: %w", err)
|
||||
}
|
||||
@ -90,42 +136,11 @@ func New(secret ed25519.PrivateKey, logger Logger, opts ...SetupOption) (*Core,
|
||||
c.log.Errorf("Failed to start listener %q: %s\n", listenaddr, err)
|
||||
}
|
||||
}
|
||||
c.Act(nil, c._addPeerLoop)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// If any static peers were provided in the configuration above then we should
|
||||
// configure them. The loop ensures that disconnected peers will eventually
|
||||
// be reconnected with.
|
||||
func (c *Core) _addPeerLoop() {
|
||||
select {
|
||||
case <-c.ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
// Add peers from the Peers section
|
||||
for peer := range c.config._peers {
|
||||
go func(peer string, intf string) {
|
||||
u, err := url.Parse(peer)
|
||||
if err != nil {
|
||||
c.log.Errorln("Failed to parse peer url:", peer, err)
|
||||
}
|
||||
if err := c.CallPeer(u, intf); err != nil {
|
||||
c.log.Errorln("Failed to add peer:", err)
|
||||
}
|
||||
}(peer.URI, peer.SourceInterface) // TODO: this should be acted and not in a goroutine?
|
||||
}
|
||||
|
||||
c.addPeerTimer = time.AfterFunc(time.Minute, func() {
|
||||
c.Act(nil, c._addPeerLoop)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Core) RetryPeersNow() {
|
||||
if c.addPeerTimer != nil && !c.addPeerTimer.Stop() {
|
||||
<-c.addPeerTimer.C
|
||||
}
|
||||
c.Act(nil, c._addPeerLoop)
|
||||
// TODO: figure out a way to retrigger peer connections.
|
||||
}
|
||||
|
||||
// Stop shuts down the Yggdrasil node.
|
||||
@ -151,11 +166,16 @@ func (c *Core) _close() error {
|
||||
|
||||
func (c *Core) MTU() uint64 {
|
||||
const sessionTypeOverhead = 1
|
||||
return c.PacketConn.MTU() - sessionTypeOverhead
|
||||
MTU := c.PacketConn.MTU() - sessionTypeOverhead
|
||||
if MTU > 65535 {
|
||||
MTU = 65535
|
||||
}
|
||||
return MTU
|
||||
}
|
||||
|
||||
func (c *Core) ReadFrom(p []byte) (n int, from net.Addr, err error) {
|
||||
buf := make([]byte, c.PacketConn.MTU(), 65535)
|
||||
buf := allocBytes(int(c.PacketConn.MTU()))
|
||||
defer freeBytes(buf)
|
||||
for {
|
||||
bs := buf
|
||||
n, from, err = c.PacketConn.ReadFrom(bs)
|
||||
@ -189,7 +209,8 @@ func (c *Core) ReadFrom(p []byte) (n int, from net.Addr, err error) {
|
||||
}
|
||||
|
||||
func (c *Core) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
buf := make([]byte, 0, 65535)
|
||||
buf := allocBytes(0)
|
||||
defer freeBytes(buf)
|
||||
buf = append(buf, typeSessionTraffic)
|
||||
buf = append(buf, p...)
|
||||
n, err = c.PacketConn.WriteTo(buf, addr)
|
||||
@ -199,6 +220,20 @@ func (c *Core) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Core) doPathNotify(key ed25519.PublicKey) {
|
||||
c.Act(nil, func() {
|
||||
if c.pathNotify != nil {
|
||||
c.pathNotify(key)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Core) SetPathNotify(notify func(ed25519.PublicKey)) {
|
||||
c.Act(nil, func() {
|
||||
c.pathNotify = notify
|
||||
})
|
||||
}
|
||||
|
||||
type Logger interface {
|
||||
Printf(string, ...interface{})
|
||||
Println(...interface{})
|
||||
|
@ -2,14 +2,14 @@ package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"math/rand"
|
||||
"crypto/rand"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gologme/log"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||
)
|
||||
|
||||
// GetLoggerWithPrefix creates a new logger instance with prefix.
|
||||
@ -29,29 +29,40 @@ func GetLoggerWithPrefix(prefix string, verbose bool) *log.Logger {
|
||||
// Verbosity flag is passed to logger.
|
||||
func CreateAndConnectTwo(t testing.TB, verbose bool) (nodeA *Core, nodeB *Core) {
|
||||
var err error
|
||||
var skA, skB ed25519.PrivateKey
|
||||
if _, skA, err = ed25519.GenerateKey(nil); err != nil {
|
||||
|
||||
cfgA, cfgB := config.GenerateConfig(), config.GenerateConfig()
|
||||
if err = cfgA.GenerateSelfSignedCertificate(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, skB, err = ed25519.GenerateKey(nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
logger := GetLoggerWithPrefix("", false)
|
||||
if nodeA, err = New(skA, logger, ListenAddress("tcp://127.0.0.1:0")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if nodeB, err = New(skB, logger, ListenAddress("tcp://127.0.0.1:0")); err != nil {
|
||||
if err = cfgB.GenerateSelfSignedCertificate(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
u, err := url.Parse("tcp://" + nodeA.links.tcp.getAddr().String())
|
||||
logger := GetLoggerWithPrefix("", false)
|
||||
logger.EnableLevel("debug")
|
||||
|
||||
if nodeA, err = New(cfgA.Certificate, logger); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if nodeB, err = New(cfgB.Certificate, logger); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
nodeAListenURL, err := url.Parse("tcp://localhost:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = nodeB.CallPeer(u, "")
|
||||
nodeAListener, err := nodeA.Listen(nodeAListenURL, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
nodeAURL, err := url.Parse("tcp://" + nodeAListener.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = nodeB.CallPeer(nodeAURL, ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
@ -70,7 +81,13 @@ func WaitConnected(nodeA, nodeB *Core) bool {
|
||||
// It may take up to 3 seconds, but let's wait 5.
|
||||
for i := 0; i < 50; i++ {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
if len(nodeA.GetPeers()) > 0 && len(nodeB.GetPeers()) > 0 {
|
||||
/*
|
||||
if len(nodeA.GetPeers()) > 0 && len(nodeB.GetPeers()) > 0 {
|
||||
return true
|
||||
}
|
||||
*/
|
||||
if len(nodeA.GetTree()) > 1 && len(nodeB.GetTree()) > 1 {
|
||||
time.Sleep(3 * time.Second) // FIXME hack, there's still stuff happening internally
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -129,7 +146,7 @@ func TestCore_Start_Transfer(t *testing.T) {
|
||||
|
||||
// Send
|
||||
msg := make([]byte, msgLen)
|
||||
rand.Read(msg[40:])
|
||||
_, _ = rand.Read(msg[40:])
|
||||
msg[0] = 0x60
|
||||
copy(msg[8:24], nodeB.Address())
|
||||
copy(msg[24:40], nodeA.Address())
|
||||
@ -161,7 +178,7 @@ func BenchmarkCore_Start_Transfer(b *testing.B) {
|
||||
|
||||
// Send
|
||||
msg := make([]byte, msgLen)
|
||||
rand.Read(msg[40:])
|
||||
_, _ = rand.Read(msg[40:])
|
||||
msg[0] = 0x60
|
||||
copy(msg[8:24], nodeB.Address())
|
||||
copy(msg[24:40], nodeA.Address())
|
||||
|
686
src/core/link.go
686
src/core/link.go
@ -2,11 +2,13 @@ package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -15,53 +17,72 @@ import (
|
||||
|
||||
"github.com/Arceliar/phony"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||
"golang.org/x/crypto/blake2b"
|
||||
)
|
||||
|
||||
type linkType int
|
||||
|
||||
const (
|
||||
linkTypePersistent linkType = iota // Statically configured
|
||||
linkTypeEphemeral // Multicast discovered
|
||||
linkTypeIncoming // Incoming connection
|
||||
)
|
||||
|
||||
type links struct {
|
||||
phony.Inbox
|
||||
core *Core
|
||||
tcp *linkTCP // TCP interface support
|
||||
tls *linkTLS // TLS interface support
|
||||
unix *linkUNIX // UNIX interface support
|
||||
socks *linkSOCKS // SOCKS interface support
|
||||
core *Core
|
||||
tcp *linkTCP // TCP interface support
|
||||
tls *linkTLS // TLS interface support
|
||||
unix *linkUNIX // UNIX interface support
|
||||
socks *linkSOCKS // SOCKS interface support
|
||||
quic *linkQUIC // QUIC interface support
|
||||
// _links can only be modified safely from within the links actor
|
||||
_links map[linkInfo]*link // *link is nil if connection in progress
|
||||
}
|
||||
|
||||
type linkProtocol interface {
|
||||
dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error)
|
||||
listen(ctx context.Context, url *url.URL, sintf string) (net.Listener, error)
|
||||
}
|
||||
|
||||
// linkInfo is used as a map key
|
||||
type linkInfo struct {
|
||||
linkType string // Type of link, e.g. TCP, AWDL
|
||||
local string // Local name or address
|
||||
remote string // Remote name or address
|
||||
}
|
||||
|
||||
type linkDial struct {
|
||||
url *url.URL
|
||||
sintf string
|
||||
uri string // Peering URI in complete form
|
||||
sintf string // Peering source interface (i.e. from InterfacePeers)
|
||||
}
|
||||
|
||||
// link tracks the state of a connection, either persistent or non-persistent
|
||||
type link struct {
|
||||
lname string
|
||||
links *links
|
||||
conn *linkConn
|
||||
options linkOptions
|
||||
info linkInfo
|
||||
incoming bool
|
||||
force bool
|
||||
kick chan struct{} // Attempt to reconnect now, if backing off
|
||||
linkType linkType // Type of link, i.e. outbound/inbound, persistent/ephemeral
|
||||
linkProto string // Protocol carrier of link, e.g. TCP, AWDL
|
||||
// The remaining fields can only be modified safely from within the links actor
|
||||
_conn *linkConn // Connected link, if any, nil if not connected
|
||||
_err error // Last error on the connection, if any
|
||||
_errtime time.Time // Last time an error occured
|
||||
}
|
||||
|
||||
type linkOptions struct {
|
||||
pinnedEd25519Keys map[keyArray]struct{}
|
||||
priority uint8
|
||||
tlsSNI string
|
||||
password []byte
|
||||
}
|
||||
|
||||
type Listener struct {
|
||||
net.Listener
|
||||
closed chan struct{}
|
||||
listener net.Listener
|
||||
ctx context.Context
|
||||
Cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func (l *Listener) Addr() net.Addr {
|
||||
return l.listener.Addr()
|
||||
}
|
||||
|
||||
func (l *Listener) Close() error {
|
||||
err := l.Listener.Close()
|
||||
<-l.closed
|
||||
l.Cancel()
|
||||
err := l.listener.Close()
|
||||
<-l.ctx.Done()
|
||||
return err
|
||||
}
|
||||
|
||||
@ -71,6 +92,7 @@ func (l *links) init(c *Core) error {
|
||||
l.tls = l.newLinkTLS(l.tcp)
|
||||
l.unix = l.newLinkUNIX()
|
||||
l.socks = l.newLinkSOCKS()
|
||||
l.quic = l.newLinkQUIC()
|
||||
l._links = make(map[linkInfo]*link)
|
||||
|
||||
var listeners []ListenAddress
|
||||
@ -102,303 +124,466 @@ func (l *links) shutdown() {
|
||||
})
|
||||
}
|
||||
|
||||
func (l *links) isConnectedTo(info linkInfo) bool {
|
||||
var isConnected bool
|
||||
type linkError string
|
||||
|
||||
func (e linkError) Error() string { return string(e) }
|
||||
|
||||
const ErrLinkAlreadyConfigured = linkError("peer is already configured")
|
||||
const ErrLinkPriorityInvalid = linkError("priority value is invalid")
|
||||
const ErrLinkPinnedKeyInvalid = linkError("pinned public key is invalid")
|
||||
const ErrLinkPasswordInvalid = linkError("password is invalid")
|
||||
const ErrLinkUnrecognisedSchema = linkError("link schema unknown")
|
||||
|
||||
func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
|
||||
var retErr error
|
||||
phony.Block(l, func() {
|
||||
_, isConnected = l._links[info]
|
||||
// Generate the link info and see whether we think we already
|
||||
// have an open peering to this peer.
|
||||
lu := urlForLinkInfo(*u)
|
||||
info := linkInfo{
|
||||
uri: lu.String(),
|
||||
sintf: sintf,
|
||||
}
|
||||
|
||||
// Collect together the link options, these are global options
|
||||
// that are not specific to any given protocol.
|
||||
var options linkOptions
|
||||
for _, pubkey := range u.Query()["key"] {
|
||||
sigPub, err := hex.DecodeString(pubkey)
|
||||
if err != nil {
|
||||
retErr = ErrLinkPinnedKeyInvalid
|
||||
return
|
||||
}
|
||||
var sigPubKey keyArray
|
||||
copy(sigPubKey[:], sigPub)
|
||||
if options.pinnedEd25519Keys == nil {
|
||||
options.pinnedEd25519Keys = map[keyArray]struct{}{}
|
||||
}
|
||||
options.pinnedEd25519Keys[sigPubKey] = struct{}{}
|
||||
}
|
||||
if p := u.Query().Get("priority"); p != "" {
|
||||
pi, err := strconv.ParseUint(p, 10, 8)
|
||||
if err != nil {
|
||||
retErr = ErrLinkPriorityInvalid
|
||||
return
|
||||
}
|
||||
options.priority = uint8(pi)
|
||||
}
|
||||
if p := u.Query().Get("password"); p != "" {
|
||||
if len(p) > blake2b.Size {
|
||||
retErr = ErrLinkPasswordInvalid
|
||||
return
|
||||
}
|
||||
options.password = []byte(p)
|
||||
}
|
||||
|
||||
// If we think we're already connected to this peer, load up
|
||||
// the existing peer state. Try to kick the peer if possible,
|
||||
// which will cause an immediate connection attempt if it is
|
||||
// backing off for some reason.
|
||||
state, ok := l._links[info]
|
||||
if ok && state != nil {
|
||||
select {
|
||||
case state.kick <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
retErr = ErrLinkAlreadyConfigured
|
||||
return
|
||||
}
|
||||
|
||||
// Create the link entry. This will contain the connection
|
||||
// in progress (if any), any error details and a context that
|
||||
// lets the link be cancelled later.
|
||||
state = &link{
|
||||
linkType: linkType,
|
||||
linkProto: strings.ToUpper(u.Scheme),
|
||||
kick: make(chan struct{}),
|
||||
}
|
||||
|
||||
// Store the state of the link so that it can be queried later.
|
||||
l._links[info] = state
|
||||
|
||||
// Track how many consecutive connection failures we have had,
|
||||
// as we will back off exponentially rather than hammering the
|
||||
// remote node endlessly.
|
||||
var backoff int
|
||||
|
||||
// backoffNow is called when there's a connection error. It
|
||||
// will wait for the specified amount of time and then return
|
||||
// true, unless the peering context was cancelled (due to a
|
||||
// peer removal most likely), in which case it returns false.
|
||||
// The caller should check the return value to decide whether
|
||||
// or not to give up trying.
|
||||
backoffNow := func() bool {
|
||||
backoff++
|
||||
duration := time.Second * time.Duration(math.Exp2(float64(backoff)))
|
||||
select {
|
||||
case <-time.After(duration):
|
||||
return true
|
||||
case <-state.kick:
|
||||
return true
|
||||
case <-l.core.ctx.Done():
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// The goroutine is responsible for attempting the connection
|
||||
// and then running the handler. If the connection is persistent
|
||||
// then the loop will run endlessly, using backoffs as needed.
|
||||
// Otherwise the loop will end, cleaning up the link entry.
|
||||
go func() {
|
||||
defer func() {
|
||||
phony.Block(l, func() {
|
||||
if l._links[info] == state {
|
||||
delete(l._links, info)
|
||||
}
|
||||
})
|
||||
}()
|
||||
|
||||
// This loop will run each and every time we want to attempt
|
||||
// a connection to this peer.
|
||||
// TODO get rid of this loop, this is *exactly* what time.AfterFunc is for, we should just send a signal to the links actor to kick off a goroutine as needed
|
||||
for {
|
||||
conn, err := l.connect(u, info, options)
|
||||
if err != nil {
|
||||
if linkType == linkTypePersistent {
|
||||
// If the link is a persistent configured peering,
|
||||
// store information about the connection error so
|
||||
// that we can report it through the admin socket.
|
||||
phony.Block(l, func() {
|
||||
state._conn = nil
|
||||
state._err = err
|
||||
state._errtime = time.Now()
|
||||
})
|
||||
|
||||
// Back off for a bit. If true is returned here, we
|
||||
// can continue onto the next loop iteration to try
|
||||
// the next connection.
|
||||
if backoffNow() {
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
// Ephemeral and incoming connections don't remain
|
||||
// after a connection failure, so exit out of the
|
||||
// loop and clean up the link entry.
|
||||
break
|
||||
}
|
||||
|
||||
// The linkConn wrapper allows us to track the number of
|
||||
// bytes written to and read from this connection without
|
||||
// the help of ironwood.
|
||||
lc := &linkConn{
|
||||
Conn: conn,
|
||||
up: time.Now(),
|
||||
}
|
||||
|
||||
// Update the link state with our newly wrapped connection.
|
||||
// Clear the error state.
|
||||
var doRet bool
|
||||
phony.Block(l, func() {
|
||||
if state._conn != nil {
|
||||
// If a peering has come up in this time, abort this one.
|
||||
doRet = true
|
||||
}
|
||||
state._conn = lc
|
||||
})
|
||||
if doRet {
|
||||
return
|
||||
}
|
||||
|
||||
// Give the connection to the handler. The handler will block
|
||||
// for the lifetime of the connection.
|
||||
if err = l.handler(linkType, options, lc); err != nil && err != io.EOF {
|
||||
l.core.log.Debugf("Link %s error: %s\n", info.uri, err)
|
||||
} else {
|
||||
backoff = 0
|
||||
}
|
||||
|
||||
// The handler has stopped running so the connection is dead,
|
||||
// try to close the underlying socket just in case and then
|
||||
// update the link state.
|
||||
_ = lc.Close()
|
||||
phony.Block(l, func() {
|
||||
state._conn = nil
|
||||
if state._err = err; state._err != nil {
|
||||
state._errtime = time.Now()
|
||||
}
|
||||
})
|
||||
|
||||
// If the link is persistently configured, back off if needed
|
||||
// and then try reconnecting. Otherwise, exit out.
|
||||
if linkType == linkTypePersistent {
|
||||
if backoffNow() {
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}()
|
||||
})
|
||||
return isConnected
|
||||
return retErr
|
||||
}
|
||||
|
||||
func (l *links) call(u *url.URL, sintf string, errch chan<- error) (info linkInfo, err error) {
|
||||
info = linkInfoFor(u.Scheme, sintf, u.Host)
|
||||
if l.isConnectedTo(info) {
|
||||
if errch != nil {
|
||||
close(errch) // already connected, no error
|
||||
}
|
||||
return info, nil
|
||||
func (l *links) listen(u *url.URL, sintf string) (*Listener, error) {
|
||||
ctx, cancel := context.WithCancel(l.core.ctx)
|
||||
var protocol linkProtocol
|
||||
switch strings.ToLower(u.Scheme) {
|
||||
case "tcp":
|
||||
protocol = l.tcp
|
||||
case "tls":
|
||||
protocol = l.tls
|
||||
case "unix":
|
||||
protocol = l.unix
|
||||
case "quic":
|
||||
protocol = l.quic
|
||||
default:
|
||||
cancel()
|
||||
return nil, ErrLinkUnrecognisedSchema
|
||||
}
|
||||
options := linkOptions{
|
||||
pinnedEd25519Keys: map[keyArray]struct{}{},
|
||||
listener, err := protocol.listen(ctx, u, sintf)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, err
|
||||
}
|
||||
for _, pubkey := range u.Query()["key"] {
|
||||
sigPub, err := hex.DecodeString(pubkey)
|
||||
if err != nil {
|
||||
if errch != nil {
|
||||
close(errch)
|
||||
}
|
||||
return info, fmt.Errorf("pinned key contains invalid hex characters")
|
||||
}
|
||||
var sigPubKey keyArray
|
||||
copy(sigPubKey[:], sigPub)
|
||||
options.pinnedEd25519Keys[sigPubKey] = struct{}{}
|
||||
li := &Listener{
|
||||
listener: listener,
|
||||
ctx: ctx,
|
||||
Cancel: cancel,
|
||||
}
|
||||
|
||||
var options linkOptions
|
||||
if p := u.Query().Get("priority"); p != "" {
|
||||
pi, err := strconv.ParseUint(p, 10, 8)
|
||||
if err != nil {
|
||||
if errch != nil {
|
||||
close(errch)
|
||||
}
|
||||
return info, fmt.Errorf("priority invalid: %w", err)
|
||||
return nil, ErrLinkPriorityInvalid
|
||||
}
|
||||
options.priority = uint8(pi)
|
||||
}
|
||||
switch info.linkType {
|
||||
if p := u.Query().Get("password"); p != "" {
|
||||
if len(p) > blake2b.Size {
|
||||
return nil, ErrLinkPasswordInvalid
|
||||
}
|
||||
options.password = []byte(p)
|
||||
}
|
||||
|
||||
go func() {
|
||||
l.core.log.Printf("%s listener started on %s", strings.ToUpper(u.Scheme), listener.Addr())
|
||||
defer l.core.log.Printf("%s listener stopped on %s", strings.ToUpper(u.Scheme), listener.Addr())
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
go func(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
// In order to populate a somewhat sane looking connection
|
||||
// URI in the admin socket, we need to replace the host in
|
||||
// the listener URL with the remote address.
|
||||
pu := *u
|
||||
pu.Host = conn.RemoteAddr().String()
|
||||
lu := urlForLinkInfo(pu)
|
||||
info := linkInfo{
|
||||
uri: lu.String(),
|
||||
sintf: sintf,
|
||||
}
|
||||
|
||||
// If there's an existing link state for this link, get it.
|
||||
// If this node is already connected to us, just drop the
|
||||
// connection. This prevents duplicate peerings.
|
||||
var lc *linkConn
|
||||
var state *link
|
||||
phony.Block(l, func() {
|
||||
var ok bool
|
||||
state, ok = l._links[info]
|
||||
if !ok || state == nil {
|
||||
state = &link{
|
||||
linkType: linkTypeIncoming,
|
||||
linkProto: strings.ToUpper(u.Scheme),
|
||||
kick: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
if state._conn != nil {
|
||||
// If a connection has come up in this time, abort
|
||||
// this one.
|
||||
return
|
||||
}
|
||||
|
||||
// The linkConn wrapper allows us to track the number of
|
||||
// bytes written to and read from this connection without
|
||||
// the help of ironwood.
|
||||
lc = &linkConn{
|
||||
Conn: conn,
|
||||
up: time.Now(),
|
||||
}
|
||||
|
||||
// Update the link state with our newly wrapped connection.
|
||||
// Clear the error state.
|
||||
state._conn = lc
|
||||
state._err = nil
|
||||
state._errtime = time.Time{}
|
||||
|
||||
// Store the state of the link so that it can be queried later.
|
||||
l._links[info] = state
|
||||
})
|
||||
if lc == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Give the connection to the handler. The handler will block
|
||||
// for the lifetime of the connection.
|
||||
if err = l.handler(linkTypeIncoming, options, lc); err != nil && err != io.EOF {
|
||||
l.core.log.Debugf("Link %s error: %s\n", u.Host, err)
|
||||
}
|
||||
|
||||
// The handler has stopped running so the connection is dead,
|
||||
// try to close the underlying socket just in case and then
|
||||
// drop the link state.
|
||||
_ = lc.Close()
|
||||
phony.Block(l, func() {
|
||||
if l._links[info] == state {
|
||||
delete(l._links, info)
|
||||
}
|
||||
})
|
||||
}(conn)
|
||||
}
|
||||
}()
|
||||
return li, nil
|
||||
}
|
||||
|
||||
func (l *links) connect(u *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
|
||||
var dialer linkProtocol
|
||||
switch strings.ToLower(u.Scheme) {
|
||||
case "tcp":
|
||||
go func() {
|
||||
if errch != nil {
|
||||
defer close(errch)
|
||||
}
|
||||
if err := l.tcp.dial(u, options, sintf); err != nil && err != io.EOF {
|
||||
l.core.log.Warnf("Failed to dial TCP %s: %s\n", u.Host, err)
|
||||
if errch != nil {
|
||||
errch <- err
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
case "socks":
|
||||
go func() {
|
||||
if errch != nil {
|
||||
defer close(errch)
|
||||
}
|
||||
if err := l.socks.dial(u, options); err != nil && err != io.EOF {
|
||||
l.core.log.Warnf("Failed to dial SOCKS %s: %s\n", u.Host, err)
|
||||
if errch != nil {
|
||||
errch <- err
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
dialer = l.tcp
|
||||
case "tls":
|
||||
// SNI headers must contain hostnames and not IP addresses, so we must make sure
|
||||
// that we do not populate the SNI with an IP literal. We do this by splitting
|
||||
// the host-port combo from the query option and then seeing if it parses to an
|
||||
// IP address successfully or not.
|
||||
var tlsSNI string
|
||||
if sni := u.Query().Get("sni"); sni != "" {
|
||||
if net.ParseIP(sni) == nil {
|
||||
tlsSNI = sni
|
||||
options.tlsSNI = sni
|
||||
}
|
||||
}
|
||||
// If the SNI is not configured still because the above failed then we'll try
|
||||
// again but this time we'll use the host part of the peering URI instead.
|
||||
if tlsSNI == "" {
|
||||
if options.tlsSNI == "" {
|
||||
if host, _, err := net.SplitHostPort(u.Host); err == nil && net.ParseIP(host) == nil {
|
||||
tlsSNI = host
|
||||
options.tlsSNI = host
|
||||
}
|
||||
}
|
||||
go func() {
|
||||
if errch != nil {
|
||||
defer close(errch)
|
||||
}
|
||||
if err := l.tls.dial(u, options, sintf, tlsSNI); err != nil && err != io.EOF {
|
||||
l.core.log.Warnf("Failed to dial TLS %s: %s\n", u.Host, err)
|
||||
if errch != nil {
|
||||
errch <- err
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
dialer = l.tls
|
||||
case "socks":
|
||||
dialer = l.socks
|
||||
case "unix":
|
||||
go func() {
|
||||
if errch != nil {
|
||||
defer close(errch)
|
||||
}
|
||||
if err := l.unix.dial(u, options, sintf); err != nil && err != io.EOF {
|
||||
l.core.log.Warnf("Failed to dial UNIX %s: %s\n", u.Host, err)
|
||||
if errch != nil {
|
||||
errch <- err
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
dialer = l.unix
|
||||
case "quic":
|
||||
dialer = l.quic
|
||||
default:
|
||||
if errch != nil {
|
||||
close(errch)
|
||||
}
|
||||
return info, errors.New("unknown call scheme: " + u.Scheme)
|
||||
return nil, ErrLinkUnrecognisedSchema
|
||||
}
|
||||
return info, nil
|
||||
return dialer.dial(l.core.ctx, u, info, options)
|
||||
}
|
||||
|
||||
func (l *links) listen(u *url.URL, sintf string) (*Listener, error) {
|
||||
var listener *Listener
|
||||
var err error
|
||||
switch u.Scheme {
|
||||
case "tcp":
|
||||
listener, err = l.tcp.listen(u, sintf)
|
||||
case "tls":
|
||||
listener, err = l.tls.listen(u, sintf)
|
||||
case "unix":
|
||||
listener, err = l.unix.listen(u, sintf)
|
||||
default:
|
||||
return nil, fmt.Errorf("unrecognised scheme %q", u.Scheme)
|
||||
}
|
||||
return listener, err
|
||||
}
|
||||
|
||||
func (l *links) create(conn net.Conn, dial *linkDial, name string, info linkInfo, incoming, force bool, options linkOptions) error {
|
||||
intf := link{
|
||||
conn: &linkConn{
|
||||
Conn: conn,
|
||||
up: time.Now(),
|
||||
},
|
||||
lname: name,
|
||||
links: l,
|
||||
options: options,
|
||||
info: info,
|
||||
incoming: incoming,
|
||||
force: force,
|
||||
}
|
||||
go func() {
|
||||
if err := intf.handler(dial); err != nil {
|
||||
l.core.log.Errorf("Link handler %s error (%s): %s", name, conn.RemoteAddr(), err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (intf *link) handler(dial *linkDial) error {
|
||||
defer intf.conn.Close() // nolint:errcheck
|
||||
|
||||
// Don't connect to this link more than once.
|
||||
if intf.links.isConnectedTo(intf.info) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Mark the connection as in progress.
|
||||
phony.Block(intf.links, func() {
|
||||
intf.links._links[intf.info] = nil
|
||||
})
|
||||
|
||||
// When we're done, clean up the connection entry.
|
||||
defer phony.Block(intf.links, func() {
|
||||
delete(intf.links._links, intf.info)
|
||||
})
|
||||
|
||||
func (l *links) handler(linkType linkType, options linkOptions, conn net.Conn) error {
|
||||
meta := version_getBaseMetadata()
|
||||
meta.key = intf.links.core.public
|
||||
metaBytes := meta.encode()
|
||||
if err := intf.conn.SetDeadline(time.Now().Add(time.Second * 6)); err != nil {
|
||||
meta.publicKey = l.core.public
|
||||
meta.priority = options.priority
|
||||
metaBytes, err := meta.encode(l.core.secret, options.password)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate handshake: %w", err)
|
||||
}
|
||||
if err := conn.SetDeadline(time.Now().Add(time.Second * 6)); err != nil {
|
||||
return fmt.Errorf("failed to set handshake deadline: %w", err)
|
||||
}
|
||||
n, err := intf.conn.Write(metaBytes)
|
||||
n, err := conn.Write(metaBytes)
|
||||
switch {
|
||||
case err != nil:
|
||||
return fmt.Errorf("write handshake: %w", err)
|
||||
case err == nil && n != len(metaBytes):
|
||||
return fmt.Errorf("incomplete handshake send")
|
||||
}
|
||||
if _, err = io.ReadFull(intf.conn, metaBytes); err != nil {
|
||||
return fmt.Errorf("read handshake: %w", err)
|
||||
}
|
||||
if err = intf.conn.SetDeadline(time.Time{}); err != nil {
|
||||
return fmt.Errorf("failed to clear handshake deadline: %w", err)
|
||||
}
|
||||
meta = version_metadata{}
|
||||
base := version_getBaseMetadata()
|
||||
if !meta.decode(metaBytes) {
|
||||
return errors.New("failed to decode metadata")
|
||||
if !meta.decode(conn, options.password) {
|
||||
return conn.Close()
|
||||
}
|
||||
if !meta.check() {
|
||||
var connectError string
|
||||
if intf.incoming {
|
||||
connectError = "Rejected incoming connection"
|
||||
} else {
|
||||
connectError = "Failed to connect"
|
||||
}
|
||||
intf.links.core.log.Debugf("%s: %s is incompatible version (local %s, remote %s)",
|
||||
connectError,
|
||||
intf.lname,
|
||||
fmt.Sprintf("%d.%d", base.ver, base.minorVer),
|
||||
fmt.Sprintf("%d.%d", meta.ver, meta.minorVer),
|
||||
return fmt.Errorf("remote node incompatible version (local %s, remote %s)",
|
||||
fmt.Sprintf("%d.%d", base.majorVer, base.minorVer),
|
||||
fmt.Sprintf("%d.%d", meta.majorVer, meta.minorVer),
|
||||
)
|
||||
return errors.New("remote node is incompatible version")
|
||||
}
|
||||
if err = conn.SetDeadline(time.Time{}); err != nil {
|
||||
return fmt.Errorf("failed to clear handshake deadline: %w", err)
|
||||
}
|
||||
// Check if the remote side matches the keys we expected. This is a bit of a weak
|
||||
// check - in future versions we really should check a signature or something like that.
|
||||
if pinned := intf.options.pinnedEd25519Keys; len(pinned) > 0 {
|
||||
if pinned := options.pinnedEd25519Keys; len(pinned) > 0 {
|
||||
var key keyArray
|
||||
copy(key[:], meta.key)
|
||||
copy(key[:], meta.publicKey)
|
||||
if _, allowed := pinned[key]; !allowed {
|
||||
return fmt.Errorf("node public key that does not match pinned keys")
|
||||
}
|
||||
}
|
||||
// Check if we're authorized to connect to this key / IP
|
||||
allowed := intf.links.core.config._allowedPublicKeys
|
||||
var allowed map[[32]byte]struct{}
|
||||
phony.Block(l.core, func() {
|
||||
allowed = l.core.config._allowedPublicKeys
|
||||
})
|
||||
isallowed := len(allowed) == 0
|
||||
for k := range allowed {
|
||||
if bytes.Equal(k[:], meta.key) {
|
||||
if bytes.Equal(k[:], meta.publicKey) {
|
||||
isallowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if intf.incoming && !intf.force && !isallowed {
|
||||
_ = intf.close()
|
||||
return fmt.Errorf("node public key %q is not in AllowedPublicKeys", hex.EncodeToString(meta.key))
|
||||
if linkType == linkTypeIncoming && !isallowed {
|
||||
return fmt.Errorf("node public key %q is not in AllowedPublicKeys", hex.EncodeToString(meta.publicKey))
|
||||
}
|
||||
|
||||
phony.Block(intf.links, func() {
|
||||
intf.links._links[intf.info] = intf
|
||||
})
|
||||
|
||||
dir := "outbound"
|
||||
if intf.incoming {
|
||||
if linkType == linkTypeIncoming {
|
||||
dir = "inbound"
|
||||
}
|
||||
remoteAddr := net.IP(address.AddrForKey(meta.key)[:]).String()
|
||||
remoteStr := fmt.Sprintf("%s@%s", remoteAddr, intf.info.remote)
|
||||
localStr := intf.conn.LocalAddr()
|
||||
intf.links.core.log.Infof("Connected %s %s: %s, source %s",
|
||||
dir, strings.ToUpper(intf.info.linkType), remoteStr, localStr)
|
||||
remoteAddr := net.IP(address.AddrForKey(meta.publicKey)[:]).String()
|
||||
remoteStr := fmt.Sprintf("%s@%s", remoteAddr, conn.RemoteAddr())
|
||||
localStr := conn.LocalAddr()
|
||||
priority := options.priority
|
||||
if meta.priority > priority {
|
||||
priority = meta.priority
|
||||
}
|
||||
l.core.log.Infof("Connected %s: %s, source %s",
|
||||
dir, remoteStr, localStr)
|
||||
|
||||
err = intf.links.core.HandleConn(meta.key, intf.conn, intf.options.priority)
|
||||
err = l.core.HandleConn(meta.publicKey, conn, priority)
|
||||
switch err {
|
||||
case io.EOF, net.ErrClosed, nil:
|
||||
intf.links.core.log.Infof("Disconnected %s %s: %s, source %s",
|
||||
dir, strings.ToUpper(intf.info.linkType), remoteStr, localStr)
|
||||
l.core.log.Infof("Disconnected %s: %s, source %s",
|
||||
dir, remoteStr, localStr)
|
||||
default:
|
||||
intf.links.core.log.Infof("Disconnected %s %s: %s, source %s; error: %s",
|
||||
dir, strings.ToUpper(intf.info.linkType), remoteStr, localStr, err)
|
||||
l.core.log.Infof("Disconnected %s: %s, source %s; error: %s",
|
||||
dir, remoteStr, localStr, err)
|
||||
}
|
||||
|
||||
if !intf.incoming && dial != nil {
|
||||
// The connection was one that we dialled, so wait a second and try to
|
||||
// dial it again.
|
||||
var retry func(attempt int)
|
||||
retry = func(attempt int) {
|
||||
// intf.links.core.log.Infof("Retrying %s (attempt %d of 5)...", dial.url.String(), attempt)
|
||||
errch := make(chan error, 1)
|
||||
if _, err := intf.links.call(dial.url, dial.sintf, errch); err != nil {
|
||||
return
|
||||
}
|
||||
if err := <-errch; err != nil {
|
||||
if attempt < 3 {
|
||||
time.AfterFunc(time.Second, func() {
|
||||
retry(attempt + 1)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
time.AfterFunc(time.Second, func() {
|
||||
retry(1)
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (intf *link) close() error {
|
||||
return intf.conn.Close()
|
||||
}
|
||||
|
||||
func linkInfoFor(linkType, sintf, remote string) linkInfo {
|
||||
return linkInfo{
|
||||
linkType: linkType,
|
||||
local: sintf,
|
||||
remote: remote,
|
||||
func urlForLinkInfo(u url.URL) url.URL {
|
||||
u.RawQuery = ""
|
||||
if host, _, err := net.SplitHostPort(u.Host); err == nil {
|
||||
if addr, err := netip.ParseAddr(host); err == nil {
|
||||
// For peers that look like multicast peers (i.e.
|
||||
// link-local addresses), we will ignore the port number,
|
||||
// otherwise we might open multiple connections to them.
|
||||
if addr.IsLinkLocalUnicast() {
|
||||
u.Host = fmt.Sprintf("[%s]", addr.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
type linkConn struct {
|
||||
@ -421,12 +606,3 @@ func (c *linkConn) Write(p []byte) (n int, err error) {
|
||||
atomic.AddUint64(&c.tx, uint64(n))
|
||||
return
|
||||
}
|
||||
|
||||
func linkOptionsForListener(u *url.URL) (l linkOptions) {
|
||||
if p := u.Query().Get("priority"); p != "" {
|
||||
if pi, err := strconv.ParseUint(p, 10, 8); err == nil {
|
||||
l.priority = uint8(pi)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
96
src/core/link_quic.go
Normal file
96
src/core/link_quic.go
Normal file
@ -0,0 +1,96 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/Arceliar/phony"
|
||||
"github.com/quic-go/quic-go"
|
||||
)
|
||||
|
||||
type linkQUIC struct {
|
||||
phony.Inbox
|
||||
*links
|
||||
tlsconfig *tls.Config
|
||||
quicconfig *quic.Config
|
||||
}
|
||||
|
||||
type linkQUICStream struct {
|
||||
quic.Connection
|
||||
quic.Stream
|
||||
}
|
||||
|
||||
type linkQUICListener struct {
|
||||
*quic.EarlyListener
|
||||
ch <-chan *linkQUICStream
|
||||
}
|
||||
|
||||
func (l *linkQUICListener) Accept() (net.Conn, error) {
|
||||
qs := <-l.ch
|
||||
if qs == nil {
|
||||
return nil, context.Canceled
|
||||
}
|
||||
return qs, nil
|
||||
}
|
||||
|
||||
func (l *links) newLinkQUIC() *linkQUIC {
|
||||
lt := &linkQUIC{
|
||||
links: l,
|
||||
tlsconfig: l.core.config.tls.Clone(),
|
||||
quicconfig: &quic.Config{
|
||||
MaxIdleTimeout: time.Minute,
|
||||
KeepAlivePeriod: time.Second * 20,
|
||||
TokenStore: quic.NewLRUTokenStore(255, 255),
|
||||
},
|
||||
}
|
||||
return lt
|
||||
}
|
||||
|
||||
func (l *linkQUIC) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
|
||||
qc, err := quic.DialAddrEarly(ctx, url.Host, l.tlsconfig, l.quicconfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qs, err := qc.OpenStream()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &linkQUICStream{
|
||||
Connection: qc,
|
||||
Stream: qs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *linkQUIC) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
|
||||
ql, err := quic.ListenAddrEarly(url.Host, l.tlsconfig, l.quicconfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ch := make(chan *linkQUICStream)
|
||||
lql := &linkQUICListener{
|
||||
EarlyListener: ql,
|
||||
ch: ch,
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
qc, err := ql.Accept(ctx)
|
||||
if err != nil {
|
||||
ql.Close()
|
||||
return
|
||||
}
|
||||
qs, err := qc.AcceptStream(ctx)
|
||||
if err != nil {
|
||||
ql.Close()
|
||||
return
|
||||
}
|
||||
ch <- &linkQUICStream{
|
||||
Connection: qc,
|
||||
Stream: qs,
|
||||
}
|
||||
}
|
||||
}()
|
||||
return lql, nil
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
@ -20,37 +21,22 @@ func (l *links) newLinkSOCKS() *linkSOCKS {
|
||||
return lt
|
||||
}
|
||||
|
||||
func (l *linkSOCKS) dial(url *url.URL, options linkOptions) error {
|
||||
info := linkInfoFor("socks", "", url.Path)
|
||||
if l.links.isConnectedTo(info) {
|
||||
return nil
|
||||
func (l *linkSOCKS) dial(_ context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
|
||||
var proxyAuth *proxy.Auth
|
||||
if url.User != nil && url.User.Username() != "" {
|
||||
proxyAuth = &proxy.Auth{
|
||||
User: url.User.Username(),
|
||||
}
|
||||
proxyAuth.Password, _ = url.User.Password()
|
||||
}
|
||||
proxyAuth := &proxy.Auth{}
|
||||
proxyAuth.User = url.User.Username()
|
||||
proxyAuth.Password, _ = url.User.Password()
|
||||
dialer, err := proxy.SOCKS5("tcp", url.Host, proxyAuth, proxy.Direct)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to configure proxy")
|
||||
return nil, fmt.Errorf("failed to configure proxy")
|
||||
}
|
||||
pathtokens := strings.Split(strings.Trim(url.Path, "/"), "/")
|
||||
conn, err := dialer.Dial("tcp", pathtokens[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dial := &linkDial{
|
||||
url: url,
|
||||
}
|
||||
return l.handler(dial, info, conn, options, false)
|
||||
return dialer.Dial("tcp", pathtokens[0])
|
||||
}
|
||||
|
||||
func (l *linkSOCKS) handler(dial *linkDial, info linkInfo, conn net.Conn, options linkOptions, incoming bool) error {
|
||||
return l.links.create(
|
||||
conn, // connection
|
||||
dial, // connection URL
|
||||
dial.url.String(), // connection name
|
||||
info, // connection info
|
||||
incoming, // not incoming
|
||||
false, // not forced
|
||||
options, // connection options
|
||||
)
|
||||
func (l *linkSOCKS) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
|
||||
return nil, fmt.Errorf("SOCKS listener not supported")
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Arceliar/phony"
|
||||
@ -15,19 +14,19 @@ import (
|
||||
type linkTCP struct {
|
||||
phony.Inbox
|
||||
*links
|
||||
listener *net.ListenConfig
|
||||
_listeners map[*Listener]context.CancelFunc
|
||||
listenconfig *net.ListenConfig
|
||||
_listeners map[*Listener]context.CancelFunc
|
||||
}
|
||||
|
||||
func (l *links) newLinkTCP() *linkTCP {
|
||||
lt := &linkTCP{
|
||||
links: l,
|
||||
listener: &net.ListenConfig{
|
||||
listenconfig: &net.ListenConfig{
|
||||
KeepAlive: -1,
|
||||
},
|
||||
_listeners: map[*Listener]context.CancelFunc{},
|
||||
}
|
||||
lt.listener.Control = lt.tcpContext
|
||||
lt.listenconfig.Control = lt.tcpContext
|
||||
return lt
|
||||
}
|
||||
|
||||
@ -37,7 +36,7 @@ type tcpDialer struct {
|
||||
addr *net.TCPAddr
|
||||
}
|
||||
|
||||
func (l *linkTCP) dialersFor(url *url.URL, options linkOptions, sintf string) ([]*tcpDialer, error) {
|
||||
func (l *linkTCP) dialersFor(url *url.URL, info linkInfo) ([]*tcpDialer, error) {
|
||||
host, p, err := net.SplitHostPort(url.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -56,14 +55,10 @@ func (l *linkTCP) dialersFor(url *url.URL, options linkOptions, sintf string) ([
|
||||
IP: ip,
|
||||
Port: port,
|
||||
}
|
||||
dialer, err := l.dialerFor(addr, sintf)
|
||||
dialer, err := l.dialerFor(addr, info.sintf)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
info := linkInfoFor("tcp", sintf, tcpIDFor(dialer.LocalAddr, addr))
|
||||
if l.links.isConnectedTo(info) {
|
||||
return nil, nil
|
||||
}
|
||||
dialers = append(dialers, &tcpDialer{
|
||||
info: info,
|
||||
dialer: dialer,
|
||||
@ -73,101 +68,34 @@ func (l *linkTCP) dialersFor(url *url.URL, options linkOptions, sintf string) ([
|
||||
return dialers, nil
|
||||
}
|
||||
|
||||
func (l *linkTCP) dial(url *url.URL, options linkOptions, sintf string) error {
|
||||
dialers, err := l.dialersFor(url, options, sintf)
|
||||
func (l *linkTCP) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
|
||||
dialers, err := l.dialersFor(url, info)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if len(dialers) == 0 {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
for _, d := range dialers {
|
||||
var conn net.Conn
|
||||
conn, err = d.dialer.DialContext(l.core.ctx, "tcp", d.addr.String())
|
||||
conn, err = d.dialer.DialContext(ctx, "tcp", d.addr.String())
|
||||
if err != nil {
|
||||
l.core.log.Warnf("Failed to connect to %s: %s", d.addr, err)
|
||||
continue
|
||||
}
|
||||
name := strings.TrimRight(strings.SplitN(url.String(), "?", 2)[0], "/")
|
||||
dial := &linkDial{
|
||||
url: url,
|
||||
sintf: sintf,
|
||||
}
|
||||
return l.handler(dial, name, d.info, conn, options, false, false)
|
||||
return conn, nil
|
||||
}
|
||||
return fmt.Errorf("failed to connect via %d address(es), last error: %w", len(dialers), err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (l *linkTCP) listen(url *url.URL, sintf string) (*Listener, error) {
|
||||
ctx, cancel := context.WithCancel(l.core.ctx)
|
||||
func (l *linkTCP) listen(ctx context.Context, url *url.URL, sintf string) (net.Listener, error) {
|
||||
hostport := url.Host
|
||||
if sintf != "" {
|
||||
if host, port, err := net.SplitHostPort(hostport); err == nil {
|
||||
hostport = fmt.Sprintf("[%s%%%s]:%s", host, sintf, port)
|
||||
}
|
||||
}
|
||||
listener, err := l.listener.Listen(ctx, "tcp", hostport)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, err
|
||||
}
|
||||
entry := &Listener{
|
||||
Listener: listener,
|
||||
closed: make(chan struct{}),
|
||||
}
|
||||
phony.Block(l, func() {
|
||||
l._listeners[entry] = cancel
|
||||
})
|
||||
l.core.log.Printf("TCP listener started on %s", listener.Addr())
|
||||
go func() {
|
||||
defer phony.Block(l, func() {
|
||||
delete(l._listeners, entry)
|
||||
})
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
cancel()
|
||||
break
|
||||
}
|
||||
laddr := conn.LocalAddr().(*net.TCPAddr)
|
||||
raddr := conn.RemoteAddr().(*net.TCPAddr)
|
||||
name := fmt.Sprintf("tcp://%s", raddr)
|
||||
info := linkInfoFor("tcp", sintf, tcpIDFor(laddr, raddr))
|
||||
if err = l.handler(nil, name, info, conn, linkOptionsForListener(url), true, raddr.IP.IsLinkLocalUnicast()); err != nil {
|
||||
l.core.log.Errorln("Failed to create inbound link:", err)
|
||||
}
|
||||
}
|
||||
_ = listener.Close()
|
||||
close(entry.closed)
|
||||
l.core.log.Printf("TCP listener stopped on %s", listener.Addr())
|
||||
}()
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func (l *linkTCP) handler(dial *linkDial, name string, info linkInfo, conn net.Conn, options linkOptions, incoming, force bool) error {
|
||||
return l.links.create(
|
||||
conn, // connection
|
||||
dial, // connection URL
|
||||
name, // connection name
|
||||
info, // connection info
|
||||
incoming, // not incoming
|
||||
force, // not forced
|
||||
options, // connection options
|
||||
)
|
||||
}
|
||||
|
||||
// Returns the address of the listener.
|
||||
func (l *linkTCP) getAddr() *net.TCPAddr {
|
||||
// TODO: Fix this, because this will currently only give a single address
|
||||
// to multicast.go, which obviously is not great, but right now multicast.go
|
||||
// doesn't have the ability to send more than one address in a packet either
|
||||
var addr *net.TCPAddr
|
||||
phony.Block(l, func() {
|
||||
for listener := range l._listeners {
|
||||
addr = listener.Addr().(*net.TCPAddr)
|
||||
}
|
||||
})
|
||||
return addr
|
||||
return l.listenconfig.Listen(ctx, "tcp", hostport)
|
||||
}
|
||||
|
||||
func (l *linkTCP) dialerFor(dst *net.TCPAddr, sintf string) (*net.Dialer, error) {
|
||||
@ -228,16 +156,3 @@ func (l *linkTCP) dialerFor(dst *net.TCPAddr, sintf string) (*net.Dialer, error)
|
||||
}
|
||||
return dialer, nil
|
||||
}
|
||||
|
||||
func tcpIDFor(local net.Addr, remoteAddr *net.TCPAddr) string {
|
||||
if localAddr, ok := local.(*net.TCPAddr); ok && localAddr.IP.Equal(remoteAddr.IP) {
|
||||
// Nodes running on the same host — include both the IP and port.
|
||||
return remoteAddr.String()
|
||||
}
|
||||
if remoteAddr.IP.IsLinkLocalUnicast() {
|
||||
// Nodes discovered via multicast — include the IP only.
|
||||
return remoteAddr.IP.String()
|
||||
}
|
||||
// Nodes connected remotely — include both the IP and port.
|
||||
return remoteAddr.String()
|
||||
}
|
||||
|
@ -12,22 +12,6 @@ import (
|
||||
// WARNING: This context is used both by net.Dialer and net.Listen in tcp.go
|
||||
|
||||
func (t *linkTCP) tcpContext(network, address string, c syscall.RawConn) error {
|
||||
var control error
|
||||
var bbr error
|
||||
|
||||
control = c.Control(func(fd uintptr) {
|
||||
bbr = unix.SetsockoptString(int(fd), unix.IPPROTO_TCP, unix.TCP_CONGESTION, "bbr")
|
||||
})
|
||||
|
||||
// Log any errors
|
||||
if bbr != nil {
|
||||
t.links.core.log.Debugln("Failed to set tcp_congestion_control to bbr for socket, SetsockoptString error:", bbr)
|
||||
}
|
||||
if control != nil {
|
||||
t.links.core.log.Debugln("Failed to set tcp_congestion_control to bbr for socket, Control error:", control)
|
||||
}
|
||||
|
||||
// Return nil because errors here are not considered fatal for the connection, it just means congestion control is suboptimal
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -1,20 +1,11 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Arceliar/phony"
|
||||
)
|
||||
@ -36,48 +27,38 @@ func (l *links) newLinkTLS(tcp *linkTCP) *linkTLS {
|
||||
Control: tcp.tcpContext,
|
||||
KeepAlive: -1,
|
||||
},
|
||||
config: l.core.config.tls.Clone(),
|
||||
_listeners: map[*Listener]context.CancelFunc{},
|
||||
}
|
||||
var err error
|
||||
lt.config, err = lt.generateConfig()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return lt
|
||||
}
|
||||
|
||||
func (l *linkTLS) dial(url *url.URL, options linkOptions, sintf, sni string) error {
|
||||
dialers, err := l.tcp.dialersFor(url, options, sintf)
|
||||
func (l *linkTLS) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
|
||||
dialers, err := l.tcp.dialersFor(url, info)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if len(dialers) == 0 {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
for _, d := range dialers {
|
||||
tlsconfig := l.config.Clone()
|
||||
tlsconfig.ServerName = sni
|
||||
tlsconfig.ServerName = options.tlsSNI
|
||||
tlsdialer := &tls.Dialer{
|
||||
NetDialer: d.dialer,
|
||||
Config: tlsconfig,
|
||||
}
|
||||
var conn net.Conn
|
||||
conn, err = tlsdialer.DialContext(l.core.ctx, "tcp", d.addr.String())
|
||||
conn, err = tlsdialer.DialContext(ctx, "tcp", d.addr.String())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
name := strings.TrimRight(strings.SplitN(url.String(), "?", 2)[0], "/")
|
||||
dial := &linkDial{
|
||||
url: url,
|
||||
sintf: sintf,
|
||||
}
|
||||
return l.handler(dial, name, d.info, conn, options, false, false)
|
||||
return conn, nil
|
||||
}
|
||||
return fmt.Errorf("failed to connect via %d address(es), last error: %w", len(dialers), err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (l *linkTLS) listen(url *url.URL, sintf string) (*Listener, error) {
|
||||
ctx, cancel := context.WithCancel(l.core.ctx)
|
||||
func (l *linkTLS) listen(ctx context.Context, url *url.URL, sintf string) (net.Listener, error) {
|
||||
hostport := url.Host
|
||||
if sintf != "" {
|
||||
if host, port, err := net.SplitHostPort(hostport); err == nil {
|
||||
@ -86,88 +67,8 @@ func (l *linkTLS) listen(url *url.URL, sintf string) (*Listener, error) {
|
||||
}
|
||||
listener, err := l.listener.Listen(ctx, "tcp", hostport)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, err
|
||||
}
|
||||
tlslistener := tls.NewListener(listener, l.config)
|
||||
entry := &Listener{
|
||||
Listener: tlslistener,
|
||||
closed: make(chan struct{}),
|
||||
}
|
||||
phony.Block(l, func() {
|
||||
l._listeners[entry] = cancel
|
||||
})
|
||||
l.core.log.Printf("TLS listener started on %s", listener.Addr())
|
||||
go func() {
|
||||
defer phony.Block(l, func() {
|
||||
delete(l._listeners, entry)
|
||||
})
|
||||
for {
|
||||
conn, err := tlslistener.Accept()
|
||||
if err != nil {
|
||||
cancel()
|
||||
break
|
||||
}
|
||||
laddr := conn.LocalAddr().(*net.TCPAddr)
|
||||
raddr := conn.RemoteAddr().(*net.TCPAddr)
|
||||
name := fmt.Sprintf("tls://%s", raddr)
|
||||
info := linkInfoFor("tls", sintf, tcpIDFor(laddr, raddr))
|
||||
if err = l.handler(nil, name, info, conn, linkOptionsForListener(url), true, raddr.IP.IsLinkLocalUnicast()); err != nil {
|
||||
l.core.log.Errorln("Failed to create inbound link:", err)
|
||||
}
|
||||
}
|
||||
_ = tlslistener.Close()
|
||||
close(entry.closed)
|
||||
l.core.log.Printf("TLS listener stopped on %s", listener.Addr())
|
||||
}()
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
// RFC5280 section 4.1.2.5
|
||||
var notAfterNeverExpires = time.Date(9999, time.December, 31, 23, 59, 59, 0, time.UTC)
|
||||
|
||||
func (l *linkTLS) generateConfig() (*tls.Config, error) {
|
||||
certBuf := &bytes.Buffer{}
|
||||
cert := x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{
|
||||
CommonName: hex.EncodeToString(l.links.core.public[:]),
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: notAfterNeverExpires,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
certbytes, err := x509.CreateCertificate(rand.Reader, &cert, &cert, l.links.core.public, l.links.core.secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := pem.Encode(certBuf, &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: certbytes,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rootCAs := x509.NewCertPool()
|
||||
rootCAs.AppendCertsFromPEM(certbytes)
|
||||
|
||||
return &tls.Config{
|
||||
RootCAs: rootCAs,
|
||||
Certificates: []tls.Certificate{
|
||||
{
|
||||
Certificate: [][]byte{certbytes},
|
||||
PrivateKey: l.links.core.secret,
|
||||
},
|
||||
},
|
||||
InsecureSkipVerify: true,
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *linkTLS) handler(dial *linkDial, name string, info linkInfo, conn net.Conn, options linkOptions, incoming, force bool) error {
|
||||
return l.tcp.handler(dial, name, info, conn, options, incoming, force)
|
||||
return tlslistener, nil
|
||||
}
|
||||
|
@ -32,70 +32,14 @@ func (l *links) newLinkUNIX() *linkUNIX {
|
||||
return lt
|
||||
}
|
||||
|
||||
func (l *linkUNIX) dial(url *url.URL, options linkOptions, _ string) error {
|
||||
info := linkInfoFor("unix", "", url.Path)
|
||||
if l.links.isConnectedTo(info) {
|
||||
return nil
|
||||
}
|
||||
func (l *linkUNIX) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
|
||||
addr, err := net.ResolveUnixAddr("unix", url.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn, err := l.dialer.DialContext(l.core.ctx, "unix", addr.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dial := &linkDial{
|
||||
url: url,
|
||||
}
|
||||
return l.handler(dial, url.String(), info, conn, options, false)
|
||||
}
|
||||
|
||||
func (l *linkUNIX) listen(url *url.URL, _ string) (*Listener, error) {
|
||||
ctx, cancel := context.WithCancel(l.core.ctx)
|
||||
listener, err := l.listener.Listen(ctx, "unix", url.Path)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, err
|
||||
}
|
||||
entry := &Listener{
|
||||
Listener: listener,
|
||||
closed: make(chan struct{}),
|
||||
}
|
||||
phony.Block(l, func() {
|
||||
l._listeners[entry] = cancel
|
||||
})
|
||||
l.core.log.Printf("UNIX listener started on %s", listener.Addr())
|
||||
go func() {
|
||||
defer phony.Block(l, func() {
|
||||
delete(l._listeners, entry)
|
||||
})
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
cancel()
|
||||
break
|
||||
}
|
||||
info := linkInfoFor("unix", "", url.String())
|
||||
if err = l.handler(nil, url.String(), info, conn, linkOptionsForListener(url), true); err != nil {
|
||||
l.core.log.Errorln("Failed to create inbound link:", err)
|
||||
}
|
||||
}
|
||||
_ = listener.Close()
|
||||
close(entry.closed)
|
||||
l.core.log.Printf("UNIX listener stopped on %s", listener.Addr())
|
||||
}()
|
||||
return entry, nil
|
||||
return l.dialer.DialContext(ctx, "unix", addr.String())
|
||||
}
|
||||
|
||||
func (l *linkUNIX) handler(dial *linkDial, name string, info linkInfo, conn net.Conn, options linkOptions, incoming bool) error {
|
||||
return l.links.create(
|
||||
conn, // connection
|
||||
dial, // connection URL
|
||||
name, // connection name
|
||||
info, // connection info
|
||||
incoming, // not incoming
|
||||
false, // not forced
|
||||
options, // connection options
|
||||
)
|
||||
func (l *linkUNIX) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
|
||||
return l.listener.Listen(ctx, "unix", url.Path)
|
||||
}
|
||||
|
@ -2,12 +2,18 @@ package core
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func (c *Core) _applyOption(opt SetupOption) {
|
||||
func (c *Core) _applyOption(opt SetupOption) (err error) {
|
||||
switch v := opt.(type) {
|
||||
case Peer:
|
||||
c.config._peers[v] = nil
|
||||
u, err := url.Parse(v.URI)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse peering URI: %w", err)
|
||||
}
|
||||
return c.links.add(u, v.SourceInterface, linkTypePersistent)
|
||||
case ListenAddress:
|
||||
c.config._listeners[v] = struct{}{}
|
||||
case NodeInfo:
|
||||
@ -19,6 +25,7 @@ func (c *Core) _applyOption(opt SetupOption) {
|
||||
copy(pk[:], v)
|
||||
c.config._allowedPublicKeys[pk] = struct{}{}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type SetupOption interface {
|
||||
|
17
src/core/pool.go
Normal file
17
src/core/pool.go
Normal file
@ -0,0 +1,17 @@
|
||||
package core
|
||||
|
||||
import "sync"
|
||||
|
||||
var bytePool = sync.Pool{New: func() interface{} { return []byte(nil) }}
|
||||
|
||||
func allocBytes(size int) []byte {
|
||||
bs := bytePool.Get().([]byte)
|
||||
if cap(bs) < size {
|
||||
bs = make([]byte, size)
|
||||
}
|
||||
return bs[:size]
|
||||
}
|
||||
|
||||
func freeBytes(bs []byte) {
|
||||
bytePool.Put(bs[:0]) //nolint:staticcheck
|
||||
}
|
@ -21,8 +21,8 @@ const (
|
||||
typeDebugGetSelfResponse
|
||||
typeDebugGetPeersRequest
|
||||
typeDebugGetPeersResponse
|
||||
typeDebugGetDHTRequest
|
||||
typeDebugGetDHTResponse
|
||||
typeDebugGetTreeRequest
|
||||
typeDebugGetTreeResponse
|
||||
)
|
||||
|
||||
type reqInfo struct {
|
||||
@ -40,7 +40,7 @@ type protoHandler struct {
|
||||
|
||||
selfRequests map[keyArray]*reqInfo
|
||||
peersRequests map[keyArray]*reqInfo
|
||||
dhtRequests map[keyArray]*reqInfo
|
||||
treeRequests map[keyArray]*reqInfo
|
||||
}
|
||||
|
||||
func (p *protoHandler) init(core *Core) {
|
||||
@ -49,7 +49,7 @@ func (p *protoHandler) init(core *Core) {
|
||||
|
||||
p.selfRequests = make(map[keyArray]*reqInfo)
|
||||
p.peersRequests = make(map[keyArray]*reqInfo)
|
||||
p.dhtRequests = make(map[keyArray]*reqInfo)
|
||||
p.treeRequests = make(map[keyArray]*reqInfo)
|
||||
}
|
||||
|
||||
// Common functions
|
||||
@ -89,10 +89,10 @@ func (p *protoHandler) _handleDebug(key keyArray, bs []byte) {
|
||||
p._handleGetPeersRequest(key)
|
||||
case typeDebugGetPeersResponse:
|
||||
p._handleGetPeersResponse(key, bs[1:])
|
||||
case typeDebugGetDHTRequest:
|
||||
p._handleGetDHTRequest(key)
|
||||
case typeDebugGetDHTResponse:
|
||||
p._handleGetDHTResponse(key, bs[1:])
|
||||
case typeDebugGetTreeRequest:
|
||||
p._handleGetTreeRequest(key)
|
||||
case typeDebugGetTreeResponse:
|
||||
p._handleGetTreeResponse(key, bs[1:])
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,8 +126,8 @@ func (p *protoHandler) sendGetSelfRequest(key keyArray, callback func([]byte)) {
|
||||
func (p *protoHandler) _handleGetSelfRequest(key keyArray) {
|
||||
self := p.core.GetSelf()
|
||||
res := map[string]string{
|
||||
"key": hex.EncodeToString(self.Key[:]),
|
||||
"coords": fmt.Sprintf("%v", self.Coords),
|
||||
"key": hex.EncodeToString(self.Key[:]),
|
||||
"routing_entries": fmt.Sprintf("%v", self.RoutingEntries),
|
||||
}
|
||||
bs, err := json.Marshal(res) // FIXME this puts keys in base64, not hex
|
||||
if err != nil {
|
||||
@ -188,47 +188,47 @@ func (p *protoHandler) _handleGetPeersResponse(key keyArray, bs []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
// Get DHT
|
||||
// Get Tree
|
||||
|
||||
func (p *protoHandler) sendGetDHTRequest(key keyArray, callback func([]byte)) {
|
||||
func (p *protoHandler) sendGetTreeRequest(key keyArray, callback func([]byte)) {
|
||||
p.Act(nil, func() {
|
||||
if info := p.dhtRequests[key]; info != nil {
|
||||
if info := p.treeRequests[key]; info != nil {
|
||||
info.timer.Stop()
|
||||
delete(p.dhtRequests, key)
|
||||
delete(p.treeRequests, key)
|
||||
}
|
||||
info := new(reqInfo)
|
||||
info.callback = callback
|
||||
info.timer = time.AfterFunc(time.Minute, func() {
|
||||
p.Act(nil, func() {
|
||||
if p.dhtRequests[key] == info {
|
||||
delete(p.dhtRequests, key)
|
||||
if p.treeRequests[key] == info {
|
||||
delete(p.treeRequests, key)
|
||||
}
|
||||
})
|
||||
})
|
||||
p.dhtRequests[key] = info
|
||||
p._sendDebug(key, typeDebugGetDHTRequest, nil)
|
||||
p.treeRequests[key] = info
|
||||
p._sendDebug(key, typeDebugGetTreeRequest, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func (p *protoHandler) _handleGetDHTRequest(key keyArray) {
|
||||
dinfos := p.core.GetDHT()
|
||||
func (p *protoHandler) _handleGetTreeRequest(key keyArray) {
|
||||
dinfos := p.core.GetTree()
|
||||
var bs []byte
|
||||
for _, dinfo := range dinfos {
|
||||
tmp := append(bs, dinfo.Key[:]...)
|
||||
const responseOverhead = 2 // 1 debug type, 1 getdht type
|
||||
const responseOverhead = 2 // 1 debug type, 1 gettree type
|
||||
if uint64(len(tmp))+responseOverhead > p.core.MTU() {
|
||||
break
|
||||
}
|
||||
bs = tmp
|
||||
}
|
||||
p._sendDebug(key, typeDebugGetDHTResponse, bs)
|
||||
p._sendDebug(key, typeDebugGetTreeResponse, bs)
|
||||
}
|
||||
|
||||
func (p *protoHandler) _handleGetDHTResponse(key keyArray, bs []byte) {
|
||||
if info := p.dhtRequests[key]; info != nil {
|
||||
func (p *protoHandler) _handleGetTreeResponse(key keyArray, bs []byte) {
|
||||
if info := p.treeRequests[key]; info != nil {
|
||||
info.timer.Stop()
|
||||
info.callback(bs)
|
||||
delete(p.dhtRequests, key)
|
||||
delete(p.treeRequests, key)
|
||||
}
|
||||
}
|
||||
|
||||
@ -322,16 +322,16 @@ func (p *protoHandler) getPeersHandler(in json.RawMessage) (interface{}, error)
|
||||
}
|
||||
}
|
||||
|
||||
// Admin socket stuff for "Get DHT"
|
||||
// Admin socket stuff for "Get Tree"
|
||||
|
||||
type DebugGetDHTRequest struct {
|
||||
type DebugGetTreeRequest struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type DebugGetDHTResponse map[string]interface{}
|
||||
type DebugGetTreeResponse map[string]interface{}
|
||||
|
||||
func (p *protoHandler) getDHTHandler(in json.RawMessage) (interface{}, error) {
|
||||
var req DebugGetDHTRequest
|
||||
func (p *protoHandler) getTreeHandler(in json.RawMessage) (interface{}, error) {
|
||||
var req DebugGetTreeRequest
|
||||
if err := json.Unmarshal(in, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -343,7 +343,7 @@ func (p *protoHandler) getDHTHandler(in json.RawMessage) (interface{}, error) {
|
||||
}
|
||||
copy(key[:], kbs)
|
||||
ch := make(chan []byte, 1)
|
||||
p.sendGetDHTRequest(key, func(info []byte) {
|
||||
p.sendGetTreeRequest(key, func(info []byte) {
|
||||
ch <- info
|
||||
})
|
||||
timer := time.NewTimer(6 * time.Second)
|
||||
@ -367,7 +367,7 @@ func (p *protoHandler) getDHTHandler(in json.RawMessage) (interface{}, error) {
|
||||
return nil, err
|
||||
}
|
||||
ip := net.IP(address.AddrForKey(kbs)[:])
|
||||
res := DebugGetDHTResponse{ip.String(): msg}
|
||||
res := DebugGetTreeResponse{ip.String(): msg}
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
29
src/core/tls.go
Normal file
29
src/core/tls.go
Normal file
@ -0,0 +1,29 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
)
|
||||
|
||||
func (c *Core) generateTLSConfig(cert *tls.Certificate) (*tls.Config, error) {
|
||||
config := &tls.Config{
|
||||
Certificates: []tls.Certificate{*cert},
|
||||
ClientAuth: tls.RequireAnyClientCert,
|
||||
GetClientCertificate: func(cri *tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
||||
return cert, nil
|
||||
},
|
||||
VerifyPeerCertificate: c.verifyTLSCertificate,
|
||||
VerifyConnection: c.verifyTLSConnection,
|
||||
InsecureSkipVerify: true,
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (c *Core) verifyTLSCertificate(_ [][]byte, _ [][]*x509.Certificate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Core) verifyTLSConnection(_ tls.ConnectionState) error {
|
||||
return nil
|
||||
}
|
@ -4,65 +4,154 @@ package core
|
||||
// Used in the initial connection setup and key exchange
|
||||
// Some of this could arguably go in wire.go instead
|
||||
|
||||
import "crypto/ed25519"
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"golang.org/x/crypto/blake2b"
|
||||
)
|
||||
|
||||
// This is the version-specific metadata exchanged at the start of a connection.
|
||||
// It must always begin with the 4 bytes "meta" and a wire formatted uint64 major version number.
|
||||
// The current version also includes a minor version number, and the box/sig/link keys that need to be exchanged to open a connection.
|
||||
type version_metadata struct {
|
||||
meta [4]byte
|
||||
ver uint8 // 1 byte in this version
|
||||
// Everything after this point potentially depends on the version number, and is subject to change in future versions
|
||||
minorVer uint8 // 1 byte in this version
|
||||
key ed25519.PublicKey
|
||||
majorVer uint16
|
||||
minorVer uint16
|
||||
publicKey ed25519.PublicKey
|
||||
priority uint8
|
||||
}
|
||||
|
||||
const (
|
||||
ProtocolVersionMajor uint16 = 0
|
||||
ProtocolVersionMinor uint16 = 5
|
||||
)
|
||||
|
||||
// Once a major/minor version is released, it is not safe to change any of these
|
||||
// (including their ordering), it is only safe to add new ones.
|
||||
const (
|
||||
metaVersionMajor uint16 = iota // uint16
|
||||
metaVersionMinor // uint16
|
||||
metaPublicKey // [32]byte
|
||||
metaPriority // uint8
|
||||
)
|
||||
|
||||
// Gets a base metadata with no keys set, but with the correct version numbers.
|
||||
func version_getBaseMetadata() version_metadata {
|
||||
return version_metadata{
|
||||
meta: [4]byte{'m', 'e', 't', 'a'},
|
||||
ver: 0,
|
||||
minorVer: 4,
|
||||
majorVer: ProtocolVersionMajor,
|
||||
minorVer: ProtocolVersionMinor,
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the length of the metadata for this version, used to know how many bytes to read from the start of a connection.
|
||||
func version_getMetaLength() (mlen int) {
|
||||
mlen += 4 // meta
|
||||
mlen++ // ver, as long as it's < 127, which it is in this version
|
||||
mlen++ // minorVer, as long as it's < 127, which it is in this version
|
||||
mlen += ed25519.PublicKeySize // key
|
||||
return
|
||||
}
|
||||
|
||||
// Encodes version metadata into its wire format.
|
||||
func (m *version_metadata) encode() []byte {
|
||||
bs := make([]byte, 0, version_getMetaLength())
|
||||
bs = append(bs, m.meta[:]...)
|
||||
bs = append(bs, m.ver)
|
||||
bs = append(bs, m.minorVer)
|
||||
bs = append(bs, m.key[:]...)
|
||||
if len(bs) != version_getMetaLength() {
|
||||
panic("Inconsistent metadata length")
|
||||
func (m *version_metadata) encode(privateKey ed25519.PrivateKey, password []byte) ([]byte, error) {
|
||||
bs := make([]byte, 0, 64)
|
||||
bs = append(bs, 'm', 'e', 't', 'a')
|
||||
bs = append(bs, 0, 0) // Remaining message length
|
||||
|
||||
bs = binary.BigEndian.AppendUint16(bs, metaVersionMajor)
|
||||
bs = binary.BigEndian.AppendUint16(bs, 2)
|
||||
bs = binary.BigEndian.AppendUint16(bs, m.majorVer)
|
||||
|
||||
bs = binary.BigEndian.AppendUint16(bs, metaVersionMinor)
|
||||
bs = binary.BigEndian.AppendUint16(bs, 2)
|
||||
bs = binary.BigEndian.AppendUint16(bs, m.minorVer)
|
||||
|
||||
bs = binary.BigEndian.AppendUint16(bs, metaPublicKey)
|
||||
bs = binary.BigEndian.AppendUint16(bs, ed25519.PublicKeySize)
|
||||
bs = append(bs, m.publicKey[:]...)
|
||||
|
||||
bs = binary.BigEndian.AppendUint16(bs, metaPriority)
|
||||
bs = binary.BigEndian.AppendUint16(bs, 1)
|
||||
bs = append(bs, m.priority)
|
||||
|
||||
hasher, err := blake2b.New512(password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bs
|
||||
n, err := hasher.Write(m.publicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n != ed25519.PublicKeySize {
|
||||
return nil, fmt.Errorf("hash writer only wrote %d bytes", n)
|
||||
}
|
||||
hash := hasher.Sum(nil)
|
||||
bs = append(bs, ed25519.Sign(privateKey, hash)...)
|
||||
|
||||
binary.BigEndian.PutUint16(bs[4:6], uint16(len(bs)-6))
|
||||
return bs, nil
|
||||
}
|
||||
|
||||
// Decodes version metadata from its wire format into the struct.
|
||||
func (m *version_metadata) decode(bs []byte) bool {
|
||||
if len(bs) != version_getMetaLength() {
|
||||
func (m *version_metadata) decode(r io.Reader, password []byte) bool {
|
||||
bh := [6]byte{}
|
||||
if _, err := io.ReadFull(r, bh[:]); err != nil {
|
||||
return false
|
||||
}
|
||||
offset := 0
|
||||
offset += copy(m.meta[:], bs[offset:])
|
||||
m.ver, offset = bs[offset], offset+1
|
||||
m.minorVer, offset = bs[offset], offset+1
|
||||
m.key = append([]byte(nil), bs[offset:]...)
|
||||
return true
|
||||
meta := [4]byte{'m', 'e', 't', 'a'}
|
||||
if !bytes.Equal(bh[:4], meta[:]) {
|
||||
return false
|
||||
}
|
||||
bs := make([]byte, binary.BigEndian.Uint16(bh[4:6]))
|
||||
if _, err := io.ReadFull(r, bs); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(bs) < ed25519.SignatureSize {
|
||||
return false
|
||||
}
|
||||
sig := bs[len(bs)-ed25519.SignatureSize:]
|
||||
bs = bs[:len(bs)-ed25519.SignatureSize]
|
||||
|
||||
for len(bs) >= 4 {
|
||||
op := binary.BigEndian.Uint16(bs[:2])
|
||||
oplen := binary.BigEndian.Uint16(bs[2:4])
|
||||
if bs = bs[4:]; len(bs) < int(oplen) {
|
||||
break
|
||||
}
|
||||
switch op {
|
||||
case metaVersionMajor:
|
||||
m.majorVer = binary.BigEndian.Uint16(bs[:2])
|
||||
|
||||
case metaVersionMinor:
|
||||
m.minorVer = binary.BigEndian.Uint16(bs[:2])
|
||||
|
||||
case metaPublicKey:
|
||||
m.publicKey = make(ed25519.PublicKey, ed25519.PublicKeySize)
|
||||
copy(m.publicKey, bs[:ed25519.PublicKeySize])
|
||||
|
||||
case metaPriority:
|
||||
m.priority = bs[0]
|
||||
}
|
||||
bs = bs[oplen:]
|
||||
}
|
||||
|
||||
hasher, err := blake2b.New512(password)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
n, err := hasher.Write(m.publicKey)
|
||||
if err != nil || n != ed25519.PublicKeySize {
|
||||
return false
|
||||
}
|
||||
hash := hasher.Sum(nil)
|
||||
return ed25519.Verify(m.publicKey, hash, sig)
|
||||
}
|
||||
|
||||
// Checks that the "meta" bytes and the version numbers are the expected values.
|
||||
func (m *version_metadata) check() bool {
|
||||
base := version_getBaseMetadata()
|
||||
return base.meta == m.meta && base.ver == m.ver && base.minorVer == m.minorVer
|
||||
switch {
|
||||
case m.majorVer != ProtocolVersionMajor:
|
||||
return false
|
||||
case m.minorVer != ProtocolVersionMinor:
|
||||
return false
|
||||
case len(m.publicKey) != ed25519.PublicKeySize:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
78
src/core/version_test.go
Normal file
78
src/core/version_test.go
Normal file
@ -0,0 +1,78 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestVersionPasswordAuth(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
password1 []byte // The password on node 1
|
||||
password2 []byte // The password on node 2
|
||||
allowed bool // Should the connection have been allowed?
|
||||
}{
|
||||
{nil, nil, true}, // Allow: No passwords (both nil)
|
||||
{nil, []byte(""), true}, // Allow: No passwords (mixed nil and empty string)
|
||||
{nil, []byte("foo"), false}, // Reject: One node has a password, the other doesn't
|
||||
{[]byte("foo"), []byte(""), false}, // Reject: One node has a password, the other doesn't
|
||||
{[]byte("foo"), []byte("foo"), true}, // Allow: Same password
|
||||
{[]byte("foo"), []byte("bar"), false}, // Reject: Different passwords
|
||||
} {
|
||||
pk1, sk1, err := ed25519.GenerateKey(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Node 1 failed to generate key: %s", err)
|
||||
}
|
||||
|
||||
metadata1 := &version_metadata{
|
||||
publicKey: pk1,
|
||||
}
|
||||
encoded, err := metadata1.encode(sk1, tt.password1)
|
||||
if err != nil {
|
||||
t.Fatalf("Node 1 failed to encode metadata: %s", err)
|
||||
}
|
||||
|
||||
var decoded version_metadata
|
||||
if allowed := decoded.decode(bytes.NewBuffer(encoded), tt.password2); allowed != tt.allowed {
|
||||
t.Fatalf("Permutation %q -> %q should have been %v but was %v", tt.password1, tt.password2, tt.allowed, allowed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionRoundtrip(t *testing.T) {
|
||||
for _, password := range [][]byte{
|
||||
nil, []byte(""), []byte("foo"),
|
||||
} {
|
||||
for _, test := range []*version_metadata{
|
||||
{majorVer: 1},
|
||||
{majorVer: 256},
|
||||
{majorVer: 2, minorVer: 4},
|
||||
{majorVer: 2, minorVer: 257},
|
||||
{majorVer: 258, minorVer: 259},
|
||||
{majorVer: 3, minorVer: 5, priority: 6},
|
||||
{majorVer: 260, minorVer: 261, priority: 7},
|
||||
} {
|
||||
// Generate a random public key for each time, since it is
|
||||
// a required field.
|
||||
pk, sk, err := ed25519.GenerateKey(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
test.publicKey = pk
|
||||
meta, err := test.encode(sk, password)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
encoded := bytes.NewBuffer(meta)
|
||||
decoded := &version_metadata{}
|
||||
if !decoded.decode(encoded, password) {
|
||||
t.Fatalf("failed to decode")
|
||||
}
|
||||
if !reflect.DeepEqual(test, decoded) {
|
||||
t.Fatalf("round-trip failed\nwant: %+v\n got: %+v", test, decoded)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
package defaults
|
||||
|
||||
import "github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||
|
||||
type MulticastInterfaceConfig = config.MulticastInterfaceConfig
|
||||
|
||||
var defaultConfig = "" // LDFLAGS='-X github.com/yggdrasil-network/yggdrasil-go/src/defaults.defaultConfig=/path/to/config
|
||||
var defaultAdminListen = "" // LDFLAGS='-X github.com/yggdrasil-network/yggdrasil-go/src/defaults.defaultAdminListen=unix://path/to/sock'
|
||||
|
||||
// Defines which parameters are expected by default for configuration on a
|
||||
// specific platform. These values are populated in the relevant defaults_*.go
|
||||
// for the platform being targeted. They must be set.
|
||||
type platformDefaultParameters struct {
|
||||
// Admin socket
|
||||
DefaultAdminListen string
|
||||
|
||||
// Configuration (used for yggdrasilctl)
|
||||
DefaultConfigFile string
|
||||
|
||||
// Multicast interfaces
|
||||
DefaultMulticastInterfaces []MulticastInterfaceConfig
|
||||
|
||||
// TUN
|
||||
MaximumIfMTU uint64
|
||||
DefaultIfMTU uint64
|
||||
DefaultIfName string
|
||||
}
|
||||
|
||||
func GetDefaults() platformDefaultParameters {
|
||||
defaults := getDefaults()
|
||||
if defaultConfig != "" {
|
||||
defaults.DefaultConfigFile = defaultConfig
|
||||
}
|
||||
if defaultAdminListen != "" {
|
||||
defaults.DefaultAdminListen = defaultAdminListen
|
||||
}
|
||||
return defaults
|
||||
}
|
||||
|
||||
// Generates default configuration and returns a pointer to the resulting
|
||||
// NodeConfig. This is used when outputting the -genconf parameter and also when
|
||||
// using -autoconf.
|
||||
func GenerateConfig() *config.NodeConfig {
|
||||
// Get the defaults for the platform.
|
||||
defaults := GetDefaults()
|
||||
// Create a node configuration and populate it.
|
||||
cfg := new(config.NodeConfig)
|
||||
cfg.NewKeys()
|
||||
cfg.Listen = []string{}
|
||||
cfg.AdminListen = defaults.DefaultAdminListen
|
||||
cfg.Peers = []string{}
|
||||
cfg.InterfacePeers = map[string][]string{}
|
||||
cfg.AllowedPublicKeys = []string{}
|
||||
cfg.MulticastInterfaces = defaults.DefaultMulticastInterfaces
|
||||
cfg.IfName = defaults.DefaultIfName
|
||||
cfg.IfMTU = defaults.DefaultIfMTU
|
||||
cfg.NodeInfoPrivacy = false
|
||||
|
||||
return cfg
|
||||
}
|
@ -19,12 +19,14 @@ import (
|
||||
|
||||
const keyStoreTimeout = 2 * time.Minute
|
||||
|
||||
/*
|
||||
// Out-of-band packet types
|
||||
const (
|
||||
typeKeyDummy = iota // nolint:deadcode,varcheck
|
||||
typeKeyLookup
|
||||
typeKeyResponse
|
||||
)
|
||||
*/
|
||||
|
||||
type keyArray [ed25519.PublicKeySize]byte
|
||||
|
||||
@ -57,10 +59,13 @@ func (k *keyStore) init(c *core.Core) {
|
||||
k.core = c
|
||||
k.address = *address.AddrForKey(k.core.PublicKey())
|
||||
k.subnet = *address.SubnetForKey(k.core.PublicKey())
|
||||
if err := k.core.SetOutOfBandHandler(k.oobHandler); err != nil {
|
||||
/*if err := k.core.SetOutOfBandHandler(k.oobHandler); err != nil {
|
||||
err = fmt.Errorf("tun.core.SetOutOfBandHander: %w", err)
|
||||
panic(err)
|
||||
}
|
||||
}*/
|
||||
k.core.SetPathNotify(func(key ed25519.PublicKey) {
|
||||
k.update(key)
|
||||
})
|
||||
k.keyToInfo = make(map[keyArray]*keyInfo)
|
||||
k.addrToInfo = make(map[address.Address]*keyInfo)
|
||||
k.addrBuffer = make(map[address.Address]*buffer)
|
||||
@ -177,7 +182,8 @@ func (k *keyStore) resetTimeout(info *keyInfo) {
|
||||
})
|
||||
}
|
||||
|
||||
func (k *keyStore) oobHandler(fromKey, toKey ed25519.PublicKey, data []byte) {
|
||||
/*
|
||||
func (k *keyStore) oobHandler(fromKey, toKey ed25519.PublicKey, data []byte) { // nolint:unused
|
||||
if len(data) != 1+ed25519.SignatureSize {
|
||||
return
|
||||
}
|
||||
@ -198,18 +204,26 @@ func (k *keyStore) oobHandler(fromKey, toKey ed25519.PublicKey, data []byte) {
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
func (k *keyStore) sendKeyLookup(partial ed25519.PublicKey) {
|
||||
sig := ed25519.Sign(k.core.PrivateKey(), partial[:])
|
||||
bs := append([]byte{typeKeyLookup}, sig...)
|
||||
_ = k.core.SendOutOfBand(partial, bs)
|
||||
/*
|
||||
sig := ed25519.Sign(k.core.PrivateKey(), partial[:])
|
||||
bs := append([]byte{typeKeyLookup}, sig...)
|
||||
//_ = k.core.SendOutOfBand(partial, bs)
|
||||
_ = bs
|
||||
*/
|
||||
k.core.SendLookup(partial)
|
||||
}
|
||||
|
||||
func (k *keyStore) sendKeyResponse(dest ed25519.PublicKey) {
|
||||
/*
|
||||
func (k *keyStore) sendKeyResponse(dest ed25519.PublicKey) { // nolint:unused
|
||||
sig := ed25519.Sign(k.core.PrivateKey(), dest[:])
|
||||
bs := append([]byte{typeKeyResponse}, sig...)
|
||||
_ = k.core.SendOutOfBand(dest, bs)
|
||||
//_ = k.core.SendOutOfBand(dest, bs)
|
||||
_ = bs
|
||||
}
|
||||
*/
|
||||
|
||||
func (k *keyStore) readPC(p []byte) (int, error) {
|
||||
buf := make([]byte, k.core.MTU(), 65535)
|
||||
|
39
src/multicast/advertisement.go
Normal file
39
src/multicast/advertisement.go
Normal file
@ -0,0 +1,39 @@
|
||||
package multicast
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type multicastAdvertisement struct {
|
||||
MajorVersion uint16
|
||||
MinorVersion uint16
|
||||
PublicKey ed25519.PublicKey
|
||||
Port uint16
|
||||
Hash []byte
|
||||
}
|
||||
|
||||
func (m *multicastAdvertisement) MarshalBinary() ([]byte, error) {
|
||||
b := make([]byte, 0, ed25519.PublicKeySize+8+len(m.Hash))
|
||||
b = binary.BigEndian.AppendUint16(b, m.MajorVersion)
|
||||
b = binary.BigEndian.AppendUint16(b, m.MinorVersion)
|
||||
b = append(b, m.PublicKey...)
|
||||
b = binary.BigEndian.AppendUint16(b, m.Port)
|
||||
b = binary.BigEndian.AppendUint16(b, uint16(len(m.Hash)))
|
||||
b = append(b, m.Hash...)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (m *multicastAdvertisement) UnmarshalBinary(b []byte) error {
|
||||
if len(b) < ed25519.PublicKeySize+8 {
|
||||
return fmt.Errorf("invalid multicast beacon")
|
||||
}
|
||||
m.MajorVersion = binary.BigEndian.Uint16(b[0:2])
|
||||
m.MinorVersion = binary.BigEndian.Uint16(b[2:4])
|
||||
m.PublicKey = append(m.PublicKey[:0], b[4:4+ed25519.PublicKeySize]...)
|
||||
m.Port = binary.BigEndian.Uint16(b[4+ed25519.PublicKeySize : 6+ed25519.PublicKeySize])
|
||||
dl := binary.BigEndian.Uint16(b[6+ed25519.PublicKeySize : 8+ed25519.PublicKeySize])
|
||||
m.Hash = append(m.Hash[:0], b[8+ed25519.PublicKeySize:8+ed25519.PublicKeySize+dl]...)
|
||||
return nil
|
||||
}
|
38
src/multicast/advertisement_test.go
Normal file
38
src/multicast/advertisement_test.go
Normal file
@ -0,0 +1,38 @@
|
||||
package multicast
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMulticastAdvertisementRoundTrip(t *testing.T) {
|
||||
pk, sk, err := ed25519.GenerateKey(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
orig := multicastAdvertisement{
|
||||
MajorVersion: 1,
|
||||
MinorVersion: 2,
|
||||
PublicKey: pk,
|
||||
Port: 3,
|
||||
Hash: sk, // any bytes will do
|
||||
}
|
||||
|
||||
ob, err := orig.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var new multicastAdvertisement
|
||||
if err := new.UnmarshalBinary(ob); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(orig, new) {
|
||||
t.Logf("original: %+v", orig)
|
||||
t.Logf("new: %+v", new)
|
||||
t.Fatalf("differences found after round-trip")
|
||||
}
|
||||
}
|
@ -4,9 +4,9 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/url"
|
||||
"time"
|
||||
@ -15,6 +15,7 @@ import (
|
||||
"github.com/gologme/log"
|
||||
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
||||
"golang.org/x/crypto/blake2b"
|
||||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
@ -44,6 +45,8 @@ type interfaceInfo struct {
|
||||
listen bool
|
||||
port uint16
|
||||
priority uint8
|
||||
password []byte
|
||||
hash []byte
|
||||
}
|
||||
|
||||
type listenerInfo struct {
|
||||
@ -177,6 +180,7 @@ func (m *Multicast) _getAllowedInterfaces() map[string]*interfaceInfo {
|
||||
return nil
|
||||
}
|
||||
// Work out which interfaces to announce on
|
||||
pk := m.core.PublicKey()
|
||||
for _, iface := range allifaces {
|
||||
switch {
|
||||
case iface.Flags&net.FlagUp == 0:
|
||||
@ -195,12 +199,23 @@ func (m *Multicast) _getAllowedInterfaces() map[string]*interfaceInfo {
|
||||
if !ifcfg.Regex.MatchString(iface.Name) {
|
||||
continue
|
||||
}
|
||||
hasher, err := blake2b.New512([]byte(ifcfg.Password))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if n, err := hasher.Write(pk); err != nil {
|
||||
continue
|
||||
} else if n != ed25519.PublicKeySize {
|
||||
continue
|
||||
}
|
||||
interfaces[iface.Name] = &interfaceInfo{
|
||||
iface: iface,
|
||||
beacon: ifcfg.Beacon,
|
||||
listen: ifcfg.Listen,
|
||||
port: ifcfg.Port,
|
||||
priority: ifcfg.Priority,
|
||||
password: []byte(ifcfg.Password),
|
||||
hash: hasher.Sum(nil),
|
||||
}
|
||||
break
|
||||
}
|
||||
@ -248,7 +263,7 @@ func (m *Multicast) _announce() {
|
||||
// It's possible that the link-local listener address has changed so if
|
||||
// that is the case then we should clean up the interface listener
|
||||
found := false
|
||||
listenaddr, err := net.ResolveTCPAddr("tcp6", info.listener.Listener.Addr().String())
|
||||
listenaddr, err := net.ResolveTCPAddr("tcp6", info.listener.Addr().String())
|
||||
if err != nil {
|
||||
stop()
|
||||
continue
|
||||
@ -295,12 +310,15 @@ func (m *Multicast) _announce() {
|
||||
}
|
||||
// Try and see if we already have a TCP listener for this interface
|
||||
var linfo *listenerInfo
|
||||
if nfo, ok := m._listeners[iface.Name]; !ok || nfo.listener.Listener == nil {
|
||||
if _, ok := m._listeners[iface.Name]; !ok {
|
||||
// No listener was found - let's create one
|
||||
urlString := fmt.Sprintf("tls://[%s]:%d", addrIP, info.port)
|
||||
u, err := url.Parse(urlString)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
v := &url.Values{}
|
||||
v.Add("priority", fmt.Sprintf("%d", info.priority))
|
||||
v.Add("password", string(info.password))
|
||||
u := &url.URL{
|
||||
Scheme: "tls",
|
||||
Host: net.JoinHostPort(addrIP.String(), fmt.Sprintf("%d", info.port)),
|
||||
RawQuery: v.Encode(),
|
||||
}
|
||||
if li, err := m.core.Listen(u, iface.Name); err == nil {
|
||||
m.log.Debugln("Started multicasting on", iface.Name)
|
||||
@ -321,17 +339,21 @@ func (m *Multicast) _announce() {
|
||||
if time.Since(linfo.time) < linfo.interval {
|
||||
continue
|
||||
}
|
||||
// Get the listener details and construct the multicast beacon
|
||||
lladdr := linfo.listener.Listener.Addr().String()
|
||||
if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil {
|
||||
a.Zone = ""
|
||||
destAddr.Zone = iface.Name
|
||||
msg := append([]byte(nil), m.core.GetSelf().Key...)
|
||||
msg = append(msg, a.IP...)
|
||||
pbs := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(pbs, uint16(a.Port))
|
||||
msg = append(msg, pbs...)
|
||||
_, _ = m.sock.WriteTo(msg, nil, destAddr)
|
||||
addr := linfo.listener.Addr().(*net.TCPAddr)
|
||||
adv := multicastAdvertisement{
|
||||
MajorVersion: core.ProtocolVersionMajor,
|
||||
MinorVersion: core.ProtocolVersionMinor,
|
||||
PublicKey: m.core.PublicKey(),
|
||||
Port: uint16(addr.Port),
|
||||
Hash: info.hash,
|
||||
}
|
||||
msg, err := adv.MarshalBinary()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
destAddr.Zone = iface.Name
|
||||
if _, err = m.sock.WriteTo(msg, nil, destAddr); err != nil {
|
||||
m.log.Warn("Failed to send multicast beacon:", err)
|
||||
}
|
||||
if linfo.interval.Seconds() < 15 {
|
||||
linfo.interval += time.Second
|
||||
@ -339,7 +361,8 @@ func (m *Multicast) _announce() {
|
||||
break
|
||||
}
|
||||
}
|
||||
m._timer = time.AfterFunc(time.Second, func() {
|
||||
annInterval := time.Second + time.Microsecond*(time.Duration(rand.Intn(1048576))) // Randomize delay
|
||||
m._timer = time.AfterFunc(annInterval, func() {
|
||||
m.Act(nil, m._announce)
|
||||
})
|
||||
}
|
||||
@ -350,8 +373,9 @@ func (m *Multicast) listen() {
|
||||
panic(err)
|
||||
}
|
||||
bs := make([]byte, 2048)
|
||||
hb := make([]byte, 0, blake2b.Size) // Reused to reduce hash allocations
|
||||
for {
|
||||
nBytes, rcm, fromAddr, err := m.sock.ReadFrom(bs)
|
||||
n, rcm, fromAddr, err := m.sock.ReadFrom(bs)
|
||||
if err != nil {
|
||||
if !m.IsStarted() {
|
||||
return
|
||||
@ -369,40 +393,45 @@ func (m *Multicast) listen() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if nBytes < ed25519.PublicKeySize {
|
||||
var adv multicastAdvertisement
|
||||
if err := adv.UnmarshalBinary(bs[:n]); err != nil {
|
||||
continue
|
||||
}
|
||||
var key ed25519.PublicKey
|
||||
key = append(key, bs[:ed25519.PublicKeySize]...)
|
||||
if bytes.Equal(key, m.core.GetSelf().Key) {
|
||||
continue // don't bother trying to peer with self
|
||||
}
|
||||
begin := ed25519.PublicKeySize
|
||||
end := nBytes - 2
|
||||
if end <= begin {
|
||||
continue // malformed address
|
||||
}
|
||||
ip := bs[begin:end]
|
||||
port := binary.BigEndian.Uint16(bs[end:nBytes])
|
||||
anAddr := net.TCPAddr{IP: ip, Port: int(port)}
|
||||
addr, err := net.ResolveTCPAddr("tcp6", anAddr.String())
|
||||
if err != nil {
|
||||
switch {
|
||||
case adv.MajorVersion != core.ProtocolVersionMajor:
|
||||
continue
|
||||
case adv.MinorVersion != core.ProtocolVersionMinor:
|
||||
continue
|
||||
case adv.PublicKey.Equal(m.core.PublicKey()):
|
||||
continue
|
||||
}
|
||||
from := fromAddr.(*net.UDPAddr)
|
||||
if !from.IP.Equal(addr.IP) {
|
||||
continue
|
||||
}
|
||||
from.Port = int(adv.Port)
|
||||
var interfaces map[string]*interfaceInfo
|
||||
phony.Block(m, func() {
|
||||
interfaces = m._interfaces
|
||||
})
|
||||
if info, ok := interfaces[from.Zone]; ok && info.listen {
|
||||
addr.Zone = ""
|
||||
pin := fmt.Sprintf("/?key=%s&priority=%d", hex.EncodeToString(key), info.priority)
|
||||
u, err := url.Parse("tls://" + addr.String() + pin)
|
||||
hasher, err := blake2b.New512(info.password)
|
||||
if err != nil {
|
||||
m.log.Debugln("Call from multicast failed, parse error:", addr.String(), err)
|
||||
continue
|
||||
}
|
||||
if n, err := hasher.Write(adv.PublicKey); err != nil {
|
||||
continue
|
||||
} else if n != ed25519.PublicKeySize {
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(hasher.Sum(hb[:0]), adv.Hash) {
|
||||
continue
|
||||
}
|
||||
v := &url.Values{}
|
||||
v.Add("key", hex.EncodeToString(adv.PublicKey))
|
||||
v.Add("priority", fmt.Sprintf("%d", info.priority))
|
||||
v.Add("password", string(info.password))
|
||||
u := &url.URL{
|
||||
Scheme: "tls",
|
||||
Host: from.String(),
|
||||
RawQuery: v.Encode(),
|
||||
}
|
||||
if err := m.core.CallPeer(u, from.Zone); err != nil {
|
||||
m.log.Debugln("Call from multicast failed:", err)
|
||||
|
@ -21,6 +21,7 @@ type MulticastInterface struct {
|
||||
Listen bool
|
||||
Port uint16
|
||||
Priority uint8
|
||||
Password string
|
||||
}
|
||||
|
||||
type GroupAddress string
|
||||
|
@ -8,43 +8,50 @@ package tun
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/Arceliar/phony"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc"
|
||||
)
|
||||
|
||||
type MTU uint16
|
||||
|
||||
type ReadWriteCloser interface {
|
||||
io.ReadWriteCloser
|
||||
Address() address.Address
|
||||
Subnet() address.Subnet
|
||||
MaxMTU() uint64
|
||||
SetMTU(uint64)
|
||||
}
|
||||
|
||||
// TunAdapter represents a running TUN interface and extends the
|
||||
// yggdrasil.Adapter type. In order to use the TUN adapter with Yggdrasil, you
|
||||
// should pass this object to the yggdrasil.SetRouterAdapter() function before
|
||||
// calling yggdrasil.Start().
|
||||
type TunAdapter struct {
|
||||
rwc *ipv6rwc.ReadWriteCloser
|
||||
rwc ReadWriteCloser
|
||||
log core.Logger
|
||||
addr address.Address
|
||||
subnet address.Subnet
|
||||
mtu uint64
|
||||
iface tun.Device
|
||||
phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below
|
||||
//mutex sync.RWMutex // Protects the below
|
||||
isOpen bool
|
||||
isEnabled bool // Used by the writer to drop sessionTraffic if not enabled
|
||||
config struct {
|
||||
fd int32
|
||||
isOpen bool
|
||||
isEnabled bool // Used by the writer to drop sessionTraffic if not enabled
|
||||
config struct {
|
||||
fd int32
|
||||
name InterfaceName
|
||||
mtu InterfaceMTU
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the maximum supported MTU for the platform based on the defaults in
|
||||
// defaults.GetDefaults().
|
||||
// config.GetDefaults().
|
||||
func getSupportedMTU(mtu uint64) uint64 {
|
||||
if mtu < 1280 {
|
||||
return 1280
|
||||
@ -73,25 +80,25 @@ func (tun *TunAdapter) MTU() uint64 {
|
||||
|
||||
// DefaultName gets the default TUN interface name for your platform.
|
||||
func DefaultName() string {
|
||||
return defaults.GetDefaults().DefaultIfName
|
||||
return config.GetDefaults().DefaultIfName
|
||||
}
|
||||
|
||||
// DefaultMTU gets the default TUN interface MTU for your platform. This can
|
||||
// be as high as MaximumMTU(), depending on platform, but is never lower than 1280.
|
||||
func DefaultMTU() uint64 {
|
||||
return defaults.GetDefaults().DefaultIfMTU
|
||||
return config.GetDefaults().DefaultIfMTU
|
||||
}
|
||||
|
||||
// MaximumMTU returns the maximum supported TUN interface MTU for your
|
||||
// platform. This can be as high as 65535, depending on platform, but is never
|
||||
// lower than 1280.
|
||||
func MaximumMTU() uint64 {
|
||||
return defaults.GetDefaults().MaximumIfMTU
|
||||
return config.GetDefaults().MaximumIfMTU
|
||||
}
|
||||
|
||||
// Init initialises the TUN module. You must have acquired a Listener from
|
||||
// the Yggdrasil core before this point and it must not be in use elsewhere.
|
||||
func New(rwc *ipv6rwc.ReadWriteCloser, log core.Logger, opts ...SetupOption) (*TunAdapter, error) {
|
||||
func New(rwc ReadWriteCloser, log core.Logger, opts ...SetupOption) (*TunAdapter, error) {
|
||||
tun := &TunAdapter{
|
||||
rwc: rwc,
|
||||
log: log,
|
||||
|
@ -143,13 +143,13 @@ func (tun *TunAdapter) setupAddress(addr string) error {
|
||||
tun.log.Infof("Interface IPv6: %s", addr)
|
||||
tun.log.Infof("Interface MTU: %d", ir.ifru_mtu)
|
||||
|
||||
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(darwin_SIOCAIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 {
|
||||
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(darwin_SIOCAIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 { // nolint:staticcheck
|
||||
err = errno
|
||||
tun.log.Errorf("Error in darwin_SIOCAIFADDR_IN6: %v", errno)
|
||||
return err
|
||||
}
|
||||
|
||||
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 {
|
||||
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 { // nolint:staticcheck
|
||||
err = errno
|
||||
tun.log.Errorf("Error in SIOCSIFMTU: %v", errno)
|
||||
return err
|
||||
|
@ -4,13 +4,12 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
wgtun "golang.zx2c4.com/wireguard/tun"
|
||||
@ -23,7 +22,7 @@ import (
|
||||
// Configures the TUN adapter with the correct IPv6 address and MTU.
|
||||
func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
|
||||
if ifname == "auto" {
|
||||
ifname = defaults.GetDefaults().DefaultIfName
|
||||
ifname = config.GetDefaults().DefaultIfName
|
||||
}
|
||||
return elevate.DoAsSystem(func() error {
|
||||
var err error
|
||||
@ -89,13 +88,9 @@ func (tun *TunAdapter) setupAddress(addr string) error {
|
||||
return errors.New("Can't configure IPv6 address as TUN adapter is not present")
|
||||
}
|
||||
if intf, ok := tun.iface.(*wgtun.NativeTun); ok {
|
||||
if ipaddr, ipnet, err := net.ParseCIDR(addr); err == nil {
|
||||
if ipnet, err := netip.ParsePrefix(addr); err == nil {
|
||||
luid := winipcfg.LUID(intf.LUID())
|
||||
addresses := append([]net.IPNet{}, net.IPNet{
|
||||
IP: ipaddr,
|
||||
Mask: ipnet.Mask,
|
||||
})
|
||||
|
||||
addresses := []netip.Prefix{ipnet}
|
||||
err := luid.SetIPAddressesForFamily(windows.AF_INET6, addresses)
|
||||
if err == windows.ERROR_OBJECT_ALREADY_EXISTS {
|
||||
cleanupAddressesOnDisconnectedInterfaces(windows.AF_INET6, addresses)
|
||||
@ -118,24 +113,13 @@ func (tun *TunAdapter) setupAddress(addr string) error {
|
||||
* SPDX-License-Identifier: MIT
|
||||
* Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []net.IPNet) {
|
||||
func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []netip.Prefix) {
|
||||
if len(addresses) == 0 {
|
||||
return
|
||||
}
|
||||
includedInAddresses := func(a net.IPNet) bool {
|
||||
// TODO: this makes the whole algorithm O(n^2). But we can't stick net.IPNet in a Go hashmap. Bummer!
|
||||
for _, addr := range addresses {
|
||||
ip := addr.IP
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
ip = ip4
|
||||
}
|
||||
mA, _ := addr.Mask.Size()
|
||||
mB, _ := a.Mask.Size()
|
||||
if bytes.Equal(ip, a.IP) && mA == mB {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
addrHash := make(map[netip.Addr]bool, len(addresses))
|
||||
for i := range addresses {
|
||||
addrHash[addresses[i].Addr()] = true
|
||||
}
|
||||
interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagDefault)
|
||||
if err != nil {
|
||||
@ -146,11 +130,10 @@ func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, add
|
||||
continue
|
||||
}
|
||||
for address := iface.FirstUnicastAddress; address != nil; address = address.Next {
|
||||
ip := address.Address.IP()
|
||||
ipnet := net.IPNet{IP: ip, Mask: net.CIDRMask(int(address.OnLinkPrefixLength), 8*len(ip))}
|
||||
if includedInAddresses(ipnet) {
|
||||
log.Printf("Cleaning up stale address %s from interface ‘%s’", ipnet.String(), iface.FriendlyName())
|
||||
iface.LUID.DeleteIPAddress(ipnet)
|
||||
if ip, _ := netip.AddrFromSlice(address.Address.IP()); addrHash[ip] {
|
||||
prefix := netip.PrefixFrom(ip, int(address.OnLinkPrefixLength))
|
||||
log.Printf("Cleaning up stale address %s from interface ‘%s’", prefix.String(), iface.FriendlyName())
|
||||
iface.LUID.DeleteIPAddress(prefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user