5
0
mirror of https://github.com/cwinfo/yggdrasil-go.git synced 2024-11-29 14:21:37 +00:00

Merge pull request #335 from yggdrasil-network/develop

Version 0.3.3
This commit is contained in:
Neil Alexander 2019-02-21 19:21:27 +00:00 committed by GitHub
commit 1f1ba3bab8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 2197 additions and 893 deletions

View File

@ -48,14 +48,12 @@ jobs:
command: | command: |
rm -f {yggdrasil,yggdrasilctl} rm -f {yggdrasil,yggdrasilctl}
GOOS=darwin GOARCH=amd64 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-darwin-amd64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-darwin-amd64; GOOS=darwin GOARCH=amd64 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-darwin-amd64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-darwin-amd64;
GOOS=darwin GOARCH=386 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-darwin-i386 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-darwin-i386;
- run: - run:
name: Build for macOS (.pkg format) name: Build for macOS (.pkg format)
command: | command: |
rm -rf {yggdrasil,yggdrasilctl} rm -rf {yggdrasil,yggdrasilctl}
GOOS=darwin GOARCH=amd64 ./build && PKGARCH=amd64 sh contrib/macos/create-pkg.sh && mv *.pkg /tmp/upload/ GOOS=darwin GOARCH=amd64 ./build && PKGARCH=amd64 sh contrib/macos/create-pkg.sh && mv *.pkg /tmp/upload/
GOOS=darwin GOARCH=386 ./build && PKGARCH=i386 sh contrib/macos/create-pkg.sh && mv *.pkg /tmp/upload/
- run: - run:
name: Build for OpenBSD name: Build for OpenBSD

View File

@ -25,6 +25,27 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- in case of vulnerabilities. - in case of vulnerabilities.
--> -->
## [0.3.3] - 2018-02-18
### Added
- Dynamic reconfiguration, which allows reloading the configuration file to make changes during runtime by sending a `SIGHUP` signal (note: this only works with `-useconffile` and not `-useconf` and currently reconfiguring TUN/TAP is not supported)
- Support for building Yggdrasil as an iOS or Android framework if the appropriate tools (e.g. `gomobile`/`gobind` + SDKs) are available
- Connection contexts used for TCP connections which allow more exotic socket options to be set, e.g.
- Reusing the multicast socket to allow multiple running Yggdrasil instances without having to disable multicast
- Allowing supported Macs to peer with other nearby Macs that aren't even on the same Wi-Fi network using AWDL
- Flexible logging support, which allows for logging at different levels of verbosity
### Changed
- Switch changes to improve parent selection
- Node configuration is now stored centrally, rather than having fragments/copies distributed at startup time
- Significant refactoring in various areas, including for link types (TCP, AWDL etc), generic streams and adapters
- macOS builds through CircleCI are now 64-bit only
### Fixed
- Simplified `systemd` service now in `contrib`
### Removed
- `ReadTimeout` option is now deprecated
## [0.3.2] - 2018-12-26 ## [0.3.2] - 2018-12-26
### Added ### Added
- The admin socket is now multithreaded, greatly improving performance of the crawler and allowing concurrent lookups to take place - The admin socket is now multithreaded, greatly improving performance of the crawler and allowing concurrent lookups to take place

18
build
View File

@ -1,17 +1,21 @@
#!/bin/sh #!/bin/sh
set -ef
PKGSRC=${PKGSRC:-github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil} PKGSRC=${PKGSRC:-github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil}
PKGNAME=${PKGNAME:-$(sh contrib/semver/name.sh)} PKGNAME=${PKGNAME:-$(sh contrib/semver/name.sh)}
PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)} PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)}
LDFLAGS="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER" LDFLAGS="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER"
while getopts "udtc:l:" option while getopts "udaitc:l:" option
do do
case "${option}" case "${option}"
in in
u) UPX=true;; u) UPX=true;;
d) DEBUG=true;; d) DEBUG=true;;
i) IOS=true;;
a) ANDROID=true;;
t) TABLES=true;; t) TABLES=true;;
c) GCFLAGS="$GCFLAGS $OPTARG";; c) GCFLAGS="$GCFLAGS $OPTARG";;
l) LDFLAGS="$LDFLAGS $OPTARG";; l) LDFLAGS="$LDFLAGS $OPTARG";;
@ -22,7 +26,14 @@ if [ -z $TABLES ]; then
STRIP="-s -w" STRIP="-s -w"
fi fi
for CMD in `ls cmd/` ; do if [ $IOS ]; then
echo "Building framework for iOS"
gomobile bind -target ios -tags mobile -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil
elif [ $ANDROID ]; then
echo "Building aar for Android"
gomobile bind -target android -tags mobile -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil
else
for CMD in `ls cmd/` ; do
echo "Building: $CMD" echo "Building: $CMD"
if [ $DEBUG ]; then if [ $DEBUG ]; then
@ -33,4 +44,5 @@ for CMD in `ls cmd/` ; do
if [ $UPX ]; then if [ $UPX ]; then
upx --brute $CMD upx --brute $CMD
fi fi
done done
fi

View File

@ -2,28 +2,23 @@ package main
import ( import (
"bytes" "bytes"
"encoding/hex"
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"math/rand"
"os" "os"
"os/signal" "os/signal"
"regexp"
"strings" "strings"
"syscall" "syscall"
"time"
"golang.org/x/text/encoding/unicode" "golang.org/x/text/encoding/unicode"
"github.com/gologme/log"
"github.com/hjson/hjson-go" "github.com/hjson/hjson-go"
"github.com/kardianos/minwinsvc" "github.com/kardianos/minwinsvc"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
"github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/config"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
) )
@ -34,99 +29,18 @@ type node struct {
core Core core Core
} }
// Generates default configuration. This is used when outputting the -genconf func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *nodeConfig {
// parameter and also when using -autoconf. The isAutoconf flag is used to
// determine whether the operating system should select a free port by itself
// (which guarantees that there will not be a conflict with any other services)
// or whether to generate a random port number. The only side effect of setting
// isAutoconf is that the TCP and UDP ports will likely end up with different
// port numbers.
func generateConfig(isAutoconf bool) *nodeConfig {
// Create a new core.
core := Core{}
// Generate encryption keys.
bpub, bpriv := core.NewEncryptionKeys()
spub, spriv := core.NewSigningKeys()
// Create a node configuration and populate it.
cfg := nodeConfig{}
if isAutoconf {
cfg.Listen = "[::]:0"
} else {
r1 := rand.New(rand.NewSource(time.Now().UnixNano()))
cfg.Listen = fmt.Sprintf("[::]:%d", r1.Intn(65534-32768)+32768)
}
cfg.AdminListen = defaults.GetDefaults().DefaultAdminListen
cfg.EncryptionPublicKey = hex.EncodeToString(bpub[:])
cfg.EncryptionPrivateKey = hex.EncodeToString(bpriv[:])
cfg.SigningPublicKey = hex.EncodeToString(spub[:])
cfg.SigningPrivateKey = hex.EncodeToString(spriv[:])
cfg.Peers = []string{}
cfg.InterfacePeers = map[string][]string{}
cfg.AllowedEncryptionPublicKeys = []string{}
cfg.MulticastInterfaces = []string{".*"}
cfg.IfName = defaults.GetDefaults().DefaultIfName
cfg.IfMTU = defaults.GetDefaults().DefaultIfMTU
cfg.IfTAPMode = defaults.GetDefaults().DefaultIfTAPMode
cfg.SessionFirewall.Enable = false
cfg.SessionFirewall.AllowFromDirect = true
cfg.SessionFirewall.AllowFromRemote = true
cfg.SwitchOptions.MaxTotalQueueSize = yggdrasil.SwitchQueueTotalMinSize
cfg.NodeInfoPrivacy = false
return &cfg
}
// Generates a new configuration and returns it in HJSON format. This is used
// with -genconf.
func doGenconf(isjson bool) string {
cfg := generateConfig(false)
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)
}
// The main function is responsible for configuring and starting Yggdrasil.
func main() {
// Configure the command line parameters.
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")
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)")
version := flag.Bool("version", false, "prints the version of this build")
flag.Parse()
var cfg *nodeConfig
switch {
case *version:
fmt.Println("Build name:", yggdrasil.GetBuildName())
fmt.Println("Build version:", yggdrasil.GetBuildVersion())
os.Exit(0)
case *autoconf:
// Use an autoconf-generated config, this will give us random keys and
// port numbers, and will use an automatically selected TUN/TAP interface.
cfg = generateConfig(true)
case *useconffile != "" || *useconf:
// 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.
var config []byte var conf []byte
var err error var err error
if *useconffile != "" { if *useconffile != "" {
// Read the file from the filesystem // Read the file from the filesystem
config, err = ioutil.ReadFile(*useconffile) conf, err = ioutil.ReadFile(*useconffile)
} else { } else {
// Read the file from stdin. // Read the file from stdin.
config, err = ioutil.ReadAll(os.Stdin) conf, err = ioutil.ReadAll(os.Stdin)
} }
if err != nil { if err != nil {
panic(err) panic(err)
@ -135,11 +49,11 @@ func main() {
// throwing everywhere when it's converting things into UTF-16 for the hell // 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 // 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 // because hjson doesn't know what to do with UTF-16 and will panic
if bytes.Compare(config[0:2], []byte{0xFF, 0xFE}) == 0 || if bytes.Compare(conf[0:2], []byte{0xFF, 0xFE}) == 0 ||
bytes.Compare(config[0:2], []byte{0xFE, 0xFF}) == 0 { bytes.Compare(conf[0:2], []byte{0xFE, 0xFF}) == 0 {
utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM) utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM)
decoder := utf.NewDecoder() decoder := utf.NewDecoder()
config, err = decoder.Bytes(config) conf, err = decoder.Bytes(conf)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -148,9 +62,9 @@ func main() {
// then parse the configuration we loaded above on top of it. The effect // 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 // of this is that any configuration item that is missing from the provided
// configuration will use a sane default. // configuration will use a sane default.
cfg = generateConfig(false) cfg := config.GenerateConfig(false)
var dat map[string]interface{} var dat map[string]interface{}
if err := hjson.Unmarshal(config, &dat); err != nil { if err := hjson.Unmarshal(conf, &dat); err != nil {
panic(err) panic(err)
} }
confJson, err := json.Marshal(dat) confJson, err := json.Marshal(dat)
@ -163,6 +77,7 @@ func main() {
// names have changed recently. // names have changed recently.
changes := map[string]string{ changes := map[string]string{
"Multicast": "", "Multicast": "",
"ReadTimeout": "",
"LinkLocal": "MulticastInterfaces", "LinkLocal": "MulticastInterfaces",
"BoxPub": "EncryptionPublicKey", "BoxPub": "EncryptionPublicKey",
"BoxPriv": "EncryptionPrivateKey", "BoxPriv": "EncryptionPrivateKey",
@ -175,11 +90,11 @@ func main() {
if _, ok := dat[from]; ok { if _, ok := dat[from]; ok {
if to == "" { if to == "" {
if !*normaliseconf { if !*normaliseconf {
log.Println("Warning: Deprecated config option", from, "- please remove") log.Println("Warning: Config option", from, "is deprecated")
} }
} else { } else {
if !*normaliseconf { if !*normaliseconf {
log.Println("Warning: Deprecated config option", from, "- please rename to", to) log.Println("Warning: Config option", from, "has been renamed - please change to", to)
} }
// If the configuration file doesn't already contain a line with the // If the configuration file doesn't already contain a line with the
// new name then set it to the old value. This makes sure that we // new name then set it to the old value. This makes sure that we
@ -224,6 +139,54 @@ func main() {
if err = mapstructure.Decode(dat, &cfg); err != nil { if err = mapstructure.Decode(dat, &cfg); err != nil {
panic(err) 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 := config.GenerateConfig(false)
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)
}
// The main function is responsible for configuring and starting Yggdrasil.
func main() {
// Configure the command line parameters.
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")
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)")
version := flag.Bool("version", false, "prints the version of this build")
logging := flag.String("logging", "info,warn,error", "comma-separated list of logging levels to enable")
flag.Parse()
var cfg *nodeConfig
var err error
switch {
case *version:
fmt.Println("Build name:", yggdrasil.GetBuildName())
fmt.Println("Build version:", yggdrasil.GetBuildVersion())
os.Exit(0)
case *autoconf:
// Use an autoconf-generated config, this will give us random keys and
// port numbers, and will use an automatically selected TUN/TAP interface.
cfg = config.GenerateConfig(true)
case *useconffile != "" || *useconf:
// Read the configuration from either stdin or from the filesystem
cfg = readConfig(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
@ -256,51 +219,30 @@ func main() {
} }
// Create a new logger that logs output to stdout. // Create a new logger that logs output to stdout.
logger := log.New(os.Stdout, "", log.Flags()) logger := log.New(os.Stdout, "", log.Flags())
//logger.EnableLevel("error")
//logger.EnableLevel("warn")
//logger.EnableLevel("info")
if levels := strings.Split(*logging, ","); len(levels) > 0 {
for _, level := range levels {
l := strings.TrimSpace(level)
switch l {
case "error", "warn", "info", "trace", "debug":
logger.EnableLevel(l)
default:
continue
}
}
}
// 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{}
// Check to see if any multicast interface expressions were provided in the
// config. If they were then set them now.
for _, ll := range cfg.MulticastInterfaces {
ifceExpr, err := regexp.Compile(ll)
if err != nil {
panic(err)
}
n.core.AddMulticastInterfaceExpr(ifceExpr)
}
// Now that we have a working configuration, we can now actually start // Now that we have a working configuration, we can now actually start
// Yggdrasil. This will start the router, switch, DHT node, TCP and UDP // Yggdrasil. This will start the router, switch, DHT node, TCP and UDP
// sockets, TUN/TAP adapter and multicast discovery port. // sockets, TUN/TAP adapter and multicast discovery port.
if err := n.core.Start(cfg, logger); err != nil { if err := n.core.Start(cfg, logger); err != nil {
logger.Println("An error occurred during startup") logger.Errorln("An error occurred during startup")
panic(err) panic(err)
} }
// Check to see if any allowed encryption keys were provided in the config.
// If they were then set them now.
for _, pBoxStr := range cfg.AllowedEncryptionPublicKeys {
n.core.AddAllowedEncryptionPublicKey(pBoxStr)
}
// 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.
go func() {
if len(cfg.Peers) == 0 && len(cfg.InterfacePeers) == 0 {
return
}
for {
for _, peer := range cfg.Peers {
n.core.AddPeer(peer, "")
time.Sleep(time.Second)
}
for intf, intfpeers := range cfg.InterfacePeers {
for _, peer := range intfpeers {
n.core.AddPeer(peer, intf)
time.Sleep(time.Second)
}
}
time.Sleep(time.Minute)
}
}()
// The Stop function ensures that the TUN/TAP adapter is correctly shut down // The Stop function ensures that the TUN/TAP adapter is correctly shut down
// before the program exits. // before the program exits.
defer func() { defer func() {
@ -310,11 +252,13 @@ func main() {
// This is just logged to stdout for the user. // This is just logged to stdout for the user.
address := n.core.GetAddress() address := n.core.GetAddress()
subnet := n.core.GetSubnet() subnet := n.core.GetSubnet()
logger.Printf("Your IPv6 address is %s", address.String()) logger.Infof("Your IPv6 address is %s", address.String())
logger.Printf("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.
c := make(chan os.Signal, 1) c := make(chan os.Signal, 1)
r := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM) signal.Notify(c, os.Interrupt, syscall.SIGTERM)
signal.Notify(r, os.Interrupt, syscall.SIGHUP)
// Create a function to capture the service being stopped on Windows. // Create a function to capture the service being stopped on Windows.
winTerminate := func() { winTerminate := func() {
c <- os.Interrupt c <- os.Interrupt
@ -322,5 +266,18 @@ func main() {
minwinsvc.SetOnExit(winTerminate) minwinsvc.SetOnExit(winTerminate)
// Wait for the terminate/interrupt signal. Once a signal is received, the // Wait for the terminate/interrupt signal. Once a signal is received, the
// deferred Stop function above will run which will shut down TUN/TAP. // deferred Stop function above will run which will shut down TUN/TAP.
<-c for {
select {
case _ = <-r:
if *useconffile != "" {
cfg = readConfig(useconf, useconffile, normaliseconf)
n.core.UpdateConfig(cfg)
} else {
logger.Errorln("Reloading config at runtime is only possible with -useconffile")
}
case _ = <-c:
goto exit
}
}
exit:
} }

125
contrib/ansible/genkeys.go Normal file
View File

@ -0,0 +1,125 @@
/*
This file generates crypto keys for [ansible-yggdrasil](https://github.com/jcgruenhage/ansible-yggdrasil/)
*/
package main
import (
"encoding/hex"
"flag"
"fmt"
"net"
"os"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
)
var numHosts = flag.Int("hosts", 1, "number of host vars to generate")
var keyTries = flag.Int("tries", 1000, "number of tries before taking the best keys")
type keySet struct {
priv []byte
pub []byte
id []byte
ip string
}
func main() {
flag.Parse()
if *numHosts > *keyTries {
println("Can't generate less keys than hosts.")
return
}
var encryptionKeys []keySet
for i := 0; i < *numHosts+1; i++ {
encryptionKeys = append(encryptionKeys, newBoxKey())
}
encryptionKeys = sortKeySetArray(encryptionKeys)
for i := 0; i < *keyTries-*numHosts-1; i++ {
encryptionKeys[0] = newBoxKey()
encryptionKeys = bubbleUpTo(encryptionKeys, 0)
}
var signatureKeys []keySet
for i := 0; i < *numHosts+1; i++ {
signatureKeys = append(signatureKeys, newSigKey())
}
signatureKeys = sortKeySetArray(signatureKeys)
for i := 0; i < *keyTries-*numHosts-1; i++ {
signatureKeys[0] = newSigKey()
signatureKeys = bubbleUpTo(signatureKeys, 0)
}
os.MkdirAll("host_vars", 0755)
for i := 1; i <= *numHosts; i++ {
os.MkdirAll(fmt.Sprintf("host_vars/%x", i), 0755)
file, err := os.Create(fmt.Sprintf("host_vars/%x/vars", i))
if err != nil {
return
}
defer file.Close()
file.WriteString(fmt.Sprintf("yggdrasil_encryption_public_key: %v\n", hex.EncodeToString(encryptionKeys[i].pub)))
file.WriteString("yggdrasil_encryption_private_key: \"{{ vault_yggdrasil_encryption_private_key }}\"\n")
file.WriteString(fmt.Sprintf("yggdrasil_signing_public_key: %v\n", hex.EncodeToString(signatureKeys[i].pub)))
file.WriteString("yggdrasil_signing_private_key: \"{{ vault_yggdrasil_signing_private_key }}\"\n")
file.WriteString(fmt.Sprintf("ansible_host: %v\n", encryptionKeys[i].ip))
file, err = os.Create(fmt.Sprintf("host_vars/%x/vault", i))
if err != nil {
return
}
defer file.Close()
file.WriteString(fmt.Sprintf("vault_yggdrasil_encryption_private_key: %v\n", hex.EncodeToString(encryptionKeys[i].priv)))
file.WriteString(fmt.Sprintf("vault_yggdrasil_signing_private_key: %v\n", hex.EncodeToString(signatureKeys[i].priv)))
}
}
func newBoxKey() keySet {
pub, priv := crypto.NewBoxKeys()
id := crypto.GetNodeID(pub)
ip := net.IP(address.AddrForNodeID(id)[:]).String()
return keySet{priv[:], pub[:], id[:], ip}
}
func newSigKey() keySet {
pub, priv := crypto.NewSigKeys()
id := crypto.GetTreeID(pub)
return keySet{priv[:], pub[:], id[:], ""}
}
func isBetter(oldID, newID []byte) bool {
for idx := range oldID {
if newID[idx] > oldID[idx] {
return true
}
if newID[idx] < oldID[idx] {
return false
}
}
return false
}
func sortKeySetArray(sets []keySet) []keySet {
for i := 0; i < len(sets); i++ {
sets = bubbleUpTo(sets, i)
}
return sets
}
func bubbleUpTo(sets []keySet, num int) []keySet {
for i := 0; i < len(sets)-num-1; i++ {
if isBetter(sets[i+1].id, sets[i].id) {
var tmp = sets[i]
sets[i] = sets[i+1]
sets[i+1] = tmp
} else {
break
}
}
return sets
}

View File

@ -110,12 +110,10 @@ EOF
cp yggdrasil /tmp/$PKGNAME/usr/bin/ cp yggdrasil /tmp/$PKGNAME/usr/bin/
cp yggdrasilctl /tmp/$PKGNAME/usr/bin/ cp yggdrasilctl /tmp/$PKGNAME/usr/bin/
cp contrib/systemd/yggdrasil.service /tmp/$PKGNAME/etc/systemd/system/ cp contrib/systemd/yggdrasil.service /tmp/$PKGNAME/etc/systemd/system/
cp contrib/systemd/yggdrasil-resume.service /tmp/$PKGNAME/etc/systemd/system/
tar -czvf /tmp/$PKGNAME/data.tar.gz -C /tmp/$PKGNAME/ \ tar -czvf /tmp/$PKGNAME/data.tar.gz -C /tmp/$PKGNAME/ \
usr/bin/yggdrasil usr/bin/yggdrasilctl \ usr/bin/yggdrasil usr/bin/yggdrasilctl \
etc/systemd/system/yggdrasil.service \ etc/systemd/system/yggdrasil.service
etc/systemd/system/yggdrasil-resume.service
tar -czvf /tmp/$PKGNAME/control.tar.gz -C /tmp/$PKGNAME/debian . tar -czvf /tmp/$PKGNAME/control.tar.gz -C /tmp/$PKGNAME/debian .
echo 2.0 > /tmp/$PKGNAME/debian-binary echo 2.0 > /tmp/$PKGNAME/debian-binary

View File

@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
sodipodi:docname="drawing.svg"
inkscape:version="0.91 r13725"
version="1.1"
id="svg4240"
viewBox="0 0 981.96461 321.60015"
height="90.762711mm"
width="277.13223mm">
<defs
id="defs4242" />
<sodipodi:namedview
fit-margin-bottom="0"
fit-margin-right="0"
fit-margin-left="0"
fit-margin-top="0"
inkscape:window-maximized="1"
inkscape:window-y="1"
inkscape:window-x="0"
inkscape:window-height="1021"
inkscape:window-width="2048"
showgrid="false"
inkscape:current-layer="layer2"
inkscape:document-units="px"
inkscape:cy="13.914395"
inkscape:cx="751.6295"
inkscape:zoom="0.66468037"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#ffffff"
id="base" />
<metadata
id="metadata4245">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(383.92494,-160.49328)" />
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Layer 2"
transform="translate(383.92494,-160.49328)">
<path
style="fill:#000000"
d="m 352.74397,478.24119 c 0.92103,-3.76903 11.87131,-30.48993 21.5083,-52.48465 9.86344,-22.51152 9.67726,-21.6278 6.92943,-32.89221 -3.42997,-14.06075 -3.22164,-36.95243 0.44688,-49.10642 13.24423,-43.87864 47.63362,-73.61698 122.30718,-105.76556 24.32504,-10.47245 37.67777,-17.18807 47.80968,-24.04538 17.86083,-12.08828 36.4402,-33.06424 42.38057,-47.84736 1.25285,-3.11781 2.66096,-5.64051 3.12912,-5.60598 1.46014,0.10767 0.73701,44.30167 -0.9768,59.69719 -10.61597,95.36545 -42.95689,157.39345 -96.20598,184.51751 -30.73114,15.65385 -79.17559,21.45357 -101.74118,12.18037 -3.19081,-1.31125 -6.5492,-2.38408 -7.46311,-2.38408 -3.43636,0 -15.75824,32.89925 -19.29523,51.51802 -1.09802,5.78003 -2.76237,13.70787 -3.00898,14.91667 -5.50064,-0.0422 -0.35371,-0.0119 -8.18026,-0.0119 l -8.29605,0 z"
id="path4918"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ssssssscssssscscs" />
<g
style="font-style:normal;font-weight:normal;font-size:150px;line-height:86.00000143%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="text4920"
transform="translate(-2,0)">
<path
d="m -345.34994,319.70594 -36.575,-55.6875 21.725,0 23.925,38.775 24.2,-38.775 20.625,0 -36.575,55.6875 0,41.6625 -17.325,0 0,-41.6625 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5638"
inkscape:connector-curvature="0" />
<path
d="m -202.55678,354.21844 q -18.0125,9.625 -40.2875,9.625 -11.275,0 -20.7625,-3.575 -9.35,-3.7125 -16.225,-10.3125 -6.7375,-6.7375 -10.5875,-16.0875 -3.85,-9.35 -3.85,-20.7625 0,-11.6875 3.85,-21.175 3.85,-9.625 10.5875,-16.3625 6.875,-6.7375 16.225,-10.3125 9.4875,-3.7125 20.7625,-3.7125 11.1375,0 20.9,2.75 9.7625,2.6125 17.4625,9.4875 l -12.7875,12.925 q -4.675,-4.5375 -11.4125,-7.0125 -6.6,-2.475 -14.025,-2.475 -7.5625,0 -13.75,2.75 -6.05,2.6125 -10.45,7.425 -4.4,4.675 -6.875,11 -2.3375,6.325 -2.3375,13.6125 0,7.8375 2.3375,14.4375 2.475,6.6 6.875,11.4125 4.4,4.8125 10.45,7.5625 6.1875,2.75 13.75,2.75 6.6,0 12.375,-1.2375 5.9125,-1.2375 10.45,-3.85 l 0,-22.9625 -19.9375,0 0,-15.675 37.2625,0 0,49.775 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5640"
inkscape:connector-curvature="0" />
<path
d="m -102.05346,354.21844 q -18.0125,9.625 -40.2875,9.625 -11.275,0 -20.7625,-3.575 -9.35,-3.7125 -16.225,-10.3125 -6.7375,-6.7375 -10.5875,-16.0875 -3.85,-9.35 -3.85,-20.7625 0,-11.6875 3.85,-21.175 3.85,-9.625 10.5875,-16.3625 6.875,-6.7375 16.225,-10.3125 9.4875,-3.7125 20.7625,-3.7125 11.1375,0 20.9,2.75 9.7625,2.6125 17.4625,9.4875 l -12.7875,12.925 q -4.675,-4.5375 -11.4125,-7.0125 -6.6,-2.475 -14.025,-2.475 -7.5625,0 -13.75,2.75 -6.05,2.6125 -10.45,7.425 -4.4,4.675 -6.875,11 -2.3375,6.325 -2.3375,13.6125 0,7.8375 2.3375,14.4375 2.475,6.6 6.875,11.4125 4.4,4.8125 10.45,7.5625 6.1875,2.75 13.75,2.75 6.6,0 12.375,-1.2375 5.9125,-1.2375 10.45,-3.85 l 0,-22.9625 -19.9375,0 0,-15.675 37.2625,0 0,49.775 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5642"
inkscape:connector-curvature="0" />
<path
d="m -90.037646,264.01844 38.3625,0 q 9.625,0 18.5625,3.025 8.9375,2.8875 15.8125,8.9375 6.875,6.05 11,15.2625 4.125,9.075 4.125,21.45 0,12.5125 -4.8125,21.725 -4.675,9.075 -12.2375,15.125 -7.425,5.9125 -16.6375,8.9375 -9.075,2.8875 -17.875,2.8875 l -36.3,0 0,-97.35 z m 30.25,81.675 q 8.1125,0 15.2625,-1.7875 7.2875,-1.925 12.65,-5.775 5.3625,-3.9875 8.3875,-10.175 3.1625,-6.325 3.1625,-15.2625 0,-8.8 -2.75,-15.125 -2.75,-6.325 -7.7,-10.175 -4.8125,-3.9875 -11.55,-5.775 -6.6,-1.925 -14.575,-1.925 l -15.8125,0 0,66 12.925,0 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5644"
inkscape:connector-curvature="0" />
<path
d="m 7.511578,264.01844 33.825,0 q 7.0125,0 13.475,1.375 6.6,1.2375 11.687502,4.4 5.0875,3.1625 8.1125,8.525 3.025,5.3625 3.025,13.6125 0,10.5875 -5.9125,17.7375 -5.775002,7.15 -16.637502,8.6625 l 25.850002,43.0375 -20.900002,0 -22.55,-41.25 -12.65,0 0,41.25 -17.325,0 0,-97.35 z m 30.8,41.25 q 3.7125,0 7.425,-0.275 3.7125,-0.4125 6.7375,-1.65 3.1625,-1.375 5.0875,-3.9875 1.925,-2.75 1.925,-7.5625 0,-4.2625 -1.7875,-6.875 -1.7875,-2.6125 -4.675,-3.85 -2.8875,-1.375 -6.4625,-1.7875 -3.4375,-0.4125 -6.7375,-0.4125 l -14.9875,0 0,26.4 13.475,0 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5646"
inkscape:connector-curvature="0" />
<path
d="m 126.82369,264.01844 14.9875,0 41.9375,97.35 -19.8,0 -9.075,-22.275 -42.2125,0 -8.8,22.275 -19.3875,0 42.35,-97.35 z m 22,60.225 -14.9875,-39.6 -15.2625,39.6 30.25,0 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5648"
inkscape:connector-curvature="0" />
<path
d="m 239.7639,284.91844 q -2.75,-3.9875 -7.425,-5.775 -4.5375,-1.925 -9.625,-1.925 -3.025,0 -5.9125,0.6875 -2.75,0.6875 -5.0875,2.2 -2.2,1.5125 -3.575,3.9875 -1.375,2.3375 -1.375,5.6375 0,4.95 3.4375,7.5625 3.4375,2.6125 8.525,4.5375 5.0875,1.925 11.1375,3.7125 6.05,1.7875 11.1375,4.95 5.0875,3.1625 8.525,8.3875 3.4375,5.225 3.4375,13.8875 0,7.8375 -2.8875,13.75 -2.8875,5.775 -7.8375,9.625 -4.8125,3.85 -11.275,5.775 -6.4625,1.925 -13.6125,1.925 -9.075,0 -17.4625,-3.025 -8.3875,-3.025 -14.4375,-10.175 l 13.0625,-12.65 q 3.1625,4.8125 8.25,7.5625 5.225,2.6125 11,2.6125 3.025,0 6.05,-0.825 3.025,-0.825 5.5,-2.475 2.475,-1.65 3.9875,-4.125 1.5125,-2.6125 1.5125,-5.9125 0,-5.3625 -3.4375,-8.25 -3.4375,-2.8875 -8.525,-4.8125 -5.0875,-2.0625 -11.1375,-3.85 -6.05,-1.7875 -11.1375,-4.8125 -5.0875,-3.1625 -8.525,-8.25 -3.4375,-5.225 -3.4375,-13.8875 0,-7.5625 3.025,-13.0625 3.1625,-5.5 8.1125,-9.075 5.0875,-3.7125 11.55,-5.5 6.4625,-1.7875 13.2,-1.7875 7.7,0 14.85,2.3375 7.2875,2.3375 13.0625,7.7 l -12.65,13.3375 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5650"
inkscape:connector-curvature="0" />
<path
d="m 264.21257,264.01844 17.325,0 0,97.35 -17.325,0 0,-97.35 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5652"
inkscape:connector-curvature="0" />
<path
d="m 296.37837,264.01844 17.325,0 0,81.675 41.3875,0 0,15.675 -58.7125,0 0,-97.35 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5654"
inkscape:connector-curvature="0" />
<path
d="m -369.13744,382.26844 22.9625,0 47.1625,72.325 0.275,0 0,-72.325 17.325,0 0,97.35 -22,0 -48.125,-74.6625 -0.275,0 0,74.6625 -17.325,0 0,-97.35 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5656"
inkscape:connector-curvature="0" />
<path
d="m -270.48568,382.26844 64.4875,0 0,15.675 -47.1625,0 0,23.925 44.6875,0 0,15.675 -44.6875,0 0,26.4 49.6375,0 0,15.675 -66.9625,0 0,-97.35 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5658"
inkscape:connector-curvature="0" />
<path
d="m -165.14057,397.94344 -29.8375,0 0,-15.675 77,0 0,15.675 -29.8375,0 0,81.675 -17.325,0 0,-81.675 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5660"
inkscape:connector-curvature="0" />
<path
d="m -111.36694,382.26844 18.974997,0 18.2875,70.125 0.275,0 21.8625,-70.125 17.05,0 21.45,70.125 0.275,0 19.1125,-70.125 17.6,0 -28.3250004,97.35 -16.4999996,0 -22.55,-74.1125 -0.275,0 -22.55,74.1125 -15.95,0 -28.737497,-97.35 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5662"
inkscape:connector-curvature="0" />
<path
d="m 24.435016,431.35594 q 0,-11.6875 3.85,-21.175 3.85,-9.625 10.5875,-16.3625 6.875,-6.7375 16.225,-10.3125 9.4875,-3.7125 20.7625,-3.7125 11.412504,-0.1375 20.900004,3.4375 9.4875,3.4375 16.3625,10.175 6.875,6.7375 10.725,16.225 3.85,9.4875 3.85,21.175 0,11.4125 -3.85,20.7625 -3.85,9.35 -10.725,16.0875 -6.875,6.7375 -16.3625,10.5875 -9.4875,3.7125 -20.900004,3.85 -11.275,0 -20.7625,-3.575 -9.35,-3.7125 -16.225,-10.3125 -6.7375,-6.7375 -10.5875,-16.0875 -3.85,-9.35 -3.85,-20.7625 z m 18.15,-1.1 q 0,7.8375 2.3375,14.4375 2.475,6.6 6.875,11.4125 4.4,4.8125 10.45,7.5625 6.1875,2.75 13.75,2.75 7.5625,0 13.750004,-2.75 6.1875,-2.75 10.5875,-7.5625 4.4,-4.8125 6.7375,-11.4125 2.475,-6.6 2.475,-14.4375 0,-7.2875 -2.475,-13.6125 -2.3375,-6.325 -6.7375,-11 -4.4,-4.8125 -10.5875,-7.425 -6.187504,-2.75 -13.750004,-2.75 -7.5625,0 -13.75,2.75 -6.05,2.6125 -10.45,7.425 -4.4,4.675 -6.875,11 -2.3375,6.325 -2.3375,13.6125 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5664"
inkscape:connector-curvature="0" />
<path
d="m 137.68287,382.26844 33.825,0 q 7.0125,0 13.475,1.375 6.6,1.2375 11.6875,4.4 5.0875,3.1625 8.1125,8.525 3.025,5.3625 3.025,13.6125 0,10.5875 -5.9125,17.7375 -5.775,7.15 -16.6375,8.6625 l 25.85,43.0375 -20.9,0 -22.55,-41.25 -12.65,0 0,41.25 -17.325,0 0,-97.35 z m 30.8,41.25 q 3.7125,0 7.425,-0.275 3.7125,-0.4125 6.7375,-1.65 3.1625,-1.375 5.0875,-3.9875 1.925,-2.75 1.925,-7.5625 0,-4.2625 -1.7875,-6.875 -1.7875,-2.6125 -4.675,-3.85 -2.8875,-1.375 -6.4625,-1.7875 -3.4375,-0.4125 -6.7375,-0.4125 l -14.9875,0 0,26.4 13.475,0 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5666"
inkscape:connector-curvature="0" />
<path
d="m 221.50746,382.26844 17.325,0 0,41.25 0.825,0 40.2875,-41.25 23.375,0 -45.5125,44.9625 48.5375,52.3875 -24.3375,0 -42.2125,-47.85 -0.9625,0 0,47.85 -17.325,0 0,-97.35 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5668"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -4,8 +4,8 @@
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null) BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null)
# Complain if the git history is not available # Complain if the git history is not available
if [ $? != 0 ]; then if [ $? != 0 ] || [ -z "$BRANCH" ]; then
printf "unknown" printf "yggdrasil"
exit 1 exit 1
fi fi

View File

@ -1,63 +1,46 @@
#!/bin/sh #!/bin/sh
# Merge commits from this branch are counted
DEVELOPBRANCH="yggdrasil-network/develop"
# Get the last tag # Get the last tag
TAG=$(git describe --abbrev=0 --tags --match="v[0-9]*\.[0-9]*\.0" 2>/dev/null) TAG=$(git describe --abbrev=0 --tags --match="v[0-9]*\.[0-9]*\.[0-9]*" 2>/dev/null)
# Get last merge to master # Did getting the tag succeed?
MERGE=$(git rev-list $TAG..master --grep "from $DEVELOPBRANCH" 2>/dev/null | head -n 1) if [ $? != 0 ] || [ -z "$TAG" ]; then
printf -- "unknown"
# Get the number of merges since the last merge to master exit 1
PATCH=$(git rev-list $TAG..master --count --merges --grep="from $DEVELOPBRANCH" --first-parent master 2>/dev/null)
# Decide whether we should prepend the version with "v" - the default is that
# we do because we use it in git tags, but we might not always need it
PREPEND="v"
if [ "$1" = "--bare" ]; then
PREPEND=""
fi fi
# If it fails then there's no last tag - go from the first commit # Get the current branch
if [ $? != 0 ]; then BRANCH=$(git symbolic-ref -q HEAD --short 2>/dev/null)
PATCH=$(git rev-list HEAD --count 2>/dev/null)
# Complain if the git history is not available # Did getting the branch succeed?
if [ $? != 0 ]; then if [ $? != 0 ] || [ -z "$BRANCH" ]; then
printf 'unknown' BRANCH="master"
exit 1
fi
printf '%s0.0.%d' "$PREPEND" "$PATCH"
exit 1
fi fi
# Split out into major, minor and patch numbers # Split out into major, minor and patch numbers
MAJOR=$(echo $TAG | cut -c 2- | cut -d "." -f 1) MAJOR=$(echo $TAG | cut -c 2- | cut -d "." -f 1)
MINOR=$(echo $TAG | cut -c 2- | cut -d "." -f 2) MINOR=$(echo $TAG | cut -c 2- | cut -d "." -f 2)
PATCH=$(echo $TAG | cut -c 2- | cut -d "." -f 3)
# Get the current checked out branch
BRANCH=$(git rev-parse --abbrev-ref HEAD)
# Output in the desired format # Output in the desired format
if [ $PATCH = 0 ]; then if [ $((PATCH)) -eq 0 ]; then
if [ ! -z $FULL ]; then printf '%s%d.%d' "$PREPEND" "$((MAJOR))" "$((MINOR))"
printf '%s%d.%d.0' "$PREPEND" "$MAJOR" "$MINOR"
else
printf '%s%d.%d' "$PREPEND" "$MAJOR" "$MINOR"
fi
else else
printf '%s%d.%d.%d' "$PREPEND" "$MAJOR" "$MINOR" "$PATCH" printf '%s%d.%d.%d' "$PREPEND" "$((MAJOR))" "$((MINOR))" "$((PATCH))"
fi fi
# Get the number of merges on the current branch since the last tag
TAG=$(git describe --abbrev=0 --tags --match="v[0-9]*\.[0-9]*\.[0-9]*" --first-parent master 2>/dev/null)
BUILD=$(git rev-list $TAG.. --count)
# Add the build tag on non-master branches # Add the build tag on non-master branches
if [ $BRANCH != "master" ]; then if [ "$BRANCH" != "master" ]; then
if [ $BUILD != 0 ]; then BUILD=$(git rev-list --count $TAG..HEAD 2>/dev/null)
printf -- "-%04d" "$BUILD"
# Did getting the count of commits since the tag succeed?
if [ $? != 0 ] || [ -z "$BUILD" ]; then
printf -- "-unknown"
exit 1
fi
# Is the build greater than zero?
if [ $((BUILD)) -gt 0 ]; then
printf -- "-%04d" "$((BUILD))"
fi fi
fi fi

View File

@ -1,10 +0,0 @@
[Unit]
Description=Restart yggdrasil on resume from sleep
After=sleep.target
[Service]
Type=oneshot
ExecStart=/bin/systemctl restart yggdrasil
[Install]
WantedBy=sleep.target

View File

@ -14,8 +14,8 @@ ExecStartPre=/bin/sh -ec "if ! test -s /etc/yggdrasil.conf; \
echo 'WARNING: A new /etc/yggdrasil.conf file has been generated.'; \ echo 'WARNING: A new /etc/yggdrasil.conf file has been generated.'; \
fi" fi"
ExecStart=/usr/bin/yggdrasil -useconffile /etc/yggdrasil.conf ExecStart=/usr/bin/yggdrasil -useconffile /etc/yggdrasil.conf
ExecReload=/bin/kill -HUP $MAINPID
Restart=always Restart=always
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
Also=yggdrasil-resume.service

1
go.mod
View File

@ -2,6 +2,7 @@ module github.com/yggdrasil-network/yggdrasil-go
require ( require (
github.com/docker/libcontainer v2.2.1+incompatible github.com/docker/libcontainer v2.2.1+incompatible
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8
github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222
github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0
github.com/mitchellh/mapstructure v1.1.2 github.com/mitchellh/mapstructure v1.1.2

2
go.sum
View File

@ -1,5 +1,7 @@
github.com/docker/libcontainer v2.2.1+incompatible h1:++SbbkCw+X8vAd4j2gOCzZ2Nn7s2xFALTf7LZKmM1/0= github.com/docker/libcontainer v2.2.1+incompatible h1:++SbbkCw+X8vAd4j2gOCzZ2Nn7s2xFALTf7LZKmM1/0=
github.com/docker/libcontainer v2.2.1+incompatible/go.mod h1:osvj61pYsqhNCMLGX31xr7klUBhHb/ZBuXS0o1Fvwbw= github.com/docker/libcontainer v2.2.1+incompatible/go.mod h1:osvj61pYsqhNCMLGX31xr7klUBhHb/ZBuXS0o1Fvwbw=
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 h1:WD8iJ37bRNwvETMfVTusVSAi0WdXTpfNVGY2aHycNKY=
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U=
github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 h1:xmvkbxXDeN1ffWq8kvrhyqVYAO2aXuRBsbpxVTR+JyU= github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 h1:xmvkbxXDeN1ffWq8kvrhyqVYAO2aXuRBsbpxVTR+JyU=
github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio=
github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk8851WTVWlqMC1ecJH07Ctz+Ezxx4u54g= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk8851WTVWlqMC1ecJH07Ctz+Ezxx4u54g=

View File

@ -51,12 +51,12 @@ ip netns exec node4 ip link set lo up
ip netns exec node5 ip link set lo up ip netns exec node5 ip link set lo up
ip netns exec node6 ip link set lo up ip netns exec node6 ip link set lo up
ip netns exec node1 env PPROFLISTEN=localhost:6060 ./run --autoconf &> /dev/null & echo '{AdminListen: "none"}' | ip netns exec node1 env PPROFLISTEN=localhost:6060 ./yggdrasil --useconf &> /dev/null &
ip netns exec node2 env PPROFLISTEN=localhost:6060 ./run --autoconf &> /dev/null & echo '{AdminListen: "none"}' | ip netns exec node2 env PPROFLISTEN=localhost:6060 ./yggdrasil --useconf &> /dev/null &
ip netns exec node3 env PPROFLISTEN=localhost:6060 ./run --autoconf &> /dev/null & echo '{AdminListen: "none"}' | ip netns exec node3 env PPROFLISTEN=localhost:6060 ./yggdrasil --useconf &> /dev/null &
ip netns exec node4 env PPROFLISTEN=localhost:6060 ./run --autoconf &> /dev/null & echo '{AdminListen: "none"}' | ip netns exec node4 env PPROFLISTEN=localhost:6060 ./yggdrasil --useconf &> /dev/null &
ip netns exec node5 env PPROFLISTEN=localhost:6060 ./run --autoconf &> /dev/null & echo '{AdminListen: "none"}' | ip netns exec node5 env PPROFLISTEN=localhost:6060 ./yggdrasil --useconf &> /dev/null &
ip netns exec node6 env PPROFLISTEN=localhost:6060 ./run --autoconf &> /dev/null & echo '{AdminListen: "none"}' | ip netns exec node6 env PPROFLISTEN=localhost:6060 ./yggdrasil --useconf &> /dev/null &
echo "Started, to continue you should (possibly w/ sudo):" echo "Started, to continue you should (possibly w/ sudo):"
echo "kill" $(jobs -p) echo "kill" $(jobs -p)

33
misc/run-twolink-test Executable file
View File

@ -0,0 +1,33 @@
#!/bin/bash
# Connects nodes in two namespaces by two links with different bandwidth (10mbit and 100mbit)
ip netns add node1
ip netns add node2
ip link add veth11 type veth peer name veth21
ip link set veth11 netns node1 up
ip link set veth21 netns node2 up
ip link add veth12 type veth peer name veth22
ip link set veth12 netns node1 up
ip link set veth22 netns node2 up
ip netns exec node1 tc qdisc add dev veth11 root tbf rate 10mbit burst 8192 latency 1ms
ip netns exec node2 tc qdisc add dev veth21 root tbf rate 10mbit burst 8192 latency 1ms
ip netns exec node1 tc qdisc add dev veth12 root tbf rate 100mbit burst 8192 latency 1ms
ip netns exec node2 tc qdisc add dev veth22 root tbf rate 100mbit burst 8192 latency 1ms
echo '{AdminListen: "unix://node1.sock"}' | ip netns exec node1 env PPROFLISTEN=localhost:6060 ./yggdrasil -logging "info,warn,error,debug" -useconf &> node1.log &
echo '{AdminListen: "unix://node2.sock"}' | ip netns exec node2 env PPROFLISTEN=localhost:6060 ./yggdrasil -logging "info,warn,error,debug" -useconf &> node2.log &
echo "Started, to continue you should (possibly w/ sudo):"
echo "kill" $(jobs -p)
wait
ip netns delete node1
ip netns delete node2
ip link delete veth11
ip link delete veth12

View File

@ -1,12 +1,21 @@
package config package config
import (
"encoding/hex"
"fmt"
"math/rand"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
)
// NodeConfig defines all configuration values needed to run a signle yggdrasil node // NodeConfig defines all configuration values needed to run a signle yggdrasil node
type NodeConfig struct { type NodeConfig struct {
Listen string `comment:"Listen address for peer connections. Default is to listen for all\nTCP connections over IPv4 and IPv6 with a random port."` Listen string `comment:"Listen address for peer connections. Default is to listen for all\nTCP connections over IPv4 and IPv6 with a random port."`
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."` 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."`
Peers []string `comment:"List of connection strings for static peers in URI format, e.g.\ntcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j."` Peers []string `comment:"List of connection strings for static peers in URI format, e.g.\ntcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j."`
InterfacePeers map[string][]string `comment:"List of connection strings for static peers in URI format, arranged\nby source interface, e.g. { \"eth0\": [ tcp://a.b.c.d:e ] }. Note that\nSOCKS peerings will NOT be affected by this option and should go in\nthe \"Peers\" section instead."` InterfacePeers map[string][]string `comment:"List of connection strings for static peers in URI format, arranged\nby source interface, e.g. { \"eth0\": [ tcp://a.b.c.d:e ] }. Note that\nSOCKS peerings will NOT be affected by this option and should go in\nthe \"Peers\" section instead."`
ReadTimeout int32 `comment:"Read timeout for connections, specified in milliseconds. If less\nthan 6000 and not negative, 6000 (the default) is used. If negative,\nreads won't time out."`
AllowedEncryptionPublicKeys []string `comment:"List of peer encryption public keys to allow or incoming TCP\nconnections from. If left empty/undefined then all connections\nwill be allowed by default."` AllowedEncryptionPublicKeys []string `comment:"List of peer encryption public keys to allow or incoming TCP\nconnections from. If left empty/undefined then all connections\nwill be allowed by default."`
EncryptionPublicKey string `comment:"Your public encryption key. Your peers may ask you for this to put\ninto their AllowedEncryptionPublicKeys configuration."` EncryptionPublicKey string `comment:"Your public encryption key. Your peers may ask you for this to put\ninto their AllowedEncryptionPublicKeys configuration."`
EncryptionPrivateKey string `comment:"Your private encryption key. DO NOT share this with anyone!"` EncryptionPrivateKey string `comment:"Your private encryption key. DO NOT share this with anyone!"`
@ -53,3 +62,45 @@ type TunnelRouting struct {
type SwitchOptions struct { type SwitchOptions struct {
MaxTotalQueueSize uint64 `comment:"Maximum size of all switch queues combined (in bytes)."` MaxTotalQueueSize uint64 `comment:"Maximum size of all switch queues combined (in bytes)."`
} }
// Generates default configuration. This is used when outputting the -genconf
// parameter and also when using -autoconf. The isAutoconf flag is used to
// determine whether the operating system should select a free port by itself
// (which guarantees that there will not be a conflict with any other services)
// or whether to generate a random port number. The only side effect of setting
// isAutoconf is that the TCP and UDP ports will likely end up with different
// port numbers.
func GenerateConfig(isAutoconf bool) *NodeConfig {
// Create a new core.
//core := Core{}
// Generate encryption keys.
bpub, bpriv := crypto.NewBoxKeys()
spub, spriv := crypto.NewSigKeys()
// Create a node configuration and populate it.
cfg := NodeConfig{}
if isAutoconf {
cfg.Listen = "[::]:0"
} else {
r1 := rand.New(rand.NewSource(time.Now().UnixNano()))
cfg.Listen = fmt.Sprintf("[::]:%d", r1.Intn(65534-32768)+32768)
}
cfg.AdminListen = defaults.GetDefaults().DefaultAdminListen
cfg.EncryptionPublicKey = hex.EncodeToString(bpub[:])
cfg.EncryptionPrivateKey = hex.EncodeToString(bpriv[:])
cfg.SigningPublicKey = hex.EncodeToString(spub[:])
cfg.SigningPrivateKey = hex.EncodeToString(spriv[:])
cfg.Peers = []string{}
cfg.InterfacePeers = map[string][]string{}
cfg.AllowedEncryptionPublicKeys = []string{}
cfg.MulticastInterfaces = []string{".*"}
cfg.IfName = defaults.GetDefaults().DefaultIfName
cfg.IfMTU = defaults.GetDefaults().DefaultIfMTU
cfg.IfTAPMode = defaults.GetDefaults().DefaultIfTAPMode
cfg.SessionFirewall.Enable = false
cfg.SessionFirewall.AllowFromDirect = true
cfg.SessionFirewall.AllowFromRemote = true
cfg.SwitchOptions.MaxTotalQueueSize = 4 * 1024 * 1024
cfg.NodeInfoPrivacy = false
return &cfg
}

View File

@ -3,6 +3,7 @@ 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 "runtime" import "runtime"
import "time"
// A wrapper around runtime.Gosched() so it doesn't need to be imported elsewhere. // A wrapper around runtime.Gosched() so it doesn't need to be imported elsewhere.
func Yield() { func Yield() {
@ -44,3 +45,14 @@ func PutBytes(bs []byte) {
default: default:
} }
} }
// This is a workaround to go's broken timer implementation
func TimerStop(t *time.Timer) bool {
if !t.Stop() {
select {
case <-t.C:
default:
}
}
return true
}

View File

@ -1,20 +1,12 @@
package yggdrasil package yggdrasil
// Defines the minimum required functions for an adapter type.
type AdapterInterface interface {
init(core *Core, send chan<- []byte, recv <-chan []byte)
read() error
write() error
close() error
}
// Defines the minimum required struct members for an adapter type (this is // Defines the minimum required struct members for an adapter type (this is
// now the base type for tunAdapter in tun.go) // now the base type for tunAdapter in tun.go)
type Adapter struct { type Adapter struct {
AdapterInterface
core *Core core *Core
send chan<- []byte send chan<- []byte
recv <-chan []byte recv <-chan []byte
reconfigure chan chan error
} }
// Initialises the adapter. // Initialises the adapter.
@ -22,4 +14,5 @@ func (adapter *Adapter) init(core *Core, send chan<- []byte, recv <-chan []byte)
adapter.core = core adapter.core = core
adapter.send = send adapter.send = send
adapter.recv = recv adapter.recv = recv
adapter.reconfigure = make(chan chan error, 1)
} }

View File

@ -23,6 +23,7 @@ import (
type admin struct { type admin struct {
core *Core core *Core
reconfigure chan chan error
listenaddr string listenaddr string
listener net.Listener listener net.Listener
handlers []admin_handlerInfo handlers []admin_handlerInfo
@ -51,9 +52,25 @@ func (a *admin) addHandler(name string, args []string, handler func(admin_info)
} }
// init runs the initial admin setup. // init runs the initial admin setup.
func (a *admin) init(c *Core, listenaddr string) { func (a *admin) init(c *Core) {
a.core = c a.core = c
a.listenaddr = listenaddr a.reconfigure = make(chan chan error, 1)
go func() {
for {
e := <-a.reconfigure
a.core.configMutex.RLock()
if a.core.config.AdminListen != a.core.configOld.AdminListen {
a.listenaddr = a.core.config.AdminListen
a.close()
a.start()
}
a.core.configMutex.RUnlock()
e <- nil
}
}()
a.core.configMutex.RLock()
a.listenaddr = a.core.config.AdminListen
a.core.configMutex.RUnlock()
a.addHandler("list", []string{}, func(in admin_info) (admin_info, error) { a.addHandler("list", []string{}, func(in admin_info) (admin_info, error) {
handlers := make(map[string]interface{}) handlers := make(map[string]interface{})
for _, handler := range a.handlers { for _, handler := range a.handlers {
@ -324,12 +341,30 @@ func (a *admin) init(c *Core, listenaddr string) {
return admin_info{}, err return admin_info{}, err
} }
}) })
a.addHandler("getNodeInfo", []string{"box_pub_key", "coords", "[nocache]"}, func(in admin_info) (admin_info, error) { a.addHandler("getNodeInfo", []string{"[box_pub_key]", "[coords]", "[nocache]"}, func(in admin_info) (admin_info, error) {
var nocache bool var nocache bool
if in["nocache"] != nil { if in["nocache"] != nil {
nocache = in["nocache"].(string) == "true" nocache = in["nocache"].(string) == "true"
} }
result, err := a.admin_getNodeInfo(in["box_pub_key"].(string), in["coords"].(string), nocache) var box_pub_key, coords string
if in["box_pub_key"] == nil && in["coords"] == nil {
var nodeinfo []byte
a.core.router.doAdmin(func() {
nodeinfo = []byte(a.core.router.nodeinfo.getNodeInfo())
})
var jsoninfo interface{}
if err := json.Unmarshal(nodeinfo, &jsoninfo); err != nil {
return admin_info{}, err
} else {
return admin_info{"nodeinfo": jsoninfo}, nil
}
} else if in["box_pub_key"] == nil || in["coords"] == nil {
return admin_info{}, errors.New("Expecting both box_pub_key and coords")
} else {
box_pub_key = in["box_pub_key"].(string)
coords = in["coords"].(string)
}
result, err := a.admin_getNodeInfo(box_pub_key, coords, nocache)
if err == nil { if err == nil {
var m map[string]interface{} var m map[string]interface{}
if err = json.Unmarshal(result, &m); err == nil { if err = json.Unmarshal(result, &m); err == nil {
@ -353,7 +388,11 @@ func (a *admin) start() error {
// cleans up when stopping // cleans up when stopping
func (a *admin) close() error { func (a *admin) close() error {
if a.listener != nil {
return a.listener.Close() return a.listener.Close()
} else {
return nil
}
} }
// listen is run by start and manages API connections. // listen is run by start and manages API connections.
@ -363,7 +402,7 @@ func (a *admin) listen() {
switch strings.ToLower(u.Scheme) { switch strings.ToLower(u.Scheme) {
case "unix": case "unix":
if _, err := os.Stat(a.listenaddr[7:]); err == nil { if _, err := os.Stat(a.listenaddr[7:]); err == nil {
a.core.log.Println("WARNING:", a.listenaddr[7:], "already exists and may be in use by another process") a.core.log.Warnln("WARNING:", a.listenaddr[7:], "already exists and may be in use by another process")
} }
a.listener, err = net.Listen("unix", a.listenaddr[7:]) a.listener, err = net.Listen("unix", a.listenaddr[7:])
if err == nil { if err == nil {
@ -371,7 +410,7 @@ func (a *admin) listen() {
case "@": // maybe abstract namespace case "@": // maybe abstract namespace
default: default:
if err := os.Chmod(a.listenaddr[7:], 0660); err != nil { if err := os.Chmod(a.listenaddr[7:], 0660); err != nil {
a.core.log.Println("WARNING:", a.listenaddr[:7], "may have unsafe permissions!") a.core.log.Warnln("WARNING:", a.listenaddr[:7], "may have unsafe permissions!")
} }
} }
} }
@ -385,10 +424,10 @@ func (a *admin) listen() {
a.listener, err = net.Listen("tcp", a.listenaddr) a.listener, err = net.Listen("tcp", a.listenaddr)
} }
if err != nil { if err != nil {
a.core.log.Printf("Admin socket failed to listen: %v", err) a.core.log.Errorf("Admin socket failed to listen: %v", err)
os.Exit(1) os.Exit(1)
} }
a.core.log.Printf("%s admin socket listening on %s", a.core.log.Infof("%s admin socket listening on %s",
strings.ToUpper(a.listener.Addr().Network()), strings.ToUpper(a.listener.Addr().Network()),
a.listener.Addr().String()) a.listener.Addr().String())
defer a.listener.Close() defer a.listener.Close()
@ -415,9 +454,9 @@ func (a *admin) handleRequest(conn net.Conn) {
"status": "error", "status": "error",
"error": "Unrecoverable error, possibly as a result of invalid input types or malformed syntax", "error": "Unrecoverable error, possibly as a result of invalid input types or malformed syntax",
} }
fmt.Println("Admin socket error:", r) a.core.log.Errorln("Admin socket error:", r)
if err := encoder.Encode(&send); err != nil { if err := encoder.Encode(&send); err != nil {
fmt.Println("Admin socket JSON encode error:", err) a.core.log.Errorln("Admin socket JSON encode error:", err)
} }
conn.Close() conn.Close()
} }
@ -730,35 +769,20 @@ func (a *admin) getData_getSessions() []admin_nodeInfo {
// getAllowedEncryptionPublicKeys returns the public keys permitted for incoming peer connections. // getAllowedEncryptionPublicKeys returns the public keys permitted for incoming peer connections.
func (a *admin) getAllowedEncryptionPublicKeys() []string { func (a *admin) getAllowedEncryptionPublicKeys() []string {
pubs := a.core.peers.getAllowedEncryptionPublicKeys() return a.core.peers.getAllowedEncryptionPublicKeys()
var out []string
for _, pub := range pubs {
out = append(out, hex.EncodeToString(pub[:]))
}
return out
} }
// addAllowedEncryptionPublicKey whitelists a key for incoming peer connections. // addAllowedEncryptionPublicKey whitelists a key for incoming peer connections.
func (a *admin) addAllowedEncryptionPublicKey(bstr string) (err error) { func (a *admin) addAllowedEncryptionPublicKey(bstr string) (err error) {
boxBytes, err := hex.DecodeString(bstr) a.core.peers.addAllowedEncryptionPublicKey(bstr)
if err == nil { return nil
var box crypto.BoxPubKey
copy(box[:], boxBytes)
a.core.peers.addAllowedEncryptionPublicKey(&box)
}
return
} }
// removeAllowedEncryptionPublicKey removes a key from the whitelist for incoming peer connections. // removeAllowedEncryptionPublicKey removes a key from the whitelist for incoming peer connections.
// If none are set, an empty list permits all incoming connections. // If none are set, an empty list permits all incoming connections.
func (a *admin) removeAllowedEncryptionPublicKey(bstr string) (err error) { func (a *admin) removeAllowedEncryptionPublicKey(bstr string) (err error) {
boxBytes, err := hex.DecodeString(bstr) a.core.peers.removeAllowedEncryptionPublicKey(bstr)
if err == nil { return nil
var box crypto.BoxPubKey
copy(box[:], boxBytes)
a.core.peers.removeAllowedEncryptionPublicKey(&box)
}
return
} }
// Send a DHT ping to the node with the provided key and coords, optionally looking up the specified target NodeID. // Send a DHT ping to the node with the provided key and coords, optionally looking up the specified target NodeID.
@ -827,7 +851,7 @@ func (a *admin) admin_getNodeInfo(keyString, coordString string, nocache bool) (
copy(key[:], keyBytes) copy(key[:], keyBytes)
} }
if !nocache { if !nocache {
if response, err := a.core.nodeinfo.getCachedNodeInfo(key); err == nil { if response, err := a.core.router.nodeinfo.getCachedNodeInfo(key); err == nil {
return response, nil return response, nil
} }
} }
@ -845,14 +869,14 @@ func (a *admin) admin_getNodeInfo(keyString, coordString string, nocache bool) (
} }
response := make(chan *nodeinfoPayload, 1) response := make(chan *nodeinfoPayload, 1)
sendNodeInfoRequest := func() { sendNodeInfoRequest := func() {
a.core.nodeinfo.addCallback(key, func(nodeinfo *nodeinfoPayload) { a.core.router.nodeinfo.addCallback(key, func(nodeinfo *nodeinfoPayload) {
defer func() { recover() }() defer func() { recover() }()
select { select {
case response <- nodeinfo: case response <- nodeinfo:
default: default:
} }
}) })
a.core.nodeinfo.sendNodeInfo(key, coords, false) a.core.router.nodeinfo.sendNodeInfo(key, coords, false)
} }
a.core.router.doAdmin(sendNodeInfoRequest) a.core.router.doAdmin(sendNodeInfoRequest)
go func() { go func() {

98
src/yggdrasil/awdl.go Normal file
View File

@ -0,0 +1,98 @@
package yggdrasil
import (
"errors"
"io"
"sync"
)
type awdl struct {
link *link
mutex sync.RWMutex // protects interfaces below
interfaces map[string]*awdlInterface
}
type awdlInterface struct {
linkif *linkInterface
rwc awdlReadWriteCloser
peer *peer
stream stream
}
type awdlReadWriteCloser struct {
fromAWDL chan []byte
toAWDL chan []byte
}
func (c awdlReadWriteCloser) Read(p []byte) (n int, err error) {
if packet, ok := <-c.fromAWDL; ok {
n = copy(p, packet)
return n, nil
}
return 0, io.EOF
}
func (c awdlReadWriteCloser) Write(p []byte) (n int, err error) {
var pc []byte
pc = append(pc, p...)
c.toAWDL <- pc
return len(pc), nil
}
func (c awdlReadWriteCloser) Close() error {
close(c.fromAWDL)
close(c.toAWDL)
return nil
}
func (a *awdl) init(l *link) error {
a.link = l
a.mutex.Lock()
a.interfaces = make(map[string]*awdlInterface)
a.mutex.Unlock()
return nil
}
func (a *awdl) create(name, local, remote string, incoming bool) (*awdlInterface, error) {
rwc := awdlReadWriteCloser{
fromAWDL: make(chan []byte, 1),
toAWDL: make(chan []byte, 1),
}
s := stream{}
s.init(rwc)
linkif, err := a.link.create(&s, name, "awdl", local, remote, incoming, true)
if err != nil {
return nil, err
}
intf := awdlInterface{
linkif: linkif,
rwc: rwc,
}
a.mutex.Lock()
a.interfaces[name] = &intf
a.mutex.Unlock()
go intf.linkif.handler()
return &intf, nil
}
func (a *awdl) getInterface(identity string) *awdlInterface {
a.mutex.RLock()
defer a.mutex.RUnlock()
if intf, ok := a.interfaces[identity]; ok {
return intf
}
return nil
}
func (a *awdl) shutdown(identity string) error {
if intf, ok := a.interfaces[identity]; ok {
close(intf.linkif.closed)
intf.rwc.Close()
a.mutex.Lock()
delete(a.interfaces, identity)
a.mutex.Unlock()
return nil
}
return errors.New("Interface not found or already closed")
}

View File

@ -18,6 +18,7 @@ import (
type cryptokey struct { type cryptokey struct {
core *Core core *Core
enabled bool enabled bool
reconfigure chan chan error
ipv4routes []cryptokey_route ipv4routes []cryptokey_route
ipv6routes []cryptokey_route ipv6routes []cryptokey_route
ipv4cache map[address.Address]cryptokey_route ipv4cache map[address.Address]cryptokey_route
@ -34,12 +35,75 @@ type cryptokey_route struct {
// Initialise crypto-key routing. This must be done before any other CKR calls. // Initialise crypto-key routing. This must be done before any other CKR calls.
func (c *cryptokey) init(core *Core) { func (c *cryptokey) init(core *Core) {
c.core = core c.core = core
c.ipv4routes = make([]cryptokey_route, 0) c.reconfigure = make(chan chan error, 1)
go func() {
for {
e := <-c.reconfigure
var err error
c.core.router.doAdmin(func() {
err = c.core.router.cryptokey.configure()
})
e <- err
}
}()
if err := c.configure(); err != nil {
c.core.log.Errorln("CKR configuration failed:", err)
}
}
// Configure the CKR routes - this must only ever be called from the router
// goroutine, e.g. through router.doAdmin
func (c *cryptokey) configure() error {
c.core.configMutex.RLock()
defer c.core.configMutex.RUnlock()
// Set enabled/disabled state
c.setEnabled(c.core.config.TunnelRouting.Enable)
// Clear out existing routes
c.ipv6routes = make([]cryptokey_route, 0) c.ipv6routes = make([]cryptokey_route, 0)
c.ipv4routes = make([]cryptokey_route, 0)
// Add IPv6 routes
for ipv6, pubkey := range c.core.config.TunnelRouting.IPv6Destinations {
if err := c.addRoute(ipv6, pubkey); err != nil {
return err
}
}
// Add IPv4 routes
for ipv4, pubkey := range c.core.config.TunnelRouting.IPv4Destinations {
if err := c.addRoute(ipv4, pubkey); err != nil {
return err
}
}
// Clear out existing sources
c.ipv6sources = make([]net.IPNet, 0)
c.ipv4sources = make([]net.IPNet, 0)
// Add IPv6 sources
c.ipv6sources = make([]net.IPNet, 0)
for _, source := range c.core.config.TunnelRouting.IPv6Sources {
if err := c.addSourceSubnet(source); err != nil {
return err
}
}
// Add IPv4 sources
c.ipv4sources = make([]net.IPNet, 0)
for _, source := range c.core.config.TunnelRouting.IPv4Sources {
if err := c.addSourceSubnet(source); err != nil {
return err
}
}
// Wipe the caches
c.ipv4cache = make(map[address.Address]cryptokey_route, 0) c.ipv4cache = make(map[address.Address]cryptokey_route, 0)
c.ipv6cache = make(map[address.Address]cryptokey_route, 0) c.ipv6cache = make(map[address.Address]cryptokey_route, 0)
c.ipv4sources = make([]net.IPNet, 0)
c.ipv6sources = make([]net.IPNet, 0) return nil
} }
// Enable or disable crypto-key routing. // Enable or disable crypto-key routing.
@ -128,7 +192,7 @@ func (c *cryptokey) addSourceSubnet(cidr string) error {
// Add the source subnet // Add the source subnet
*routingsources = append(*routingsources, *ipnet) *routingsources = append(*routingsources, *ipnet)
c.core.log.Println("Added CKR source subnet", cidr) c.core.log.Infoln("Added CKR source subnet", cidr)
return nil return nil
} }
@ -200,7 +264,7 @@ func (c *cryptokey) addRoute(cidr string, dest string) error {
delete(*routingcache, k) delete(*routingcache, k)
} }
c.core.log.Println("Added CKR destination subnet", cidr) c.core.log.Infoln("Added CKR destination subnet", cidr)
return nil return nil
} }
} }
@ -294,7 +358,7 @@ func (c *cryptokey) removeSourceSubnet(cidr string) error {
for idx, subnet := range *routingsources { for idx, subnet := range *routingsources {
if subnet.String() == ipnet.String() { if subnet.String() == ipnet.String() {
*routingsources = append((*routingsources)[:idx], (*routingsources)[idx+1:]...) *routingsources = append((*routingsources)[:idx], (*routingsources)[idx+1:]...)
c.core.log.Println("Removed CKR source subnet", cidr) c.core.log.Infoln("Removed CKR source subnet", cidr)
return nil return nil
} }
} }
@ -343,7 +407,7 @@ func (c *cryptokey) removeRoute(cidr string, dest string) error {
for k := range *routingcache { for k := range *routingcache {
delete(*routingcache, k) delete(*routingcache, k)
} }
c.core.log.Printf("Removed CKR destination subnet %s via %s\n", cidr, dest) c.core.log.Infoln("Removed CKR destination subnet %s via %s\n", cidr, dest)
return nil return nil
} }
} }

View File

@ -2,11 +2,12 @@ package yggdrasil
import ( import (
"encoding/hex" "encoding/hex"
"fmt"
"io/ioutil" "io/ioutil"
"log"
"net" "net"
"regexp" "sync"
"time"
"github.com/gologme/log"
"github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/config"
@ -17,10 +18,20 @@ import (
var buildName string var buildName string
var buildVersion string var buildVersion string
type module interface {
init(*Core, *config.NodeConfig) error
start() error
}
// The Core object represents the Yggdrasil node. You should create a Core // The Core object represents the Yggdrasil node. You should create a Core
// object for each Yggdrasil node you plan to run. // object for each Yggdrasil node you plan to run.
type Core struct { type Core struct {
// This is the main data structure that holds everything else for a node // This is the main data structure that holds everything else for a node
// We're going to keep our own copy of the provided config - that way we can
// guarantee that it will be covered by the mutex
config config.NodeConfig // Active config
configOld config.NodeConfig // Previous config
configMutex sync.RWMutex // Protects both config and configOld
boxPub crypto.BoxPubKey boxPub crypto.BoxPubKey
boxPriv crypto.BoxPrivKey boxPriv crypto.BoxPrivKey
sigPub crypto.SigPubKey sigPub crypto.SigPubKey
@ -33,16 +44,12 @@ type Core struct {
admin admin admin admin
searches searches searches searches
multicast multicast multicast multicast
nodeinfo nodeinfo
tcp tcpInterface tcp tcpInterface
link link
log *log.Logger log *log.Logger
ifceExpr []*regexp.Regexp // the zone of link-local IPv6 peers must match this
} }
func (c *Core) init(bpub *crypto.BoxPubKey, func (c *Core) init() error {
bpriv *crypto.BoxPrivKey,
spub *crypto.SigPubKey,
spriv *crypto.SigPrivKey) {
// TODO separate init and start functions // TODO separate init and start functions
// Init sets up structs // Init sets up structs
// Start launches goroutines that depend on structs being set up // Start launches goroutines that depend on structs being set up
@ -50,20 +57,104 @@ func (c *Core) init(bpub *crypto.BoxPubKey,
if c.log == nil { if c.log == nil {
c.log = log.New(ioutil.Discard, "", 0) c.log = log.New(ioutil.Discard, "", 0)
} }
c.boxPub, c.boxPriv = *bpub, *bpriv
c.sigPub, c.sigPriv = *spub, *spriv boxPubHex, err := hex.DecodeString(c.config.EncryptionPublicKey)
c.admin.core = c if err != nil {
return err
}
boxPrivHex, err := hex.DecodeString(c.config.EncryptionPrivateKey)
if err != nil {
return err
}
sigPubHex, err := hex.DecodeString(c.config.SigningPublicKey)
if err != nil {
return err
}
sigPrivHex, err := hex.DecodeString(c.config.SigningPrivateKey)
if err != nil {
return err
}
copy(c.boxPub[:], boxPubHex)
copy(c.boxPriv[:], boxPrivHex)
copy(c.sigPub[:], sigPubHex)
copy(c.sigPriv[:], sigPrivHex)
c.admin.init(c)
c.searches.init(c) c.searches.init(c)
c.dht.init(c) c.dht.init(c)
c.sessions.init(c) c.sessions.init(c)
c.multicast.init(c) c.multicast.init(c)
c.peers.init(c) c.peers.init(c)
c.router.init(c) c.router.init(c)
c.switchTable.init(c, c.sigPub) // TODO move before peers? before router? c.switchTable.init(c) // TODO move before peers? before router?
return nil
} }
// Get the current build name. This is usually injected if built from git, // If any static peers were provided in the configuration above then we should
// or returns "unknown" otherwise. // configure them. The loop ensures that disconnected peers will eventually
// be reconnected with.
func (c *Core) addPeerLoop() {
for {
// Get the peers from the config - these could change!
c.configMutex.RLock()
peers := c.config.Peers
interfacepeers := c.config.InterfacePeers
c.configMutex.RUnlock()
// Add peers from the Peers section
for _, peer := range peers {
c.AddPeer(peer, "")
time.Sleep(time.Second)
}
// Add peers from the InterfacePeers section
for intf, intfpeers := range interfacepeers {
for _, peer := range intfpeers {
c.AddPeer(peer, intf)
time.Sleep(time.Second)
}
}
// Sit for a while
time.Sleep(time.Minute)
}
}
// UpdateConfig updates the configuration in Core and then signals the
// various module goroutines to reconfigure themselves if needed
func (c *Core) UpdateConfig(config *config.NodeConfig) {
c.configMutex.Lock()
c.configOld = c.config
c.config = *config
c.configMutex.Unlock()
components := []chan chan error{
c.admin.reconfigure,
c.searches.reconfigure,
c.dht.reconfigure,
c.sessions.reconfigure,
c.peers.reconfigure,
c.router.reconfigure,
c.router.tun.reconfigure,
c.router.cryptokey.reconfigure,
c.switchTable.reconfigure,
c.tcp.reconfigure,
c.multicast.reconfigure,
}
for _, component := range components {
response := make(chan error)
component <- response
if err := <-response; err != nil {
c.log.Println(err)
}
}
}
// GetBuildName gets the current build name. This is usually injected if built
// from git, or returns "unknown" otherwise.
func GetBuildName() string { func GetBuildName() string {
if buildName == "" { if buildName == "" {
return "unknown" return "unknown"
@ -88,47 +179,28 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error {
c.log = log c.log = log
if name := GetBuildName(); name != "unknown" { if name := GetBuildName(); name != "unknown" {
c.log.Println("Build name:", name) c.log.Infoln("Build name:", name)
} }
if version := GetBuildVersion(); version != "unknown" { if version := GetBuildVersion(); version != "unknown" {
c.log.Println("Build version:", version) c.log.Infoln("Build version:", version)
} }
c.log.Println("Starting up...") c.log.Infoln("Starting up...")
var boxPub crypto.BoxPubKey c.configMutex.Lock()
var boxPriv crypto.BoxPrivKey c.config = *nc
var sigPub crypto.SigPubKey c.configOld = c.config
var sigPriv crypto.SigPrivKey c.configMutex.Unlock()
boxPubHex, err := hex.DecodeString(nc.EncryptionPublicKey)
if err != nil { c.init()
if err := c.tcp.init(c); err != nil {
c.log.Errorln("Failed to start TCP interface")
return err return err
} }
boxPrivHex, err := hex.DecodeString(nc.EncryptionPrivateKey)
if err != nil {
return err
}
sigPubHex, err := hex.DecodeString(nc.SigningPublicKey)
if err != nil {
return err
}
sigPrivHex, err := hex.DecodeString(nc.SigningPrivateKey)
if err != nil {
return err
}
copy(boxPub[:], boxPubHex)
copy(boxPriv[:], boxPrivHex)
copy(sigPub[:], sigPubHex)
copy(sigPriv[:], sigPrivHex)
c.init(&boxPub, &boxPriv, &sigPub, &sigPriv) if err := c.link.init(c); err != nil {
c.admin.init(c, nc.AdminListen) c.log.Errorln("Failed to start link interfaces")
c.nodeinfo.init(c)
c.nodeinfo.setNodeInfo(nc.NodeInfo, nc.NodeInfoPrivacy)
if err := c.tcp.init(c, nc.Listen, nc.ReadTimeout); err != nil {
c.log.Println("Failed to start TCP interface")
return err return err
} }
@ -137,72 +209,39 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error {
} }
if err := c.switchTable.start(); err != nil { if err := c.switchTable.start(); err != nil {
c.log.Println("Failed to start switch") c.log.Errorln("Failed to start switch")
return err return err
} }
c.sessions.setSessionFirewallState(nc.SessionFirewall.Enable)
c.sessions.setSessionFirewallDefaults(
nc.SessionFirewall.AllowFromDirect,
nc.SessionFirewall.AllowFromRemote,
nc.SessionFirewall.AlwaysAllowOutbound,
)
c.sessions.setSessionFirewallWhitelist(nc.SessionFirewall.WhitelistEncryptionPublicKeys)
c.sessions.setSessionFirewallBlacklist(nc.SessionFirewall.BlacklistEncryptionPublicKeys)
if err := c.router.start(); err != nil { if err := c.router.start(); err != nil {
c.log.Println("Failed to start router") c.log.Errorln("Failed to start router")
return err return err
} }
c.router.cryptokey.setEnabled(nc.TunnelRouting.Enable)
if c.router.cryptokey.isEnabled() {
c.log.Println("Crypto-key routing enabled")
for ipv6, pubkey := range nc.TunnelRouting.IPv6Destinations {
if err := c.router.cryptokey.addRoute(ipv6, pubkey); err != nil {
panic(err)
}
}
for _, source := range nc.TunnelRouting.IPv6Sources {
if c.router.cryptokey.addSourceSubnet(source); err != nil {
panic(err)
}
}
for ipv4, pubkey := range nc.TunnelRouting.IPv4Destinations {
if err := c.router.cryptokey.addRoute(ipv4, pubkey); err != nil {
panic(err)
}
}
for _, source := range nc.TunnelRouting.IPv4Sources {
if c.router.cryptokey.addSourceSubnet(source); err != nil {
panic(err)
}
}
}
if err := c.admin.start(); err != nil { if err := c.admin.start(); err != nil {
c.log.Println("Failed to start admin socket") c.log.Errorln("Failed to start admin socket")
return err return err
} }
if err := c.multicast.start(); err != nil { if err := c.multicast.start(); err != nil {
c.log.Println("Failed to start multicast interface") c.log.Errorln("Failed to start multicast interface")
return err return err
} }
ip := net.IP(c.router.addr[:]).String() if err := c.router.tun.start(); err != nil {
if err := c.router.tun.start(nc.IfName, nc.IfTAPMode, fmt.Sprintf("%s/%d", ip, 8*len(address.GetPrefix())-1), nc.IfMTU); err != nil { c.log.Errorln("Failed to start TUN/TAP")
c.log.Println("Failed to start TUN/TAP")
return err return err
} }
c.log.Println("Startup complete") go c.addPeerLoop()
c.log.Infoln("Startup complete")
return nil return nil
} }
// Stops the Yggdrasil node. // Stops the Yggdrasil node.
func (c *Core) Stop() { func (c *Core) Stop() {
c.log.Println("Stopping...") c.log.Infoln("Stopping...")
c.router.tun.close() c.router.tun.close()
c.admin.close() c.admin.close()
} }
@ -244,12 +283,12 @@ func (c *Core) GetSubnet() *net.IPNet {
// Gets the nodeinfo. // Gets the nodeinfo.
func (c *Core) GetNodeInfo() nodeinfoPayload { func (c *Core) GetNodeInfo() nodeinfoPayload {
return c.nodeinfo.getNodeInfo() return c.router.nodeinfo.getNodeInfo()
} }
// Sets the nodeinfo. // Sets the nodeinfo.
func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) { func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) {
c.nodeinfo.setNodeInfo(nodeinfo, nodeinfoprivacy) c.router.nodeinfo.setNodeInfo(nodeinfo, nodeinfoprivacy)
} }
// Sets the output logger of the Yggdrasil node after startup. This may be // Sets the output logger of the Yggdrasil node after startup. This may be
@ -264,13 +303,6 @@ func (c *Core) AddPeer(addr string, sintf string) error {
return c.admin.addPeer(addr, sintf) return c.admin.addPeer(addr, sintf)
} }
// Adds an expression to select multicast interfaces for peer discovery. This
// should be done before calling Start. This function can be called multiple
// times to add multiple search expressions.
func (c *Core) AddMulticastInterfaceExpr(expr *regexp.Regexp) {
c.ifceExpr = append(c.ifceExpr, expr)
}
// Adds an allowed public key. This allow peerings to be restricted only to // Adds an allowed public key. This allow peerings to be restricted only to
// keys that you have selected. // keys that you have selected.
func (c *Core) AddAllowedEncryptionPublicKey(boxStr string) error { func (c *Core) AddAllowedEncryptionPublicKey(boxStr string) error {

View File

@ -14,15 +14,18 @@ import _ "golang.org/x/net/ipv6" // TODO put this somewhere better
import "fmt" import "fmt"
import "net" import "net"
import "log"
import "regexp" import "regexp"
import "encoding/hex"
import _ "net/http/pprof" import _ "net/http/pprof"
import "net/http" import "net/http"
import "runtime" import "runtime"
import "os" import "os"
import "github.com/gologme/log"
import "github.com/yggdrasil-network/yggdrasil-go/src/address" import "github.com/yggdrasil-network/yggdrasil-go/src/address"
import "github.com/yggdrasil-network/yggdrasil-go/src/config"
import "github.com/yggdrasil-network/yggdrasil-go/src/crypto" import "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
import "github.com/yggdrasil-network/yggdrasil-go/src/defaults" import "github.com/yggdrasil-network/yggdrasil-go/src/defaults"
@ -52,7 +55,17 @@ func StartProfiler(log *log.Logger) error {
func (c *Core) Init() { func (c *Core) Init() {
bpub, bpriv := crypto.NewBoxKeys() bpub, bpriv := crypto.NewBoxKeys()
spub, spriv := crypto.NewSigKeys() spub, spriv := crypto.NewSigKeys()
c.init(bpub, bpriv, spub, spriv) hbpub := hex.EncodeToString(bpub[:])
hbpriv := hex.EncodeToString(bpriv[:])
hspub := hex.EncodeToString(spub[:])
hspriv := hex.EncodeToString(spriv[:])
c.config = config.NodeConfig{
EncryptionPublicKey: hbpub,
EncryptionPrivateKey: hbpriv,
SigningPublicKey: hspub,
SigningPrivateKey: hspriv,
}
c.init( /*bpub, bpriv, spub, spriv*/ )
c.switchTable.start() c.switchTable.start()
c.router.start() c.router.start()
} }
@ -84,9 +97,7 @@ func (c *Core) DEBUG_getPeers() *peers {
} }
func (ps *peers) DEBUG_newPeer(box crypto.BoxPubKey, sig crypto.SigPubKey, link crypto.BoxSharedKey) *peer { func (ps *peers) DEBUG_newPeer(box crypto.BoxPubKey, sig crypto.SigPubKey, link crypto.BoxSharedKey) *peer {
//in <-chan []byte, return ps.newPeer(&box, &sig, &link, "(simulator)", nil)
//out chan<- []byte) *peer {
return ps.newPeer(&box, &sig, &link, "(simulator)") //, in, out)
} }
/* /*
@ -350,7 +361,7 @@ func (c *Core) DEBUG_init(bpub []byte,
bpriv []byte, bpriv []byte,
spub []byte, spub []byte,
spriv []byte) { spriv []byte) {
var boxPub crypto.BoxPubKey /*var boxPub crypto.BoxPubKey
var boxPriv crypto.BoxPrivKey var boxPriv crypto.BoxPrivKey
var sigPub crypto.SigPubKey var sigPub crypto.SigPubKey
var sigPriv crypto.SigPrivKey var sigPriv crypto.SigPrivKey
@ -358,7 +369,18 @@ func (c *Core) DEBUG_init(bpub []byte,
copy(boxPriv[:], bpriv) copy(boxPriv[:], bpriv)
copy(sigPub[:], spub) copy(sigPub[:], spub)
copy(sigPriv[:], spriv) copy(sigPriv[:], spriv)
c.init(&boxPub, &boxPriv, &sigPub, &sigPriv) c.init(&boxPub, &boxPriv, &sigPub, &sigPriv)*/
hbpub := hex.EncodeToString(bpub[:])
hbpriv := hex.EncodeToString(bpriv[:])
hspub := hex.EncodeToString(spub[:])
hspriv := hex.EncodeToString(spriv[:])
c.config = config.NodeConfig{
EncryptionPublicKey: hbpub,
EncryptionPrivateKey: hbpriv,
SigningPublicKey: hspub,
SigningPrivateKey: hspriv,
}
c.init( /*bpub, bpriv, spub, spriv*/ )
if err := c.router.start(); err != nil { if err := c.router.start(); err != nil {
panic(err) panic(err)
@ -427,7 +449,8 @@ func (c *Core) DEBUG_addSOCKSConn(socksaddr, peeraddr string) {
//* //*
func (c *Core) DEBUG_setupAndStartGlobalTCPInterface(addrport string) { func (c *Core) DEBUG_setupAndStartGlobalTCPInterface(addrport string) {
if err := c.tcp.init(c, addrport, 0); err != nil { c.config.Listen = addrport
if err := c.tcp.init(c /*, addrport, 0*/); err != nil {
c.log.Println("Failed to start TCP interface:", err) c.log.Println("Failed to start TCP interface:", err)
panic(err) panic(err)
} }
@ -474,7 +497,8 @@ func (c *Core) DEBUG_addKCPConn(saddr string) {
func (c *Core) DEBUG_setupAndStartAdminInterface(addrport string) { func (c *Core) DEBUG_setupAndStartAdminInterface(addrport string) {
a := admin{} a := admin{}
a.init(c, addrport) c.config.AdminListen = addrport
a.init(c /*, addrport*/)
c.admin = a c.admin = a
} }
@ -492,7 +516,7 @@ func (c *Core) DEBUG_setLogger(log *log.Logger) {
} }
func (c *Core) DEBUG_setIfceExpr(expr *regexp.Regexp) { func (c *Core) DEBUG_setIfceExpr(expr *regexp.Regexp) {
c.ifceExpr = append(c.ifceExpr, expr) c.log.Println("DEBUG_setIfceExpr no longer implemented")
} }
func (c *Core) DEBUG_addAllowedEncryptionPublicKey(boxStr string) { func (c *Core) DEBUG_addAllowedEncryptionPublicKey(boxStr string) {

View File

@ -66,6 +66,7 @@ type dhtReqKey struct {
// The main DHT struct. // The main DHT struct.
type dht struct { type dht struct {
core *Core core *Core
reconfigure chan chan error
nodeID crypto.NodeID nodeID crypto.NodeID
peers chan *dhtInfo // other goroutines put incoming dht updates here peers chan *dhtInfo // other goroutines put incoming dht updates here
reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests
@ -78,6 +79,13 @@ type dht struct {
// Initializes the DHT. // Initializes the DHT.
func (t *dht) init(c *Core) { func (t *dht) init(c *Core) {
t.core = c t.core = c
t.reconfigure = make(chan chan error, 1)
go func() {
for {
e := <-t.reconfigure
e <- nil
}
}()
t.nodeID = *t.core.GetNodeID() t.nodeID = *t.core.GetNodeID()
t.peers = make(chan *dhtInfo, 1024) t.peers = make(chan *dhtInfo, 1024)
t.callbacks = make(map[dhtReqKey]dht_callbackInfo) t.callbacks = make(map[dhtReqKey]dht_callbackInfo)

321
src/yggdrasil/link.go Normal file
View File

@ -0,0 +1,321 @@
package yggdrasil
import (
"encoding/hex"
"errors"
"fmt"
"net"
"strings"
"sync"
//"sync/atomic"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
type link struct {
core *Core
mutex sync.RWMutex // protects interfaces below
interfaces map[linkInfo]*linkInterface
awdl awdl // AWDL interface support
// TODO timeout (to remove from switch), read from config.ReadTimeout
}
type linkInfo struct {
box crypto.BoxPubKey // Their encryption key
sig crypto.SigPubKey // Their signing key
linkType string // Type of link, e.g. TCP, AWDL
local string // Local name or address
remote string // Remote name or address
}
type linkInterfaceMsgIO interface {
readMsg() ([]byte, error)
writeMsg([]byte) (int, error)
close() error
// These are temporary workarounds to stream semantics
_sendMetaBytes([]byte) error
_recvMetaBytes() ([]byte, error)
}
type linkInterface struct {
name string
link *link
peer *peer
msgIO linkInterfaceMsgIO
info linkInfo
incoming bool
force bool
closed chan struct{}
}
func (l *link) init(c *Core) error {
l.core = c
l.mutex.Lock()
l.interfaces = make(map[linkInfo]*linkInterface)
l.mutex.Unlock()
if err := l.awdl.init(l); err != nil {
l.core.log.Errorln("Failed to start AWDL interface")
return err
}
return nil
}
func (l *link) create(msgIO linkInterfaceMsgIO, name, linkType, local, remote string, incoming, force bool) (*linkInterface, error) {
// Technically anything unique would work for names, but lets pick something human readable, just for debugging
intf := linkInterface{
name: name,
link: l,
msgIO: msgIO,
info: linkInfo{
linkType: linkType,
local: local,
remote: remote,
},
incoming: incoming,
force: force,
}
return &intf, nil
}
func (intf *linkInterface) handler() error {
// TODO split some of this into shorter functions, so it's easier to read, and for the FIXME duplicate peer issue mentioned later
myLinkPub, myLinkPriv := crypto.NewBoxKeys()
meta := version_getBaseMetadata()
meta.box = intf.link.core.boxPub
meta.sig = intf.link.core.sigPub
meta.link = *myLinkPub
metaBytes := meta.encode()
// TODO timeouts on send/recv (goroutine for send/recv, channel select w/ timer)
err := intf.msgIO._sendMetaBytes(metaBytes)
if err != nil {
return err
}
metaBytes, err = intf.msgIO._recvMetaBytes()
if err != nil {
return err
}
meta = version_metadata{}
if !meta.decode(metaBytes) || !meta.check() {
return errors.New("failed to decode metadata")
}
base := version_getBaseMetadata()
if meta.ver > base.ver || meta.ver == base.ver && meta.minorVer > base.minorVer {
intf.link.core.log.Errorln("Failed to connect to node: " + intf.name + " version: " + fmt.Sprintf("%d.%d", meta.ver, meta.minorVer))
return errors.New("failed to connect: wrong version")
}
// Check if we're authorized to connect to this key / IP
if !intf.force && !intf.link.core.peers.isAllowedEncryptionPublicKey(&meta.box) {
intf.link.core.log.Debugf("%s connection to %s forbidden: AllowedEncryptionPublicKeys does not contain key %s",
strings.ToUpper(intf.info.linkType), intf.info.remote, hex.EncodeToString(meta.box[:]))
intf.msgIO.close()
return nil
}
// Check if we already have a link to this node
intf.info.box = meta.box
intf.info.sig = meta.sig
intf.link.mutex.Lock()
if oldIntf, isIn := intf.link.interfaces[intf.info]; isIn {
intf.link.mutex.Unlock()
// FIXME we should really return an error and let the caller block instead
// That lets them do things like close connections on its own, avoid printing a connection message in the first place, etc.
intf.link.core.log.Debugln("DEBUG: found existing interface for", intf.name)
intf.msgIO.close()
<-oldIntf.closed
return nil
} else {
intf.closed = make(chan struct{})
intf.link.interfaces[intf.info] = intf
defer func() {
intf.link.mutex.Lock()
delete(intf.link.interfaces, intf.info)
intf.link.mutex.Unlock()
close(intf.closed)
}()
intf.link.core.log.Debugln("DEBUG: registered interface for", intf.name)
}
intf.link.mutex.Unlock()
// Create peer
shared := crypto.GetSharedKey(myLinkPriv, &meta.link)
intf.peer = intf.link.core.peers.newPeer(&meta.box, &meta.sig, shared, intf.name, func() { intf.msgIO.close() })
if intf.peer == nil {
return errors.New("failed to create peer")
}
defer func() {
// More cleanup can go here
intf.link.core.peers.removePeer(intf.peer.port)
}()
// Finish setting up the peer struct
out := make(chan []byte, 1)
defer close(out)
intf.peer.out = func(msg []byte) {
defer func() { recover() }()
out <- msg
}
intf.peer.linkOut = make(chan []byte, 1)
themAddr := address.AddrForNodeID(crypto.GetNodeID(&intf.info.box))
themAddrString := net.IP(themAddr[:]).String()
themString := fmt.Sprintf("%s@%s", themAddrString, intf.info.remote)
intf.link.core.log.Infof("Connected %s: %s, source %s",
strings.ToUpper(intf.info.linkType), themString, intf.info.local)
defer intf.link.core.log.Infof("Disconnected %s: %s, source %s",
strings.ToUpper(intf.info.linkType), themString, intf.info.local)
// Start the link loop
go intf.peer.linkLoop()
// Start the writer
signalReady := make(chan struct{}, 1)
signalSent := make(chan bool, 1)
sendAck := make(chan struct{}, 1)
go func() {
defer close(signalReady)
defer close(signalSent)
interval := 4 * time.Second
tcpTimer := time.NewTimer(interval) // used for backwards compat with old tcp
defer util.TimerStop(tcpTimer)
send := func(bs []byte) {
intf.msgIO.writeMsg(bs)
select {
case signalSent <- len(bs) > 0:
default:
}
}
for {
// First try to send any link protocol traffic
select {
case msg := <-intf.peer.linkOut:
send(msg)
continue
default:
}
// No protocol traffic to send, so reset the timer
util.TimerStop(tcpTimer)
tcpTimer.Reset(interval)
// Now block until something is ready or the timer triggers keepalive traffic
select {
case <-tcpTimer.C:
intf.link.core.log.Debugf("Sending (legacy) keep-alive to %s: %s, source %s",
strings.ToUpper(intf.info.linkType), themString, intf.info.local)
send(nil)
case <-sendAck:
intf.link.core.log.Debugf("Sending ack to %s: %s, source %s",
strings.ToUpper(intf.info.linkType), themString, intf.info.local)
send(nil)
case msg := <-intf.peer.linkOut:
intf.msgIO.writeMsg(msg)
case msg, ok := <-out:
if !ok {
return
}
send(msg)
util.PutBytes(msg)
select {
case signalReady <- struct{}{}:
default:
}
intf.link.core.log.Debugf("Sending packet to %s: %s, source %s",
strings.ToUpper(intf.info.linkType), themString, intf.info.local)
}
}
}()
//intf.link.core.switchTable.idleIn <- intf.peer.port // notify switch that we're idle
// Used to enable/disable activity in the switch
signalAlive := make(chan bool, 1) // True = real packet, false = keep-alive
defer close(signalAlive)
go func() {
var isAlive bool
var isReady bool
var sendTimerRunning bool
var recvTimerRunning bool
recvTime := 6 * time.Second // TODO set to ReadTimeout from the config, reset if it gets changed
sendTime := time.Second
sendTimer := time.NewTimer(sendTime)
defer util.TimerStop(sendTimer)
recvTimer := time.NewTimer(recvTime)
defer util.TimerStop(recvTimer)
for {
intf.link.core.log.Debugf("State of %s: %s, source %s :: isAlive %t isReady %t sendTimerRunning %t recvTimerRunning %t",
strings.ToUpper(intf.info.linkType), themString, intf.info.local,
isAlive, isReady, sendTimerRunning, recvTimerRunning)
select {
case gotMsg, ok := <-signalAlive:
if !ok {
return
}
util.TimerStop(recvTimer)
recvTimerRunning = false
isAlive = true
if !isReady {
// (Re-)enable in the switch
intf.link.core.switchTable.idleIn <- intf.peer.port
isReady = true
}
if gotMsg && !sendTimerRunning {
// We got a message
// Start a timer, if it expires then send a 0-sized ack to let them know we're alive
util.TimerStop(sendTimer)
sendTimer.Reset(sendTime)
sendTimerRunning = true
}
if !gotMsg {
intf.link.core.log.Debugf("Received ack from %s: %s, source %s",
strings.ToUpper(intf.info.linkType), themString, intf.info.local)
}
case sentMsg, ok := <-signalSent:
// Stop any running ack timer
if !ok {
return
}
util.TimerStop(sendTimer)
sendTimerRunning = false
if sentMsg && !recvTimerRunning {
// We sent a message
// Start a timer, if it expires and we haven't gotten any return traffic (including a 0-sized ack), then assume there's a problem
util.TimerStop(recvTimer)
recvTimer.Reset(recvTime)
recvTimerRunning = true
}
case _, ok := <-signalReady:
if !ok {
return
}
if !isAlive {
// Disable in the switch
isReady = false
} else {
// Keep enabled in the switch
intf.link.core.switchTable.idleIn <- intf.peer.port
isReady = true
}
case <-sendTimer.C:
// We haven't sent anything, so signal a send of a 0 packet to let them know we're alive
select {
case sendAck <- struct{}{}:
default:
}
case <-recvTimer.C:
// We haven't received anything, so assume there's a problem and don't return this node to the switch until they start responding
isAlive = false
}
}
}()
// Run reader loop
for {
msg, err := intf.msgIO.readMsg()
if len(msg) > 0 {
intf.peer.handlePacket(msg)
}
if err != nil {
return err
}
select {
case signalAlive <- len(msg) > 0:
default:
}
}
////////////////////////////////////////////////////////////////////////////////
return nil
}

130
src/yggdrasil/mobile.go Normal file
View File

@ -0,0 +1,130 @@
// +build mobile
package yggdrasil
import (
"encoding/hex"
"encoding/json"
"os"
"time"
"github.com/gologme/log"
hjson "github.com/hjson/hjson-go"
"github.com/mitchellh/mapstructure"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
// This file is meant to "plug the gap" for mobile support, as Gomobile will
// not create headers for Swift/Obj-C etc if they have complex (non-native)
// types. Therefore for iOS we will expose some nice simple functions. Note
// that in the case of iOS we handle reading/writing to/from TUN in Swift
// therefore we use the "dummy" TUN interface instead.
func (c *Core) addStaticPeers(cfg *config.NodeConfig) {
if len(cfg.Peers) == 0 && len(cfg.InterfacePeers) == 0 {
return
}
for {
for _, peer := range cfg.Peers {
c.AddPeer(peer, "")
time.Sleep(time.Second)
}
for intf, intfpeers := range cfg.InterfacePeers {
for _, peer := range intfpeers {
c.AddPeer(peer, intf)
time.Sleep(time.Second)
}
}
time.Sleep(time.Minute)
}
}
// Starts a node with a randomly generated config.
func (c *Core) StartAutoconfigure() error {
mobilelog := MobileLogger{}
logger := log.New(mobilelog, "", 0)
nc := config.GenerateConfig(true)
nc.IfName = "dummy"
nc.AdminListen = "tcp://localhost:9001"
nc.Peers = []string{}
if hostname, err := os.Hostname(); err == nil {
nc.NodeInfo = map[string]interface{}{"name": hostname}
}
if err := c.Start(nc, logger); err != nil {
return err
}
go c.addStaticPeers(nc)
return nil
}
// Starts a node with the given JSON config. You can get JSON config (rather
// than HJSON) by using the GenerateConfigJSON() function.
func (c *Core) StartJSON(configjson []byte) error {
mobilelog := MobileLogger{}
logger := log.New(mobilelog, "", 0)
nc := config.GenerateConfig(false)
var dat map[string]interface{}
if err := hjson.Unmarshal(configjson, &dat); err != nil {
return err
}
if err := mapstructure.Decode(dat, &nc); err != nil {
return err
}
nc.IfName = "dummy"
if err := c.Start(nc, logger); err != nil {
return err
}
go c.addStaticPeers(nc)
return nil
}
// Generates mobile-friendly configuration in JSON format.
func GenerateConfigJSON() []byte {
nc := config.GenerateConfig(false)
nc.IfName = "dummy"
if json, err := json.Marshal(nc); err == nil {
return json
} else {
return nil
}
}
// Gets the node's IPv6 address.
func (c *Core) GetAddressString() string {
return c.GetAddress().String()
}
// Gets the node's IPv6 subnet in CIDR notation.
func (c *Core) GetSubnetString() string {
return c.GetSubnet().String()
}
// Gets the node's public encryption key.
func (c *Core) GetBoxPubKeyString() string {
return hex.EncodeToString(c.boxPub[:])
}
// Gets the node's public signing key.
func (c *Core) GetSigPubKeyString() string {
return hex.EncodeToString(c.sigPub[:])
}
// Wait for a packet from the router. You will use this when implementing a
// dummy adapter in place of real TUN - when this call returns a packet, you
// will probably want to give it to the OS to write to TUN.
func (c *Core) RouterRecvPacket() ([]byte, error) {
packet := <-c.router.tun.recv
return packet, nil
}
// Send a packet to the router. You will use this when implementing a
// dummy adapter in place of real TUN - when the operating system tells you
// that a new packet is available from TUN, call this function to give it to
// Yggdrasil.
func (c *Core) RouterSendPacket(buf []byte) error {
packet := append(util.GetBytes(), buf[:]...)
c.router.tun.send <- packet
return nil
}

View File

@ -0,0 +1,12 @@
// +build android
package yggdrasil
import "log"
type MobileLogger struct{}
func (nsl MobileLogger) Write(p []byte) (n int, err error) {
log.Println(string(p))
return len(p), nil
}

View File

@ -0,0 +1,62 @@
// +build mobile,darwin
package yggdrasil
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation
#import <Foundation/Foundation.h>
void Log(const char *text) {
NSString *nss = [NSString stringWithUTF8String:text];
NSLog(@"%@", nss);
}
*/
import "C"
import (
"errors"
"unsafe"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
type MobileLogger struct {
}
func (nsl MobileLogger) Write(p []byte) (n int, err error) {
p = append(p, 0)
cstr := (*C.char)(unsafe.Pointer(&p[0]))
C.Log(cstr)
return len(p), nil
}
func (c *Core) AWDLCreateInterface(name, local, remote string, incoming bool) error {
if intf, err := c.link.awdl.create(name, local, remote, incoming); err != nil || intf == nil {
c.log.Println("c.link.awdl.create:", err)
return err
}
return nil
}
func (c *Core) AWDLShutdownInterface(name string) error {
return c.link.awdl.shutdown(name)
}
func (c *Core) AWDLRecvPacket(identity string) ([]byte, error) {
if intf := c.link.awdl.getInterface(identity); intf != nil {
read, ok := <-intf.rwc.toAWDL
if !ok {
return nil, errors.New("AWDLRecvPacket: channel closed")
}
return read, nil
}
return nil, errors.New("AWDLRecvPacket identity not known: " + identity)
}
func (c *Core) AWDLSendPacket(identity string, buf []byte) error {
packet := append(util.GetBytes(), buf[:]...)
if intf := c.link.awdl.getInterface(identity); intf != nil {
intf.rwc.fromAWDL <- packet
return nil
}
return errors.New("AWDLSendPacket identity not known: " + identity)
}

View File

@ -4,6 +4,8 @@ import (
"context" "context"
"fmt" "fmt"
"net" "net"
"regexp"
"sync"
"time" "time"
"golang.org/x/net/ipv6" "golang.org/x/net/ipv6"
@ -11,26 +13,37 @@ import (
type multicast struct { type multicast struct {
core *Core core *Core
reconfigure chan chan error
sock *ipv6.PacketConn sock *ipv6.PacketConn
groupAddr string groupAddr string
myAddr *net.TCPAddr
myAddrMutex sync.RWMutex
} }
func (m *multicast) init(core *Core) { func (m *multicast) init(core *Core) {
m.core = core m.core = core
m.reconfigure = make(chan chan error, 1)
go func() {
for {
e := <-m.reconfigure
m.myAddrMutex.Lock()
m.myAddr = m.core.tcp.getAddr()
m.myAddrMutex.Unlock()
e <- nil
}
}()
m.groupAddr = "[ff02::114]:9001" m.groupAddr = "[ff02::114]:9001"
// Check if we've been given any expressions // Check if we've been given any expressions
if len(m.core.ifceExpr) == 0 { if count := len(m.interfaces()); count != 0 {
return m.core.log.Infoln("Found", count, "multicast interface(s)")
} }
// Ask the system for network interfaces
m.core.log.Println("Found", len(m.interfaces()), "multicast interface(s)")
} }
func (m *multicast) start() error { func (m *multicast) start() error {
if len(m.core.ifceExpr) == 0 { if len(m.interfaces()) == 0 {
m.core.log.Println("Multicast discovery is disabled") m.core.log.Infoln("Multicast discovery is disabled")
} else { } else {
m.core.log.Println("Multicast discovery is enabled") m.core.log.Infoln("Multicast discovery is enabled")
addr, err := net.ResolveUDPAddr("udp", m.groupAddr) addr, err := net.ResolveUDPAddr("udp", m.groupAddr)
if err != nil { if err != nil {
return err return err
@ -55,6 +68,10 @@ func (m *multicast) start() error {
} }
func (m *multicast) interfaces() []net.Interface { func (m *multicast) interfaces() []net.Interface {
// Get interface expressions from config
m.core.configMutex.RLock()
exprs := m.core.config.MulticastInterfaces
m.core.configMutex.RUnlock()
// Ask the system for network interfaces // Ask the system for network interfaces
var interfaces []net.Interface var interfaces []net.Interface
allifaces, err := net.Interfaces() allifaces, err := net.Interfaces()
@ -75,8 +92,12 @@ func (m *multicast) interfaces() []net.Interface {
// Ignore point-to-point interfaces // Ignore point-to-point interfaces
continue continue
} }
for _, expr := range m.core.ifceExpr { for _, expr := range exprs {
if expr.MatchString(iface.Name) { e, err := regexp.Compile(expr)
if err != nil {
panic(err)
}
if e.MatchString(iface.Name) {
interfaces = append(interfaces, iface) interfaces = append(interfaces, iface)
} }
} }
@ -85,13 +106,14 @@ func (m *multicast) interfaces() []net.Interface {
} }
func (m *multicast) announce() { func (m *multicast) announce() {
var anAddr net.TCPAddr
m.myAddrMutex.Lock()
m.myAddr = m.core.tcp.getAddr()
m.myAddrMutex.Unlock()
groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr) groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr)
if err != nil { if err != nil {
panic(err) panic(err)
} }
var anAddr net.TCPAddr
myAddr := m.core.tcp.getAddr()
anAddr.Port = myAddr.Port
destAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr) destAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr)
if err != nil { if err != nil {
panic(err) panic(err)
@ -103,6 +125,9 @@ func (m *multicast) announce() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
m.myAddrMutex.RLock()
anAddr.Port = m.myAddr.Port
m.myAddrMutex.RUnlock()
for _, addr := range addrs { for _, addr := range addrs {
addrIP, _, _ := net.ParseCIDR(addr.String()) addrIP, _, _ := net.ParseCIDR(addr.String())
if addrIP.To4() != nil { if addrIP.To4() != nil {
@ -157,6 +182,6 @@ func (m *multicast) listen() {
} }
addr.Zone = from.Zone addr.Zone = from.Zone
saddr := addr.String() saddr := addr.String()
m.core.tcp.connect(saddr, "") m.core.tcp.connect(saddr, addr.Zone)
} }
} }

View File

@ -0,0 +1,28 @@
// +build darwin
package yggdrasil
import "syscall"
import "golang.org/x/sys/unix"
func multicastReuse(network string, address string, c syscall.RawConn) error {
var control error
var reuseport error
var recvanyif error
control = c.Control(func(fd uintptr) {
reuseport = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
// sys/socket.h: #define SO_RECV_ANYIF 0x1104
recvanyif = unix.SetsockoptInt(int(fd), syscall.SOL_SOCKET, 0x1104, 1)
})
switch {
case reuseport != nil:
return reuseport
case recvanyif != nil:
return recvanyif
default:
return control
}
}

View File

@ -1,4 +1,4 @@
// +build linux darwin netbsd freebsd openbsd dragonflybsd // +build linux netbsd freebsd openbsd dragonflybsd
package yggdrasil package yggdrasil

View File

@ -170,7 +170,7 @@ func (m *nodeinfo) sendNodeInfo(key crypto.BoxPubKey, coords []byte, isResponse
nodeinfo := nodeinfoReqRes{ nodeinfo := nodeinfoReqRes{
SendCoords: table.self.getCoords(), SendCoords: table.self.getCoords(),
IsResponse: isResponse, IsResponse: isResponse,
NodeInfo: m.core.nodeinfo.getNodeInfo(), NodeInfo: m.getNodeInfo(),
} }
bs := nodeinfo.encode() bs := nodeinfo.encode()
shared := m.core.sessions.getSharedKey(&m.core.boxPriv, &key) shared := m.core.sessions.getSharedKey(&m.core.boxPriv, &key)

View File

@ -5,6 +5,7 @@ package yggdrasil
// Live code should be better commented // Live code should be better commented
import ( import (
"encoding/hex"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@ -14,15 +15,14 @@ import (
) )
// The peers struct represents peers with an active connection. // The peers struct represents peers with an active connection.
// Incomping packets are passed to the corresponding peer, which handles them somehow. // Incoming packets are passed to the corresponding peer, which handles them somehow.
// In most cases, this involves passing the packet to the handler for outgoing traffic to another peer. // In most cases, this involves passing the packet to the handler for outgoing traffic to another peer.
// In other cases, it's link protocol traffic used to build the spanning tree, in which case this checks signatures and passes the message along to the switch. // In other cases, it's link protocol traffic used to build the spanning tree, in which case this checks signatures and passes the message along to the switch.
type peers struct { type peers struct {
core *Core core *Core
reconfigure chan chan error
mutex sync.Mutex // Synchronize writes to atomic mutex sync.Mutex // Synchronize writes to atomic
ports atomic.Value //map[switchPort]*peer, use CoW semantics ports atomic.Value //map[switchPort]*peer, use CoW semantics
authMutex sync.RWMutex
allowedEncryptionPublicKeys map[crypto.BoxPubKey]struct{}
} }
// Initializes the peers struct. // Initializes the peers struct.
@ -31,40 +31,55 @@ func (ps *peers) init(c *Core) {
defer ps.mutex.Unlock() defer ps.mutex.Unlock()
ps.putPorts(make(map[switchPort]*peer)) ps.putPorts(make(map[switchPort]*peer))
ps.core = c ps.core = c
ps.allowedEncryptionPublicKeys = make(map[crypto.BoxPubKey]struct{}) ps.reconfigure = make(chan chan error, 1)
go func() {
for {
e := <-ps.reconfigure
e <- nil
}
}()
} }
// Returns true if an incoming peer connection to a key is allowed, either because the key is in the whitelist or because the whitelist is empty. // Returns true if an incoming peer connection to a key is allowed, either
// because the key is in the whitelist or because the whitelist is empty.
func (ps *peers) isAllowedEncryptionPublicKey(box *crypto.BoxPubKey) bool { func (ps *peers) isAllowedEncryptionPublicKey(box *crypto.BoxPubKey) bool {
ps.authMutex.RLock() boxstr := hex.EncodeToString(box[:])
defer ps.authMutex.RUnlock() ps.core.configMutex.RLock()
_, isIn := ps.allowedEncryptionPublicKeys[*box] defer ps.core.configMutex.RUnlock()
return isIn || len(ps.allowedEncryptionPublicKeys) == 0 for _, v := range ps.core.config.AllowedEncryptionPublicKeys {
if v == boxstr {
return true
}
}
return len(ps.core.config.AllowedEncryptionPublicKeys) == 0
} }
// Adds a key to the whitelist. // Adds a key to the whitelist.
func (ps *peers) addAllowedEncryptionPublicKey(box *crypto.BoxPubKey) { func (ps *peers) addAllowedEncryptionPublicKey(box string) {
ps.authMutex.Lock() ps.core.configMutex.RLock()
defer ps.authMutex.Unlock() defer ps.core.configMutex.RUnlock()
ps.allowedEncryptionPublicKeys[*box] = struct{}{} ps.core.config.AllowedEncryptionPublicKeys =
append(ps.core.config.AllowedEncryptionPublicKeys, box)
} }
// Removes a key from the whitelist. // Removes a key from the whitelist.
func (ps *peers) removeAllowedEncryptionPublicKey(box *crypto.BoxPubKey) { func (ps *peers) removeAllowedEncryptionPublicKey(box string) {
ps.authMutex.Lock() ps.core.configMutex.RLock()
defer ps.authMutex.Unlock() defer ps.core.configMutex.RUnlock()
delete(ps.allowedEncryptionPublicKeys, *box) for k, v := range ps.core.config.AllowedEncryptionPublicKeys {
if v == box {
ps.core.config.AllowedEncryptionPublicKeys =
append(ps.core.config.AllowedEncryptionPublicKeys[:k],
ps.core.config.AllowedEncryptionPublicKeys[k+1:]...)
}
}
} }
// Gets the whitelist of allowed keys for incoming connections. // Gets the whitelist of allowed keys for incoming connections.
func (ps *peers) getAllowedEncryptionPublicKeys() []crypto.BoxPubKey { func (ps *peers) getAllowedEncryptionPublicKeys() []string {
ps.authMutex.RLock() ps.core.configMutex.RLock()
defer ps.authMutex.RUnlock() defer ps.core.configMutex.RUnlock()
keys := make([]crypto.BoxPubKey, 0, len(ps.allowedEncryptionPublicKeys)) return ps.core.config.AllowedEncryptionPublicKeys
for key := range ps.allowedEncryptionPublicKeys {
keys = append(keys, key)
}
return keys
} }
// Atomically gets a map[switchPort]*peer of known peers. // Atomically gets a map[switchPort]*peer of known peers.
@ -97,8 +112,8 @@ type peer struct {
close func() // Called when a peer is removed, to close the underlying connection, or via admin api close func() // Called when a peer is removed, to close the underlying connection, or via admin api
} }
// Creates a new peer with the specified box, sig, and linkShared keys, using the lowest unocupied port number. // Creates a new peer with the specified box, sig, and linkShared keys, using the lowest unoccupied port number.
func (ps *peers) newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShared *crypto.BoxSharedKey, endpoint string) *peer { func (ps *peers) newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShared *crypto.BoxSharedKey, endpoint string, closer func()) *peer {
now := time.Now() now := time.Now()
p := peer{box: *box, p := peer{box: *box,
sig: *sig, sig: *sig,
@ -108,6 +123,7 @@ func (ps *peers) newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShare
firstSeen: now, firstSeen: now,
doSend: make(chan struct{}, 1), doSend: make(chan struct{}, 1),
dinfo: make(chan *dhtInfo, 1), dinfo: make(chan *dhtInfo, 1),
close: closer,
core: ps.core} core: ps.core}
ps.mutex.Lock() ps.mutex.Lock()
defer ps.mutex.Unlock() defer ps.mutex.Unlock()
@ -217,6 +233,7 @@ func (p *peer) handlePacket(packet []byte) {
default: default:
util.PutBytes(packet) util.PutBytes(packet)
} }
return
} }
// Called to handle traffic or protocolTraffic packets. // Called to handle traffic or protocolTraffic packets.
@ -234,6 +251,7 @@ func (p *peer) handleTraffic(packet []byte, pTypeLen int) {
func (p *peer) sendPacket(packet []byte) { func (p *peer) sendPacket(packet []byte) {
// Is there ever a case where something more complicated is needed? // Is there ever a case where something more complicated is needed?
// What if p.out blocks? // What if p.out blocks?
atomic.AddUint64(&p.bytesSent, uint64(len(packet)))
p.out(packet) p.out(packet)
} }
@ -341,7 +359,7 @@ func (p *peer) handleSwitchMsg(packet []byte) {
} }
// This generates the bytes that we sign or check the signature of for a switchMsg. // This generates the bytes that we sign or check the signature of for a switchMsg.
// It begins with the next node's key, followed by the root and the timetsamp, followed by coords being advertised to the next node. // It begins with the next node's key, followed by the root and the timestamp, followed by coords being advertised to the next node.
func getBytesForSig(next *crypto.SigPubKey, msg *switchMsg) []byte { func getBytesForSig(next *crypto.SigPubKey, msg *switchMsg) []byte {
var loc switchLocator var loc switchLocator
for _, hop := range msg.Hops { for _, hop := range msg.Hops {

View File

@ -38,6 +38,7 @@ import (
// The router's mainLoop goroutine is responsible for managing all information related to the dht, searches, and crypto sessions. // The router's mainLoop goroutine is responsible for managing all information related to the dht, searches, and crypto sessions.
type router struct { type router struct {
core *Core core *Core
reconfigure chan chan error
addr address.Address addr address.Address
subnet address.Subnet subnet address.Subnet
in <-chan []byte // packets we received from the network, link to peer's "out" in <-chan []byte // packets we received from the network, link to peer's "out"
@ -50,6 +51,7 @@ type router struct {
reset chan struct{} // signal that coords changed (re-init sessions/dht) reset chan struct{} // signal that coords changed (re-init sessions/dht)
admin chan func() // pass a lambda for the admin socket to query stuff admin chan func() // pass a lambda for the admin socket to query stuff
cryptokey cryptokey cryptokey cryptokey
nodeinfo nodeinfo
} }
// Packet and session info, used to check that the packet matches a valid IP range or CKR prefix before sending to the tun. // Packet and session info, used to check that the packet matches a valid IP range or CKR prefix before sending to the tun.
@ -61,10 +63,11 @@ type router_recvPacket struct {
// Initializes the router struct, which includes setting up channels to/from the tun/tap. // Initializes the router struct, which includes setting up channels to/from the tun/tap.
func (r *router) init(core *Core) { func (r *router) init(core *Core) {
r.core = core r.core = core
r.reconfigure = make(chan chan error, 1)
r.addr = *address.AddrForNodeID(&r.core.dht.nodeID) r.addr = *address.AddrForNodeID(&r.core.dht.nodeID)
r.subnet = *address.SubnetForNodeID(&r.core.dht.nodeID) r.subnet = *address.SubnetForNodeID(&r.core.dht.nodeID)
in := make(chan []byte, 32) // TODO something better than this... in := make(chan []byte, 32) // TODO something better than this...
p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &crypto.BoxSharedKey{}, "(self)") p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &crypto.BoxSharedKey{}, "(self)", nil)
p.out = func(packet []byte) { p.out = func(packet []byte) {
// This is to make very sure it never blocks // This is to make very sure it never blocks
select { select {
@ -83,13 +86,17 @@ func (r *router) init(core *Core) {
r.send = send r.send = send
r.reset = make(chan struct{}, 1) r.reset = make(chan struct{}, 1)
r.admin = make(chan func(), 32) r.admin = make(chan func(), 32)
r.nodeinfo.init(r.core)
r.core.configMutex.RLock()
r.nodeinfo.setNodeInfo(r.core.config.NodeInfo, r.core.config.NodeInfoPrivacy)
r.core.configMutex.RUnlock()
r.cryptokey.init(r.core) r.cryptokey.init(r.core)
r.tun.init(r.core, send, recv) r.tun.init(r.core, send, recv)
} }
// Starts the mainLoop goroutine. // Starts the mainLoop goroutine.
func (r *router) start() error { func (r *router) start() error {
r.core.log.Println("Starting router") r.core.log.Infoln("Starting router")
go r.mainLoop() go r.mainLoop()
return nil return nil
} }
@ -124,6 +131,10 @@ func (r *router) mainLoop() {
} }
case f := <-r.admin: case f := <-r.admin:
f() f()
case e := <-r.reconfigure:
r.core.configMutex.RLock()
e <- r.nodeinfo.setNodeInfo(r.core.config.NodeInfo, r.core.config.NodeInfoPrivacy)
r.core.configMutex.RUnlock()
} }
} }
} }
@ -463,7 +474,7 @@ func (r *router) handleNodeInfo(bs []byte, fromKey *crypto.BoxPubKey) {
return return
} }
req.SendPermPub = *fromKey req.SendPermPub = *fromKey
r.core.nodeinfo.handleNodeInfo(&req) r.nodeinfo.handleNodeInfo(&req)
} }
// Passed a function to call. // Passed a function to call.

View File

@ -30,7 +30,7 @@ const search_MAX_SEARCH_SIZE = 16
const search_RETRY_TIME = time.Second const search_RETRY_TIME = time.Second
// Information about an ongoing search. // Information about an ongoing search.
// Includes the targed NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited. // Includes the target NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited.
type searchInfo struct { type searchInfo struct {
dest crypto.NodeID dest crypto.NodeID
mask crypto.NodeID mask crypto.NodeID
@ -43,12 +43,20 @@ type searchInfo struct {
// This stores a map of active searches. // This stores a map of active searches.
type searches struct { type searches struct {
core *Core core *Core
reconfigure chan chan error
searches map[crypto.NodeID]*searchInfo searches map[crypto.NodeID]*searchInfo
} }
// Intializes the searches struct. // Intializes the searches struct.
func (s *searches) init(core *Core) { func (s *searches) init(core *Core) {
s.core = core s.core = core
s.reconfigure = make(chan chan error, 1)
go func() {
for {
e := <-s.reconfigure
e <- nil
}
}()
s.searches = make(map[crypto.NodeID]*searchInfo) s.searches = make(map[crypto.NodeID]*searchInfo)
} }

View File

@ -18,6 +18,7 @@ import (
// This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API. // This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API.
type sessionInfo struct { type sessionInfo struct {
core *Core core *Core
reconfigure chan chan error
theirAddr address.Address theirAddr address.Address
theirSubnet address.Subnet theirSubnet address.Subnet
theirPermPub crypto.BoxPubKey theirPermPub crypto.BoxPubKey
@ -101,6 +102,7 @@ func (s *sessionInfo) timedout() bool {
// Additionally, stores maps of address/subnet onto keys, and keys onto handles. // Additionally, stores maps of address/subnet onto keys, and keys onto handles.
type sessions struct { type sessions struct {
core *Core core *Core
reconfigure chan chan error
lastCleanup time.Time lastCleanup time.Time
// Maps known permanent keys to their shared key, used by DHT a lot // Maps known permanent keys to their shared key, used by DHT a lot
permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey
@ -112,18 +114,29 @@ type sessions struct {
byTheirPerm map[crypto.BoxPubKey]*crypto.Handle byTheirPerm map[crypto.BoxPubKey]*crypto.Handle
addrToPerm map[address.Address]*crypto.BoxPubKey addrToPerm map[address.Address]*crypto.BoxPubKey
subnetToPerm map[address.Subnet]*crypto.BoxPubKey subnetToPerm map[address.Subnet]*crypto.BoxPubKey
// Options from the session firewall
sessionFirewallEnabled bool
sessionFirewallAllowsDirect bool
sessionFirewallAllowsRemote bool
sessionFirewallAlwaysAllowsOutbound bool
sessionFirewallWhitelist []string
sessionFirewallBlacklist []string
} }
// Initializes the session struct. // Initializes the session struct.
func (ss *sessions) init(core *Core) { func (ss *sessions) init(core *Core) {
ss.core = core ss.core = core
ss.reconfigure = make(chan chan error, 1)
go func() {
for {
e := <-ss.reconfigure
responses := make(map[crypto.Handle]chan error)
for index, session := range ss.sinfos {
responses[index] = make(chan error)
session.reconfigure <- responses[index]
}
for _, response := range responses {
if err := <-response; err != nil {
e <- err
continue
}
}
e <- nil
}
}()
ss.permShared = make(map[crypto.BoxPubKey]*crypto.BoxSharedKey) ss.permShared = make(map[crypto.BoxPubKey]*crypto.BoxSharedKey)
ss.sinfos = make(map[crypto.Handle]*sessionInfo) ss.sinfos = make(map[crypto.Handle]*sessionInfo)
ss.byMySes = make(map[crypto.BoxPubKey]*crypto.Handle) ss.byMySes = make(map[crypto.BoxPubKey]*crypto.Handle)
@ -133,40 +146,28 @@ func (ss *sessions) init(core *Core) {
ss.lastCleanup = time.Now() ss.lastCleanup = time.Now()
} }
// Enable or disable the session firewall // Determines whether the session firewall is enabled.
func (ss *sessions) setSessionFirewallState(enabled bool) { func (ss *sessions) isSessionFirewallEnabled() bool {
ss.sessionFirewallEnabled = enabled ss.core.configMutex.RLock()
} defer ss.core.configMutex.RUnlock()
// Set the session firewall defaults (first parameter is whether to allow return ss.core.config.SessionFirewall.Enable
// sessions from direct peers, second is whether to allow from remote nodes).
func (ss *sessions) setSessionFirewallDefaults(allowsDirect bool, allowsRemote bool, alwaysAllowsOutbound bool) {
ss.sessionFirewallAllowsDirect = allowsDirect
ss.sessionFirewallAllowsRemote = allowsRemote
ss.sessionFirewallAlwaysAllowsOutbound = alwaysAllowsOutbound
}
// Set the session firewall whitelist - nodes always allowed to open sessions.
func (ss *sessions) setSessionFirewallWhitelist(whitelist []string) {
ss.sessionFirewallWhitelist = whitelist
}
// Set the session firewall blacklist - nodes never allowed to open sessions.
func (ss *sessions) setSessionFirewallBlacklist(blacklist []string) {
ss.sessionFirewallBlacklist = blacklist
} }
// Determines whether the session with a given publickey is allowed based on // Determines whether the session with a given publickey is allowed based on
// session firewall rules. // session firewall rules.
func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) bool { func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) bool {
ss.core.configMutex.RLock()
defer ss.core.configMutex.RUnlock()
// Allow by default if the session firewall is disabled // Allow by default if the session firewall is disabled
if !ss.sessionFirewallEnabled { if !ss.isSessionFirewallEnabled() {
return true return true
} }
// Prepare for checking whitelist/blacklist // Prepare for checking whitelist/blacklist
var box crypto.BoxPubKey var box crypto.BoxPubKey
// Reject blacklisted nodes // Reject blacklisted nodes
for _, b := range ss.sessionFirewallBlacklist { for _, b := range ss.core.config.SessionFirewall.BlacklistEncryptionPublicKeys {
key, err := hex.DecodeString(b) key, err := hex.DecodeString(b)
if err == nil { if err == nil {
copy(box[:crypto.BoxPubKeyLen], key) copy(box[:crypto.BoxPubKeyLen], key)
@ -176,7 +177,7 @@ func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) b
} }
} }
// Allow whitelisted nodes // Allow whitelisted nodes
for _, b := range ss.sessionFirewallWhitelist { for _, b := range ss.core.config.SessionFirewall.WhitelistEncryptionPublicKeys {
key, err := hex.DecodeString(b) key, err := hex.DecodeString(b)
if err == nil { if err == nil {
copy(box[:crypto.BoxPubKeyLen], key) copy(box[:crypto.BoxPubKeyLen], key)
@ -186,7 +187,7 @@ func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) b
} }
} }
// Allow outbound sessions if appropriate // Allow outbound sessions if appropriate
if ss.sessionFirewallAlwaysAllowsOutbound { if ss.core.config.SessionFirewall.AlwaysAllowOutbound {
if initiator { if initiator {
return true return true
} }
@ -200,11 +201,11 @@ func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) b
} }
} }
// Allow direct peers if appropriate // Allow direct peers if appropriate
if ss.sessionFirewallAllowsDirect && isDirectPeer { if ss.core.config.SessionFirewall.AllowFromDirect && isDirectPeer {
return true return true
} }
// Allow remote nodes if appropriate // Allow remote nodes if appropriate
if ss.sessionFirewallAllowsRemote && !isDirectPeer { if ss.core.config.SessionFirewall.AllowFromRemote && !isDirectPeer {
return true return true
} }
// Finally, default-deny if not matching any of the above rules // Finally, default-deny if not matching any of the above rules
@ -264,13 +265,12 @@ func (ss *sessions) getByTheirSubnet(snet *address.Subnet) (*sessionInfo, bool)
// Creates a new session and lazily cleans up old/timedout existing sessions. // Creates a new session and lazily cleans up old/timedout existing sessions.
// This includse initializing session info to sane defaults (e.g. lowest supported MTU). // This includse initializing session info to sane defaults (e.g. lowest supported MTU).
func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo {
if ss.sessionFirewallEnabled {
if !ss.isSessionAllowed(theirPermKey, true) { if !ss.isSessionAllowed(theirPermKey, true) {
return nil return nil
} }
}
sinfo := sessionInfo{} sinfo := sessionInfo{}
sinfo.core = ss.core sinfo.core = ss.core
sinfo.reconfigure = make(chan chan error, 1)
sinfo.theirPermPub = *theirPermKey sinfo.theirPermPub = *theirPermKey
pub, priv := crypto.NewBoxKeys() pub, priv := crypto.NewBoxKeys()
sinfo.mySesPub = *pub sinfo.mySesPub = *pub
@ -442,7 +442,7 @@ func (ss *sessions) handlePing(ping *sessionPing) {
// Get the corresponding session (or create a new session) // Get the corresponding session (or create a new session)
sinfo, isIn := ss.getByTheirPerm(&ping.SendPermPub) sinfo, isIn := ss.getByTheirPerm(&ping.SendPermPub)
// Check the session firewall // Check the session firewall
if !isIn && ss.sessionFirewallEnabled { if !isIn && ss.isSessionFirewallEnabled() {
if !ss.isSessionAllowed(&ping.SendPermPub, false) { if !ss.isSessionAllowed(&ping.SendPermPub, false) {
return return
} }
@ -539,6 +539,8 @@ func (sinfo *sessionInfo) doWorker() {
} else { } else {
return return
} }
case e := <-sinfo.reconfigure:
e <- nil
} }
} }
} }
@ -552,17 +554,30 @@ func (sinfo *sessionInfo) doSend(bs []byte) {
} }
// code isn't multithreaded so appending to this is safe // code isn't multithreaded so appending to this is safe
coords := sinfo.coords coords := sinfo.coords
// Read IPv6 flowlabel field (20 bits). // Work out the flowkey - this is used to determine which switch queue
// Assumes packet at least contains IPv6 header. // traffic will be pushed to in the event of congestion
flowkey := uint64(bs[1]&0x0f)<<16 | uint64(bs[2])<<8 | uint64(bs[3]) var flowkey uint64
// Check if the flowlabel was specified // Get the IP protocol version from the packet
if flowkey == 0 { switch bs[0] & 0xf0 {
// Does the packet meet the minimum UDP packet size? (others are bigger) case 0x40: // IPv4 packet
if len(bs) >= 48 { // Check the packet meets minimum UDP packet length
// Is the protocol TCP, UDP, SCTP? if len(bs) >= 24 {
// Is the protocol TCP, UDP or SCTP?
if bs[9] == 0x06 || bs[9] == 0x11 || bs[9] == 0x84 {
ihl := bs[0] & 0x0f * 4 // Header length
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 {
// Is the protocol TCP, UDP or SCTP?
if bs[6] == 0x06 || bs[6] == 0x11 || bs[6] == 0x84 { if bs[6] == 0x06 || bs[6] == 0x11 || bs[6] == 0x84 {
// if flowlabel was unspecified (0), try to use known protocols' ports
// protokey: proto | sport | dport
flowkey = uint64(bs[6])<<32 /* proto */ | flowkey = uint64(bs[6])<<32 /* proto */ |
uint64(bs[40])<<24 | uint64(bs[41])<<16 /* sport */ | uint64(bs[40])<<24 | uint64(bs[41])<<16 /* sport */ |
uint64(bs[42])<<8 | uint64(bs[43]) /* dport */ uint64(bs[42])<<8 | uint64(bs[43]) /* dport */
@ -610,5 +625,8 @@ func (sinfo *sessionInfo) doRecv(p *wire_trafficPacket) {
sinfo.updateNonce(&p.Nonce) sinfo.updateNonce(&p.Nonce)
sinfo.time = time.Now() sinfo.time = time.Now()
sinfo.bytesRecvd += uint64(len(bs)) sinfo.bytesRecvd += uint64(len(bs))
sinfo.core.router.toRecv <- router_recvPacket{bs, sinfo} select {
case sinfo.core.router.toRecv <- router_recvPacket{bs, sinfo}:
default: // avoid deadlocks, maybe do this somewhere else?...
}
} }

141
src/yggdrasil/stream.go Normal file
View File

@ -0,0 +1,141 @@
package yggdrasil
import (
"errors"
"fmt"
"io"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
// Test that this matches the interface we expect
var _ = linkInterfaceMsgIO(&stream{})
type stream struct {
rwc io.ReadWriteCloser
inputBuffer []byte // Incoming packet stream
frag [2 * streamMsgSize]byte // Temporary data read off the underlying rwc, on its way to the inputBuffer
outputBuffer [2 * streamMsgSize]byte // Temporary data about to be written to the rwc
}
func (s *stream) close() error {
return s.rwc.Close()
}
const streamMsgSize = 2048 + 65535
var streamMsg = [...]byte{0xde, 0xad, 0xb1, 0x75} // "dead bits"
func (s *stream) init(rwc io.ReadWriteCloser) {
// TODO have this also do the metadata handshake and create the peer struct
s.rwc = rwc
// TODO call something to do the metadata exchange
}
// writeMsg writes a message with stream padding, and is *not* thread safe.
func (s *stream) writeMsg(bs []byte) (int, error) {
buf := s.outputBuffer[:0]
buf = append(buf, streamMsg[:]...)
buf = wire_put_uint64(uint64(len(bs)), buf)
padLen := len(buf)
buf = append(buf, bs...)
var bn int
for bn < len(buf) {
n, err := s.rwc.Write(buf[bn:])
bn += n
if err != nil {
l := bn - padLen
if l < 0 {
l = 0
}
return l, err
}
}
return len(bs), nil
}
// readMsg reads a message from the stream, accounting for stream padding, and is *not* thread safe.
func (s *stream) readMsg() ([]byte, error) {
for {
buf := s.inputBuffer
msg, ok, err := stream_chopMsg(&buf)
switch {
case err != nil:
// Something in the stream format is corrupt
return nil, fmt.Errorf("message error: %v", err)
case ok:
// Copy the packet into bs, shift the buffer, and return
msg = append(util.GetBytes(), msg...)
s.inputBuffer = append(s.inputBuffer[:0], buf...)
return msg, nil
default:
// Wait for the underlying reader to return enough info for us to proceed
n, err := s.rwc.Read(s.frag[:])
if n > 0 {
s.inputBuffer = append(s.inputBuffer, s.frag[:n]...)
} else if err != nil {
return nil, err
}
}
}
}
// Writes metadata bytes without stream padding, meant to be temporary
func (s *stream) _sendMetaBytes(metaBytes []byte) error {
var written int
for written < len(metaBytes) {
n, err := s.rwc.Write(metaBytes)
written += n
if err != nil {
return err
}
}
return nil
}
// Reads metadata bytes without stream padding, meant to be temporary
func (s *stream) _recvMetaBytes() ([]byte, error) {
var meta version_metadata
frag := meta.encode()
metaBytes := make([]byte, 0, len(frag))
for len(metaBytes) < len(frag) {
n, err := s.rwc.Read(frag)
if err != nil {
return nil, err
}
metaBytes = append(metaBytes, frag[:n]...)
}
return metaBytes, nil
}
// This takes a pointer to a slice as an argument. It checks if there's a
// complete message and, if so, slices out those parts and returns the message,
// true, and nil. If there's no error, but also no complete message, it returns
// nil, false, and nil. If there's an error, it returns nil, false, and the
// error, which the reader then handles (currently, by returning from the
// reader, which causes the connection to close).
func stream_chopMsg(bs *[]byte) ([]byte, bool, error) {
// Returns msg, ok, err
if len(*bs) < len(streamMsg) {
return nil, false, nil
}
for idx := range streamMsg {
if (*bs)[idx] != streamMsg[idx] {
return nil, false, errors.New("bad message")
}
}
msgLen, msgLenLen := wire_decode_uint64((*bs)[len(streamMsg):])
if msgLen > streamMsgSize {
return nil, false, errors.New("oversized message")
}
msgBegin := len(streamMsg) + msgLenLen
msgEnd := msgBegin + int(msgLen)
if msgLenLen == 0 || len(*bs) < msgEnd {
// We don't have the full message
// Need to buffer this and wait for the rest to come in
return nil, false, nil
}
msg := (*bs)[msgBegin:msgEnd]
(*bs) = (*bs)[msgEnd:]
return msg, true, nil
}

View File

@ -4,7 +4,7 @@ package yggdrasil
// It routes packets based on distance on the spanning tree // It routes packets based on distance on the spanning tree
// In general, this is *not* equivalent to routing on the tree // In general, this is *not* equivalent to routing on the tree
// It falls back to the tree in the worst case, but it can take shortcuts too // It falls back to the tree in the worst case, but it can take shortcuts too
// This is the part that makse routing reasonably efficient on scale-free graphs // This is the part that makes routing reasonably efficient on scale-free graphs
// TODO document/comment everything in a lot more detail // TODO document/comment everything in a lot more detail
@ -162,6 +162,7 @@ type switchData struct {
// All the information stored by the switch. // All the information stored by the switch.
type switchTable struct { type switchTable struct {
core *Core core *Core
reconfigure chan chan error
key crypto.SigPubKey // Our own key key crypto.SigPubKey // Our own key
time time.Time // Time when locator.tstamp was last updated time time.Time // Time when locator.tstamp was last updated
drop map[crypto.SigPubKey]int64 // Tstamp associated with a dropped root drop map[crypto.SigPubKey]int64 // Tstamp associated with a dropped root
@ -181,11 +182,14 @@ type switchTable struct {
const SwitchQueueTotalMinSize = 4 * 1024 * 1024 const SwitchQueueTotalMinSize = 4 * 1024 * 1024
// Initializes the switchTable struct. // Initializes the switchTable struct.
func (t *switchTable) init(core *Core, key crypto.SigPubKey) { func (t *switchTable) init(core *Core) {
now := time.Now() now := time.Now()
t.core = core t.core = core
t.key = key t.reconfigure = make(chan chan error, 1)
locator := switchLocator{root: key, tstamp: now.Unix()} t.core.configMutex.RLock()
t.key = t.core.sigPub
t.core.configMutex.RUnlock()
locator := switchLocator{root: t.key, tstamp: now.Unix()}
peers := make(map[switchPort]peerInfo) peers := make(map[switchPort]peerInfo)
t.data = switchData{locator: locator, peers: peers} t.data = switchData{locator: locator, peers: peers}
t.updater.Store(&sync.Once{}) t.updater.Store(&sync.Once{})
@ -277,6 +281,7 @@ func (t *switchTable) cleanPeers() {
if now.Sub(peer.time) > switch_timeout+switch_throttle { if now.Sub(peer.time) > switch_timeout+switch_throttle {
// Longer than switch_timeout to make sure we don't remove a working peer because the root stopped responding. // Longer than switch_timeout to make sure we don't remove a working peer because the root stopped responding.
delete(t.data.peers, port) delete(t.data.peers, port)
go t.core.peers.removePeer(port) // TODO figure out if it's safe to do this without a goroutine, or make it safe
} }
} }
if _, isIn := t.data.peers[t.parent]; !isIn { if _, isIn := t.data.peers[t.parent]; !isIn {
@ -410,6 +415,7 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep
// Update the matrix of peer "faster" thresholds // Update the matrix of peer "faster" thresholds
if reprocessing { if reprocessing {
sender.faster = oldSender.faster sender.faster = oldSender.faster
sender.time = oldSender.time
} else { } else {
sender.faster = make(map[switchPort]uint64, len(oldSender.faster)) sender.faster = make(map[switchPort]uint64, len(oldSender.faster))
for port, peer := range t.data.peers { for port, peer := range t.data.peers {
@ -559,7 +565,7 @@ func (t *switchTable) getTable() lookupTable {
// Starts the switch worker // Starts the switch worker
func (t *switchTable) start() error { func (t *switchTable) start() error {
t.core.log.Println("Starting switch") t.core.log.Infoln("Starting switch")
go t.doWorker() go t.doWorker()
return nil return nil
} }
@ -774,6 +780,7 @@ func (t *switchTable) doWorker() {
t.queues.bufs = make(map[string]switch_buffer) // Packets per PacketStreamID (string) t.queues.bufs = make(map[string]switch_buffer) // Packets per PacketStreamID (string)
idle := make(map[switchPort]struct{}) // this is to deduplicate things idle := make(map[switchPort]struct{}) // this is to deduplicate things
for { for {
t.core.log.Debugf("Switch state: idle = %d, buffers = %d", len(idle), len(t.queues.bufs))
select { select {
case bytes := <-t.packetIn: case bytes := <-t.packetIn:
// Try to send it somewhere (or drop it if it's corrupt or at a dead end) // Try to send it somewhere (or drop it if it's corrupt or at a dead end)
@ -808,6 +815,8 @@ func (t *switchTable) doWorker() {
} }
case f := <-t.admin: case f := <-t.admin:
f() f()
case e := <-t.reconfigure:
e <- nil
} }
} }
} }

View File

@ -15,31 +15,28 @@ package yggdrasil
// See version.go for version metadata format // See version.go for version metadata format
import ( import (
"errors" "context"
"fmt" "fmt"
"io"
"math/rand" "math/rand"
"net" "net"
"sync" "sync"
"sync/atomic"
"time" "time"
"golang.org/x/net/proxy" "golang.org/x/net/proxy"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
) )
const tcp_msgSize = 2048 + 65535 // TODO figure out what makes sense const default_timeout = 6 * time.Second
const default_tcp_timeout = 6 * time.Second const tcp_ping_interval = (default_timeout * 2 / 3)
const tcp_ping_interval = (default_tcp_timeout * 2 / 3)
// The TCP listener and information about active TCP connections, to avoid duplication. // The TCP listener and information about active TCP connections, to avoid duplication.
type tcpInterface struct { type tcpInterface struct {
core *Core core *Core
reconfigure chan chan error
serv net.Listener serv net.Listener
tcp_timeout time.Duration stop chan bool
addr string
mutex sync.Mutex // Protecting the below mutex sync.Mutex // Protecting the below
calls map[string]struct{} calls map[string]struct{}
conns map[tcpInfo](chan struct{}) conns map[tcpInfo](chan struct{})
@ -80,19 +77,48 @@ func (iface *tcpInterface) connectSOCKS(socksaddr, peeraddr string) {
} }
// Initializes the struct. // Initializes the struct.
func (iface *tcpInterface) init(core *Core, addr string, readTimeout int32) (err error) { func (iface *tcpInterface) init(core *Core) (err error) {
iface.core = core iface.core = core
iface.stop = make(chan bool, 1)
iface.tcp_timeout = time.Duration(readTimeout) * time.Millisecond iface.reconfigure = make(chan chan error, 1)
if iface.tcp_timeout >= 0 && iface.tcp_timeout < default_tcp_timeout { go func() {
iface.tcp_timeout = default_tcp_timeout for {
e := <-iface.reconfigure
iface.core.configMutex.RLock()
updated := iface.core.config.Listen != iface.core.configOld.Listen
iface.core.configMutex.RUnlock()
if updated {
iface.stop <- true
iface.serv.Close()
e <- iface.listen()
} else {
e <- nil
} }
}
}()
iface.serv, err = net.Listen("tcp", addr) return iface.listen()
}
func (iface *tcpInterface) listen() error {
var err error
iface.core.configMutex.RLock()
iface.addr = iface.core.config.Listen
iface.core.configMutex.RUnlock()
ctx := context.Background()
lc := net.ListenConfig{
Control: iface.tcpContext,
}
iface.serv, err = lc.Listen(ctx, "tcp", iface.addr)
if err == nil { if err == nil {
iface.mutex.Lock()
iface.calls = make(map[string]struct{}) iface.calls = make(map[string]struct{})
iface.conns = make(map[tcpInfo](chan struct{})) iface.conns = make(map[tcpInfo](chan struct{}))
iface.mutex.Unlock()
go iface.listener() go iface.listener()
return nil
} }
return err return err
@ -101,14 +127,40 @@ func (iface *tcpInterface) init(core *Core, addr string, readTimeout int32) (err
// Runs the listener, which spawns off goroutines for incoming connections. // Runs the listener, which spawns off goroutines for incoming connections.
func (iface *tcpInterface) listener() { func (iface *tcpInterface) listener() {
defer iface.serv.Close() defer iface.serv.Close()
iface.core.log.Println("Listening for TCP on:", iface.serv.Addr().String()) iface.core.log.Infoln("Listening for TCP on:", iface.serv.Addr().String())
for { for {
sock, err := iface.serv.Accept() sock, err := iface.serv.Accept()
if err != nil {
iface.core.log.Errorln("Failed to accept connection:", err)
return
}
select {
case <-iface.stop:
iface.core.log.Errorln("Stopping listener")
return
default:
if err != nil { if err != nil {
panic(err) panic(err)
} }
go iface.handler(sock, true) go iface.handler(sock, true)
} }
}
}
// Checks if we already have a connection to this node
func (iface *tcpInterface) isAlreadyConnected(info tcpInfo) bool {
iface.mutex.Lock()
defer iface.mutex.Unlock()
_, isIn := iface.conns[info]
return isIn
}
// Checks if we already are calling this address
func (iface *tcpInterface) isAlreadyCalling(saddr string) bool {
iface.mutex.Lock()
defer iface.mutex.Unlock()
_, isIn := iface.calls[saddr]
return isIn
} }
// Checks if a connection already exists. // Checks if a connection already exists.
@ -122,25 +174,20 @@ func (iface *tcpInterface) call(saddr string, socksaddr *string, sintf string) {
if sintf != "" { if sintf != "" {
callname = fmt.Sprintf("%s/%s", saddr, sintf) callname = fmt.Sprintf("%s/%s", saddr, sintf)
} }
quit := false if iface.isAlreadyCalling(callname) {
return
}
iface.mutex.Lock() iface.mutex.Lock()
if _, isIn := iface.calls[callname]; isIn {
quit = true
} else {
iface.calls[callname] = struct{}{} iface.calls[callname] = struct{}{}
iface.mutex.Unlock()
defer func() { defer func() {
// Block new calls for a little while, to mitigate livelock scenarios // Block new calls for a little while, to mitigate livelock scenarios
time.Sleep(default_tcp_timeout) time.Sleep(default_timeout)
time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond) time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
iface.mutex.Lock() iface.mutex.Lock()
delete(iface.calls, callname) delete(iface.calls, callname)
iface.mutex.Unlock() iface.mutex.Unlock()
}() }()
}
iface.mutex.Unlock()
if quit {
return
}
var conn net.Conn var conn net.Conn
var err error var err error
if socksaddr != nil { if socksaddr != nil {
@ -164,12 +211,14 @@ func (iface *tcpInterface) call(saddr string, socksaddr *string, sintf string) {
}, },
} }
} else { } else {
dialer := net.Dialer{} dialer := net.Dialer{
Control: iface.tcpContext,
}
if sintf != "" { if sintf != "" {
ief, err := net.InterfaceByName(sintf) ief, err := net.InterfaceByName(sintf)
if err != nil { if err != nil {
return return
} else { }
if ief.Flags&net.FlagUp == 0 { if ief.Flags&net.FlagUp == 0 {
return return
} }
@ -179,15 +228,30 @@ func (iface *tcpInterface) call(saddr string, socksaddr *string, sintf string) {
if err != nil { if err != nil {
return return
} }
for _, addr := range addrs { for addrindex, addr := range addrs {
src, _, err := net.ParseCIDR(addr.String()) src, _, err := net.ParseCIDR(addr.String())
if err != nil { if err != nil {
continue continue
} }
if (src.To4() != nil) == (dst.IP.To4() != nil) && src.IsGlobalUnicast() { if src.Equal(dst.IP) {
continue
}
if !src.IsGlobalUnicast() && !src.IsLinkLocalUnicast() {
continue
}
bothglobal := src.IsGlobalUnicast() == dst.IP.IsGlobalUnicast()
bothlinklocal := src.IsLinkLocalUnicast() == dst.IP.IsLinkLocalUnicast()
if !bothglobal && !bothlinklocal {
continue
}
if (src.To4() != nil) != (dst.IP.To4() != nil) {
continue
}
if bothglobal || bothlinklocal || addrindex == len(addrs)-1 {
dialer.LocalAddr = &net.TCPAddr{ dialer.LocalAddr = &net.TCPAddr{
IP: src, IP: src,
Port: 0, Port: 0,
Zone: sintf,
} }
break break
} }
@ -197,7 +261,7 @@ func (iface *tcpInterface) call(saddr string, socksaddr *string, sintf string) {
} }
} }
} }
}
conn, err = dialer.Dial("tcp", saddr) conn, err = dialer.Dial("tcp", saddr)
if err != nil { if err != nil {
return return
@ -207,237 +271,21 @@ func (iface *tcpInterface) call(saddr string, socksaddr *string, sintf string) {
}() }()
} }
// This exchanges/checks connection metadata, sets up the peer struct, sets up the writer goroutine, and then runs the reader within the current goroutine.
// It defers a bunch of cleanup stuff to tear down all of these things when the reader exists (e.g. due to a closed connection or a timeout).
func (iface *tcpInterface) handler(sock net.Conn, incoming bool) { func (iface *tcpInterface) handler(sock net.Conn, incoming bool) {
defer sock.Close() defer sock.Close()
iface.setExtraOptions(sock) iface.setExtraOptions(sock)
// Get our keys stream := stream{}
myLinkPub, myLinkPriv := crypto.NewBoxKeys() // ephemeral link keys stream.init(sock)
meta := version_getBaseMetadata() local, _, _ := net.SplitHostPort(sock.LocalAddr().String())
meta.box = iface.core.boxPub remote, _, _ := net.SplitHostPort(sock.RemoteAddr().String())
meta.sig = iface.core.sigPub remotelinklocal := net.ParseIP(remote).IsLinkLocalUnicast()
meta.link = *myLinkPub name := "tcp://" + sock.RemoteAddr().String()
metaBytes := meta.encode() link, err := iface.core.link.create(&stream, name, "tcp", local, remote, incoming, remotelinklocal)
_, err := sock.Write(metaBytes)
if err != nil { if err != nil {
return iface.core.log.Println(err)
panic(err)
} }
if iface.tcp_timeout > 0 { iface.core.log.Debugln("DEBUG: starting handler for", name)
sock.SetReadDeadline(time.Now().Add(iface.tcp_timeout)) err = link.handler()
} iface.core.log.Debugln("DEBUG: stopped handler for", name, err)
_, err = sock.Read(metaBytes)
if err != nil {
return
}
meta = version_metadata{} // Reset to zero value
if !meta.decode(metaBytes) || !meta.check() {
// Failed to decode and check the metadata
// If it's a version mismatch issue, then print an error message
base := version_getBaseMetadata()
if meta.meta == base.meta {
if meta.ver > base.ver {
iface.core.log.Println("Failed to connect to node:", sock.RemoteAddr().String(), "version:", meta.ver)
} else if meta.ver == base.ver && meta.minorVer > base.minorVer {
iface.core.log.Println("Failed to connect to node:", sock.RemoteAddr().String(), "version:", fmt.Sprintf("%d.%d", meta.ver, meta.minorVer))
}
}
// TODO? Block forever to prevent future connection attempts? suppress future messages about the same node?
return
}
info := tcpInfo{ // used as a map key, so don't include ephemeral link key
box: meta.box,
sig: meta.sig,
}
// Quit the parent call if this is a connection to ourself
equiv := func(k1, k2 []byte) bool {
for idx := range k1 {
if k1[idx] != k2[idx] {
return false
}
}
return true
}
if equiv(info.box[:], iface.core.boxPub[:]) {
return
}
if equiv(info.sig[:], iface.core.sigPub[:]) {
return
}
// Check if we're authorized to connect to this key / IP
if incoming && !iface.core.peers.isAllowedEncryptionPublicKey(&info.box) {
// Allow unauthorized peers if they're link-local
raddrStr, _, _ := net.SplitHostPort(sock.RemoteAddr().String())
raddr := net.ParseIP(raddrStr)
if !raddr.IsLinkLocalUnicast() {
return
}
}
// Check if we already have a connection to this node, close and block if yes
info.localAddr, _, _ = net.SplitHostPort(sock.LocalAddr().String())
info.remoteAddr, _, _ = net.SplitHostPort(sock.RemoteAddr().String())
iface.mutex.Lock()
if blockChan, isIn := iface.conns[info]; isIn {
iface.mutex.Unlock()
sock.Close()
<-blockChan
return
}
blockChan := make(chan struct{})
iface.conns[info] = blockChan
iface.mutex.Unlock()
defer func() {
iface.mutex.Lock()
delete(iface.conns, info)
iface.mutex.Unlock()
close(blockChan)
}()
// Note that multiple connections to the same node are allowed
// E.g. over different interfaces
p := iface.core.peers.newPeer(&info.box, &info.sig, crypto.GetSharedKey(myLinkPriv, &meta.link), sock.RemoteAddr().String())
p.linkOut = make(chan []byte, 1)
in := func(bs []byte) {
p.handlePacket(bs)
}
out := make(chan []byte, 1)
defer close(out)
go func() {
// This goroutine waits for outgoing packets, link protocol traffic, or sends idle keep-alive traffic
send := func(msg []byte) {
msgLen := wire_encode_uint64(uint64(len(msg)))
buf := net.Buffers{tcp_msg[:], msgLen, msg}
buf.WriteTo(sock)
atomic.AddUint64(&p.bytesSent, uint64(len(tcp_msg)+len(msgLen)+len(msg)))
util.PutBytes(msg)
}
timerInterval := tcp_ping_interval
timer := time.NewTimer(timerInterval)
defer timer.Stop()
for {
select {
case msg := <-p.linkOut:
// Always send outgoing link traffic first, if needed
send(msg)
continue
default:
}
// Otherwise wait reset the timer and wait for something to do
timer.Stop()
select {
case <-timer.C:
default:
}
timer.Reset(timerInterval)
select {
case _ = <-timer.C:
send(nil) // TCP keep-alive traffic
case msg := <-p.linkOut:
send(msg)
case msg, ok := <-out:
if !ok {
return
}
send(msg) // Block until the socket write has finished
// Now inform the switch that we're ready for more traffic
p.core.switchTable.idleIn <- p.port
}
}
}()
p.core.switchTable.idleIn <- p.port // Start in the idle state
p.out = func(msg []byte) {
defer func() { recover() }()
out <- msg
}
p.close = func() { sock.Close() }
go p.linkLoop()
defer func() {
// Put all of our cleanup here...
p.core.peers.removePeer(p.port)
}()
us, _, _ := net.SplitHostPort(sock.LocalAddr().String())
them, _, _ := net.SplitHostPort(sock.RemoteAddr().String())
themNodeID := crypto.GetNodeID(&info.box)
themAddr := address.AddrForNodeID(themNodeID)
themAddrString := net.IP(themAddr[:]).String()
themString := fmt.Sprintf("%s@%s", themAddrString, them)
iface.core.log.Println("Connected:", themString, "source", us)
err = iface.reader(sock, in) // In this goroutine, because of defers
if err == nil {
iface.core.log.Println("Disconnected:", themString, "source", us)
} else {
iface.core.log.Println("Disconnected:", themString, "source", us, "with error:", err)
}
return
}
// This reads from the socket into a []byte buffer for incomping messages.
// It copies completed messages out of the cache into a new slice, and passes them to the peer struct via the provided `in func([]byte)` argument.
// Then it shifts the incomplete fragments of data forward so future reads won't overwrite it.
func (iface *tcpInterface) reader(sock net.Conn, in func([]byte)) error {
bs := make([]byte, 2*tcp_msgSize)
frag := bs[:0]
for {
if iface.tcp_timeout > 0 {
sock.SetReadDeadline(time.Now().Add(iface.tcp_timeout))
}
n, err := sock.Read(bs[len(frag):])
if n > 0 {
frag = bs[:len(frag)+n]
for {
msg, ok, err2 := tcp_chop_msg(&frag)
if err2 != nil {
return fmt.Errorf("Message error: %v", err2)
}
if !ok {
// We didn't get the whole message yet
break
}
newMsg := append(util.GetBytes(), msg...)
in(newMsg)
util.Yield()
}
frag = append(bs[:0], frag...)
}
if err != nil || n == 0 {
if err != io.EOF {
return err
}
return nil
}
}
}
////////////////////////////////////////////////////////////////////////////////
// These are 4 bytes of padding used to catch if something went horribly wrong with the tcp connection.
var tcp_msg = [...]byte{0xde, 0xad, 0xb1, 0x75} // "dead bits"
// This takes a pointer to a slice as an argument.
// It checks if there's a complete message and, if so, slices out those parts and returns the message, true, and nil.
// If there's no error, but also no complete message, it returns nil, false, and nil.
// If there's an error, it returns nil, false, and the error, which the reader then handles (currently, by returning from the reader, which causes the connection to close).
func tcp_chop_msg(bs *[]byte) ([]byte, bool, error) {
// Returns msg, ok, err
if len(*bs) < len(tcp_msg) {
return nil, false, nil
}
for idx := range tcp_msg {
if (*bs)[idx] != tcp_msg[idx] {
return nil, false, errors.New("Bad message!")
}
}
msgLen, msgLenLen := wire_decode_uint64((*bs)[len(tcp_msg):])
if msgLen > tcp_msgSize {
return nil, false, errors.New("Oversized message!")
}
msgBegin := len(tcp_msg) + msgLenLen
msgEnd := msgBegin + int(msgLen)
if msgLenLen == 0 || len(*bs) < msgEnd {
// We don't have the full message
// Need to buffer this and wait for the rest to come in
return nil, false, nil
}
msg := (*bs)[msgBegin:msgEnd]
(*bs) = (*bs)[msgEnd:]
return msg, true, nil
} }

View File

@ -0,0 +1,28 @@
// +build darwin
package yggdrasil
import (
"syscall"
"golang.org/x/sys/unix"
)
// WARNING: This context is used both by net.Dialer and net.Listen in tcp.go
func (iface *tcpInterface) tcpContext(network, address string, c syscall.RawConn) error {
var control error
var recvanyif error
control = c.Control(func(fd uintptr) {
// sys/socket.h: #define SO_RECV_ANYIF 0x1104
recvanyif = unix.SetsockoptInt(int(fd), syscall.SOL_SOCKET, 0x1104, 1)
})
switch {
case recvanyif != nil:
return recvanyif
default:
return control
}
}

View File

@ -0,0 +1,13 @@
// +build !darwin
package yggdrasil
import (
"syscall"
)
// WARNING: This context is used both by net.Dialer and net.Listen in tcp.go
func (iface *tcpInterface) tcpContext(network, address string, c syscall.RawConn) error {
return nil
}

View File

@ -5,6 +5,8 @@ package yggdrasil
import ( import (
"bytes" "bytes"
"errors" "errors"
"fmt"
"net"
"sync" "sync"
"time" "time"
@ -42,22 +44,46 @@ func getSupportedMTU(mtu int) int {
func (tun *tunAdapter) init(core *Core, send chan<- []byte, recv <-chan []byte) { func (tun *tunAdapter) init(core *Core, send chan<- []byte, recv <-chan []byte) {
tun.Adapter.init(core, send, recv) tun.Adapter.init(core, send, recv)
tun.icmpv6.init(tun) tun.icmpv6.init(tun)
go func() {
for {
e := <-tun.reconfigure
tun.core.configMutex.RLock()
updated := tun.core.config.IfName != tun.core.configOld.IfName ||
tun.core.config.IfTAPMode != tun.core.configOld.IfTAPMode ||
tun.core.config.IfMTU != tun.core.configOld.IfMTU
tun.core.configMutex.RUnlock()
if updated {
tun.core.log.Warnln("Reconfiguring TUN/TAP is not supported yet")
e <- nil
} else {
e <- nil
}
}
}()
} }
// Starts the setup process for the TUN/TAP adapter, and if successful, starts // Starts the setup process for the TUN/TAP adapter, and if successful, starts
// the read/write goroutines to handle packets on that interface. // the read/write goroutines to handle packets on that interface.
func (tun *tunAdapter) start(ifname string, iftapmode bool, addr string, mtu int) error { func (tun *tunAdapter) start() error {
if ifname == "none" { tun.core.configMutex.RLock()
return nil ifname := tun.core.config.IfName
} iftapmode := tun.core.config.IfTAPMode
addr := fmt.Sprintf("%s/%d", net.IP(tun.core.router.addr[:]).String(), 8*len(address.GetPrefix())-1)
mtu := tun.core.config.IfMTU
tun.core.configMutex.RUnlock()
if ifname != "none" {
if err := tun.setup(ifname, iftapmode, addr, mtu); err != nil { if err := tun.setup(ifname, iftapmode, addr, mtu); err != nil {
return err return err
} }
}
if ifname == "none" || ifname == "dummy" {
return nil
}
tun.mutex.Lock() tun.mutex.Lock()
tun.isOpen = true tun.isOpen = true
tun.mutex.Unlock() tun.mutex.Unlock()
go func() { tun.core.log.Println("WARNING: tun.read() exited with error:", tun.read()) }() go func() { tun.core.log.Errorln("WARNING: tun.read() exited with error:", tun.read()) }()
go func() { tun.core.log.Println("WARNING: tun.write() exited with error:", tun.write()) }() go func() { tun.core.log.Errorln("WARNING: tun.write() exited with error:", tun.write()) }()
if iftapmode { if iftapmode {
go func() { go func() {
for { for {

View File

@ -114,9 +114,9 @@ func (tun *tunAdapter) setupAddress(addr string) error {
} }
// Friendly output // Friendly output
tun.core.log.Printf("Interface name: %s", tun.iface.Name()) tun.core.log.Infof("Interface name: %s", tun.iface.Name())
tun.core.log.Printf("Interface IPv6: %s", addr) tun.core.log.Infof("Interface IPv6: %s", addr)
tun.core.log.Printf("Interface MTU: %d", tun.mtu) tun.core.log.Infof("Interface MTU: %d", tun.mtu)
// Create the MTU request // Create the MTU request
var ir in6_ifreq_mtu var ir in6_ifreq_mtu
@ -126,15 +126,15 @@ func (tun *tunAdapter) setupAddress(addr string) error {
// Set the MTU // Set the MTU
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(syscall.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 { if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(syscall.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 {
err = errno err = errno
tun.core.log.Printf("Error in SIOCSIFMTU: %v", errno) tun.core.log.Errorf("Error in SIOCSIFMTU: %v", errno)
// Fall back to ifconfig to set the MTU // Fall back to ifconfig to set the MTU
cmd := exec.Command("ifconfig", tun.iface.Name(), "mtu", string(tun.mtu)) cmd := exec.Command("ifconfig", tun.iface.Name(), "mtu", string(tun.mtu))
tun.core.log.Printf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " ")) tun.core.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
tun.core.log.Printf("SIOCSIFMTU fallback failed: %v.", err) tun.core.log.Errorf("SIOCSIFMTU fallback failed: %v.", err)
tun.core.log.Println(string(output)) tun.core.log.Traceln(string(output))
} }
} }
@ -155,15 +155,15 @@ func (tun *tunAdapter) setupAddress(addr string) error {
// Set the interface address // Set the interface address
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(SIOCSIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 { if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(SIOCSIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 {
err = errno err = errno
tun.core.log.Printf("Error in SIOCSIFADDR_IN6: %v", errno) tun.core.log.Errorf("Error in SIOCSIFADDR_IN6: %v", errno)
// Fall back to ifconfig to set the address // Fall back to ifconfig to set the address
cmd := exec.Command("ifconfig", tun.iface.Name(), "inet6", addr) cmd := exec.Command("ifconfig", tun.iface.Name(), "inet6", addr)
tun.core.log.Printf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " ")) tun.core.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
tun.core.log.Printf("SIOCSIFADDR_IN6 fallback failed: %v.", err) tun.core.log.Errorf("SIOCSIFADDR_IN6 fallback failed: %v.", err)
tun.core.log.Println(string(output)) tun.core.log.Traceln(string(output))
} }
} }

View File

@ -1,3 +1,5 @@
// +build !mobile
package yggdrasil package yggdrasil
// The darwin platform specific tun parts // The darwin platform specific tun parts
@ -16,7 +18,7 @@ import (
// Configures the "utun" adapter with the correct IPv6 address and MTU. // Configures the "utun" adapter with the correct IPv6 address and MTU.
func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
if iftapmode { if iftapmode {
tun.core.log.Printf("TAP mode is not supported on this platform, defaulting to TUN") tun.core.log.Warnln("TAP mode is not supported on this platform, defaulting to TUN")
} }
config := water.Config{DeviceType: water.TUN} config := water.Config{DeviceType: water.TUN}
iface, err := water.New(config) iface, err := water.New(config)
@ -96,19 +98,19 @@ func (tun *tunAdapter) setupAddress(addr string) error {
copy(ir.ifr_name[:], tun.iface.Name()) copy(ir.ifr_name[:], tun.iface.Name())
ir.ifru_mtu = uint32(tun.mtu) ir.ifru_mtu = uint32(tun.mtu)
tun.core.log.Printf("Interface name: %s", ar.ifra_name) tun.core.log.Infof("Interface name: %s", ar.ifra_name)
tun.core.log.Printf("Interface IPv6: %s", addr) tun.core.log.Infof("Interface IPv6: %s", addr)
tun.core.log.Printf("Interface MTU: %d", ir.ifru_mtu) tun.core.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 {
err = errno err = errno
tun.core.log.Printf("Error in darwin_SIOCAIFADDR_IN6: %v", errno) tun.core.log.Errorf("Error in darwin_SIOCAIFADDR_IN6: %v", errno)
return err 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 {
err = errno err = errno
tun.core.log.Printf("Error in SIOCSIFMTU: %v", errno) tun.core.log.Errorf("Error in SIOCSIFMTU: %v", errno)
return err return err
} }

View File

@ -0,0 +1,19 @@
// +build mobile
package yggdrasil
// This is to catch unsupported platforms
// If your platform supports tun devices, you could try configuring it manually
// Creates the TUN/TAP adapter, if supported by the Water library. Note that
// no guarantees are made at this point on an unsupported platform.
func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
tun.mtu = getSupportedMTU(mtu)
return tun.setupAddress(addr)
}
// We don't know how to set the IPv6 address on an unknown platform, therefore
// write about it to stdout and don't try to do anything further.
func (tun *tunAdapter) setupAddress(addr string) error {
return nil
}

View File

@ -1,3 +1,5 @@
// +build !mobile
package yggdrasil package yggdrasil
// The linux platform specific tun parts // The linux platform specific tun parts
@ -38,9 +40,9 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
} }
} }
// Friendly output // Friendly output
tun.core.log.Printf("Interface name: %s", tun.iface.Name()) tun.core.log.Infof("Interface name: %s", tun.iface.Name())
tun.core.log.Printf("Interface IPv6: %s", addr) tun.core.log.Infof("Interface IPv6: %s", addr)
tun.core.log.Printf("Interface MTU: %d", tun.mtu) tun.core.log.Infof("Interface MTU: %d", tun.mtu)
return tun.setupAddress(addr) return tun.setupAddress(addr)
} }

View File

@ -1,4 +1,4 @@
// +build !linux,!darwin,!windows,!openbsd,!freebsd,!netbsd // +build !linux,!darwin,!windows,!openbsd,!freebsd,!netbsd,!mobile
package yggdrasil package yggdrasil
@ -28,6 +28,6 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
// We don't know how to set the IPv6 address on an unknown platform, therefore // We don't know how to set the IPv6 address on an unknown platform, therefore
// write about it to stdout and don't try to do anything further. // write about it to stdout and don't try to do anything further.
func (tun *tunAdapter) setupAddress(addr string) error { func (tun *tunAdapter) setupAddress(addr string) error {
tun.core.log.Println("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr) tun.core.log.Warnln("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr)
return nil return nil
} }

View File

@ -15,7 +15,7 @@ import (
// delegate the hard work to "netsh". // delegate the hard work to "netsh".
func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
if !iftapmode { if !iftapmode {
tun.core.log.Printf("TUN mode is not supported on this platform, defaulting to TAP") tun.core.log.Warnln("TUN mode is not supported on this platform, defaulting to TAP")
} }
config := water.Config{DeviceType: water.TAP} config := water.Config{DeviceType: water.TAP}
config.PlatformSpecificParams.ComponentID = "tap0901" config.PlatformSpecificParams.ComponentID = "tap0901"
@ -34,16 +34,16 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
tun.core.log.Printf("netsh command: %v", strings.Join(cmd.Args, " ")) tun.core.log.Printf("netsh command: %v", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
tun.core.log.Printf("Windows netsh failed: %v.", err) tun.core.log.Errorf("Windows netsh failed: %v.", err)
tun.core.log.Println(string(output)) tun.core.log.Traceln(string(output))
return err return err
} }
cmd = exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=ENABLED") cmd = exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=ENABLED")
tun.core.log.Printf("netsh command: %v", strings.Join(cmd.Args, " ")) tun.core.log.Printf("netsh command: %v", strings.Join(cmd.Args, " "))
output, err = cmd.CombinedOutput() output, err = cmd.CombinedOutput()
if err != nil { if err != nil {
tun.core.log.Printf("Windows netsh failed: %v.", err) tun.core.log.Errorf("Windows netsh failed: %v.", err)
tun.core.log.Println(string(output)) tun.core.log.Traceln(string(output))
return err return err
} }
// Get a new iface // Get a new iface
@ -58,9 +58,9 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
panic(err) panic(err)
} }
// Friendly output // Friendly output
tun.core.log.Printf("Interface name: %s", tun.iface.Name()) tun.core.log.Infof("Interface name: %s", tun.iface.Name())
tun.core.log.Printf("Interface IPv6: %s", addr) tun.core.log.Infof("Interface IPv6: %s", addr)
tun.core.log.Printf("Interface MTU: %d", tun.mtu) tun.core.log.Infof("Interface MTU: %d", tun.mtu)
return tun.setupAddress(addr) return tun.setupAddress(addr)
} }
@ -71,11 +71,11 @@ func (tun *tunAdapter) setupMTU(mtu int) error {
fmt.Sprintf("interface=%s", tun.iface.Name()), fmt.Sprintf("interface=%s", tun.iface.Name()),
fmt.Sprintf("mtu=%d", mtu), fmt.Sprintf("mtu=%d", mtu),
"store=active") "store=active")
tun.core.log.Printf("netsh command: %v", strings.Join(cmd.Args, " ")) tun.core.log.Debugln("netsh command: %v", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
tun.core.log.Printf("Windows netsh failed: %v.", err) tun.core.log.Errorf("Windows netsh failed: %v.", err)
tun.core.log.Println(string(output)) tun.core.log.Traceln(string(output))
return err return err
} }
return nil return nil
@ -88,11 +88,11 @@ func (tun *tunAdapter) setupAddress(addr string) error {
fmt.Sprintf("interface=%s", tun.iface.Name()), fmt.Sprintf("interface=%s", tun.iface.Name()),
fmt.Sprintf("addr=%s", addr), fmt.Sprintf("addr=%s", addr),
"store=active") "store=active")
tun.core.log.Printf("netsh command: %v", strings.Join(cmd.Args, " ")) tun.core.log.Debugln("netsh command: %v", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
tun.core.log.Printf("Windows netsh failed: %v.", err) tun.core.log.Errorf("Windows netsh failed: %v.", err)
tun.core.log.Println(string(output)) tun.core.log.Traceln(string(output))
return err return err
} }
return nil return nil