mirror of
https://github.com/yggdrasil-network/yggdrasil-go.git
synced 2025-04-30 07:05:06 +03:00
commit
6c33805027
12 changed files with 88 additions and 423 deletions
|
@ -16,6 +16,11 @@ jobs:
|
||||||
go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.31.0
|
go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.31.0
|
||||||
golangci-lint run
|
golangci-lint run
|
||||||
|
|
||||||
|
- run:
|
||||||
|
name: Run Go tests
|
||||||
|
command: |
|
||||||
|
go test ./...
|
||||||
|
|
||||||
build-linux:
|
build-linux:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/golang:1.16
|
- image: circleci/golang:1.16
|
||||||
|
|
|
@ -27,7 +27,6 @@ import (
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||||
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/module"
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/multicast"
|
"github.com/yggdrasil-network/yggdrasil-go/src/multicast"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/tuntap"
|
"github.com/yggdrasil-network/yggdrasil-go/src/tuntap"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/version"
|
"github.com/yggdrasil-network/yggdrasil-go/src/version"
|
||||||
|
@ -36,12 +35,12 @@ import (
|
||||||
type node struct {
|
type node struct {
|
||||||
core core.Core
|
core core.Core
|
||||||
config *config.NodeConfig
|
config *config.NodeConfig
|
||||||
tuntap module.Module // tuntap.TunAdapter
|
tuntap *tuntap.TunAdapter
|
||||||
multicast module.Module // multicast.Multicast
|
multicast *multicast.Multicast
|
||||||
admin module.Module // admin.AdminSocket
|
admin *admin.AdminSocket
|
||||||
}
|
}
|
||||||
|
|
||||||
func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *config.NodeConfig {
|
func readConfig(log *log.Logger, useconf *bool, useconffile *string, normaliseconf *bool) *config.NodeConfig {
|
||||||
// Use a configuration file. If -useconf, the configuration will be read
|
// Use a configuration file. If -useconf, the configuration will be read
|
||||||
// from stdin. If -useconffile, the configuration will be read from the
|
// from stdin. If -useconffile, the configuration will be read from the
|
||||||
// filesystem.
|
// filesystem.
|
||||||
|
@ -79,27 +78,21 @@ func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *config
|
||||||
if err := hjson.Unmarshal(conf, &dat); err != nil {
|
if err := hjson.Unmarshal(conf, &dat); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
// Check for fields that have changed type recently, e.g. the Listen config
|
// Check if we have old field names
|
||||||
// option is now a []string rather than a string
|
if _, ok := dat["TunnelRouting"]; ok {
|
||||||
if listen, ok := dat["Listen"].(string); ok {
|
log.Warnln("WARNING: Tunnel routing is no longer supported")
|
||||||
dat["Listen"] = []string{listen}
|
|
||||||
}
|
}
|
||||||
if tunnelrouting, ok := dat["TunnelRouting"].(map[string]interface{}); ok {
|
if old, ok := dat["SigningPrivateKey"]; ok {
|
||||||
if c, ok := tunnelrouting["IPv4Sources"]; ok {
|
log.Warnln("WARNING: The \"SigningPrivateKey\" configuration option has been renamed to \"PrivateKey\"")
|
||||||
delete(tunnelrouting, "IPv4Sources")
|
if _, ok := dat["PrivateKey"]; !ok {
|
||||||
tunnelrouting["IPv4LocalSubnets"] = c
|
if privstr, err := hex.DecodeString(old.(string)); err == nil {
|
||||||
}
|
priv := ed25519.PrivateKey(privstr)
|
||||||
if c, ok := tunnelrouting["IPv6Sources"]; ok {
|
pub := priv.Public().(ed25519.PublicKey)
|
||||||
delete(tunnelrouting, "IPv6Sources")
|
dat["PrivateKey"] = hex.EncodeToString(priv[:])
|
||||||
tunnelrouting["IPv6LocalSubnets"] = c
|
dat["PublicKey"] = hex.EncodeToString(pub[:])
|
||||||
}
|
} else {
|
||||||
if c, ok := tunnelrouting["IPv4Destinations"]; ok {
|
log.Warnln("WARNING: The \"SigningPrivateKey\" configuration option contains an invalid value and will be ignored")
|
||||||
delete(tunnelrouting, "IPv4Destinations")
|
}
|
||||||
tunnelrouting["IPv4RemoteSubnets"] = c
|
|
||||||
}
|
|
||||||
if c, ok := tunnelrouting["IPv6Destinations"]; ok {
|
|
||||||
delete(tunnelrouting, "IPv6Destinations")
|
|
||||||
tunnelrouting["IPv6RemoteSubnets"] = c
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Sanitise the config
|
// Sanitise the config
|
||||||
|
@ -177,6 +170,31 @@ func main() {
|
||||||
loglevel := flag.String("loglevel", "info", "loglevel to enable")
|
loglevel := flag.String("loglevel", "info", "loglevel to enable")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
// Create a new logger that logs output to stdout.
|
||||||
|
var logger *log.Logger
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if logfd, err := os.OpenFile(*logto, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil {
|
||||||
|
logger = log.New(logfd, "", log.Flags())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if logger == nil {
|
||||||
|
logger = log.New(os.Stdout, "", log.Flags())
|
||||||
|
logger.Warnln("Logging defaulting to stdout")
|
||||||
|
}
|
||||||
|
|
||||||
|
if *normaliseconf {
|
||||||
|
setLogLevel("error", logger)
|
||||||
|
} else {
|
||||||
|
setLogLevel(*loglevel, logger)
|
||||||
|
}
|
||||||
|
|
||||||
var cfg *config.NodeConfig
|
var cfg *config.NodeConfig
|
||||||
var err error
|
var err error
|
||||||
switch {
|
switch {
|
||||||
|
@ -190,7 +208,7 @@ func main() {
|
||||||
cfg = config.GenerateConfig()
|
cfg = config.GenerateConfig()
|
||||||
case *useconffile != "" || *useconf:
|
case *useconffile != "" || *useconf:
|
||||||
// Read the configuration from either stdin or from the filesystem
|
// Read the configuration from either stdin or from the filesystem
|
||||||
cfg = readConfig(useconf, useconffile, normaliseconf)
|
cfg = readConfig(logger, useconf, useconffile, normaliseconf)
|
||||||
// If the -normaliseconf option was specified then remarshal the above
|
// If the -normaliseconf option was specified then remarshal the above
|
||||||
// configuration and print it back to stdout. This lets the user update
|
// configuration and print it back to stdout. This lets the user update
|
||||||
// their configuration file with newly mapped names (like above) or to
|
// their configuration file with newly mapped names (like above) or to
|
||||||
|
@ -223,8 +241,8 @@ func main() {
|
||||||
}
|
}
|
||||||
// Have we been asked for the node address yet? If so, print it and then stop.
|
// Have we been asked for the node address yet? If so, print it and then stop.
|
||||||
getNodeKey := func() ed25519.PublicKey {
|
getNodeKey := func() ed25519.PublicKey {
|
||||||
if pubkey, err := hex.DecodeString(cfg.PublicKey); err == nil {
|
if pubkey, err := hex.DecodeString(cfg.PrivateKey); err == nil {
|
||||||
return ed25519.PublicKey(pubkey)
|
return ed25519.PrivateKey(pubkey).Public().(ed25519.PublicKey)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -248,30 +266,10 @@ func main() {
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
// Create a new logger that logs output to stdout.
|
|
||||||
var logger *log.Logger
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if logfd, err := os.OpenFile(*logto, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil {
|
|
||||||
logger = log.New(logfd, "", log.Flags())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if logger == nil {
|
|
||||||
logger = log.New(os.Stdout, "", log.Flags())
|
|
||||||
logger.Warnln("Logging defaulting to stdout")
|
|
||||||
}
|
|
||||||
|
|
||||||
setLogLevel(*loglevel, logger)
|
|
||||||
|
|
||||||
// Setup the Yggdrasil node itself. The node{} type includes a Core, so we
|
// Setup the Yggdrasil node itself. The node{} type includes a Core, so we
|
||||||
// don't need to create this manually.
|
// don't need to create this manually.
|
||||||
n := node{}
|
n := node{config: cfg}
|
||||||
// Now start Yggdrasil - this starts the DHT, router, switch and other core
|
// Now start Yggdrasil - this starts the DHT, router, switch and other core
|
||||||
// components needed for Yggdrasil to operate
|
// components needed for Yggdrasil to operate
|
||||||
if err = n.core.Start(cfg, logger); err != nil {
|
if err = n.core.Start(cfg, logger); err != nil {
|
||||||
|
@ -283,32 +281,34 @@ func main() {
|
||||||
n.admin = &admin.AdminSocket{}
|
n.admin = &admin.AdminSocket{}
|
||||||
n.multicast = &multicast.Multicast{}
|
n.multicast = &multicast.Multicast{}
|
||||||
n.tuntap = &tuntap.TunAdapter{}
|
n.tuntap = &tuntap.TunAdapter{}
|
||||||
n.tuntap.(*tuntap.TunAdapter).SetSessionGatekeeper(n.sessionFirewall)
|
n.tuntap.SetSessionGatekeeper(n.sessionFirewall)
|
||||||
// Start the admin socket
|
// Start the admin socket
|
||||||
if err := n.admin.Init(&n.core, cfg, logger, nil); err != nil {
|
if err := n.admin.Init(&n.core, cfg, logger, nil); err != nil {
|
||||||
logger.Errorln("An error occured initialising admin socket:", err)
|
logger.Errorln("An error occurred initialising admin socket:", err)
|
||||||
} else if err := n.admin.Start(); err != nil {
|
} else if err := n.admin.Start(); err != nil {
|
||||||
logger.Errorln("An error occurred starting admin socket:", err)
|
logger.Errorln("An error occurred starting admin socket:", err)
|
||||||
}
|
}
|
||||||
n.admin.SetupAdminHandlers(n.admin.(*admin.AdminSocket))
|
n.admin.SetupAdminHandlers(n.admin)
|
||||||
// Start the multicast interface
|
// Start the multicast interface
|
||||||
if err := n.multicast.Init(&n.core, cfg, logger, nil); err != nil {
|
if err := n.multicast.Init(&n.core, cfg, logger, nil); err != nil {
|
||||||
logger.Errorln("An error occured initialising multicast:", err)
|
logger.Errorln("An error occurred initialising multicast:", err)
|
||||||
} else if err := n.multicast.Start(); err != nil {
|
} else if err := n.multicast.Start(); err != nil {
|
||||||
logger.Errorln("An error occurred starting multicast:", err)
|
logger.Errorln("An error occurred starting multicast:", err)
|
||||||
}
|
}
|
||||||
n.multicast.SetupAdminHandlers(n.admin.(*admin.AdminSocket))
|
n.multicast.SetupAdminHandlers(n.admin)
|
||||||
// Start the TUN/TAP interface
|
// Start the TUN/TAP interface
|
||||||
if err := n.tuntap.Init(&n.core, cfg, logger, nil); err != nil {
|
if err := n.tuntap.Init(&n.core, cfg, logger, nil); err != nil {
|
||||||
logger.Errorln("An error occurred initialising TUN/TAP:", err)
|
logger.Errorln("An error occurred initialising TUN/TAP:", err)
|
||||||
} else if err := n.tuntap.Start(); err != nil {
|
} else if err := n.tuntap.Start(); err != nil {
|
||||||
logger.Errorln("An error occurred starting TUN/TAP:", err)
|
logger.Errorln("An error occurred starting TUN/TAP:", err)
|
||||||
}
|
}
|
||||||
n.tuntap.SetupAdminHandlers(n.admin.(*admin.AdminSocket))
|
n.tuntap.SetupAdminHandlers(n.admin)
|
||||||
// Make some nice output that tells us what our IPv6 address and subnet are.
|
// Make some nice output that tells us what our IPv6 address and subnet are.
|
||||||
// This is just logged to stdout for the user.
|
// This is just logged to stdout for the user.
|
||||||
address := n.core.Address()
|
address := n.core.Address()
|
||||||
subnet := n.core.Subnet()
|
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 address is %s", address.String())
|
||||||
logger.Infof("Your IPv6 subnet is %s", subnet.String())
|
logger.Infof("Your IPv6 subnet is %s", subnet.String())
|
||||||
// Catch interrupts from the operating system to exit gracefully.
|
// Catch interrupts from the operating system to exit gracefully.
|
||||||
|
|
|
@ -47,8 +47,11 @@ func run() int {
|
||||||
fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] command [key=value] [key=value] ...\n\n", os.Args[0])
|
fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] command [key=value] [key=value] ...\n\n", os.Args[0])
|
||||||
fmt.Println("Options:")
|
fmt.Println("Options:")
|
||||||
flag.PrintDefaults()
|
flag.PrintDefaults()
|
||||||
fmt.Println("\nPlease note that options must always specified BEFORE the command\non the command line or they will be ignored.\n") // nolint:govet
|
fmt.Println()
|
||||||
fmt.Println("Commands:\n - Use \"list\" for a list of available commands\n") // nolint:govet
|
fmt.Println("Please note that options must always specified BEFORE the command\non the command line or they will be ignored.")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Commands:\n - Use \"list\" for a list of available commands")
|
||||||
|
fmt.Println()
|
||||||
fmt.Println("Examples:")
|
fmt.Println("Examples:")
|
||||||
fmt.Println(" - ", os.Args[0], "list")
|
fmt.Println(" - ", os.Args[0], "list")
|
||||||
fmt.Println(" - ", os.Args[0], "getPeers")
|
fmt.Println(" - ", os.Args[0], "getPeers")
|
||||||
|
@ -288,6 +291,9 @@ func run() int {
|
||||||
if subnet, ok := v.(map[string]interface{})["subnet"].(string); ok {
|
if subnet, ok := v.(map[string]interface{})["subnet"].(string); ok {
|
||||||
fmt.Println("IPv6 subnet:", subnet)
|
fmt.Println("IPv6 subnet:", subnet)
|
||||||
}
|
}
|
||||||
|
if boxSigKey, ok := v.(map[string]interface{})["key"].(string); ok {
|
||||||
|
fmt.Println("Public key:", boxSigKey)
|
||||||
|
}
|
||||||
if coords, ok := v.(map[string]interface{})["coords"].(string); ok {
|
if coords, ok := v.(map[string]interface{})["coords"].(string); ok {
|
||||||
fmt.Println("Coords:", coords)
|
fmt.Println("Coords:", coords)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +1,8 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
# Get the last tag
|
if [[ $* == *--bare* ]]; then
|
||||||
TAG=$(git describe --abbrev=0 --tags --match="v[0-9]*\.[0-9]*\.[0-9]*" 2>/dev/null)
|
# Remove the "v" prefix
|
||||||
|
git describe --tags --match="v[0-9]*\.[0-9]*\.[0-9]*" | cut -c 2-
|
||||||
# Did getting the tag succeed?
|
|
||||||
if [ $? != 0 ] || [ -z "$TAG" ]; then
|
|
||||||
printf -- "unknown"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Get the current branch
|
|
||||||
BRANCH=$(git symbolic-ref -q HEAD --short 2>/dev/null)
|
|
||||||
|
|
||||||
# Did getting the branch succeed?
|
|
||||||
if [ $? != 0 ] || [ -z "$BRANCH" ]; then
|
|
||||||
BRANCH="master"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Split out into major, minor and patch numbers
|
|
||||||
MAJOR=$(echo $TAG | cut -c 2- | cut -d "." -f 1)
|
|
||||||
MINOR=$(echo $TAG | cut -c 2- | cut -d "." -f 2)
|
|
||||||
PATCH=$(echo $TAG | cut -c 2- | cut -d "." -f 3)
|
|
||||||
|
|
||||||
# Output in the desired format
|
|
||||||
if [ $((PATCH)) -eq 0 ]; then
|
|
||||||
printf '%s%d.%d' "$PREPEND" "$((MAJOR))" "$((MINOR))"
|
|
||||||
else
|
else
|
||||||
printf '%s%d.%d.%d' "$PREPEND" "$((MAJOR))" "$((MINOR))" "$((PATCH))"
|
git describe --tags --match="v[0-9]*\.[0-9]*\.[0-9]*"
|
||||||
fi
|
|
||||||
|
|
||||||
# Add the build tag on non-master branches
|
|
||||||
if [ "$BRANCH" != "master" ]; then
|
|
||||||
BUILD=$(git rev-list --count $TAG..HEAD 2>/dev/null)
|
|
||||||
|
|
||||||
# Did getting the count of commits since the tag succeed?
|
|
||||||
if [ $? != 0 ] || [ -z "$BUILD" ]; then
|
|
||||||
printf -- "-unknown"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Is the build greater than zero?
|
|
||||||
if [ $((BUILD)) -gt 0 ]; then
|
|
||||||
printf -- "-%04d" "$((BUILD))"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -28,7 +28,7 @@ import (
|
||||||
// options that are necessary for an Yggdrasil node to run. You will need to
|
// 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.
|
// supply one of these structs to the Yggdrasil core when starting a node.
|
||||||
type NodeConfig struct {
|
type NodeConfig struct {
|
||||||
sync.RWMutex
|
sync.RWMutex `json:"-"`
|
||||||
Peers []string `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tcp://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."`
|
Peers []string `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tcp://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\": [ tcp://a.b.c.d:e ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."`
|
InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ tcp://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.\ntcp://0.0.0.0:0 or tcp://[::]:0 to listen on all interfaces."`
|
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.\ntcp://0.0.0.0:0 or tcp://[::]:0 to listen on all interfaces."`
|
||||||
|
|
176
src/core/doc.go
176
src/core/doc.go
|
@ -1,176 +0,0 @@
|
||||||
/*
|
|
||||||
Package core implements the core functionality of the Yggdrasil Network.
|
|
||||||
|
|
||||||
Introduction
|
|
||||||
|
|
||||||
Yggdrasil is a proof-of-concept mesh network which provides end-to-end encrypted
|
|
||||||
communication between nodes in a decentralised fashion. The network is arranged
|
|
||||||
using a globally-agreed spanning tree which provides each node with a locator
|
|
||||||
(coordinates relative to the root) and a distributed hash table (DHT) mechanism
|
|
||||||
for finding other nodes.
|
|
||||||
|
|
||||||
Each node also implements a router, which is responsible for encryption of
|
|
||||||
traffic, searches and connections, and a switch, which is responsible ultimately
|
|
||||||
for forwarding traffic across the network.
|
|
||||||
|
|
||||||
While many Yggdrasil nodes in existence today are IP nodes - that is, they are
|
|
||||||
transporting IPv6 packets, like a kind of mesh VPN - it is also possible to
|
|
||||||
integrate Yggdrasil into your own applications and use it as a generic data
|
|
||||||
transport, similar to UDP.
|
|
||||||
|
|
||||||
This library is what you need to integrate and use Yggdrasil in your own
|
|
||||||
application.
|
|
||||||
|
|
||||||
Basics
|
|
||||||
|
|
||||||
In order to start an Yggdrasil node, you should start by generating node
|
|
||||||
configuration, which amongst other things, includes encryption keypairs which
|
|
||||||
are used to generate the node's identity, and supply a logger which Yggdrasil's
|
|
||||||
output will be written to.
|
|
||||||
|
|
||||||
This may look something like this:
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"github.com/gologme/log"
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
type node struct {
|
|
||||||
core core.Core
|
|
||||||
config *config.NodeConfig
|
|
||||||
log *log.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
You then can supply node configuration and a logger:
|
|
||||||
|
|
||||||
n := node{}
|
|
||||||
n.log = log.New(os.Stdout, "", log.Flags())
|
|
||||||
n.config = config.GenerateConfig()
|
|
||||||
|
|
||||||
In the above example, we ask the config package to supply new configuration each
|
|
||||||
time, which results in fresh encryption keys and therefore a new identity. It is
|
|
||||||
normally preferable in most cases to persist node configuration onto the
|
|
||||||
filesystem or into some configuration store so that the node's identity does not
|
|
||||||
change each time that the program starts. Note that Yggdrasil will automatically
|
|
||||||
fill in any missing configuration items with sane defaults.
|
|
||||||
|
|
||||||
Once you have supplied a logger and some node configuration, you can then start
|
|
||||||
the node:
|
|
||||||
|
|
||||||
n.core.Start(n.config, n.log)
|
|
||||||
|
|
||||||
Add some peers to connect to the network:
|
|
||||||
|
|
||||||
n.core.AddPeer("tcp://some-host.net:54321", "")
|
|
||||||
n.core.AddPeer("tcp://[2001::1:2:3]:54321", "")
|
|
||||||
n.core.AddPeer("tcp://1.2.3.4:54321", "")
|
|
||||||
|
|
||||||
You can also ask the API for information about our node:
|
|
||||||
|
|
||||||
n.log.Println("My node ID is", n.core.NodeID())
|
|
||||||
n.log.Println("My public key is", n.core.EncryptionPublicKey())
|
|
||||||
n.log.Println("My coords are", n.core.Coords())
|
|
||||||
|
|
||||||
Incoming Connections
|
|
||||||
|
|
||||||
Once your node is started, you can then listen for connections from other nodes
|
|
||||||
by asking the API for a Listener:
|
|
||||||
|
|
||||||
listener, err := n.core.ConnListen()
|
|
||||||
if err != nil {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
The Listener has a blocking Accept function which will wait for incoming
|
|
||||||
connections from remote nodes. It will return a Conn when a connection is
|
|
||||||
received. If the node never receives any incoming connections then this function
|
|
||||||
can block forever, so be prepared for that, perhaps by listening in a separate
|
|
||||||
goroutine.
|
|
||||||
|
|
||||||
Assuming that you have defined a myConnectionHandler function to deal with
|
|
||||||
incoming connections:
|
|
||||||
|
|
||||||
for {
|
|
||||||
conn, err := listener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
// We've got a new connection
|
|
||||||
go myConnectionHandler(conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
Outgoing Connections
|
|
||||||
|
|
||||||
If you know the node ID of the remote node that you want to talk to, you can
|
|
||||||
dial an outbound connection to it. To do this, you should first ask the API for
|
|
||||||
a Dialer:
|
|
||||||
|
|
||||||
dialer, err := n.core.ConnDialer()
|
|
||||||
if err != nil {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
You can then dial using the node's public key in hexadecimal format, for example:
|
|
||||||
|
|
||||||
conn, err := dialer.Dial("curve25519", "55071be281f50d0abbda63aadc59755624280c44b2f1f47684317aa4e0325604")
|
|
||||||
if err != nil {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
Using Connections
|
|
||||||
|
|
||||||
Conn objects are implementations of io.ReadWriteCloser, and as such, you can
|
|
||||||
Read, Write and Close them as necessary.
|
|
||||||
|
|
||||||
Each Read or Write operation can deal with a buffer with a maximum size of 65535
|
|
||||||
bytes - any bigger than this and the operation will return an error.
|
|
||||||
|
|
||||||
For example, to write to the Conn from the supplied buffer:
|
|
||||||
|
|
||||||
buf := []byte{1, 2, 3, 4, 5}
|
|
||||||
w, err := conn.Write(buf)
|
|
||||||
if err != nil {
|
|
||||||
// ...
|
|
||||||
} else {
|
|
||||||
// written w bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
Reading from the Conn into the supplied buffer:
|
|
||||||
|
|
||||||
buf := make([]byte, 65535)
|
|
||||||
r, err := conn.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
// ...
|
|
||||||
} else {
|
|
||||||
// read r bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
When you are happy that a connection is no longer required, you can discard it:
|
|
||||||
|
|
||||||
err := conn.Close()
|
|
||||||
if err != nil {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
Limitations
|
|
||||||
|
|
||||||
You should be aware of the following limitations when working with the Yggdrasil
|
|
||||||
library:
|
|
||||||
|
|
||||||
Individual messages written through Yggdrasil connections can not exceed 65535
|
|
||||||
bytes in size. Yggdrasil has no concept of fragmentation, so if you try to send
|
|
||||||
a message that exceeds 65535 bytes in size, it will be dropped altogether and
|
|
||||||
an error will be returned.
|
|
||||||
|
|
||||||
Yggdrasil connections are unreliable by nature. Messages are delivered on a
|
|
||||||
best-effort basis, and employs congestion control where appropriate to ensure
|
|
||||||
that congestion does not affect message transport, but Yggdrasil will not
|
|
||||||
retransmit any messages that have been lost. If reliable delivery is important
|
|
||||||
then you should manually implement acknowledgement and retransmission of
|
|
||||||
messages.
|
|
||||||
|
|
||||||
*/
|
|
||||||
package core
|
|
|
@ -1,19 +0,0 @@
|
||||||
package module
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gologme/log"
|
|
||||||
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Module is an interface that defines which functions must be supported by a
|
|
||||||
// given Yggdrasil module.
|
|
||||||
type Module interface {
|
|
||||||
Init(core *core.Core, state *config.NodeConfig, log *log.Logger, options interface{}) error
|
|
||||||
Start() error
|
|
||||||
Stop() error
|
|
||||||
SetupAdminHandlers(a *admin.AdminSocket)
|
|
||||||
IsStarted() bool
|
|
||||||
}
|
|
|
@ -2,6 +2,8 @@ package multicast
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -303,7 +305,8 @@ func (m *Multicast) _announce() {
|
||||||
if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil {
|
if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil {
|
||||||
a.Zone = ""
|
a.Zone = ""
|
||||||
destAddr.Zone = iface.Name
|
destAddr.Zone = iface.Name
|
||||||
msg := []byte(a.String())
|
msg := append([]byte(nil), m.core.GetSelf().Key...)
|
||||||
|
msg = append(msg, a.String()...)
|
||||||
_, _ = m.sock.WriteTo(msg, nil, destAddr)
|
_, _ = m.sock.WriteTo(msg, nil, destAddr)
|
||||||
}
|
}
|
||||||
if info.interval.Seconds() < 15 {
|
if info.interval.Seconds() < 15 {
|
||||||
|
@ -342,7 +345,12 @@ func (m *Multicast) listen() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
anAddr := string(bs[:nBytes])
|
if len(bs) < ed25519.PublicKeySize {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var key ed25519.PublicKey
|
||||||
|
key = append(key, bs[:ed25519.PublicKeySize]...)
|
||||||
|
anAddr := string(bs[ed25519.PublicKeySize:nBytes])
|
||||||
addr, err := net.ResolveTCPAddr("tcp6", anAddr)
|
addr, err := net.ResolveTCPAddr("tcp6", anAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
|
@ -357,7 +365,8 @@ func (m *Multicast) listen() {
|
||||||
})
|
})
|
||||||
if _, ok := interfaces[from.Zone]; ok {
|
if _, ok := interfaces[from.Zone]; ok {
|
||||||
addr.Zone = ""
|
addr.Zone = ""
|
||||||
u, err := url.Parse("tcp://" + addr.String())
|
pin := fmt.Sprintf("/?ed25519=%s", hex.EncodeToString(key))
|
||||||
|
u, err := url.Parse("tcp://" + addr.String() + pin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.log.Debugln("Call from multicast failed, parse error:", addr.String(), err)
|
m.log.Debugln("Call from multicast failed, parse error:", addr.String(), err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ func (tun *TunAdapter) read() {
|
||||||
copy(srcSubnet[:], bs[8:])
|
copy(srcSubnet[:], bs[8:])
|
||||||
copy(dstSubnet[:], bs[24:])
|
copy(dstSubnet[:], bs[24:])
|
||||||
if srcAddr != tun.addr && srcSubnet != tun.subnet {
|
if srcAddr != tun.addr && srcSubnet != tun.subnet {
|
||||||
continue // Wrong soruce address
|
continue // Wrong source address
|
||||||
}
|
}
|
||||||
bs = buf[begin-1 : end]
|
bs = buf[begin-1 : end]
|
||||||
bs[0] = typeSessionTraffic
|
bs[0] = typeSessionTraffic
|
||||||
|
|
|
@ -90,7 +90,7 @@ func (tun *TunAdapter) setupAddress(addr string) error {
|
||||||
ar.ifra_prefixmask.sin6_len = uint8(unsafe.Sizeof(ar.ifra_prefixmask))
|
ar.ifra_prefixmask.sin6_len = uint8(unsafe.Sizeof(ar.ifra_prefixmask))
|
||||||
b := make([]byte, 16)
|
b := make([]byte, 16)
|
||||||
binary.LittleEndian.PutUint16(b, uint16(0xFE00))
|
binary.LittleEndian.PutUint16(b, uint16(0xFE00))
|
||||||
ar.ifra_prefixmask.sin6_addr[0] = uint16(binary.BigEndian.Uint16(b))
|
ar.ifra_prefixmask.sin6_addr[0] = binary.BigEndian.Uint16(b)
|
||||||
|
|
||||||
ar.ifra_addr.sin6_len = uint8(unsafe.Sizeof(ar.ifra_addr))
|
ar.ifra_addr.sin6_len = uint8(unsafe.Sizeof(ar.ifra_addr))
|
||||||
ar.ifra_addr.sin6_family = unix.AF_INET6
|
ar.ifra_addr.sin6_family = unix.AF_INET6
|
||||||
|
@ -99,7 +99,7 @@ func (tun *TunAdapter) setupAddress(addr string) error {
|
||||||
addr, _ := strconv.ParseUint(parts[i], 16, 16)
|
addr, _ := strconv.ParseUint(parts[i], 16, 16)
|
||||||
b := make([]byte, 16)
|
b := make([]byte, 16)
|
||||||
binary.LittleEndian.PutUint16(b, uint16(addr))
|
binary.LittleEndian.PutUint16(b, uint16(addr))
|
||||||
ar.ifra_addr.sin6_addr[i] = uint16(binary.BigEndian.Uint16(b))
|
ar.ifra_addr.sin6_addr[i] = binary.BigEndian.Uint16(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
ar.ifra_flags |= darwin_IN6_IFF_NODAD
|
ar.ifra_flags |= darwin_IN6_IFF_NODAD
|
||||||
|
|
|
@ -5,35 +5,9 @@ package util
|
||||||
// These are misc. utility functions that didn't really fit anywhere else
|
// These are misc. utility functions that didn't really fit anywhere else
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Yield just executes runtime.Gosched(), and is included so we don't need to explicitly import runtime elsewhere.
|
|
||||||
func Yield() {
|
|
||||||
runtime.Gosched()
|
|
||||||
}
|
|
||||||
|
|
||||||
// LockThread executes runtime.LockOSThread(), and is included so we don't need to explicitly import runtime elsewhere.
|
|
||||||
func LockThread() {
|
|
||||||
runtime.LockOSThread()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnlockThread executes runtime.UnlockOSThread(), and is included so we don't need to explicitly import runtime elsewhere.
|
|
||||||
func UnlockThread() {
|
|
||||||
runtime.UnlockOSThread()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResizeBytes returns a slice of the specified length. If the provided slice has sufficient capacity, it will be resized and returned rather than allocating a new slice.
|
|
||||||
func ResizeBytes(bs []byte, length int) []byte {
|
|
||||||
if cap(bs) >= length {
|
|
||||||
return bs[:length]
|
|
||||||
}
|
|
||||||
return make([]byte, length)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimerStop stops a timer and makes sure the channel is drained, returns true if the timer was stopped before firing.
|
// TimerStop stops a timer and makes sure the channel is drained, returns true if the timer was stopped before firing.
|
||||||
func TimerStop(t *time.Timer) bool {
|
func TimerStop(t *time.Timer) bool {
|
||||||
stopped := t.Stop()
|
stopped := t.Stop()
|
||||||
|
@ -61,70 +35,3 @@ func FuncTimeout(timeout time.Duration, f func()) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Difference loops over two strings and returns the elements of A which do not appear in B.
|
|
||||||
// This is somewhat useful when needing to determine which elements of a configuration file have changed.
|
|
||||||
func Difference(a, b []string) []string {
|
|
||||||
ab := []string{}
|
|
||||||
mb := map[string]bool{}
|
|
||||||
for _, x := range b {
|
|
||||||
mb[x] = true
|
|
||||||
}
|
|
||||||
for _, x := range a {
|
|
||||||
if !mb[x] {
|
|
||||||
ab = append(ab, x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ab
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeCoordString decodes a string representing coordinates in [1 2 3] format
|
|
||||||
// and returns a []uint64.
|
|
||||||
func DecodeCoordString(in string) (out []uint64) {
|
|
||||||
s := strings.Trim(in, "[]")
|
|
||||||
t := strings.Split(s, " ")
|
|
||||||
for _, a := range t {
|
|
||||||
if u, err := strconv.ParseUint(a, 0, 64); err == nil {
|
|
||||||
out = append(out, u)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFlowKey takes an IP packet as an argument and returns some information about the traffic flow.
|
|
||||||
// For IPv4 packets, this is derived from the source and destination protocol and port numbers.
|
|
||||||
// For IPv6 packets, this is derived from the FlowLabel field of the packet if this was set, otherwise it's handled like IPv4.
|
|
||||||
// The FlowKey is then used internally by Yggdrasil for congestion control.
|
|
||||||
func GetFlowKey(bs []byte) uint64 {
|
|
||||||
// Work out the flowkey - this is used to determine which switch queue
|
|
||||||
// traffic will be pushed to in the event of congestion
|
|
||||||
var flowkey uint64
|
|
||||||
// Get the IP protocol version from the packet
|
|
||||||
switch bs[0] & 0xf0 {
|
|
||||||
case 0x40: // IPv4 packet
|
|
||||||
ihl := (bs[0] & 0x0f) * 4 // whole IPv4 header length (min 20)
|
|
||||||
// 8 is minimum UDP packet length
|
|
||||||
if ihl >= 20 && len(bs)-int(ihl) >= 8 {
|
|
||||||
switch bs[9] /* protocol */ {
|
|
||||||
case 0x06 /* TCP */, 0x11 /* UDP */, 0x84 /* SCTP */ :
|
|
||||||
flowkey = uint64(bs[9])<<32 /* proto */ |
|
|
||||||
uint64(bs[ihl+0])<<24 | uint64(bs[ihl+1])<<16 /* sport */ |
|
|
||||||
uint64(bs[ihl+2])<<8 | uint64(bs[ihl+3]) /* dport */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 0x60: // IPv6 packet
|
|
||||||
// Check if the flowlabel was specified in the packet header
|
|
||||||
flowkey = uint64(bs[1]&0x0f)<<16 | uint64(bs[2])<<8 | uint64(bs[3])
|
|
||||||
// If the flowlabel isn't present, make protokey from proto | sport | dport
|
|
||||||
// if the packet meets minimum UDP packet length
|
|
||||||
if flowkey == 0 && len(bs) >= 48 {
|
|
||||||
switch bs[9] /* protocol */ {
|
|
||||||
case 0x06 /* TCP */, 0x11 /* UDP */, 0x84 /* SCTP */ :
|
|
||||||
flowkey = uint64(bs[6])<<32 /* proto */ |
|
|
||||||
uint64(bs[40])<<24 | uint64(bs[41])<<16 /* sport */ |
|
|
||||||
uint64(bs[42])<<8 | uint64(bs[43]) /* dport */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return flowkey
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
package util
|
|
||||||
|
|
||||||
import "runtime"
|
|
||||||
|
|
||||||
var workerPool chan func()
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
maxProcs := runtime.GOMAXPROCS(0)
|
|
||||||
if maxProcs < 1 {
|
|
||||||
maxProcs = 1
|
|
||||||
}
|
|
||||||
workerPool = make(chan func(), maxProcs)
|
|
||||||
for idx := 0; idx < maxProcs; idx++ {
|
|
||||||
go func() {
|
|
||||||
for f := range workerPool {
|
|
||||||
f()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WorkerGo submits a job to a pool of GOMAXPROCS worker goroutines.
|
|
||||||
// This is meant for short non-blocking functions f() where you could just go f(),
|
|
||||||
// but you want some kind of backpressure to prevent spawning endless goroutines.
|
|
||||||
// WorkerGo returns as soon as the function is queued to run, not when it finishes.
|
|
||||||
// In Yggdrasil, these workers are used for certain cryptographic operations.
|
|
||||||
func WorkerGo(f func()) {
|
|
||||||
workerPool <- f
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue