5
0
mirror of https://github.com/cwinfo/yggdrasil-go.git synced 2024-11-26 17:41:36 +00:00

Merge pull request #478 from yggdrasil-network/develop

Version 0.3.6
This commit is contained in:
Neil Alexander 2019-08-03 12:00:00 +01:00 committed by GitHub
commit a2966291b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 4183 additions and 3084 deletions

View File

@ -5,7 +5,7 @@ version: 2.1
jobs:
build-linux:
docker:
- image: circleci/golang:1.12
- image: circleci/golang:1.12.7
steps:
- checkout
@ -16,13 +16,21 @@ jobs:
mkdir /tmp/upload
echo 'export CINAME=$(sh contrib/semver/name.sh)' >> $BASH_ENV
echo 'export CIVERSION=$(sh contrib/semver/version.sh --bare)' >> $BASH_ENV
echo 'export CIVERSIONRPM=$(sh contrib/semver/version.sh --bare | tr "-" ".")' >> $BASH_ENV
echo 'export CIBRANCH=$(echo $CIRCLE_BRANCH | tr -d "/")' >> $BASH_ENV
case "$CINAME" in \
"yggdrasil") (echo 'export CICONFLICTS=yggdrasil-develop' >> $BASH_ENV) ;; \
"yggdrasil-develop") (echo 'export CICONFLICTS=yggdrasil' >> $BASH_ENV) ;; \
*) (echo 'export CICONFLICTS="yggdrasil yggdrasil-develop"' >> $BASH_ENV) ;; \
esac
git config --global user.email "$(git log --format='%ae' HEAD -1)";
git config --global user.name "$(git log --format='%an' HEAD -1)";
- run:
name: Install alien
name: Install RPM utilities
command: |
sudo apt-get install -y alien
sudo apt-get install -y rpm file
mkdir -p ~/rpmbuild/BUILD ~/rpmbuild/RPMS ~/rpmbuild/SOURCES ~/rpmbuild/SPECS ~/rpmbuild/SRPMS
- run:
name: Test debug builds
@ -31,7 +39,7 @@ jobs:
test -f yggdrasil && test -f yggdrasilctl
- run:
name: Build for Linux (including Debian packages and RPMs)
name: Build for Linux (including Debian packages)
command: |
rm -f {yggdrasil,yggdrasilctl}
PKGARCH=amd64 sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-amd64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-amd64;
@ -40,9 +48,24 @@ jobs:
PKGARCH=mips sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-mips && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-mips;
PKGARCH=armhf sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-armhf && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-armhf;
PKGARCH=arm64 sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-arm64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-arm64;
sudo alien --to-rpm yggdrasil*.deb --scripts --keep-version && mv *.rpm /tmp/upload/;
mv *.deb /tmp/upload/
- run:
name: Build for Linux (RPM packages)
command: |
git clone https://github.com/yggdrasil-network/yggdrasil-package-rpm ~/rpmbuild/SPECS
cd ../ && tar -czvf ~/rpmbuild/SOURCES/v$CIVERSIONRPM --transform "s/project/yggdrasil-go-$CIBRANCH-$CIVERSIONRPM/" project
sed -i "s/yggdrasil-go/yggdrasil-go-$CIBRANCH/" ~/rpmbuild/SPECS/yggdrasil.spec
sed -i "s/^PKGNAME=yggdrasil/PKGNAME=yggdrasil-$CIBRANCH/" ~/rpmbuild/SPECS/yggdrasil.spec
sed -i "s/^Name\:.*/Name\: $CINAME/" ~/rpmbuild/SPECS/yggdrasil.spec
sed -i "s/^Version\:.*/Version\: $CIVERSIONRPM/" ~/rpmbuild/SPECS/yggdrasil.spec
sed -i "s/^Conflicts\:.*/Conflicts\: $CICONFLICTS/" ~/rpmbuild/SPECS/yggdrasil.spec
cat ~/rpmbuild/SPECS/yggdrasil.spec
GOARCH=amd64 rpmbuild -v --nodeps --target=x86_64 -ba ~/rpmbuild/SPECS/yggdrasil.spec
#GOARCH=386 rpmbuild -v --nodeps --target=i386 -bb ~/rpmbuild/SPECS/yggdrasil.spec
find ~/rpmbuild/RPMS/ -name '*.rpm' -exec mv {} /tmp/upload \;
find ~/rpmbuild/SRPMS/ -name '*.rpm' -exec mv {} /tmp/upload \;
- run:
name: Build for EdgeRouter
command: |
@ -79,11 +102,11 @@ jobs:
echo -e "Host *\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config
- run:
name: Install Go 1.12
name: Install Go 1.12.7
command: |
cd /tmp
curl -LO https://dl.google.com/go/go1.12.darwin-amd64.pkg
sudo installer -pkg /tmp/go1.12.darwin-amd64.pkg -target /
curl -LO https://dl.google.com/go/go1.12.7.darwin-amd64.pkg
sudo installer -pkg /tmp/go1.12.7.darwin-amd64.pkg -target /
#- run:
# name: Install Gomobile
@ -119,7 +142,7 @@ jobs:
build-other:
docker:
- image: circleci/golang:1.12
- image: circleci/golang:1.12.7
steps:
- checkout

View File

@ -25,6 +25,39 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- in case of vulnerabilities.
-->
## [0.3.6] - 2019-08-03
### Added
- Yggdrasil now has a public API with interfaces such as `yggdrasil.ConnDialer`, `yggdrasil.ConnListener` and `yggdrasil.Conn` for using Yggdrasil as a transport directly within applications
- Session gatekeeper functions, part of the API, which can be used to control whether to allow or reject incoming or outgoing sessions dynamically (compared to the previous fixed whitelist/blacklist approach)
- Support for logging to files or syslog (where supported)
- Platform defaults now include the ability to set sane defaults for multicast interfaces
### Changed
- Following a massive refactoring exercise, Yggdrasil's codebase has now been broken out into modules
- Core node functionality in the `yggdrasil` package with a public API
- This allows Yggdrasil to be integrated directly into other applications and used as a transport
- IP-specific code has now been moved out of the core `yggdrasil` package, making Yggdrasil effectively protocol-agnostic
- Multicast peer discovery functionality is now in the `multicast` package
- Admin socket functionality is now in the `admin` package and uses the Yggdrasil public API
- TUN/TAP, ICMPv6 and all IP-specific functionality is now in the `tuntap` package
- `PPROF` debug output is now sent to `stderr` instead of `stdout`
- Node IPv6 addresses on macOS are now configured as `secured`
- Upstream dependency references have been updated, which includes a number of fixes in the Water library
### Fixed
- Multicast discovery is no longer disabled if the nominated interfaces aren't available on the system yet, e.g. during boot
- Multicast interfaces are now re-evaluated more frequently so that Yggdrasil doesn't need to be restarted to use interfaces that have become available since startup
- Admin socket error cases are now handled better
- Various fixes in the TUN/TAP module, particularly surrounding Windows platform support
- Invalid keys will now cause the node to fail to start, rather than starting but silently not working as before
- Session MTUs are now always calculated correctly, in some cases they were incorrectly defaulting to 1280 before
- Multiple searches now don't take place for a single connection
- Concurrency bugs fixed
- Fixed a number of bugs in the ICMPv6 neighbor solicitation in the TUN/TAP code
- A case where peers weren't always added correctly if one or more peers were unreachable has been fixed
- Searches which include the local node are now handled correctly
- Lots of small bug tweaks and clean-ups throughout the codebase
## [0.3.5] - 2019-03-13
### Fixed
- The `AllowedEncryptionPublicKeys` option has now been fixed to handle incoming connections properly and no longer blocks outgoing connections (this was broken in v0.3.4)

View File

@ -35,6 +35,7 @@ some of the below:
- FreeBSD
- OpenBSD
- NetBSD
- OpenWrt
Please see our [Platforms](https://yggdrasil-network.github.io/) pages for more
specific information about each of our supported platforms, including

29
build
View File

@ -7,40 +7,45 @@ PKGNAME=${PKGNAME:-$(sh contrib/semver/name.sh)}
PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)}
LDFLAGS="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER"
ARGS="-v"
while getopts "udaitc:l:" option
while getopts "uaitc:l:dro:" option
do
case "${option}"
case "$option"
in
u) UPX=true;;
d) DEBUG=true;;
i) IOS=true;;
a) ANDROID=true;;
t) TABLES=true;;
c) GCFLAGS="$GCFLAGS $OPTARG";;
l) LDFLAGS="$LDFLAGS $OPTARG";;
d) ARGS="$ARGS -tags debug" DEBUG=true;;
r) ARGS="$ARGS -race";;
o) ARGS="$ARGS -o $OPTARG";;
esac
done
if [ -z $TABLES ]; then
STRIP="-s -w"
if [ -z $TABLES ] && [ -z $DEBUG ]; then
LDFLAGS="$LDFLAGS -s -w"
fi
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
gomobile bind -target ios -tags mobile -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \
github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil \
github.com/yggdrasil-network/yggdrasil-go/src/config \
github.com/yggdrasil-network/yggdrasil-extras/src/mobile
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
gomobile bind -target android -tags mobile -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \
github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil \
github.com/yggdrasil-network/yggdrasil-go/src/config \
github.com/yggdrasil-network/yggdrasil-extras/src/mobile
else
for CMD in `ls cmd/` ; do
echo "Building: $CMD"
go build $ARGS -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" ./cmd/$CMD
if [ $DEBUG ]; then
go build -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" -tags debug -v ./cmd/$CMD
else
go build -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" -v ./cmd/$CMD
fi
if [ $UPX ]; then
upx --brute $CMD
fi

View File

@ -2,6 +2,7 @@ package main
import (
"bytes"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
@ -14,22 +15,28 @@ import (
"golang.org/x/text/encoding/unicode"
"github.com/gologme/log"
gsyslog "github.com/hashicorp/go-syslog"
"github.com/hjson/hjson-go"
"github.com/kardianos/minwinsvc"
"github.com/mitchellh/mapstructure"
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/multicast"
"github.com/yggdrasil-network/yggdrasil-go/src/tuntap"
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
)
type nodeConfig = config.NodeConfig
type Core = yggdrasil.Core
type node struct {
core Core
core yggdrasil.Core
state *config.NodeState
tuntap tuntap.TunAdapter
multicast multicast.Multicast
admin admin.AdminSocket
}
func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *nodeConfig {
func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *config.NodeConfig {
// Use a configuration file. If -useconf, the configuration will be read
// from stdin. If -useconffile, the configuration will be read from the
// filesystem.
@ -72,77 +79,6 @@ func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *nodeCo
panic(err)
}
json.Unmarshal(confJson, &cfg)
// For now we will do a little bit to help the user adjust their
// configuration to match the new configuration format, as some of the key
// names have changed recently.
changes := map[string]string{
"Multicast": "",
"ReadTimeout": "",
"LinkLocal": "MulticastInterfaces",
"BoxPub": "EncryptionPublicKey",
"BoxPriv": "EncryptionPrivateKey",
"SigPub": "SigningPublicKey",
"SigPriv": "SigningPrivateKey",
"AllowedBoxPubs": "AllowedEncryptionPublicKeys",
}
// Loop over the mappings aove and see if we have anything to fix.
for from, to := range changes {
if _, ok := dat[from]; ok {
if to == "" {
if !*normaliseconf {
log.Println("Warning: Config option", from, "is deprecated")
}
} else {
if !*normaliseconf {
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
// new name then set it to the old value. This makes sure that we
// don't overwrite something that was put there intentionally.
if _, ok := dat[to]; !ok {
dat[to] = dat[from]
}
}
}
}
// Check to see if the peers are in a parsable format, if not then default
// them to the TCP scheme
if peers, ok := dat["Peers"].([]interface{}); ok {
for index, peer := range peers {
uri := peer.(string)
if strings.HasPrefix(uri, "tcp://") || strings.HasPrefix(uri, "socks://") {
continue
}
if strings.HasPrefix(uri, "tcp:") {
uri = uri[4:]
}
(dat["Peers"].([]interface{}))[index] = "tcp://" + uri
}
}
// Now do the same with the interface peers
if interfacepeers, ok := dat["InterfacePeers"].(map[string]interface{}); ok {
for intf, peers := range interfacepeers {
for index, peer := range peers.([]interface{}) {
uri := peer.(string)
if strings.HasPrefix(uri, "tcp://") || strings.HasPrefix(uri, "socks://") {
continue
}
if strings.HasPrefix(uri, "tcp:") {
uri = uri[4:]
}
((dat["InterfacePeers"].(map[string]interface{}))[intf]).([]interface{})[index] = "tcp://" + uri
}
}
}
// Do a quick check for old-format Listen statement so that mapstructure
// doesn't fail and crash
if listen, ok := dat["Listen"].(string); ok {
if strings.HasPrefix(listen, "tcp://") {
dat["Listen"] = []string{listen}
} else {
dat["Listen"] = []string{"tcp://" + listen}
}
}
// Overlay our newly mapped configuration onto the autoconf node config that
// we generated above.
if err = mapstructure.Decode(dat, &cfg); err != nil {
@ -179,14 +115,15 @@ func main() {
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")
logto := flag.String("logto", "stdout", "file path to log to, \"syslog\" or \"stdout\"")
flag.Parse()
var cfg *nodeConfig
var cfg *config.NodeConfig
var err error
switch {
case *version:
fmt.Println("Build name:", yggdrasil.GetBuildName())
fmt.Println("Build version:", yggdrasil.GetBuildVersion())
fmt.Println("Build name:", yggdrasil.BuildName())
fmt.Println("Build version:", yggdrasil.BuildVersion())
os.Exit(0)
case *autoconf:
// Use an autoconf-generated config, this will give us random keys and
@ -226,7 +163,23 @@ func main() {
return
}
// Create a new logger that logs output to stdout.
logger := log.New(os.Stdout, "", log.Flags())
var logger *log.Logger
switch *logto {
case "stdout":
logger = log.New(os.Stdout, "", log.Flags())
case "syslog":
if syslogger, err := gsyslog.NewLogger(gsyslog.LOG_NOTICE, "DAEMON", yggdrasil.BuildName()); err == nil {
logger = log.New(syslogger, "", log.Flags())
}
default:
if logfd, err := os.OpenFile(*logto, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil {
logger = log.New(logfd, "", log.Flags())
}
}
if logger == nil {
logger = log.New(os.Stdout, "", log.Flags())
logger.Warnln("Logging defaulting to stdout")
}
//logger.EnableLevel("error")
//logger.EnableLevel("warn")
//logger.EnableLevel("info")
@ -244,22 +197,44 @@ func main() {
// Setup the Yggdrasil node itself. The node{} type includes a Core, so we
// don't need to create this manually.
n := node{}
// Now that we have a working configuration, we can now actually start
// Yggdrasil. This will start the router, switch, DHT node, TCP and UDP
// sockets, TUN/TAP adapter and multicast discovery port.
if err := n.core.Start(cfg, logger); err != nil {
// Now start Yggdrasil - this starts the DHT, router, switch and other core
// components needed for Yggdrasil to operate
n.state, err = n.core.Start(cfg, logger)
if err != nil {
logger.Errorln("An error occurred during startup")
panic(err)
}
// The Stop function ensures that the TUN/TAP adapter is correctly shut down
// before the program exits.
defer func() {
n.core.Stop()
}()
// Register the session firewall gatekeeper function
n.core.SetSessionGatekeeper(n.sessionFirewall)
// Start the admin socket
n.admin.Init(&n.core, n.state, logger, nil)
if err := n.admin.Start(); err != nil {
logger.Errorln("An error occurred starting admin socket:", err)
}
// Start the multicast interface
n.multicast.Init(&n.core, n.state, logger, nil)
if err := n.multicast.Start(); err != nil {
logger.Errorln("An error occurred starting multicast:", err)
}
n.multicast.SetupAdminHandlers(&n.admin)
// Start the TUN/TAP interface
if listener, err := n.core.ConnListen(); err == nil {
if dialer, err := n.core.ConnDialer(); err == nil {
n.tuntap.Init(n.state, logger, listener, dialer)
if err := n.tuntap.Start(); err != nil {
logger.Errorln("An error occurred starting TUN/TAP:", err)
}
n.tuntap.SetupAdminHandlers(&n.admin)
} else {
logger.Errorln("Unable to get Dialer:", err)
}
} else {
logger.Errorln("Unable to get Listener:", err)
}
// Make some nice output that tells us what our IPv6 address and subnet are.
// This is just logged to stdout for the user.
address := n.core.GetAddress()
subnet := n.core.GetSubnet()
address := n.core.Address()
subnet := n.core.Subnet()
logger.Infof("Your IPv6 address is %s", address.String())
logger.Infof("Your IPv6 subnet is %s", subnet.String())
// Catch interrupts from the operating system to exit gracefully.
@ -267,25 +242,96 @@ func main() {
r := make(chan os.Signal, 1)
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.
winTerminate := func() {
c <- os.Interrupt
}
minwinsvc.SetOnExit(winTerminate)
// Capture the service being stopped on Windows.
minwinsvc.SetOnExit(n.shutdown)
defer n.shutdown()
// Wait for the terminate/interrupt signal. Once a signal is received, the
// deferred Stop function above will run which will shut down TUN/TAP.
for {
select {
case _ = <-c:
goto exit
case _ = <-r:
if *useconffile != "" {
cfg = readConfig(useconf, useconffile, normaliseconf)
n.core.UpdateConfig(cfg)
n.tuntap.UpdateConfig(cfg)
n.multicast.UpdateConfig(cfg)
} else {
logger.Errorln("Reloading config at runtime is only possible with -useconffile")
}
case _ = <-c:
goto exit
}
}
exit:
}
func (n *node) shutdown() {
n.core.Stop()
n.admin.Stop()
n.multicast.Stop()
n.tuntap.Stop()
os.Exit(0)
}
func (n *node) sessionFirewall(pubkey *crypto.BoxPubKey, initiator bool) bool {
n.state.Mutex.RLock()
defer n.state.Mutex.RUnlock()
// Allow by default if the session firewall is disabled
if !n.state.Current.SessionFirewall.Enable {
return true
}
// Prepare for checking whitelist/blacklist
var box crypto.BoxPubKey
// Reject blacklisted nodes
for _, b := range n.state.Current.SessionFirewall.BlacklistEncryptionPublicKeys {
key, err := hex.DecodeString(b)
if err == nil {
copy(box[:crypto.BoxPubKeyLen], key)
if box == *pubkey {
return false
}
}
}
// Allow whitelisted nodes
for _, b := range n.state.Current.SessionFirewall.WhitelistEncryptionPublicKeys {
key, err := hex.DecodeString(b)
if err == nil {
copy(box[:crypto.BoxPubKeyLen], key)
if box == *pubkey {
return true
}
}
}
// Allow outbound sessions if appropriate
if n.state.Current.SessionFirewall.AlwaysAllowOutbound {
if initiator {
return true
}
}
// Look and see if the pubkey is that of a direct peer
var isDirectPeer bool
for _, peer := range n.core.GetPeers() {
if peer.PublicKey == *pubkey {
isDirectPeer = true
break
}
}
// Allow direct peers if appropriate
if n.state.Current.SessionFirewall.AllowFromDirect && isDirectPeer {
return true
}
// Allow remote nodes if appropriate
if n.state.Current.SessionFirewall.AllowFromRemote && !isDirectPeer {
return true
}
// Finally, default-deny if not matching any of the above rules
return false
}

View File

@ -200,7 +200,7 @@ func main() {
if !keysOrdered {
for k := range slv.(map[string]interface{}) {
if !*verbose {
if k == "box_pub_key" || k == "box_sig_key" || k == "nodeinfo" {
if k == "box_pub_key" || k == "box_sig_key" || k == "nodeinfo" || k == "was_mtu_fixed" {
continue
}
}
@ -277,6 +277,9 @@ func main() {
fmt.Println("Coords:", coords)
}
if *verbose {
if nodeID, ok := v.(map[string]interface{})["node_id"].(string); ok {
fmt.Println("Node ID:", nodeID)
}
if boxPubKey, ok := v.(map[string]interface{})["box_pub_key"].(string); ok {
fmt.Println("Public encryption key:", boxPubKey)
}

View File

@ -27,8 +27,9 @@ elif [ $PKGARCH = "mipsel" ]; then GOARCH=mipsle GOOS=linux ./build
elif [ $PKGARCH = "mips" ]; then GOARCH=mips64 GOOS=linux ./build
elif [ $PKGARCH = "armhf" ]; then GOARCH=arm GOOS=linux GOARM=6 ./build
elif [ $PKGARCH = "arm64" ]; then GOARCH=arm64 GOOS=linux ./build
elif [ $PKGARCH = "armel" ]; then GOARCH=arm GOOS=linux GOARM=5 ./build
else
echo "Specify PKGARCH=amd64,i386,mips,mipsel,armhf,arm64"
echo "Specify PKGARCH=amd64,i386,mips,mipsel,armhf,arm64,armel"
exit 1
fi

View File

@ -0,0 +1,150 @@
This software is released into the public domain. As such, it can be
used under the Unlicense or CC0 public domain dedications.
The Unlicense
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
CC0 1.0 Universal
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator and
subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for the
purpose of contributing to a commons of creative, cultural and scientific
works ("Commons") that the public can reliably and without fear of later
claims of infringement build upon, modify, incorporate in other works, reuse
and redistribute as freely as possible in any form whatsoever and for any
purposes, including without limitation commercial purposes. These owners may
contribute to the Commons to promote the ideal of a free culture and the
further production of creative, cultural and scientific works, or to gain
reputation or greater distribution for their Work in part through the use and
efforts of others.
For these and/or other purposes and motivations, and without any expectation
of additional consideration or compensation, the person associating CC0 with a
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
and publicly distribute the Work under its terms, with knowledge of his or her
Copyright and Related Rights in the Work and the meaning and intended legal
effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not limited
to, the following:
i. the right to reproduce, adapt, distribute, perform, display, communicate,
and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or likeness
depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data in
a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation thereof,
including any amended or successor version of such directive); and
vii. other similar, equivalent or corresponding rights throughout the world
based on applicable law or treaty, and any national implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention of,
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
and Related Rights and associated claims and causes of action, whether now
known or unknown (including existing as well as future claims and causes of
action), in the Work (i) in all territories worldwide, (ii) for the maximum
duration provided by applicable law or treaty (including future time
extensions), (iii) in any current or future medium and for any number of
copies, and (iv) for any purpose whatsoever, including without limitation
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
the Waiver for the benefit of each member of the public at large and to the
detriment of Affirmer's heirs and successors, fully intending that such Waiver
shall not be subject to revocation, rescission, cancellation, termination, or
any other legal or equitable action to disrupt the quiet enjoyment of the Work
by the public as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason be
judged legally invalid or ineffective under applicable law, then the Waiver
shall be preserved to the maximum extent permitted taking into account
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
is so judged Affirmer hereby grants to each affected person a royalty-free,
non transferable, non sublicensable, non exclusive, irrevocable and
unconditional license to exercise Affirmer's Copyright and Related Rights in
the Work (i) in all territories worldwide, (ii) for the maximum duration
provided by applicable law or treaty (including future time extensions), (iii)
in any current or future medium and for any number of copies, and (iv) for any
purpose whatsoever, including without limitation commercial, advertising or
promotional purposes (the "License"). The License shall be deemed effective as
of the date CC0 was applied by Affirmer to the Work. Should any part of the
License for any reason be judged legally invalid or ineffective under
applicable law, such partial invalidity or ineffectiveness shall not
invalidate the remainder of the License, and in such case Affirmer hereby
affirms that he or she will not (i) exercise any of his or her remaining
Copyright and Related Rights in the Work or (ii) assert any associated claims
and causes of action with respect to the Work, in either case contrary to
Affirmer's express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or warranties
of any kind concerning the Work, express, implied, statutory or otherwise,
including without limitation warranties of title, merchantability, fitness
for a particular purpose, non infringement, or the absence of latent or
other defects, accuracy, or the present or absence of errors, whether or not
discoverable, all to the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without limitation
any person's Copyright and Related Rights in the Work. Further, Affirmer
disclaims responsibility for obtaining any necessary consents, permissions
or other rights required for any use of the Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to this
CC0 or use of the Work.
For more information, please see
<http://creativecommons.org/publicdomain/zero/1.0/>

View File

@ -0,0 +1,12 @@
.PHONY: all
all: util yggdrasil-brute-multi-curve25519 yggdrasil-brute-multi-ed25519
util: util.c
gcc -Wall -std=c89 -O3 -c -o util.o util.c
yggdrasil-brute-multi-ed25519: yggdrasil-brute-multi-ed25519.c util.o
gcc -Wall -std=c89 -O3 -o yggdrasil-brute-multi-ed25519 -lsodium yggdrasil-brute-multi-ed25519.c util.o
yggdrasil-brute-multi-curve25519: yggdrasil-brute-multi-curve25519.c util.o
gcc -Wall -std=c89 -O3 -o yggdrasil-brute-multi-curve25519 -lsodium yggdrasil-brute-multi-curve25519.c util.o

View File

@ -0,0 +1,8 @@
# yggdrasil-brute-simple
Simple program for finding curve25519 and ed25519 public keys whose sha512 hash has many leading ones.
Because ed25519 private keys consist of a seed that is hashed to find the secret part of the keypair,
this program is near optimal for finding ed25519 keypairs. Curve25519 key generation, on the other hand,
could be further optimized with elliptic curve magic.
Depends on libsodium.

View File

@ -0,0 +1,62 @@
#include "yggdrasil-brute.h"
int find_where(unsigned char hash[64], unsigned char besthashlist[NUMKEYS][64]) {
/* Where to insert hash into sorted hashlist */
int j;
int where = -1;
for (j = 0; j < NUMKEYS; ++j) {
if (memcmp(hash, besthashlist[j], 64) > 0) ++where;
else break;
}
return where;
}
void insert_64(unsigned char itemlist[NUMKEYS][64], unsigned char item[64], int where) {
int j;
for (j = 0; j < where; ++j) {
memcpy(itemlist[j], itemlist[j+1], 64);
}
memcpy(itemlist[where], item, 64);
}
void insert_32(unsigned char itemlist[NUMKEYS][32], unsigned char item[32], int where) {
int j;
for (j = 0; j < where; ++j) {
memcpy(itemlist[j], itemlist[j+1], 32);
}
memcpy(itemlist[where], item, 32);
}
void make_addr(unsigned char addr[32], unsigned char hash[64]) {
/* Public key hash to yggdrasil ipv6 address */
int i;
int offset;
unsigned char mask;
unsigned char c;
int ones = 0;
unsigned char br = 0; /* false */
for (i = 0; i < 64 && !br; ++i) {
mask = 128;
c = hash[i];
while (mask) {
if (c & mask) {
++ones;
} else {
br = 1; /* true */
break;
}
mask >>= 1;
}
}
addr[0] = 2;
addr[1] = ones;
offset = ones + 1;
for (i = 0; i < 14; ++i) {
c = hash[offset/8] << (offset%8);
c |= hash[offset/8 + 1] >> (8 - offset%8);
addr[i + 2] = c;
offset += 8;
}
}

View File

@ -0,0 +1,105 @@
/*
sk: 32 random bytes
sk[0] &= 248;
sk[31] &= 127;
sk[31] |= 64;
increment sk
pk = curve25519_scalarmult_base(mysecret)
hash = sha512(pk)
if besthash:
bestsk = sk
besthash = hash
*/
#include "yggdrasil-brute.h"
void seed(unsigned char sk[32]) {
randombytes_buf(sk, 32);
sk[0] &= 248;
sk[31] &= 127;
sk[31] |= 64;
}
int main(int argc, char **argv) {
int i;
int j;
unsigned char addr[16];
time_t starttime;
time_t requestedtime;
unsigned char bestsklist[NUMKEYS][32];
unsigned char bestpklist[NUMKEYS][32];
unsigned char besthashlist[NUMKEYS][64];
unsigned char sk[32];
unsigned char pk[32];
unsigned char hash[64];
unsigned int runs = 0;
int where;
if (argc != 2) {
fprintf(stderr, "usage: ./yggdrasil-brute-multi-curve25519 <seconds>\n");
return 1;
}
if (sodium_init() < 0) {
/* panic! the library couldn't be initialized, it is not safe to use */
printf("sodium init failed!\n");
return 1;
}
starttime = time(NULL);
requestedtime = atoi(argv[1]);
if (requestedtime < 0) requestedtime = 0;
fprintf(stderr, "Searching for yggdrasil curve25519 keys (this will take slightly longer than %ld seconds)\n", requestedtime);
sodium_memzero(bestsklist, NUMKEYS * 32);
sodium_memzero(bestpklist, NUMKEYS * 32);
sodium_memzero(besthashlist, NUMKEYS * 64);
seed(sk);
do {
/* generate pubkey, hash, compare, increment secret.
* this loop should take 4 seconds on modern hardware */
for (i = 0; i < (1 << 16); ++i) {
++runs;
if (crypto_scalarmult_curve25519_base(pk, sk) != 0) {
printf("scalarmult to create pub failed!\n");
return 1;
}
crypto_hash_sha512(hash, pk, 32);
where = find_where(hash, besthashlist);
if (where >= 0) {
insert_32(bestsklist, sk, where);
insert_32(bestpklist, pk, where);
insert_64(besthashlist, hash, where);
seed(sk);
}
for (j = 1; j < 31; ++j) if (++sk[j]) break;
}
} while (time(NULL) - starttime < requestedtime || runs < NUMKEYS);
fprintf(stderr, "--------------addr-------------- -----------------------------secret----------------------------- -----------------------------public-----------------------------\n");
for (i = 0; i < NUMKEYS; ++i) {
make_addr(addr, besthashlist[i]);
for (j = 0; j < 16; ++j) printf("%02x", addr[j]);
printf(" ");
for (j = 0; j < 32; ++j) printf("%02x", bestsklist[i][j]);
printf(" ");
for (j = 0; j < 32; ++j) printf("%02x", bestpklist[i][j]);
printf("\n");
}
sodium_memzero(bestsklist, NUMKEYS * 32);
sodium_memzero(sk, 32);
return 0;
}

View File

@ -0,0 +1,106 @@
/*
seed: 32 random bytes
sk: sha512(seed)
sk[0] &= 248
sk[31] &= 127
sk[31] |= 64
pk: scalarmult_ed25519_base(sk)
increment seed
generate sk
generate pk
hash = sha512(mypub)
if besthash:
bestseed = seed
bestseckey = sk
bestpubkey = pk
besthash = hash
*/
#include "yggdrasil-brute.h"
int main(int argc, char **argv) {
int i;
int j;
time_t starttime;
time_t requestedtime;
unsigned char bestsklist[NUMKEYS][64]; /* sk contains pk */
unsigned char besthashlist[NUMKEYS][64];
unsigned char seed[32];
unsigned char sk[64];
unsigned char pk[32];
unsigned char hash[64];
unsigned int runs = 0;
int where;
if (argc != 2) {
fprintf(stderr, "usage: ./yggdrasil-brute-multi-curve25519 <seconds>\n");
return 1;
}
if (sodium_init() < 0) {
/* panic! the library couldn't be initialized, it is not safe to use */
printf("sodium init failed!\n");
return 1;
}
starttime = time(NULL);
requestedtime = atoi(argv[1]);
if (requestedtime < 0) requestedtime = 0;
fprintf(stderr, "Searching for yggdrasil ed25519 keys (this will take slightly longer than %ld seconds)\n", requestedtime);
sodium_memzero(bestsklist, NUMKEYS * 64);
sodium_memzero(besthashlist, NUMKEYS * 64);
randombytes_buf(seed, 32);
do {
/* generate pubkey, hash, compare, increment secret.
* this loop should take 4 seconds on modern hardware */
for (i = 0; i < (1 << 17); ++i) {
++runs;
crypto_hash_sha512(sk, seed, 32);
if (crypto_scalarmult_ed25519_base(pk, sk) != 0) {
printf("scalarmult to create pub failed!\n");
return 1;
}
memcpy(sk + 32, pk, 32);
crypto_hash_sha512(hash, pk, 32);
/* insert into local list of good key */
where = find_where(hash, besthashlist);
if (where >= 0) {
insert_64(bestsklist, sk, where);
insert_64(besthashlist, hash, where);
randombytes_buf(seed, 32);
}
for (j = 1; j < 31; ++j) if (++seed[j]) break;
}
} while (time(NULL) - starttime < requestedtime || runs < NUMKEYS);
fprintf(stderr, "!! Secret key is seed concatenated with public !!\n");
fprintf(stderr, "---hash--- ------------------------------seed------------------------------ -----------------------------public-----------------------------\n");
for (i = 0; i < NUMKEYS; ++i) {
for (j = 0; j < 5; ++j) printf("%02x", besthashlist[i][j]);
printf(" ");
for (j = 0; j < 32; ++j) printf("%02x", bestsklist[i][j]);
printf(" ");
for (j = 32; j < 64; ++j) printf("%02x", bestsklist[i][j]);
printf("\n");
}
sodium_memzero(bestsklist, NUMKEYS * 64);
sodium_memzero(sk, 64);
sodium_memzero(seed, 32);
return 0;
}

View File

@ -0,0 +1,12 @@
#include <sodium.h>
#include <stdio.h> /* printf */
#include <string.h> /* memcpy */
#include <stdlib.h> /* atoi */
#include <time.h> /* time */
#define NUMKEYS 10
void make_addr(unsigned char addr[32], unsigned char hash[64]);
int find_where(unsigned char hash[64], unsigned char besthashlist[NUMKEYS][64]);
void insert_64(unsigned char itemlist[NUMKEYS][64], unsigned char item[64], int where);
void insert_32(unsigned char itemlist[NUMKEYS][32], unsigned char item[32], int where);

View File

@ -1,4 +1,4 @@
# Yggdasil
# Yggdrasil
Note: This is a very rough early draft.

12
go.mod
View File

@ -3,13 +3,15 @@ module github.com/yggdrasil-network/yggdrasil-go
require (
github.com/docker/libcontainer v2.2.1+incompatible
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8
github.com/hashicorp/go-syslog v1.0.0
github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222
github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0
github.com/mitchellh/mapstructure v1.1.2
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091
github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9
golang.org/x/net v0.0.0-20181207154023-610586996380
golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e
golang.org/x/text v0.3.0
github.com/yggdrasil-network/water v0.0.0-20190725123504-a16161896c34
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3
golang.org/x/text v0.3.2
golang.org/x/tools v0.0.0-20190802003818-e9bb7d36c060 // indirect
)

33
go.sum
View File

@ -2,6 +2,8 @@ github.com/docker/libcontainer v2.2.1+incompatible h1:++SbbkCw+X8vAd4j2gOCzZ2Nn7
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/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hjson/hjson-go 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/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk8851WTVWlqMC1ecJH07Ctz+Ezxx4u54g=
@ -12,11 +14,42 @@ github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 h1:1zN6ImoqhSJhN8h
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091/go.mod h1:N20Z5Y8oye9a7HmytmZ+tr8Q2vlP0tAHP13kTHzwvQY=
github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae h1:MYCANF1kehCG6x6G+/9txLfq6n3lS5Vp0Mxn1hdiBAc=
github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs=
github.com/yggdrasil-network/water v0.0.0-20190719211521-a76871ea954b/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs=
github.com/yggdrasil-network/water v0.0.0-20190719213007-b160316e362e/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs=
github.com/yggdrasil-network/water v0.0.0-20190720101301-5db94379a5eb/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs=
github.com/yggdrasil-network/water v0.0.0-20190720145626-28ccb9101d55/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs=
github.com/yggdrasil-network/water v0.0.0-20190725073841-250edb919f8a h1:mQ0mPD+dyB/vaDPyVkCBiXUQu9Or7/cRSTjPlV8tXvw=
github.com/yggdrasil-network/water v0.0.0-20190725073841-250edb919f8a/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs=
github.com/yggdrasil-network/water v0.0.0-20190725123504-a16161896c34 h1:Qh5FE+Q5iGqpmR/FPMYHuoZLN921au/nxAlmKe+Hdbo=
github.com/yggdrasil-network/water v0.0.0-20190725123504-a16161896c34/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20181207154023-610586996380 h1:zPQexyRtNYBc7bcHmehl1dH6TB3qn8zytv8cBGLDNY0=
golang.org/x/net v0.0.0-20181207154023-610586996380/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e h1:njOxP/wVblhCLIUhjHXf6X+dzTt5OQ3vMQo9mkOIKIo=
golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI=
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20190724185037-8aa4eac1a7c1/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20190802003818-e9bb7d36c060/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=

604
src/admin/admin.go Normal file
View File

@ -0,0 +1,604 @@
package admin
import (
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"net"
"net/url"
"os"
"strconv"
"strings"
"time"
"github.com/gologme/log"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
)
// TODO: Add authentication
type AdminSocket struct {
core *yggdrasil.Core
log *log.Logger
reconfigure chan chan error
listenaddr string
listener net.Listener
handlers map[string]handler
}
// Info refers to information that is returned to the admin socket handler.
type Info map[string]interface{}
type handler struct {
args []string // List of human-readable argument names
handler func(Info) (Info, error) // First is input map, second is output
}
// AddHandler is called for each admin function to add the handler and help documentation to the API.
func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc func(Info) (Info, error)) error {
if _, ok := a.handlers[strings.ToLower(name)]; ok {
return errors.New("handler already exists")
}
a.handlers[strings.ToLower(name)] = handler{
args: args,
handler: handlerfunc,
}
return nil
}
// init runs the initial admin setup.
func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) {
a.core = c
a.log = log
a.reconfigure = make(chan chan error, 1)
a.handlers = make(map[string]handler)
go func() {
for {
e := <-a.reconfigure
current, previous := state.GetCurrent(), state.GetPrevious()
if current.AdminListen != previous.AdminListen {
a.listenaddr = current.AdminListen
a.Stop()
a.Start()
}
e <- nil
}
}()
current := state.GetCurrent()
a.listenaddr = current.AdminListen
a.AddHandler("list", []string{}, func(in Info) (Info, error) {
handlers := make(map[string]interface{})
for handlername, handler := range a.handlers {
handlers[handlername] = Info{"fields": handler.args}
}
return Info{"list": handlers}, nil
})
/*
a.AddHandler("dot", []string{}, func(in Info) (Info, error) {
return Info{"dot": string(a.getResponse_dot())}, nil
})
*/
a.AddHandler("getSelf", []string{}, func(in Info) (Info, error) {
ip := c.Address().String()
subnet := c.Subnet()
return Info{
"self": Info{
ip: Info{
"box_pub_key": c.EncryptionPublicKey(),
"build_name": yggdrasil.BuildName(),
"build_version": yggdrasil.BuildVersion(),
"coords": fmt.Sprintf("%v", c.Coords()),
"subnet": subnet.String(),
},
},
}, nil
})
a.AddHandler("getPeers", []string{}, func(in Info) (Info, error) {
peers := make(Info)
for _, p := range a.core.GetPeers() {
addr := *address.AddrForNodeID(crypto.GetNodeID(&p.PublicKey))
so := net.IP(addr[:]).String()
peers[so] = Info{
"port": p.Port,
"uptime": p.Uptime.Seconds(),
"bytes_sent": p.BytesSent,
"bytes_recvd": p.BytesRecvd,
"proto": p.Protocol,
"endpoint": p.Endpoint,
"box_pub_key": p.PublicKey,
}
}
return Info{"peers": peers}, nil
})
a.AddHandler("getSwitchPeers", []string{}, func(in Info) (Info, error) {
switchpeers := make(Info)
for _, s := range a.core.GetSwitchPeers() {
addr := *address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey))
so := fmt.Sprint(s.Port)
switchpeers[so] = Info{
"ip": net.IP(addr[:]).String(),
"coords": fmt.Sprintf("%v", s.Coords),
"port": s.Port,
"bytes_sent": s.BytesSent,
"bytes_recvd": s.BytesRecvd,
"proto": s.Protocol,
"endpoint": s.Endpoint,
"box_pub_key": s.PublicKey,
}
}
return Info{"switchpeers": switchpeers}, nil
})
/*
a.AddHandler("getSwitchQueues", []string{}, func(in Info) (Info, error) {
queues := a.core.GetSwitchQueues()
return Info{"switchqueues": queues.asMap()}, nil
})
*/
a.AddHandler("getDHT", []string{}, func(in Info) (Info, error) {
dht := make(Info)
for _, d := range a.core.GetDHT() {
addr := *address.AddrForNodeID(crypto.GetNodeID(&d.PublicKey))
so := net.IP(addr[:]).String()
dht[so] = Info{
"coords": fmt.Sprintf("%v", d.Coords),
"last_seen": d.LastSeen.Seconds(),
"box_pub_key": d.PublicKey,
}
}
return Info{"dht": dht}, nil
})
a.AddHandler("getSessions", []string{}, func(in Info) (Info, error) {
sessions := make(Info)
for _, s := range a.core.GetSessions() {
addr := *address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey))
so := net.IP(addr[:]).String()
sessions[so] = Info{
"coords": fmt.Sprintf("%v", s.Coords),
"bytes_sent": s.BytesSent,
"bytes_recvd": s.BytesRecvd,
"mtu": s.MTU,
"uptime": s.Uptime.Seconds(),
"was_mtu_fixed": s.WasMTUFixed,
"box_pub_key": s.PublicKey,
}
}
return Info{"sessions": sessions}, nil
})
a.AddHandler("addPeer", []string{"uri", "[interface]"}, func(in Info) (Info, error) {
// Set sane defaults
intf := ""
// Has interface been specified?
if itf, ok := in["interface"]; ok {
intf = itf.(string)
}
if a.core.AddPeer(in["uri"].(string), intf) == nil {
return Info{
"added": []string{
in["uri"].(string),
},
}, nil
} else {
return Info{
"not_added": []string{
in["uri"].(string),
},
}, errors.New("Failed to add peer")
}
})
a.AddHandler("removePeer", []string{"port"}, func(in Info) (Info, error) {
port, err := strconv.ParseInt(fmt.Sprint(in["port"]), 10, 64)
if err != nil {
return Info{}, err
}
if a.core.DisconnectPeer(uint64(port)) == nil {
return Info{
"removed": []string{
fmt.Sprint(port),
},
}, nil
} else {
return Info{
"not_removed": []string{
fmt.Sprint(port),
},
}, errors.New("Failed to remove peer")
}
})
a.AddHandler("getAllowedEncryptionPublicKeys", []string{}, func(in Info) (Info, error) {
return Info{"allowed_box_pubs": a.core.GetAllowedEncryptionPublicKeys()}, nil
})
a.AddHandler("addAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) {
if a.core.AddAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil {
return Info{
"added": []string{
in["box_pub_key"].(string),
},
}, nil
} else {
return Info{
"not_added": []string{
in["box_pub_key"].(string),
},
}, errors.New("Failed to add allowed key")
}
})
a.AddHandler("removeAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) {
if a.core.RemoveAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil {
return Info{
"removed": []string{
in["box_pub_key"].(string),
},
}, nil
} else {
return Info{
"not_removed": []string{
in["box_pub_key"].(string),
},
}, errors.New("Failed to remove allowed key")
}
})
a.AddHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in Info) (Info, error) {
if in["target"] == nil {
in["target"] = "none"
}
result, err := a.core.DHTPing(in["box_pub_key"].(string), in["coords"].(string), in["target"].(string))
if err == nil {
infos := make(map[string]map[string]string, len(result.Infos))
for _, dinfo := range result.Infos {
info := map[string]string{
"box_pub_key": hex.EncodeToString(dinfo.PublicKey[:]),
"coords": fmt.Sprintf("%v", dinfo.Coords),
}
addr := net.IP(address.AddrForNodeID(crypto.GetNodeID(&dinfo.PublicKey))[:]).String()
infos[addr] = info
}
return Info{"nodes": infos}, nil
} else {
return Info{}, err
}
})
a.AddHandler("getNodeInfo", []string{"[box_pub_key]", "[coords]", "[nocache]"}, func(in Info) (Info, error) {
var nocache bool
if in["nocache"] != nil {
nocache = in["nocache"].(string) == "true"
}
var box_pub_key, coords string
if in["box_pub_key"] == nil && in["coords"] == nil {
nodeinfo := a.core.MyNodeInfo()
var jsoninfo interface{}
if err := json.Unmarshal(nodeinfo, &jsoninfo); err != nil {
return Info{}, err
} else {
return Info{"nodeinfo": jsoninfo}, nil
}
} else if in["box_pub_key"] == nil || in["coords"] == nil {
return 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.core.GetNodeInfo(box_pub_key, coords, nocache)
if err == nil {
var m map[string]interface{}
if err = json.Unmarshal(result, &m); err == nil {
return Info{"nodeinfo": m}, nil
} else {
return Info{}, err
}
} else {
return Info{}, err
}
})
}
// start runs the admin API socket to listen for / respond to admin API calls.
func (a *AdminSocket) Start() error {
if a.listenaddr != "none" && a.listenaddr != "" {
go a.listen()
}
return nil
}
// cleans up when stopping
func (a *AdminSocket) Stop() error {
if a.listener != nil {
return a.listener.Close()
} else {
return nil
}
}
// listen is run by start and manages API connections.
func (a *AdminSocket) listen() {
u, err := url.Parse(a.listenaddr)
if err == nil {
switch strings.ToLower(u.Scheme) {
case "unix":
if _, err := os.Stat(a.listenaddr[7:]); err == nil {
a.log.Debugln("Admin socket", a.listenaddr[7:], "already exists, trying to clean up")
if _, err := net.DialTimeout("unix", a.listenaddr[7:], time.Second*2); err == nil || err.(net.Error).Timeout() {
a.log.Errorln("Admin socket", a.listenaddr[7:], "already exists and is in use by another process")
os.Exit(1)
} else {
if err := os.Remove(a.listenaddr[7:]); err == nil {
a.log.Debugln(a.listenaddr[7:], "was cleaned up")
} else {
a.log.Errorln(a.listenaddr[7:], "already exists and was not cleaned up:", err)
os.Exit(1)
}
}
}
a.listener, err = net.Listen("unix", a.listenaddr[7:])
if err == nil {
switch a.listenaddr[7:8] {
case "@": // maybe abstract namespace
default:
if err := os.Chmod(a.listenaddr[7:], 0660); err != nil {
a.log.Warnln("WARNING:", a.listenaddr[:7], "may have unsafe permissions!")
}
}
}
case "tcp":
a.listener, err = net.Listen("tcp", u.Host)
default:
// err = errors.New(fmt.Sprint("protocol not supported: ", u.Scheme))
a.listener, err = net.Listen("tcp", a.listenaddr)
}
} else {
a.listener, err = net.Listen("tcp", a.listenaddr)
}
if err != nil {
a.log.Errorf("Admin socket failed to listen: %v", err)
os.Exit(1)
}
a.log.Infof("%s admin socket listening on %s",
strings.ToUpper(a.listener.Addr().Network()),
a.listener.Addr().String())
defer a.listener.Close()
for {
conn, err := a.listener.Accept()
if err == nil {
go a.handleRequest(conn)
}
}
}
// handleRequest calls the request handler for each request sent to the admin API.
func (a *AdminSocket) handleRequest(conn net.Conn) {
decoder := json.NewDecoder(conn)
encoder := json.NewEncoder(conn)
encoder.SetIndent("", " ")
recv := make(Info)
send := make(Info)
defer func() {
r := recover()
if r != nil {
send = Info{
"status": "error",
"error": "Check your syntax and input types",
}
a.log.Debugln("Admin socket error:", r)
if err := encoder.Encode(&send); err != nil {
a.log.Debugln("Admin socket JSON encode error:", err)
}
conn.Close()
}
}()
for {
// Start with a clean slate on each request
recv = Info{}
send = Info{}
// Decode the input
if err := decoder.Decode(&recv); err != nil {
a.log.Debugln("Admin socket JSON decode error:", err)
return
}
// Send the request back with the response, and default to "error"
// unless the status is changed below by one of the handlers
send["request"] = recv
send["status"] = "error"
n := strings.ToLower(recv["request"].(string))
if _, ok := recv["request"]; !ok {
send["error"] = "No request sent"
goto respond
}
if h, ok := a.handlers[n]; ok {
// Check that we have all the required arguments
for _, arg := range h.args {
// An argument in [square brackets] is optional and not required,
// so we can safely ignore those
if strings.HasPrefix(arg, "[") && strings.HasSuffix(arg, "]") {
continue
}
// Check if the field is missing
if _, ok := recv[arg]; !ok {
send = Info{
"status": "error",
"error": "Expected field missing: " + arg,
"expecting": arg,
}
goto respond
}
}
// By this point we should have all the fields we need, so call
// the handler
response, err := h.handler(recv)
if err != nil {
send["error"] = err.Error()
if response != nil {
send["response"] = response
goto respond
}
} else {
send["status"] = "success"
if response != nil {
send["response"] = response
goto respond
}
}
} else {
// Start with a clean response on each request, which defaults to an error
// state. If a handler is found below then this will be overwritten
send = Info{
"request": recv,
"status": "error",
"error": fmt.Sprintf("Unknown action '%s', try 'list' for help", recv["request"].(string)),
}
goto respond
}
// Send the response back
respond:
if err := encoder.Encode(&send); err != nil {
return
}
// If "keepalive" isn't true then close the connection
if keepalive, ok := recv["keepalive"]; !ok || !keepalive.(bool) {
conn.Close()
}
}
}
// getResponse_dot returns a response for a graphviz dot formatted
// representation of the known parts of the network. This is color-coded and
// labeled, and includes the self node, switch peers, nodes known to the DHT,
// and nodes with open sessions. The graph is structured as a tree with directed
// links leading away from the root.
/*
func (a *AdminSocket) getResponse_dot() []byte {
//self := a.getData_getSelf()
peers := a.core.GetSwitchPeers()
dht := a.core.GetDHT()
sessions := a.core.GetSessions()
// Start building a tree from all known nodes
type nodeInfo struct {
name string
key string
parent string
port uint64
options string
}
infos := make(map[string]nodeInfo)
// Get coords as a slice of strings, FIXME? this looks very fragile
coordSlice := func(coords string) []string {
tmp := strings.Replace(coords, "[", "", -1)
tmp = strings.Replace(tmp, "]", "", -1)
return strings.Split(tmp, " ")
}
// First fill the tree with all known nodes, no parents
addInfo := func(nodes []admin_nodeInfo, options string, tag string) {
for _, node := range nodes {
n := node.asMap()
info := nodeInfo{
key: n["coords"].(string),
options: options,
}
if len(tag) > 0 {
info.name = fmt.Sprintf("%s\n%s", n["ip"].(string), tag)
} else {
info.name = n["ip"].(string)
}
coordsSplit := coordSlice(info.key)
if len(coordsSplit) != 0 {
portStr := coordsSplit[len(coordsSplit)-1]
portUint, err := strconv.ParseUint(portStr, 10, 64)
if err == nil {
info.port = portUint
}
}
infos[info.key] = info
}
}
addInfo(dht, "fillcolor=\"#ffffff\" style=filled fontname=\"sans serif\"", "Known in DHT") // white
addInfo(sessions, "fillcolor=\"#acf3fd\" style=filled fontname=\"sans serif\"", "Open session") // blue
addInfo(peers, "fillcolor=\"#ffffb5\" style=filled fontname=\"sans serif\"", "Connected peer") // yellow
addInfo(append([]admin_nodeInfo(nil), *self), "fillcolor=\"#a5ff8a\" style=filled fontname=\"sans serif\"", "This node") // green
// Now go through and create placeholders for any missing nodes
for _, info := range infos {
// This is ugly string manipulation
coordsSplit := coordSlice(info.key)
for idx := range coordsSplit {
key := fmt.Sprintf("[%v]", strings.Join(coordsSplit[:idx], " "))
newInfo, isIn := infos[key]
if isIn {
continue
}
newInfo.name = "?"
newInfo.key = key
newInfo.options = "fontname=\"sans serif\" style=dashed color=\"#999999\" fontcolor=\"#999999\""
coordsSplit := coordSlice(newInfo.key)
if len(coordsSplit) != 0 {
portStr := coordsSplit[len(coordsSplit)-1]
portUint, err := strconv.ParseUint(portStr, 10, 64)
if err == nil {
newInfo.port = portUint
}
}
infos[key] = newInfo
}
}
// Now go through and attach parents
for _, info := range infos {
pSplit := coordSlice(info.key)
if len(pSplit) > 0 {
pSplit = pSplit[:len(pSplit)-1]
}
info.parent = fmt.Sprintf("[%v]", strings.Join(pSplit, " "))
infos[info.key] = info
}
// Finally, get a sorted list of keys, which we use to organize the output
var keys []string
for _, info := range infos {
keys = append(keys, info.key)
}
// sort
sort.SliceStable(keys, func(i, j int) bool {
return keys[i] < keys[j]
})
sort.SliceStable(keys, func(i, j int) bool {
return infos[keys[i]].port < infos[keys[j]].port
})
// Now print it all out
var out []byte
put := func(s string) {
out = append(out, []byte(s)...)
}
put("digraph {\n")
// First set the labels
for _, key := range keys {
info := infos[key]
put(fmt.Sprintf("\"%v\" [ label = \"%v\" %v ];\n", info.key, info.name, info.options))
}
// Then print the tree structure
for _, key := range keys {
info := infos[key]
if info.key == info.parent {
continue
} // happens for the root, skip it
port := fmt.Sprint(info.port)
style := "fontname=\"sans serif\""
if infos[info.parent].name == "?" || infos[info.key].name == "?" {
style = "fontname=\"sans serif\" style=dashed color=\"#999999\" fontcolor=\"#999999\""
}
put(fmt.Sprintf(" \"%+v\" -> \"%+v\" [ label = \"%v\" %s ];\n", info.parent, info.key, port, style))
}
put("}\n")
return out
}
*/

View File

@ -2,11 +2,43 @@ package config
import (
"encoding/hex"
"sync"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
)
// NodeState represents the active and previous configuration of the node and
// protects it with a mutex
type NodeState struct {
Current NodeConfig
Previous NodeConfig
Mutex sync.RWMutex
}
// Current returns the current node config
func (s *NodeState) GetCurrent() NodeConfig {
s.Mutex.RLock()
defer s.Mutex.RUnlock()
return s.Current
}
// Previous returns the previous node config
func (s *NodeState) GetPrevious() NodeConfig {
s.Mutex.RLock()
defer s.Mutex.RUnlock()
return s.Previous
}
// Replace the node configuration with new configuration. This method returns
// both the new and the previous node configs
func (s *NodeState) Replace(n NodeConfig) {
s.Mutex.Lock()
defer s.Mutex.Unlock()
s.Previous = s.Current
s.Current = n
}
// NodeConfig defines all configuration values needed to run a signle yggdrasil node
type NodeConfig struct {
Peers []string `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j. These connections\nwill obey the operating system routing table, therefore you should\nuse this section when you may connect via different interfaces."`
@ -76,7 +108,7 @@ func GenerateConfig() *NodeConfig {
cfg.Peers = []string{}
cfg.InterfacePeers = map[string][]string{}
cfg.AllowedEncryptionPublicKeys = []string{}
cfg.MulticastInterfaces = []string{".*"}
cfg.MulticastInterfaces = defaults.GetDefaults().DefaultMulticastInterfaces
cfg.IfName = defaults.GetDefaults().DefaultIfName
cfg.IfMTU = defaults.GetDefaults().DefaultIfMTU
cfg.IfTAPMode = defaults.GetDefaults().DefaultIfTAPMode
@ -89,3 +121,19 @@ func GenerateConfig() *NodeConfig {
return &cfg
}
// NewEncryptionKeys generates a new encryption keypair. The encryption keys are
// used to encrypt traffic and to derive the IPv6 address/subnet of the node.
func (cfg *NodeConfig) NewEncryptionKeys() {
bpub, bpriv := crypto.NewBoxKeys()
cfg.EncryptionPublicKey = hex.EncodeToString(bpub[:])
cfg.EncryptionPrivateKey = hex.EncodeToString(bpriv[:])
}
// NewSigningKeys generates a new signing keypair. The signing keys are used to
// derive the structure of the spanning tree.
func (cfg *NodeConfig) NewSigningKeys() {
spub, spriv := crypto.NewSigKeys()
cfg.SigningPublicKey = hex.EncodeToString(spub[:])
cfg.SigningPrivateKey = hex.EncodeToString(spriv[:])
}

View File

@ -13,7 +13,9 @@ It also defines NodeID and TreeID as hashes of keys, and wraps hash functions
import (
"crypto/rand"
"crypto/sha512"
"encoding/hex"
"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/nacl/box"
@ -32,6 +34,41 @@ type NodeID [NodeIDLen]byte
type TreeID [TreeIDLen]byte
type Handle [handleLen]byte
func (n *NodeID) String() string {
return hex.EncodeToString(n[:])
}
// Network returns "nodeid" nearly always right now.
func (n *NodeID) Network() string {
return "nodeid"
}
// PrefixLength returns the number of bits set in a masked NodeID.
func (n *NodeID) PrefixLength() int {
var len int
for i, v := range *n {
_, _ = i, v
if v == 0xff {
len += 8
continue
}
for v&0x80 != 0 {
len++
v <<= 1
}
if v != 0 {
return -1
}
for i++; i < NodeIDLen; i++ {
if n[i] != 0 {
return -1
}
}
break
}
return len
}
func GetNodeID(pub *BoxPubKey) *NodeID {
h := sha512.Sum512(pub[:])
return (*NodeID)(&h)
@ -88,6 +125,15 @@ func Verify(pub *SigPubKey, msg []byte, sig *SigBytes) bool {
return ed25519.Verify(pub[:], msg, sig[:])
}
func (p SigPrivKey) Public() SigPubKey {
priv := make(ed25519.PrivateKey, ed25519.PrivateKeySize)
copy(priv[:], p[:])
pub := priv.Public().(ed25519.PublicKey)
var sigPub SigPubKey
copy(sigPub[:], pub[:])
return sigPub
}
////////////////////////////////////////////////////////////////////////////////
// NaCl-like crypto "box" (curve25519+xsalsa20+poly1305)
@ -168,6 +214,14 @@ func (n *BoxNonce) Increment() {
}
}
func (p BoxPrivKey) Public() BoxPubKey {
var boxPub [BoxPubKeyLen]byte
var boxPriv [BoxPrivKeyLen]byte
copy(boxPriv[:BoxPrivKeyLen], p[:BoxPrivKeyLen])
curve25519.ScalarBaseMult(&boxPub, &boxPriv)
return boxPub
}
// Used to subtract one nonce from another, staying in the range +- 64.
// This is used by the nonce progression machinery to advance the bitmask of recently received packets (indexed by nonce), or to check the appropriate bit of the bitmask.
// It's basically part of the machinery that prevents replays and duplicate packets.

View File

@ -10,6 +10,9 @@ type platformDefaultParameters struct {
// Configuration (used for yggdrasilctl)
DefaultConfigFile string
// Multicast interfaces
DefaultMulticastInterfaces []string
// TUN/TAP
MaximumIfMTU int
DefaultIfMTU int

View File

@ -12,6 +12,12 @@ func GetDefaults() platformDefaultParameters {
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "/etc/yggdrasil.conf",
// Multicast interfaces
DefaultMulticastInterfaces: []string{
"en.*",
"bridge.*",
},
// TUN/TAP
MaximumIfMTU: 65535,
DefaultIfMTU: 65535,

View File

@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters {
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "/etc/yggdrasil.conf",
// Multicast interfaces
DefaultMulticastInterfaces: []string{
".*",
},
// TUN/TAP
MaximumIfMTU: 32767,
DefaultIfMTU: 32767,

View File

@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters {
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "/etc/yggdrasil.conf",
// Multicast interfaces
DefaultMulticastInterfaces: []string{
".*",
},
// TUN/TAP
MaximumIfMTU: 65535,
DefaultIfMTU: 65535,

View File

@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters {
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "/etc/yggdrasil.conf",
// Multicast interfaces
DefaultMulticastInterfaces: []string{
".*",
},
// TUN/TAP
MaximumIfMTU: 9000,
DefaultIfMTU: 9000,

View File

@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters {
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "/etc/yggdrasil.conf",
// Multicast interfaces
DefaultMulticastInterfaces: []string{
".*",
},
// TUN/TAP
MaximumIfMTU: 16384,
DefaultIfMTU: 16384,

View File

@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters {
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "/etc/yggdrasil.conf",
// Multicast interfaces
DefaultMulticastInterfaces: []string{
".*",
},
// TUN/TAP
MaximumIfMTU: 65535,
DefaultIfMTU: 65535,

View File

@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters {
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "C:\\Program Files\\Yggdrasil\\yggdrasil.conf",
// Multicast interfaces
DefaultMulticastInterfaces: []string{
".*",
},
// TUN/TAP
MaximumIfMTU: 65535,
DefaultIfMTU: 65535,

13
src/multicast/admin.go Normal file
View File

@ -0,0 +1,13 @@
package multicast
import "github.com/yggdrasil-network/yggdrasil-go/src/admin"
func (m *Multicast) SetupAdminHandlers(a *admin.AdminSocket) {
a.AddHandler("getMulticastInterfaces", []string{}, func(in admin.Info) (admin.Info, error) {
var intfs []string
for _, v := range m.Interfaces() {
intfs = append(intfs, v.Name)
}
return admin.Info{"multicast_interfaces": intfs}, nil
})
}

View File

@ -1,4 +1,4 @@
package yggdrasil
package multicast
import (
"context"
@ -7,74 +7,94 @@ import (
"regexp"
"time"
"github.com/gologme/log"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
"golang.org/x/net/ipv6"
)
type multicast struct {
core *Core
reconfigure chan chan error
sock *ipv6.PacketConn
groupAddr string
listeners map[string]*tcpListener
listenPort uint16
// Multicast represents the multicast advertisement and discovery mechanism used
// by Yggdrasil to find peers on the same subnet. When a beacon is received on a
// configured multicast interface, Yggdrasil will attempt to peer with that node
// automatically.
type Multicast struct {
core *yggdrasil.Core
config *config.NodeState
log *log.Logger
sock *ipv6.PacketConn
groupAddr string
listeners map[string]*yggdrasil.TcpListener
listenPort uint16
isOpen bool
}
func (m *multicast) init(core *Core) {
// Init prepares the multicast interface for use.
func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) error {
m.core = core
m.reconfigure = make(chan chan error, 1)
m.listeners = make(map[string]*tcpListener)
m.core.configMutex.RLock()
m.listenPort = m.core.config.LinkLocalTCPPort
m.core.configMutex.RUnlock()
go func() {
for {
e := <-m.reconfigure
e <- nil
}
}()
m.config = state
m.log = log
m.listeners = make(map[string]*yggdrasil.TcpListener)
current := m.config.GetCurrent()
m.listenPort = current.LinkLocalTCPPort
m.groupAddr = "[ff02::114]:9001"
// Check if we've been given any expressions
if count := len(m.interfaces()); count != 0 {
m.core.log.Infoln("Found", count, "multicast interface(s)")
}
}
func (m *multicast) start() error {
if len(m.interfaces()) == 0 {
m.core.log.Infoln("Multicast discovery is disabled")
} else {
m.core.log.Infoln("Multicast discovery is enabled")
addr, err := net.ResolveUDPAddr("udp", m.groupAddr)
if err != nil {
return err
}
listenString := fmt.Sprintf("[::]:%v", addr.Port)
lc := net.ListenConfig{
Control: m.multicastReuse,
}
conn, err := lc.ListenPacket(context.Background(), "udp6", listenString)
if err != nil {
return err
}
m.sock = ipv6.NewPacketConn(conn)
if err = m.sock.SetControlMessage(ipv6.FlagDst, true); err != nil {
// Windows can't set this flag, so we need to handle it in other ways
}
go m.multicastStarted()
go m.listen()
go m.announce()
}
return nil
}
func (m *multicast) interfaces() map[string]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
// Start starts the multicast interface. This launches goroutines which will
// listen for multicast beacons from other hosts and will advertise multicast
// beacons out to the network.
func (m *Multicast) Start() error {
addr, err := net.ResolveUDPAddr("udp", m.groupAddr)
if err != nil {
return err
}
listenString := fmt.Sprintf("[::]:%v", addr.Port)
lc := net.ListenConfig{
Control: m.multicastReuse,
}
conn, err := lc.ListenPacket(context.Background(), "udp6", listenString)
if err != nil {
return err
}
m.sock = ipv6.NewPacketConn(conn)
if err = m.sock.SetControlMessage(ipv6.FlagDst, true); err != nil {
// Windows can't set this flag, so we need to handle it in other ways
}
m.isOpen = true
go m.multicastStarted()
go m.listen()
go m.announce()
return nil
}
// Stop is not implemented for multicast yet.
func (m *Multicast) Stop() error {
m.isOpen = false
m.sock.Close()
return nil
}
// UpdateConfig updates the multicast module with the provided config.NodeConfig
// and then signals the various module goroutines to reconfigure themselves if
// needed.
func (m *Multicast) UpdateConfig(config *config.NodeConfig) {
m.log.Debugln("Reloading multicast configuration...")
m.config.Replace(*config)
m.log.Infoln("Multicast configuration reloaded successfully")
}
// GetInterfaces returns the currently known/enabled multicast interfaces. It is
// expected that UpdateInterfaces has been called at least once before calling
// this method.
func (m *Multicast) Interfaces() map[string]net.Interface {
interfaces := make(map[string]net.Interface)
// Get interface expressions from config
current := m.config.GetCurrent()
exprs := current.MulticastInterfaces
// Ask the system for network interfaces
allifaces, err := net.Interfaces()
if err != nil {
panic(err)
@ -108,7 +128,7 @@ func (m *multicast) interfaces() map[string]net.Interface {
return interfaces
}
func (m *multicast) announce() {
func (m *Multicast) announce() {
groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr)
if err != nil {
panic(err)
@ -118,15 +138,15 @@ func (m *multicast) announce() {
panic(err)
}
for {
interfaces := m.interfaces()
interfaces := m.Interfaces()
// There might be interfaces that we configured listeners for but are no
// longer up - if that's the case then we should stop the listeners
for name, listener := range m.listeners {
// Prepare our stop function!
stop := func() {
listener.stop <- true
listener.Stop <- true
delete(m.listeners, name)
m.core.log.Debugln("No longer multicasting on", name)
m.log.Debugln("No longer multicasting on", name)
}
// If the interface is no longer visible on the system then stop the
// listener, as another one will be started further down
@ -137,7 +157,7 @@ func (m *multicast) announce() {
// It's possible that the link-local listener address has changed so if
// that is the case then we should clean up the interface listener
found := false
listenaddr, err := net.ResolveTCPAddr("tcp6", listener.listener.Addr().String())
listenaddr, err := net.ResolveTCPAddr("tcp6", listener.Listener.Addr().String())
if err != nil {
stop()
continue
@ -186,17 +206,17 @@ func (m *multicast) announce() {
// Join the multicast group
m.sock.JoinGroup(&iface, groupAddr)
// Try and see if we already have a TCP listener for this interface
var listener *tcpListener
if l, ok := m.listeners[iface.Name]; !ok || l.listener == nil {
var listener *yggdrasil.TcpListener
if l, ok := m.listeners[iface.Name]; !ok || l.Listener == nil {
// No listener was found - let's create one
listenaddr := fmt.Sprintf("[%s%%%s]:%d", addrIP, iface.Name, m.listenPort)
if li, err := m.core.link.tcp.listen(listenaddr); err == nil {
m.core.log.Debugln("Started multicasting on", iface.Name)
if li, err := m.core.ListenTCP(listenaddr); err == nil {
m.log.Debugln("Started multicasting on", iface.Name)
// Store the listener so that we can stop it later if needed
m.listeners[iface.Name] = li
listener = li
} else {
m.core.log.Warnln("Not multicasting on", iface.Name, "due to error:", err)
m.log.Warnln("Not multicasting on", iface.Name, "due to error:", err)
}
} else {
// An existing listener was found
@ -207,7 +227,7 @@ func (m *multicast) announce() {
continue
}
// Get the listener details and construct the multicast beacon
lladdr := listener.listener.Addr().String()
lladdr := listener.Listener.Addr().String()
if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil {
a.Zone = ""
destAddr.Zone = iface.Name
@ -221,7 +241,7 @@ func (m *multicast) announce() {
}
}
func (m *multicast) listen() {
func (m *Multicast) listen() {
groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr)
if err != nil {
panic(err)
@ -230,6 +250,9 @@ func (m *multicast) listen() {
for {
nBytes, rcm, fromAddr, err := m.sock.ReadFrom(bs)
if err != nil {
if !m.isOpen {
return
}
panic(err)
}
if rcm != nil {
@ -252,9 +275,11 @@ func (m *multicast) listen() {
if addr.IP.String() != from.IP.String() {
continue
}
addr.Zone = ""
if err := m.core.link.call("tcp://"+addr.String(), from.Zone); err != nil {
m.core.log.Debugln("Call from multicast failed:", err)
if _, ok := m.Interfaces()[from.Zone]; ok {
addr.Zone = ""
if err := m.core.CallPeer("tcp://"+addr.String(), from.Zone); err != nil {
m.log.Debugln("Call from multicast failed:", err)
}
}
}
}

View File

@ -1,6 +1,6 @@
// +build darwin
package yggdrasil
package multicast
/*
#cgo CFLAGS: -x objective-c
@ -31,16 +31,16 @@ import (
var awdlGoroutineStarted bool
func (m *multicast) multicastStarted() {
func (m *Multicast) multicastStarted() {
if awdlGoroutineStarted {
return
}
m.core.log.Infoln("Multicast discovery will wake up AWDL if required")
awdlGoroutineStarted = true
for {
C.StopAWDLBrowsing()
for _, intf := range m.interfaces() {
if intf.Name == "awdl0" {
for intf := range m.Interfaces() {
if intf == "awdl0" {
m.log.Infoln("Multicast discovery is using AWDL discovery")
C.StartAWDLBrowsing()
break
}
@ -49,7 +49,7 @@ func (m *multicast) multicastStarted() {
}
}
func (m *multicast) multicastReuse(network string, address string, c syscall.RawConn) error {
func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error {
var control error
var reuseport error
var recvanyif error

View File

@ -1,13 +1,13 @@
// +build !linux,!darwin,!netbsd,!freebsd,!openbsd,!dragonflybsd,!windows
package yggdrasil
package multicast
import "syscall"
func (m *multicast) multicastStarted() {
func (m *Multicast) multicastStarted() {
}
func (m *multicast) multicastReuse(network string, address string, c syscall.RawConn) error {
func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error {
return nil
}

View File

@ -1,15 +1,15 @@
// +build linux netbsd freebsd openbsd dragonflybsd
package yggdrasil
package multicast
import "syscall"
import "golang.org/x/sys/unix"
func (m *multicast) multicastStarted() {
func (m *Multicast) multicastStarted() {
}
func (m *multicast) multicastReuse(network string, address string, c syscall.RawConn) error {
func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error {
var control error
var reuseport error

View File

@ -1,15 +1,15 @@
// +build windows
package yggdrasil
package multicast
import "syscall"
import "golang.org/x/sys/windows"
func (m *multicast) multicastStarted() {
func (m *Multicast) multicastStarted() {
}
func (m *multicast) multicastReuse(network string, address string, c syscall.RawConn) error {
func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error {
var control error
var reuseaddr error

119
src/tuntap/admin.go Normal file
View File

@ -0,0 +1,119 @@
package tuntap
import (
"encoding/hex"
"errors"
"fmt"
"net"
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
)
func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) {
a.AddHandler("getTunTap", []string{}, func(in admin.Info) (r admin.Info, e error) {
defer func() {
if err := recover(); err != nil {
r = admin.Info{"none": admin.Info{}}
e = nil
}
}()
return admin.Info{
t.iface.Name(): admin.Info{
"tap_mode": t.iface.IsTAP(),
"mtu": t.mtu,
},
}, nil
})
/*
// TODO: rewrite this as I'm fairly sure it doesn't work right on many
// platforms anyway, but it may require changes to Water
a.AddHandler("setTunTap", []string{"name", "[tap_mode]", "[mtu]"}, func(in Info) (Info, error) {
// Set sane defaults
iftapmode := defaults.GetDefaults().DefaultIfTAPMode
ifmtu := defaults.GetDefaults().DefaultIfMTU
// Has TAP mode been specified?
if tap, ok := in["tap_mode"]; ok {
iftapmode = tap.(bool)
}
// Check we have enough params for MTU
if mtu, ok := in["mtu"]; ok {
if mtu.(float64) >= 1280 && ifmtu <= defaults.GetDefaults().MaximumIfMTU {
ifmtu = int(in["mtu"].(float64))
}
}
// Start the TUN adapter
if err := a.startTunWithMTU(in["name"].(string), iftapmode, ifmtu); err != nil {
return Info{}, errors.New("Failed to configure adapter")
} else {
return Info{
a.core.router.tun.iface.Name(): Info{
"tap_mode": a.core.router.tun.iface.IsTAP(),
"mtu": ifmtu,
},
}, nil
}
})
*/
a.AddHandler("getTunnelRouting", []string{}, func(in admin.Info) (admin.Info, error) {
return admin.Info{"enabled": t.ckr.isEnabled()}, nil
})
a.AddHandler("setTunnelRouting", []string{"enabled"}, func(in admin.Info) (admin.Info, error) {
enabled := false
if e, ok := in["enabled"].(bool); ok {
enabled = e
}
t.ckr.setEnabled(enabled)
return admin.Info{"enabled": enabled}, nil
})
a.AddHandler("addSourceSubnet", []string{"subnet"}, func(in admin.Info) (admin.Info, error) {
if err := t.ckr.addSourceSubnet(in["subnet"].(string)); err == nil {
return admin.Info{"added": []string{in["subnet"].(string)}}, nil
} else {
return admin.Info{"not_added": []string{in["subnet"].(string)}}, errors.New("Failed to add source subnet")
}
})
a.AddHandler("addRoute", []string{"subnet", "box_pub_key"}, func(in admin.Info) (admin.Info, error) {
if err := t.ckr.addRoute(in["subnet"].(string), in["box_pub_key"].(string)); err == nil {
return admin.Info{"added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil
} else {
return admin.Info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to add route")
}
})
a.AddHandler("getSourceSubnets", []string{}, func(in admin.Info) (admin.Info, error) {
var subnets []string
getSourceSubnets := func(snets []net.IPNet) {
for _, subnet := range snets {
subnets = append(subnets, subnet.String())
}
}
getSourceSubnets(t.ckr.ipv4sources)
getSourceSubnets(t.ckr.ipv6sources)
return admin.Info{"source_subnets": subnets}, nil
})
a.AddHandler("getRoutes", []string{}, func(in admin.Info) (admin.Info, error) {
routes := make(admin.Info)
getRoutes := func(ckrs []cryptokey_route) {
for _, ckr := range ckrs {
routes[ckr.subnet.String()] = hex.EncodeToString(ckr.destination[:])
}
}
getRoutes(t.ckr.ipv4routes)
getRoutes(t.ckr.ipv6routes)
return admin.Info{"routes": routes}, nil
})
a.AddHandler("removeSourceSubnet", []string{"subnet"}, func(in admin.Info) (admin.Info, error) {
if err := t.ckr.removeSourceSubnet(in["subnet"].(string)); err == nil {
return admin.Info{"removed": []string{in["subnet"].(string)}}, nil
} else {
return admin.Info{"not_removed": []string{in["subnet"].(string)}}, errors.New("Failed to remove source subnet")
}
})
a.AddHandler("removeRoute", []string{"subnet", "box_pub_key"}, func(in admin.Info) (admin.Info, error) {
if err := t.ckr.removeRoute(in["subnet"].(string), in["box_pub_key"].(string)); err == nil {
return admin.Info{"removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil
} else {
return admin.Info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route")
}
})
}

View File

@ -1,4 +1,4 @@
package yggdrasil
package tuntap
import (
"bytes"
@ -7,6 +7,8 @@ import (
"fmt"
"net"
"sort"
"sync"
"sync/atomic"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
@ -16,15 +18,18 @@ import (
// allow traffic for non-Yggdrasil ranges to be routed over Yggdrasil.
type cryptokey struct {
core *Core
enabled bool
reconfigure chan chan error
ipv4routes []cryptokey_route
ipv6routes []cryptokey_route
ipv4cache map[address.Address]cryptokey_route
ipv6cache map[address.Address]cryptokey_route
ipv4sources []net.IPNet
ipv6sources []net.IPNet
tun *TunAdapter
enabled atomic.Value // bool
reconfigure chan chan error
ipv4routes []cryptokey_route
ipv6routes []cryptokey_route
ipv4cache map[address.Address]cryptokey_route
ipv6cache map[address.Address]cryptokey_route
ipv4sources []net.IPNet
ipv6sources []net.IPNet
mutexroutes sync.RWMutex
mutexcaches sync.RWMutex
mutexsources sync.RWMutex
}
type cryptokey_route struct {
@ -33,59 +38,61 @@ type cryptokey_route struct {
}
// Initialise crypto-key routing. This must be done before any other CKR calls.
func (c *cryptokey) init(core *Core) {
c.core = core
func (c *cryptokey) init(tun *TunAdapter) {
c.tun = tun
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
e <- nil
}
}()
c.tun.log.Debugln("Configuring CKR...")
if err := c.configure(); err != nil {
c.core.log.Errorln("CKR configuration failed:", err)
c.tun.log.Errorln("CKR configuration failed:", err)
} else {
c.tun.log.Debugln("CKR configured")
}
}
// 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()
current := c.tun.config.GetCurrent()
// Set enabled/disabled state
c.setEnabled(c.core.config.TunnelRouting.Enable)
c.setEnabled(current.TunnelRouting.Enable)
// Clear out existing routes
c.mutexroutes.Lock()
c.ipv6routes = make([]cryptokey_route, 0)
c.ipv4routes = make([]cryptokey_route, 0)
c.mutexroutes.Unlock()
// Add IPv6 routes
for ipv6, pubkey := range c.core.config.TunnelRouting.IPv6Destinations {
for ipv6, pubkey := range current.TunnelRouting.IPv6Destinations {
if err := c.addRoute(ipv6, pubkey); err != nil {
return err
}
}
// Add IPv4 routes
for ipv4, pubkey := range c.core.config.TunnelRouting.IPv4Destinations {
for ipv4, pubkey := range current.TunnelRouting.IPv4Destinations {
if err := c.addRoute(ipv4, pubkey); err != nil {
return err
}
}
// Clear out existing sources
c.mutexsources.Lock()
c.ipv6sources = make([]net.IPNet, 0)
c.ipv4sources = make([]net.IPNet, 0)
c.mutexsources.Unlock()
// Add IPv6 sources
c.ipv6sources = make([]net.IPNet, 0)
for _, source := range c.core.config.TunnelRouting.IPv6Sources {
for _, source := range current.TunnelRouting.IPv6Sources {
if err := c.addSourceSubnet(source); err != nil {
return err
}
@ -93,43 +100,49 @@ func (c *cryptokey) configure() error {
// Add IPv4 sources
c.ipv4sources = make([]net.IPNet, 0)
for _, source := range c.core.config.TunnelRouting.IPv4Sources {
for _, source := range current.TunnelRouting.IPv4Sources {
if err := c.addSourceSubnet(source); err != nil {
return err
}
}
// Wipe the caches
c.mutexcaches.Lock()
c.ipv4cache = make(map[address.Address]cryptokey_route, 0)
c.ipv6cache = make(map[address.Address]cryptokey_route, 0)
c.mutexcaches.Unlock()
return nil
}
// Enable or disable crypto-key routing.
func (c *cryptokey) setEnabled(enabled bool) {
c.enabled = enabled
c.enabled.Store(enabled)
}
// Check if crypto-key routing is enabled.
func (c *cryptokey) isEnabled() bool {
return c.enabled
enabled, ok := c.enabled.Load().(bool)
return ok && enabled
}
// Check whether the given address (with the address length specified in bytes)
// matches either the current node's address, the node's routed subnet or the
// list of subnets specified in IPv4Sources/IPv6Sources.
func (c *cryptokey) isValidSource(addr address.Address, addrlen int) bool {
c.mutexsources.RLock()
defer c.mutexsources.RUnlock()
ip := net.IP(addr[:addrlen])
if addrlen == net.IPv6len {
// Does this match our node's address?
if bytes.Equal(addr[:16], c.core.router.addr[:16]) {
if bytes.Equal(addr[:16], c.tun.addr[:16]) {
return true
}
// Does this match our node's subnet?
if bytes.Equal(addr[:8], c.core.router.subnet[:8]) {
if bytes.Equal(addr[:8], c.tun.subnet[:8]) {
return true
}
}
@ -162,6 +175,9 @@ func (c *cryptokey) isValidSource(addr address.Address, addrlen int) bool {
// Adds a source subnet, which allows traffic with these source addresses to
// be tunnelled using crypto-key routing.
func (c *cryptokey) addSourceSubnet(cidr string) error {
c.mutexsources.Lock()
defer c.mutexsources.Unlock()
// Is the CIDR we've been given valid?
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
@ -192,13 +208,18 @@ func (c *cryptokey) addSourceSubnet(cidr string) error {
// Add the source subnet
*routingsources = append(*routingsources, *ipnet)
c.core.log.Infoln("Added CKR source subnet", cidr)
c.tun.log.Infoln("Added CKR source subnet", cidr)
return nil
}
// Adds a destination route for the given CIDR to be tunnelled to the node
// with the given BoxPubKey.
func (c *cryptokey) addRoute(cidr string, dest string) error {
c.mutexroutes.Lock()
c.mutexcaches.Lock()
defer c.mutexroutes.Unlock()
defer c.mutexcaches.Unlock()
// Is the CIDR we've been given valid?
ipaddr, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
@ -264,7 +285,7 @@ func (c *cryptokey) addRoute(cidr string, dest string) error {
delete(*routingcache, k)
}
c.core.log.Infoln("Added CKR destination subnet", cidr)
c.tun.log.Infoln("Added CKR destination subnet", cidr)
return nil
}
}
@ -273,6 +294,8 @@ func (c *cryptokey) addRoute(cidr string, dest string) error {
// length specified in bytes) from the crypto-key routing table. An error is
// returned if the address is not suitable or no route was found.
func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (crypto.BoxPubKey, error) {
c.mutexcaches.RLock()
// Check if the address is a valid Yggdrasil address - if so it
// is exempt from all CKR checking
if addr.IsValid() {
@ -285,10 +308,8 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c
// Check if the prefix is IPv4 or IPv6
if addrlen == net.IPv6len {
routingtable = &c.ipv6routes
routingcache = &c.ipv6cache
} else if addrlen == net.IPv4len {
routingtable = &c.ipv4routes
routingcache = &c.ipv4cache
} else {
return crypto.BoxPubKey{}, errors.New("Unexpected prefix size")
@ -296,9 +317,24 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c
// Check if there's a cache entry for this addr
if route, ok := (*routingcache)[addr]; ok {
c.mutexcaches.RUnlock()
return route.destination, nil
}
c.mutexcaches.RUnlock()
c.mutexroutes.RLock()
defer c.mutexroutes.RUnlock()
// Check if the prefix is IPv4 or IPv6
if addrlen == net.IPv6len {
routingtable = &c.ipv6routes
} else if addrlen == net.IPv4len {
routingtable = &c.ipv4routes
} else {
return crypto.BoxPubKey{}, errors.New("Unexpected prefix size")
}
// No cache was found - start by converting the address into a net.IP
ip := make(net.IP, addrlen)
copy(ip[:addrlen], addr[:])
@ -308,6 +344,9 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c
for _, route := range *routingtable {
// Does this subnet match the given IP?
if route.subnet.Contains(ip) {
c.mutexcaches.Lock()
defer c.mutexcaches.Unlock()
// Check if the routing cache is above a certain size, if it is evict
// a random entry so we can make room for this one. We take advantage
// of the fact that the iteration order is random here
@ -333,6 +372,9 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c
// Removes a source subnet, which allows traffic with these source addresses to
// be tunnelled using crypto-key routing.
func (c *cryptokey) removeSourceSubnet(cidr string) error {
c.mutexsources.Lock()
defer c.mutexsources.Unlock()
// Is the CIDR we've been given valid?
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
@ -358,7 +400,7 @@ func (c *cryptokey) removeSourceSubnet(cidr string) error {
for idx, subnet := range *routingsources {
if subnet.String() == ipnet.String() {
*routingsources = append((*routingsources)[:idx], (*routingsources)[idx+1:]...)
c.core.log.Infoln("Removed CKR source subnet", cidr)
c.tun.log.Infoln("Removed CKR source subnet", cidr)
return nil
}
}
@ -368,6 +410,11 @@ func (c *cryptokey) removeSourceSubnet(cidr string) error {
// Removes a destination route for the given CIDR to be tunnelled to the node
// with the given BoxPubKey.
func (c *cryptokey) removeRoute(cidr string, dest string) error {
c.mutexroutes.Lock()
c.mutexcaches.Lock()
defer c.mutexroutes.Unlock()
defer c.mutexcaches.Unlock()
// Is the CIDR we've been given valid?
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
@ -407,7 +454,7 @@ func (c *cryptokey) removeRoute(cidr string, dest string) error {
for k := range *routingcache {
delete(*routingcache, k)
}
c.core.log.Infoln("Removed CKR destination subnet %s via %s\n", cidr, dest)
c.tun.log.Infof("Removed CKR destination subnet %s via %s\n", cidr, dest)
return nil
}
}

159
src/tuntap/conn.go Normal file
View File

@ -0,0 +1,159 @@
package tuntap
import (
"errors"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv6"
)
const tunConnTimeout = 2 * time.Minute
type tunConn struct {
tun *TunAdapter
conn *yggdrasil.Conn
addr address.Address
snet address.Subnet
send chan []byte
stop chan struct{}
alive chan struct{}
}
func (s *tunConn) close() {
s.tun.mutex.Lock()
defer s.tun.mutex.Unlock()
s._close_nomutex()
}
func (s *tunConn) _close_nomutex() {
s.conn.Close()
delete(s.tun.addrToConn, s.addr)
delete(s.tun.subnetToConn, s.snet)
func() {
defer func() { recover() }()
close(s.stop) // Closes reader/writer goroutines
}()
func() {
defer func() { recover() }()
close(s.alive) // Closes timeout goroutine
}()
}
func (s *tunConn) reader() (err error) {
select {
case _, ok := <-s.stop:
if !ok {
return errors.New("session was already closed")
}
default:
}
s.tun.log.Debugln("Starting conn reader for", s.conn.String())
defer s.tun.log.Debugln("Stopping conn reader for", s.conn.String())
var n int
b := make([]byte, 65535)
for {
select {
case <-s.stop:
return nil
default:
}
if n, err = s.conn.Read(b); err != nil {
if e, eok := err.(yggdrasil.ConnError); eok && !e.Temporary() {
if e.Closed() {
s.tun.log.Debugln(s.conn.String(), "TUN/TAP conn read debug:", err)
} else {
s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn read error:", err)
}
return e
}
} else if n > 0 {
bs := append(util.GetBytes(), b[:n]...)
select {
case s.tun.send <- bs:
default:
util.PutBytes(bs)
}
s.stillAlive()
}
}
}
func (s *tunConn) writer() error {
select {
case _, ok := <-s.stop:
if !ok {
return errors.New("session was already closed")
}
default:
}
s.tun.log.Debugln("Starting conn writer for", s.conn.String())
defer s.tun.log.Debugln("Stopping conn writer for", s.conn.String())
for {
select {
case <-s.stop:
return nil
case b, ok := <-s.send:
if !ok {
return errors.New("send closed")
}
// TODO write timeout and close
if _, err := s.conn.Write(b); err != nil {
if e, eok := err.(yggdrasil.ConnError); !eok {
if e.Closed() {
s.tun.log.Debugln(s.conn.String(), "TUN/TAP generic write debug:", err)
} else {
s.tun.log.Errorln(s.conn.String(), "TUN/TAP generic write error:", err)
}
} else if e.PacketTooBig() {
// TODO: This currently isn't aware of IPv4 for CKR
ptb := &icmp.PacketTooBig{
MTU: int(e.PacketMaximumSize()),
Data: b[:900],
}
if packet, err := CreateICMPv6(b[8:24], b[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb); err == nil {
s.tun.send <- packet
}
} else {
if e.Closed() {
s.tun.log.Debugln(s.conn.String(), "TUN/TAP conn write debug:", err)
} else {
s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn write error:", err)
}
}
} else {
s.stillAlive()
}
util.PutBytes(b)
}
}
}
func (s *tunConn) stillAlive() {
defer func() { recover() }()
select {
case s.alive <- struct{}{}:
default:
}
}
func (s *tunConn) checkForTimeouts() error {
timer := time.NewTimer(tunConnTimeout)
defer util.TimerStop(timer)
defer s.close()
for {
select {
case _, ok := <-s.alive:
if !ok {
return errors.New("connection closed")
}
util.TimerStop(timer)
timer.Reset(tunConnTimeout)
case <-timer.C:
return errors.New("timed out")
}
}
}

View File

@ -1,4 +1,4 @@
package yggdrasil
package tuntap
// The ICMPv6 module implements functions to easily create ICMPv6
// packets. These functions, when mixed with the built-in Go IPv6
@ -13,6 +13,7 @@ import (
"encoding/binary"
"errors"
"net"
"sync"
"time"
"golang.org/x/net/icmp"
@ -21,19 +22,18 @@ import (
"github.com/yggdrasil-network/yggdrasil-go/src/address"
)
type macAddress [6]byte
const len_ETHER = 14
type icmpv6 struct {
tun *tunAdapter
mylladdr net.IP
mymac macAddress
peermacs map[address.Address]neighbor
type ICMPv6 struct {
tun *TunAdapter
mylladdr net.IP
mymac net.HardwareAddr
peermacs map[address.Address]neighbor
peermacsmutex sync.RWMutex
}
type neighbor struct {
mac macAddress
mac net.HardwareAddr
learned bool
lastadvertisement time.Time
lastsolicitation time.Time
@ -59,56 +59,60 @@ func ipv6Header_Marshal(h *ipv6.Header) ([]byte, error) {
// Initialises the ICMPv6 module by assigning our link-local IPv6 address and
// our MAC address. ICMPv6 messages will always appear to originate from these
// addresses.
func (i *icmpv6) init(t *tunAdapter) {
func (i *ICMPv6) Init(t *TunAdapter) {
i.tun = t
i.peermacsmutex.Lock()
i.peermacs = make(map[address.Address]neighbor)
i.peermacsmutex.Unlock()
// Our MAC address and link-local address
i.mymac = macAddress{
i.mymac = net.HardwareAddr{
0x02, 0x00, 0x00, 0x00, 0x00, 0x02}
i.mylladdr = net.IP{
0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFE}
copy(i.mymac[:], i.tun.core.router.addr[:])
copy(i.mylladdr[9:], i.tun.core.router.addr[1:])
copy(i.mymac[:], i.tun.addr[:])
copy(i.mylladdr[9:], i.tun.addr[1:])
}
// Parses an incoming ICMPv6 packet. The packet provided may be either an
// ethernet frame containing an IP packet, or the IP packet alone. This is
// determined by whether the TUN/TAP adapter is running in TUN (layer 3) or
// TAP (layer 2) mode.
func (i *icmpv6) parse_packet(datain []byte) {
// TAP (layer 2) mode. Returns an error condition which is nil if the ICMPv6
// module handled the packet or contains the error if not.
func (i *ICMPv6) ParsePacket(datain []byte) error {
var response []byte
var err error
// Parse the frame/packet
if i.tun.iface.IsTAP() {
response, err = i.parse_packet_tap(datain)
if i.tun.IsTAP() {
response, err = i.UnmarshalPacketL2(datain)
} else {
response, err = i.parse_packet_tun(datain, nil)
response, err = i.UnmarshalPacket(datain, nil)
}
if err != nil {
return
return err
}
// Write the packet to TUN/TAP
i.tun.iface.Write(response)
return nil
}
// Unwraps the ethernet headers of an incoming ICMPv6 packet and hands off
// the IP packet to the parse_packet_tun function for further processing.
// the IP packet to the ParsePacket function for further processing.
// A response buffer is also created for the response message, also complete
// with ethernet headers.
func (i *icmpv6) parse_packet_tap(datain []byte) ([]byte, error) {
func (i *ICMPv6) UnmarshalPacketL2(datain []byte) ([]byte, error) {
// Ignore non-IPv6 frames
if binary.BigEndian.Uint16(datain[12:14]) != uint16(0x86DD) {
return nil, nil
return nil, errors.New("Ignoring non-IPv6 frame")
}
// Hand over to parse_packet_tun to interpret the IPv6 packet
// Hand over to ParsePacket to interpret the IPv6 packet
mac := datain[6:12]
ipv6packet, err := i.parse_packet_tun(datain[len_ETHER:], &mac)
ipv6packet, err := i.UnmarshalPacket(datain[len_ETHER:], &mac)
if err != nil {
return nil, err
}
@ -130,7 +134,7 @@ func (i *icmpv6) parse_packet_tap(datain []byte) ([]byte, error) {
// sanity checks on the packet - i.e. is the packet an ICMPv6 packet, does the
// ICMPv6 message match a known expected type. The relevant handler function
// is then called and a response packet may be returned.
func (i *icmpv6) parse_packet_tun(datain []byte, datamac *[]byte) ([]byte, error) {
func (i *ICMPv6) UnmarshalPacket(datain []byte, datamac *[]byte) ([]byte, error) {
// Parse the IPv6 packet headers
ipv6Header, err := ipv6.ParseHeader(datain[:ipv6.HeaderLen])
if err != nil {
@ -139,12 +143,12 @@ func (i *icmpv6) parse_packet_tun(datain []byte, datamac *[]byte) ([]byte, error
// Check if the packet is IPv6
if ipv6Header.Version != ipv6.Version {
return nil, err
return nil, errors.New("Ignoring non-IPv6 packet")
}
// Check if the packet is ICMPv6
if ipv6Header.NextHeader != 58 {
return nil, err
return nil, errors.New("Ignoring non-ICMPv6 packet")
}
// Parse the ICMPv6 message contents
@ -156,42 +160,55 @@ func (i *icmpv6) parse_packet_tun(datain []byte, datamac *[]byte) ([]byte, error
// Check for a supported message type
switch icmpv6Header.Type {
case ipv6.ICMPTypeNeighborSolicitation:
if !i.tun.iface.IsTAP() {
if !i.tun.IsTAP() {
return nil, errors.New("Ignoring Neighbor Solicitation in TUN mode")
}
response, err := i.handle_ndp(datain[ipv6.HeaderLen:])
response, err := i.HandleNDP(datain[ipv6.HeaderLen:])
if err == nil {
// Create our ICMPv6 response
responsePacket, err := i.create_icmpv6_tun(
responsePacket, err := CreateICMPv6(
ipv6Header.Src, i.mylladdr,
ipv6.ICMPTypeNeighborAdvertisement, 0,
&icmp.DefaultMessageBody{Data: response})
if err != nil {
return nil, err
}
// Send it back
return responsePacket, nil
} else {
return nil, err
}
case ipv6.ICMPTypeNeighborAdvertisement:
if !i.tun.iface.IsTAP() {
if !i.tun.IsTAP() {
return nil, errors.New("Ignoring Neighbor Advertisement in TUN mode")
}
if datamac != nil {
var addr address.Address
var target address.Address
var mac macAddress
mac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
copy(addr[:], ipv6Header.Src[:])
copy(target[:], datain[48:64])
copy(mac[:], (*datamac)[:])
// i.tun.core.log.Printf("Learning peer MAC %x for %x\n", mac, target)
i.peermacsmutex.Lock()
neighbor := i.peermacs[target]
neighbor.mac = mac
neighbor.learned = true
neighbor.lastadvertisement = time.Now()
i.peermacs[target] = neighbor
i.peermacsmutex.Unlock()
i.tun.log.Debugln("Learned peer MAC", mac.String(), "for", net.IP(target[:]).String())
/*
i.tun.log.Debugln("Peer MAC table:")
i.peermacsmutex.RLock()
for t, n := range i.peermacs {
if n.learned {
i.tun.log.Debugln("- Target", net.IP(t[:]).String(), "has MAC", n.mac.String())
} else {
i.tun.log.Debugln("- Target", net.IP(t[:]).String(), "is not learned yet")
}
}
i.peermacsmutex.RUnlock()
*/
}
return nil, errors.New("No response needed")
}
@ -202,9 +219,9 @@ func (i *icmpv6) parse_packet_tun(datain []byte, datamac *[]byte) ([]byte, error
// Creates an ICMPv6 packet based on the given icmp.MessageBody and other
// parameters, complete with ethernet and IP headers, which can be written
// directly to a TAP adapter.
func (i *icmpv6) create_icmpv6_tap(dstmac macAddress, dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) {
// Pass through to create_icmpv6_tun
ipv6packet, err := i.create_icmpv6_tun(dst, src, mtype, mcode, mbody)
func (i *ICMPv6) CreateICMPv6L2(dstmac net.HardwareAddr, dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) {
// Pass through to CreateICMPv6
ipv6packet, err := CreateICMPv6(dst, src, mtype, mcode, mbody)
if err != nil {
return nil, err
}
@ -224,9 +241,9 @@ func (i *icmpv6) create_icmpv6_tap(dstmac macAddress, dst net.IP, src net.IP, mt
// Creates an ICMPv6 packet based on the given icmp.MessageBody and other
// parameters, complete with IP headers only, which can be written directly to
// a TUN adapter, or called directly by the create_icmpv6_tap function when
// a TUN adapter, or called directly by the CreateICMPv6L2 function when
// generating a message for TAP adapters.
func (i *icmpv6) create_icmpv6_tun(dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) {
func CreateICMPv6(dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) {
// Create the ICMPv6 message
icmpMessage := icmp.Message{
Type: mtype,
@ -265,13 +282,54 @@ func (i *icmpv6) create_icmpv6_tun(dst net.IP, src net.IP, mtype ipv6.ICMPType,
return responsePacket, nil
}
func (i *icmpv6) create_ndp_tap(dst address.Address) ([]byte, error) {
func (i *ICMPv6) Solicit(addr address.Address) {
retries := 5
for retries > 0 {
retries--
i.peermacsmutex.RLock()
if n, ok := i.peermacs[addr]; ok && n.learned {
i.tun.log.Debugln("MAC learned for", net.IP(addr[:]).String())
i.peermacsmutex.RUnlock()
return
}
i.peermacsmutex.RUnlock()
i.tun.log.Debugln("Sending neighbor solicitation for", net.IP(addr[:]).String())
i.peermacsmutex.Lock()
if n, ok := i.peermacs[addr]; !ok {
i.peermacs[addr] = neighbor{
lastsolicitation: time.Now(),
}
} else {
n.lastsolicitation = time.Now()
}
i.peermacsmutex.Unlock()
request, err := i.createNDPL2(addr)
if err != nil {
panic(err)
}
if _, err := i.tun.iface.Write(request); err != nil {
panic(err)
}
i.tun.log.Debugln("Sent neighbor solicitation for", net.IP(addr[:]).String())
time.Sleep(time.Second)
}
}
func (i *ICMPv6) getNeighbor(addr address.Address) (neighbor, bool) {
i.peermacsmutex.RLock()
defer i.peermacsmutex.RUnlock()
n, ok := i.peermacs[addr]
return n, ok
}
func (i *ICMPv6) createNDPL2(dst address.Address) ([]byte, error) {
// Create the ND payload
var payload [28]byte
copy(payload[:4], []byte{0x00, 0x00, 0x00, 0x00})
copy(payload[4:20], dst[:])
copy(payload[20:22], []byte{0x01, 0x01})
copy(payload[22:28], i.mymac[:6])
copy(payload[:4], []byte{0x00, 0x00, 0x00, 0x00}) // Flags
copy(payload[4:20], dst[:]) // Destination
copy(payload[20:22], []byte{0x01, 0x01}) // Type & length
copy(payload[22:28], i.mymac[:6]) // Link layer address
// Create the ICMPv6 solicited-node address
var dstaddr address.Address
@ -282,21 +340,18 @@ func (i *icmpv6) create_ndp_tap(dst address.Address) ([]byte, error) {
copy(dstaddr[13:], dst[13:16])
// Create the multicast MAC
var dstmac macAddress
dstmac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
copy(dstmac[:2], []byte{0x33, 0x33})
copy(dstmac[2:6], dstaddr[12:16])
// Create the ND request
requestPacket, err := i.create_icmpv6_tap(
requestPacket, err := i.CreateICMPv6L2(
dstmac, dstaddr[:], i.mylladdr,
ipv6.ICMPTypeNeighborSolicitation, 0,
&icmp.DefaultMessageBody{Data: payload[:]})
if err != nil {
return nil, err
}
neighbor := i.peermacs[dstaddr]
neighbor.lastsolicitation = time.Now()
i.peermacs[dstaddr] = neighbor
return requestPacket, nil
}
@ -305,7 +360,7 @@ func (i *icmpv6) create_ndp_tap(dst address.Address) ([]byte, error) {
// when the host operating system generates an NDP request for any address in
// the fd00::/8 range, so that the operating system knows to route that traffic
// to the Yggdrasil TAP adapter.
func (i *icmpv6) handle_ndp(in []byte) ([]byte, error) {
func (i *ICMPv6) HandleNDP(in []byte) ([]byte, error) {
// Ignore NDP requests for anything outside of fd00::/8
var source address.Address
copy(source[:], in[8:])
@ -320,10 +375,10 @@ func (i *icmpv6) handle_ndp(in []byte) ([]byte, error) {
// Create our NDP message body response
body := make([]byte, 28)
binary.BigEndian.PutUint32(body[:4], uint32(0x20000000))
copy(body[4:20], in[8:24]) // Target address
body[20] = uint8(2)
body[21] = uint8(1)
binary.BigEndian.PutUint32(body[:4], uint32(0x40000000)) // Flags
copy(body[4:20], in[8:24]) // Target address
body[20] = uint8(2) // Type: Target link-layer address
body[21] = uint8(1) // Length: 1x address (8 bytes)
copy(body[22:28], i.mymac[:6])
// Send it back

306
src/tuntap/iface.go Normal file
View File

@ -0,0 +1,306 @@
package tuntap
import (
"bytes"
"errors"
"net"
"time"
"github.com/songgao/packets/ethernet"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
func (tun *TunAdapter) writer() error {
var w int
var err error
for {
b := <-tun.send
n := len(b)
if n == 0 {
continue
}
if tun.iface.IsTAP() {
var dstAddr address.Address
if b[0]&0xf0 == 0x60 {
if len(b) < 40 {
//panic("Tried to send a packet shorter than an IPv6 header...")
util.PutBytes(b)
continue
}
copy(dstAddr[:16], b[24:])
} else if b[0]&0xf0 == 0x40 {
if len(b) < 20 {
//panic("Tried to send a packet shorter than an IPv4 header...")
util.PutBytes(b)
continue
}
copy(dstAddr[:4], b[16:])
} else {
return errors.New("Invalid address family")
}
sendndp := func(dstAddr address.Address) {
neigh, known := tun.icmpv6.getNeighbor(dstAddr)
known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30)
if !known {
tun.icmpv6.Solicit(dstAddr)
}
}
peermac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
var peerknown bool
if b[0]&0xf0 == 0x40 {
dstAddr = tun.addr
} else if b[0]&0xf0 == 0x60 {
if !bytes.Equal(tun.addr[:16], dstAddr[:16]) && !bytes.Equal(tun.subnet[:8], dstAddr[:8]) {
dstAddr = tun.addr
}
}
if neighbor, ok := tun.icmpv6.getNeighbor(dstAddr); ok && neighbor.learned {
// If we've learned the MAC of a 300::/7 address, for example, or a CKR
// address, use the MAC address of that
peermac = neighbor.mac
peerknown = true
} else if neighbor, ok := tun.icmpv6.getNeighbor(tun.addr); ok && neighbor.learned {
// Otherwise send directly to the MAC address of the host if that's
// known instead
peermac = neighbor.mac
peerknown = true
} else {
// Nothing has been discovered, try to discover the destination
sendndp(tun.addr)
}
if peerknown {
var proto ethernet.Ethertype
switch {
case b[0]&0xf0 == 0x60:
proto = ethernet.IPv6
case b[0]&0xf0 == 0x40:
proto = ethernet.IPv4
}
var frame ethernet.Frame
frame.Prepare(
peermac[:6], // Destination MAC address
tun.icmpv6.mymac[:6], // Source MAC address
ethernet.NotTagged, // VLAN tagging
proto, // Ethertype
len(b)) // Payload length
copy(frame[tun_ETHER_HEADER_LENGTH:], b[:n])
n += tun_ETHER_HEADER_LENGTH
w, err = tun.iface.Write(frame[:n])
} else {
tun.log.Errorln("TUN/TAP iface write error: no peer MAC known for", net.IP(dstAddr[:]).String(), "- dropping packet")
}
} else {
w, err = tun.iface.Write(b[:n])
util.PutBytes(b)
}
if err != nil {
if !tun.isOpen {
return err
}
tun.log.Errorln("TUN/TAP iface write error:", err)
continue
}
if w != n {
tun.log.Errorln("TUN/TAP iface write mismatch:", w, "bytes written vs", n, "bytes given")
continue
}
}
}
// Run in a separate goroutine by the reader
// Does all of the per-packet ICMP checks, passes packets to the right Conn worker
func (tun *TunAdapter) readerPacketHandler(ch chan []byte) {
for recvd := range ch {
// If it's a TAP adapter, update the buffer slice so that we no longer
// include the ethernet headers
offset := 0
if tun.iface.IsTAP() {
// Set our offset to beyond the ethernet headers
offset = tun_ETHER_HEADER_LENGTH
// Check first of all that we can go beyond the ethernet headers
if len(recvd) <= offset {
continue
}
}
// Offset the buffer from now on so that we can ignore ethernet frames if
// they are present
bs := recvd[offset:]
// If we detect an ICMP packet then hand it to the ICMPv6 module
if bs[6] == 58 {
// Found an ICMPv6 packet - we need to make sure to give ICMPv6 the full
// Ethernet frame rather than just the IPv6 packet as this is needed for
// NDP to work correctly
if err := tun.icmpv6.ParsePacket(recvd); err == nil {
// We acted on the packet in the ICMPv6 module so don't forward or do
// anything else with it
continue
}
}
// Shift forward to avoid leaking bytes off the front of the slide when we eventually store it
bs = append(recvd[:0], bs...)
// From the IP header, work out what our source and destination addresses
// and node IDs are. We will need these in order to work out where to send
// the packet
var srcAddr address.Address
var dstAddr address.Address
var dstNodeID *crypto.NodeID
var dstNodeIDMask *crypto.NodeID
var dstSnet address.Subnet
var addrlen int
n := len(bs)
// Check the IP protocol - if it doesn't match then we drop the packet and
// do nothing with it
if bs[0]&0xf0 == 0x60 {
// Check if we have a fully-sized IPv6 header
if len(bs) < 40 {
continue
}
// Check the packet size
if n-tun_IPv6_HEADER_LENGTH != 256*int(bs[4])+int(bs[5]) {
continue
}
// IPv6 address
addrlen = 16
copy(srcAddr[:addrlen], bs[8:])
copy(dstAddr[:addrlen], bs[24:])
copy(dstSnet[:addrlen/2], bs[24:])
} else if bs[0]&0xf0 == 0x40 {
// Check if we have a fully-sized IPv4 header
if len(bs) < 20 {
continue
}
// Check the packet size
if n != 256*int(bs[2])+int(bs[3]) {
continue
}
// IPv4 address
addrlen = 4
copy(srcAddr[:addrlen], bs[12:])
copy(dstAddr[:addrlen], bs[16:])
} else {
// Unknown address length or protocol, so drop the packet and ignore it
tun.log.Traceln("Unknown packet type, dropping")
continue
}
if tun.ckr.isEnabled() && !tun.ckr.isValidSource(srcAddr, addrlen) {
// The packet had a source address that doesn't belong to us or our
// configured crypto-key routing source subnets
continue
}
if !dstAddr.IsValid() && !dstSnet.IsValid() {
if key, err := tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil {
// A public key was found, get the node ID for the search
dstNodeID = crypto.GetNodeID(&key)
// Do a quick check to ensure that the node ID refers to a vaild
// Yggdrasil address or subnet - this might be superfluous
addr := *address.AddrForNodeID(dstNodeID)
copy(dstAddr[:], addr[:])
copy(dstSnet[:], addr[:])
// Are we certain we looked up a valid node?
if !dstAddr.IsValid() && !dstSnet.IsValid() {
continue
}
} else {
// No public key was found in the CKR table so we've exhausted our options
continue
}
}
// Do we have an active connection for this node address?
tun.mutex.RLock()
session, isIn := tun.addrToConn[dstAddr]
if !isIn || session == nil {
session, isIn = tun.subnetToConn[dstSnet]
if !isIn || session == nil {
// Neither an address nor a subnet mapping matched, therefore populate
// the node ID and mask to commence a search
if dstAddr.IsValid() {
dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask()
} else {
dstNodeID, dstNodeIDMask = dstSnet.GetNodeIDandMask()
}
}
}
tun.mutex.RUnlock()
// If we don't have a connection then we should open one
if !isIn || session == nil {
// Check we haven't been given empty node ID, really this shouldn't ever
// happen but just to be sure...
if dstNodeID == nil || dstNodeIDMask == nil {
panic("Given empty dstNodeID and dstNodeIDMask - this shouldn't happen")
}
// Dial to the remote node
go func() {
// FIXME just spitting out a goroutine to do this is kind of ugly and means we drop packets until the dial finishes
tun.mutex.Lock()
_, known := tun.dials[*dstNodeID]
tun.dials[*dstNodeID] = append(tun.dials[*dstNodeID], bs)
for len(tun.dials[*dstNodeID]) > 32 {
util.PutBytes(tun.dials[*dstNodeID][0])
tun.dials[*dstNodeID] = tun.dials[*dstNodeID][1:]
}
tun.mutex.Unlock()
if known {
return
}
var tc *tunConn
if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil {
// We've been given a connection so prepare the session wrapper
if tc, err = tun.wrap(conn); err != nil {
// Something went wrong when storing the connection, typically that
// something already exists for this address or subnet
tun.log.Debugln("TUN/TAP iface wrap:", err)
}
}
tun.mutex.Lock()
packets := tun.dials[*dstNodeID]
delete(tun.dials, *dstNodeID)
tun.mutex.Unlock()
if tc != nil {
for _, packet := range packets {
select {
case tc.send <- packet:
default:
util.PutBytes(packet)
}
}
}
}()
// While the dial is going on we can't do much else
// continuing this iteration - skip to the next one
continue
}
// If we have a connection now, try writing to it
if isIn && session != nil {
select {
case session.send <- bs:
default:
util.PutBytes(bs)
}
}
}
}
func (tun *TunAdapter) reader() error {
recvd := make([]byte, 65535+tun_ETHER_HEADER_LENGTH)
toWorker := make(chan []byte, 32)
defer close(toWorker)
go tun.readerPacketHandler(toWorker)
for {
// Wait for a packet to be delivered to us through the TUN/TAP adapter
n, err := tun.iface.Read(recvd)
if err != nil {
if !tun.isOpen {
return err
}
panic(err)
}
if n == 0 {
continue
}
bs := append(util.GetBytes(), recvd[:n]...)
toWorker <- bs
}
}

265
src/tuntap/tun.go Normal file
View File

@ -0,0 +1,265 @@
package tuntap
// This manages the tun driver to send/recv packets to/from applications
// TODO: Crypto-key routing support
// TODO: Set MTU of session properly
// TODO: Reject packets that exceed session MTU with ICMPv6 for PMTU Discovery
// TODO: Connection timeouts (call Conn.Close() when we want to time out)
// TODO: Don't block in reader on writes that are pending searches
import (
"encoding/hex"
"errors"
"fmt"
"net"
"sync"
"github.com/gologme/log"
"github.com/yggdrasil-network/water"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
)
const tun_IPv6_HEADER_LENGTH = 40
const tun_ETHER_HEADER_LENGTH = 14
// TunAdapter represents a running TUN/TAP interface and extends the
// yggdrasil.Adapter type. In order to use the TUN/TAP adapter with Yggdrasil,
// you should pass this object to the yggdrasil.SetRouterAdapter() function
// before calling yggdrasil.Start().
type TunAdapter struct {
config *config.NodeState
log *log.Logger
reconfigure chan chan error
listener *yggdrasil.Listener
dialer *yggdrasil.Dialer
addr address.Address
subnet address.Subnet
ckr cryptokey
icmpv6 ICMPv6
mtu int
iface *water.Interface
send chan []byte
mutex sync.RWMutex // Protects the below
addrToConn map[address.Address]*tunConn
subnetToConn map[address.Subnet]*tunConn
dials map[crypto.NodeID][][]byte // Buffer of packets to send after dialing finishes
isOpen bool
}
// Gets the maximum supported MTU for the platform based on the defaults in
// defaults.GetDefaults().
func getSupportedMTU(mtu int) int {
if mtu > defaults.GetDefaults().MaximumIfMTU {
return defaults.GetDefaults().MaximumIfMTU
}
return mtu
}
// Name returns the name of the adapter, e.g. "tun0". On Windows, this may
// return a canonical adapter name instead.
func (tun *TunAdapter) Name() string {
return tun.iface.Name()
}
// MTU gets the adapter's MTU. This can range between 1280 and 65535, although
// the maximum value is determined by your platform. The returned value will
// never exceed that of MaximumMTU().
func (tun *TunAdapter) MTU() int {
return getSupportedMTU(tun.mtu)
}
// IsTAP returns true if the adapter is a TAP adapter (Layer 2) or false if it
// is a TUN adapter (Layer 3).
func (tun *TunAdapter) IsTAP() bool {
return tun.iface.IsTAP()
}
// DefaultName gets the default TUN/TAP interface name for your platform.
func DefaultName() string {
return defaults.GetDefaults().DefaultIfName
}
// DefaultMTU gets the default TUN/TAP interface MTU for your platform. This can
// be as high as MaximumMTU(), depending on platform, but is never lower than 1280.
func DefaultMTU() int {
return defaults.GetDefaults().DefaultIfMTU
}
// DefaultIsTAP returns true if the default adapter mode for the current
// platform is TAP (Layer 2) and returns false for TUN (Layer 3).
func DefaultIsTAP() bool {
return defaults.GetDefaults().DefaultIfTAPMode
}
// MaximumMTU returns the maximum supported TUN/TAP interface MTU for your
// platform. This can be as high as 65535, depending on platform, but is never
// lower than 1280.
func MaximumMTU() int {
return defaults.GetDefaults().MaximumIfMTU
}
// Init initialises the TUN/TAP module. You must have acquired a Listener from
// the Yggdrasil core before this point and it must not be in use elsewhere.
func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener *yggdrasil.Listener, dialer *yggdrasil.Dialer) {
tun.config = config
tun.log = log
tun.listener = listener
tun.dialer = dialer
tun.addrToConn = make(map[address.Address]*tunConn)
tun.subnetToConn = make(map[address.Subnet]*tunConn)
tun.dials = make(map[crypto.NodeID][][]byte)
}
// Start the setup process for the TUN/TAP adapter. If successful, starts the
// read/write goroutines to handle packets on that interface.
func (tun *TunAdapter) Start() error {
current := tun.config.GetCurrent()
if tun.config == nil || tun.listener == nil || tun.dialer == nil {
return errors.New("No configuration available to TUN/TAP")
}
var boxPub crypto.BoxPubKey
boxPubHex, err := hex.DecodeString(current.EncryptionPublicKey)
if err != nil {
return err
}
copy(boxPub[:], boxPubHex)
nodeID := crypto.GetNodeID(&boxPub)
tun.addr = *address.AddrForNodeID(nodeID)
tun.subnet = *address.SubnetForNodeID(nodeID)
tun.mtu = current.IfMTU
ifname := current.IfName
iftapmode := current.IfTAPMode
addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1)
if ifname != "none" {
if err := tun.setup(ifname, iftapmode, addr, tun.mtu); err != nil {
return err
}
}
if ifname == "none" || ifname == "dummy" {
tun.log.Debugln("Not starting TUN/TAP as ifname is none or dummy")
return nil
}
tun.mutex.Lock()
tun.isOpen = true
tun.send = make(chan []byte, 32) // TODO: is this a sensible value?
tun.reconfigure = make(chan chan error)
tun.mutex.Unlock()
go func() {
for {
e := <-tun.reconfigure
e <- nil
}
}()
go tun.handler()
go tun.reader()
go tun.writer()
tun.icmpv6.Init(tun)
if iftapmode {
go tun.icmpv6.Solicit(tun.addr)
}
tun.ckr.init(tun)
return nil
}
// Start the setup process for the TUN/TAP adapter. If successful, starts the
// read/write goroutines to handle packets on that interface.
func (tun *TunAdapter) Stop() error {
tun.isOpen = false
// TODO: we have nothing that cleanly stops all the various goroutines opened
// by TUN/TAP, e.g. readers/writers, sessions
tun.iface.Close()
return nil
}
// UpdateConfig updates the TUN/TAP module with the provided config.NodeConfig
// and then signals the various module goroutines to reconfigure themselves if
// needed.
func (tun *TunAdapter) UpdateConfig(config *config.NodeConfig) {
tun.log.Debugln("Reloading TUN/TAP configuration...")
tun.config.Replace(*config)
errors := 0
components := []chan chan error{
tun.reconfigure,
tun.ckr.reconfigure,
}
for _, component := range components {
response := make(chan error)
component <- response
if err := <-response; err != nil {
tun.log.Errorln(err)
errors++
}
}
if errors > 0 {
tun.log.Warnln(errors, "TUN/TAP module(s) reported errors during configuration reload")
} else {
tun.log.Infoln("TUN/TAP configuration reloaded successfully")
}
}
func (tun *TunAdapter) handler() error {
for {
// Accept the incoming connection
conn, err := tun.listener.Accept()
if err != nil {
tun.log.Errorln("TUN/TAP connection accept error:", err)
return err
}
if _, err := tun.wrap(conn); err != nil {
// Something went wrong when storing the connection, typically that
// something already exists for this address or subnet
tun.log.Debugln("TUN/TAP handler wrap:", err)
}
}
}
func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) {
// Prepare a session wrapper for the given connection
s := tunConn{
tun: tun,
conn: conn,
send: make(chan []byte, 32), // TODO: is this a sensible value?
stop: make(chan struct{}),
alive: make(chan struct{}, 1),
}
c = &s
// Get the remote address and subnet of the other side
remoteNodeID := conn.RemoteAddr()
s.addr = *address.AddrForNodeID(&remoteNodeID)
s.snet = *address.SubnetForNodeID(&remoteNodeID)
// Work out if this is already a destination we already know about
tun.mutex.Lock()
defer tun.mutex.Unlock()
atc, aok := tun.addrToConn[s.addr]
stc, sok := tun.subnetToConn[s.snet]
// If we know about a connection for this destination already then assume it
// is no longer valid and close it
if aok {
atc._close_nomutex()
err = errors.New("replaced connection for address")
} else if sok {
stc._close_nomutex()
err = errors.New("replaced connection for subnet")
}
// Save the session wrapper so that we can look it up quickly next time
// we receive a packet through the interface for this address
tun.addrToConn[s.addr] = &s
tun.subnetToConn[s.snet] = &s
// Start the connection goroutines
go s.reader()
go s.writer()
go s.checkForTimeouts()
// Return
return c, err
}

View File

@ -1,6 +1,6 @@
// +build openbsd freebsd netbsd
package yggdrasil
package tuntap
import (
"encoding/binary"
@ -77,7 +77,7 @@ type in6_ifreq_lifetime struct {
// a system socket and making syscalls to the kernel. This is not refined though
// and often doesn't work (if at all), therefore if a call fails, it resorts
// to calling "ifconfig" instead.
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 {
var config water.Config
if ifname[:4] == "auto" {
ifname = "/dev/tap0"
@ -103,20 +103,20 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
return tun.setupAddress(addr)
}
func (tun *tunAdapter) setupAddress(addr string) error {
func (tun *TunAdapter) setupAddress(addr string) error {
var sfd int
var err error
// Create system socket
if sfd, err = unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0); err != nil {
tun.core.log.Printf("Create AF_INET socket failed: %v.", err)
tun.log.Printf("Create AF_INET socket failed: %v.", err)
return err
}
// Friendly output
tun.core.log.Infof("Interface name: %s", tun.iface.Name())
tun.core.log.Infof("Interface IPv6: %s", addr)
tun.core.log.Infof("Interface MTU: %d", tun.mtu)
tun.log.Infof("Interface name: %s", tun.iface.Name())
tun.log.Infof("Interface IPv6: %s", addr)
tun.log.Infof("Interface MTU: %d", tun.mtu)
// Create the MTU request
var ir in6_ifreq_mtu
@ -126,15 +126,15 @@ func (tun *tunAdapter) setupAddress(addr string) error {
// Set the MTU
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(syscall.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 {
err = errno
tun.core.log.Errorf("Error in SIOCSIFMTU: %v", errno)
tun.log.Errorf("Error in SIOCSIFMTU: %v", errno)
// Fall back to ifconfig to set the MTU
cmd := exec.Command("ifconfig", tun.iface.Name(), "mtu", string(tun.mtu))
tun.core.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
tun.core.log.Errorf("SIOCSIFMTU fallback failed: %v.", err)
tun.core.log.Traceln(string(output))
tun.log.Errorf("SIOCSIFMTU fallback failed: %v.", err)
tun.log.Traceln(string(output))
}
}
@ -155,15 +155,15 @@ func (tun *tunAdapter) setupAddress(addr string) error {
// Set the interface address
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(SIOCSIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 {
err = errno
tun.core.log.Errorf("Error in SIOCSIFADDR_IN6: %v", errno)
tun.log.Errorf("Error in SIOCSIFADDR_IN6: %v", errno)
// Fall back to ifconfig to set the address
cmd := exec.Command("ifconfig", tun.iface.Name(), "inet6", addr)
tun.core.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
tun.core.log.Errorf("SIOCSIFADDR_IN6 fallback failed: %v.", err)
tun.core.log.Traceln(string(output))
tun.log.Errorf("SIOCSIFADDR_IN6 fallback failed: %v.", err)
tun.log.Traceln(string(output))
}
}

View File

@ -1,6 +1,6 @@
// +build !mobile
package yggdrasil
package tuntap
// The darwin platform specific tun parts
@ -16,9 +16,9 @@ import (
)
// 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 {
tun.core.log.Warnln("TAP mode is not supported on this platform, defaulting to TUN")
tun.log.Warnln("TAP mode is not supported on this platform, defaulting to TUN")
}
config := water.Config{DeviceType: water.TUN}
iface, err := water.New(config)
@ -30,7 +30,12 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
return tun.setupAddress(addr)
}
const darwin_SIOCAIFADDR_IN6 = 2155899162
const (
darwin_SIOCAIFADDR_IN6 = 2155899162 // netinet6/in6_var.h
darwin_IN6_IFF_NODAD = 0x0020 // netinet6/in6_var.h
darwin_IN6_IFF_SECURED = 0x0400 // netinet6/in6_var.h
darwin_ND6_INFINITE_LIFETIME = 0xFFFFFFFF // netinet6/nd6.h
)
type in6_addrlifetime struct {
ia6t_expire float64
@ -64,12 +69,12 @@ type ifreq struct {
// Sets the IPv6 address of the utun adapter. On Darwin/macOS this is done using
// a system socket and making direct syscalls to the kernel.
func (tun *tunAdapter) setupAddress(addr string) error {
func (tun *TunAdapter) setupAddress(addr string) error {
var fd int
var err error
if fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, 0); err != nil {
tun.core.log.Printf("Create AF_SYSTEM socket failed: %v.", err)
tun.log.Printf("Create AF_SYSTEM socket failed: %v.", err)
return err
}
@ -91,26 +96,29 @@ func (tun *tunAdapter) setupAddress(addr string) error {
ar.ifra_addr.sin6_addr[i] = uint16(binary.BigEndian.Uint16(b))
}
ar.ifra_lifetime.ia6t_vltime = 0xFFFFFFFF
ar.ifra_lifetime.ia6t_pltime = 0xFFFFFFFF
ar.ifra_flags |= darwin_IN6_IFF_NODAD
ar.ifra_flags |= darwin_IN6_IFF_SECURED
ar.ifra_lifetime.ia6t_vltime = darwin_ND6_INFINITE_LIFETIME
ar.ifra_lifetime.ia6t_pltime = darwin_ND6_INFINITE_LIFETIME
var ir ifreq
copy(ir.ifr_name[:], tun.iface.Name())
ir.ifru_mtu = uint32(tun.mtu)
tun.core.log.Infof("Interface name: %s", ar.ifra_name)
tun.core.log.Infof("Interface IPv6: %s", addr)
tun.core.log.Infof("Interface MTU: %d", ir.ifru_mtu)
tun.log.Infof("Interface name: %s", ar.ifra_name)
tun.log.Infof("Interface IPv6: %s", addr)
tun.log.Infof("Interface MTU: %d", ir.ifru_mtu)
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(darwin_SIOCAIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 {
err = errno
tun.core.log.Errorf("Error in darwin_SIOCAIFADDR_IN6: %v", errno)
tun.log.Errorf("Error in darwin_SIOCAIFADDR_IN6: %v", errno)
return err
}
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 {
err = errno
tun.core.log.Errorf("Error in SIOCSIFMTU: %v", errno)
tun.log.Errorf("Error in SIOCSIFMTU: %v", errno)
return err
}

View File

@ -1,6 +1,6 @@
// +build !mobile
package yggdrasil
package tuntap
// The linux platform specific tun parts
@ -15,7 +15,7 @@ import (
)
// Configures the TAP 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 {
var config water.Config
if iftapmode {
config = water.Config{DeviceType: water.TAP}
@ -40,9 +40,9 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
}
}
// Friendly output
tun.core.log.Infof("Interface name: %s", tun.iface.Name())
tun.core.log.Infof("Interface IPv6: %s", addr)
tun.core.log.Infof("Interface MTU: %d", tun.mtu)
tun.log.Infof("Interface name: %s", tun.iface.Name())
tun.log.Infof("Interface IPv6: %s", addr)
tun.log.Infof("Interface MTU: %d", tun.mtu)
return tun.setupAddress(addr)
}
@ -50,7 +50,7 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
// is used to do this, so there is not a hard requirement on "ip" or "ifconfig"
// to exist on the system, but this will fail if Netlink is not present in the
// kernel (it nearly always is).
func (tun *tunAdapter) setupAddress(addr string) error {
func (tun *TunAdapter) setupAddress(addr string) error {
// Set address
var netIF *net.Interface
ifces, err := net.Interfaces()

View File

@ -1,6 +1,6 @@
// +build !linux,!darwin,!windows,!openbsd,!freebsd,!netbsd,!mobile
package yggdrasil
package tuntap
import water "github.com/yggdrasil-network/water"
@ -9,7 +9,7 @@ import water "github.com/yggdrasil-network/water"
// 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 {
func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
var config water.Config
if iftapmode {
config = water.Config{DeviceType: water.TAP}
@ -27,7 +27,7 @@ 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
// write about it to stdout and don't try to do anything further.
func (tun *tunAdapter) setupAddress(addr string) error {
tun.core.log.Warnln("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr)
func (tun *TunAdapter) setupAddress(addr string) error {
tun.log.Warnln("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr)
return nil
}

View File

@ -1,6 +1,7 @@
package yggdrasil
package tuntap
import (
"errors"
"fmt"
"os/exec"
"strings"
@ -13,9 +14,9 @@ import (
// Configures the TAP adapter with the correct IPv6 address and MTU. On Windows
// we don't make use of a direct operating system API to do this - we instead
// 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 {
tun.core.log.Warnln("TUN mode is not supported on this platform, defaulting to TAP")
tun.log.Warnln("TUN mode is not supported on this platform, defaulting to TAP")
}
config := water.Config{DeviceType: water.TAP}
config.PlatformSpecificParams.ComponentID = "tap0901"
@ -27,23 +28,27 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
}
iface, err := water.New(config)
if err != nil {
panic(err)
}
// Disable/enable the interface to resets its configuration (invalidating iface)
cmd := exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=DISABLED")
tun.core.log.Printf("netsh command: %v", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
tun.core.log.Errorf("Windows netsh failed: %v.", err)
tun.core.log.Traceln(string(output))
return err
}
if iface.Name() == "" {
return errors.New("unable to find TAP adapter with component ID " + config.PlatformSpecificParams.ComponentID)
}
// Reset the adapter - this invalidates iface so we'll need to get a new one
cmd := exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=DISABLED")
tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
tun.log.Errorln("Windows netsh failed:", err)
tun.log.Traceln(string(output))
return err
}
// Bring the interface back up
cmd = exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=ENABLED")
tun.core.log.Printf("netsh command: %v", strings.Join(cmd.Args, " "))
tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " "))
output, err = cmd.CombinedOutput()
if err != nil {
tun.core.log.Errorf("Windows netsh failed: %v.", err)
tun.core.log.Traceln(string(output))
tun.log.Errorln("Windows netsh failed:", err)
tun.log.Traceln(string(output))
return err
}
// Get a new iface
@ -58,41 +63,47 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
panic(err)
}
// Friendly output
tun.core.log.Infof("Interface name: %s", tun.iface.Name())
tun.core.log.Infof("Interface IPv6: %s", addr)
tun.core.log.Infof("Interface MTU: %d", tun.mtu)
tun.log.Infof("Interface name: %s", tun.iface.Name())
tun.log.Infof("Interface IPv6: %s", addr)
tun.log.Infof("Interface MTU: %d", tun.mtu)
return tun.setupAddress(addr)
}
// Sets the MTU of the TAP adapter.
func (tun *tunAdapter) setupMTU(mtu int) error {
func (tun *TunAdapter) setupMTU(mtu int) error {
if tun.iface == nil || tun.iface.Name() == "" {
return errors.New("Can't configure MTU as TAP adapter is not present")
}
// Set MTU
cmd := exec.Command("netsh", "interface", "ipv6", "set", "subinterface",
fmt.Sprintf("interface=%s", tun.iface.Name()),
fmt.Sprintf("mtu=%d", mtu),
"store=active")
tun.core.log.Debugln("netsh command: %v", strings.Join(cmd.Args, " "))
tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
tun.core.log.Errorf("Windows netsh failed: %v.", err)
tun.core.log.Traceln(string(output))
tun.log.Errorln("Windows netsh failed:", err)
tun.log.Traceln(string(output))
return err
}
return nil
}
// Sets the IPv6 address of the TAP adapter.
func (tun *tunAdapter) setupAddress(addr string) error {
func (tun *TunAdapter) setupAddress(addr string) error {
if tun.iface == nil || tun.iface.Name() == "" {
return errors.New("Can't configure IPv6 address as TAP adapter is not present")
}
// Set address
cmd := exec.Command("netsh", "interface", "ipv6", "add", "address",
fmt.Sprintf("interface=%s", tun.iface.Name()),
fmt.Sprintf("addr=%s", addr),
"store=active")
tun.core.log.Debugln("netsh command: %v", strings.Join(cmd.Args, " "))
tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
tun.core.log.Errorf("Windows netsh failed: %v.", err)
tun.core.log.Traceln(string(output))
tun.log.Errorln("Windows netsh failed:", err)
tun.log.Traceln(string(output))
return err
}
return nil

90
src/util/cancellation.go Normal file
View File

@ -0,0 +1,90 @@
package util
import (
"errors"
"runtime"
"sync"
"time"
)
type Cancellation interface {
Finished() <-chan struct{}
Cancel(error) error
Error() error
}
var CancellationFinalized = errors.New("finalizer called")
var CancellationTimeoutError = errors.New("timeout")
func CancellationFinalizer(c Cancellation) {
c.Cancel(CancellationFinalized)
}
type cancellation struct {
cancel chan struct{}
mutex sync.RWMutex
err error
done bool
}
func NewCancellation() Cancellation {
c := cancellation{
cancel: make(chan struct{}),
}
runtime.SetFinalizer(&c, CancellationFinalizer)
return &c
}
func (c *cancellation) Finished() <-chan struct{} {
return c.cancel
}
func (c *cancellation) Cancel(err error) error {
c.mutex.Lock()
defer c.mutex.Unlock()
if c.done {
return c.err
} else {
c.err = err
c.done = true
close(c.cancel)
return nil
}
}
func (c *cancellation) Error() error {
c.mutex.RLock()
err := c.err
c.mutex.RUnlock()
return err
}
func CancellationChild(parent Cancellation) Cancellation {
child := NewCancellation()
go func() {
select {
case <-child.Finished():
case <-parent.Finished():
child.Cancel(parent.Error())
}
}()
return child
}
func CancellationWithTimeout(parent Cancellation, timeout time.Duration) Cancellation {
child := CancellationChild(parent)
go func() {
timer := time.NewTimer(timeout)
defer TimerStop(timer)
select {
case <-child.Finished():
case <-timer.C:
child.Cancel(CancellationTimeoutError)
}
}()
return child
}
func CancellationWithDeadline(parent Cancellation, deadline time.Time) Cancellation {
return CancellationWithTimeout(parent, deadline.Sub(time.Now()))
}

View File

@ -3,6 +3,7 @@ package util
// These are misc. utility functions that didn't really fit anywhere else
import "runtime"
import "sync"
import "time"
// A wrapper around runtime.Gosched() so it doesn't need to be imported elsewhere.
@ -21,40 +22,37 @@ func UnlockThread() {
}
// This is used to buffer recently used slices of bytes, to prevent allocations in the hot loops.
// It's used like a sync.Pool, but with a fixed size and typechecked without type casts to/from interface{} (which were making the profiles look ugly).
var byteStore chan []byte
var byteStoreMutex sync.Mutex
var byteStore [][]byte
func init() {
byteStore = make(chan []byte, 32)
}
// Gets an empty slice from the byte store, if one is available, or else returns a new nil slice.
// Gets an empty slice from the byte store.
func GetBytes() []byte {
select {
case bs := <-byteStore:
return bs[:0]
default:
byteStoreMutex.Lock()
defer byteStoreMutex.Unlock()
if len(byteStore) > 0 {
var bs []byte
bs, byteStore = byteStore[len(byteStore)-1][:0], byteStore[:len(byteStore)-1]
return bs
} else {
return nil
}
}
// Puts a slice in the store, if there's room, or else returns and lets the slice get collected.
// Puts a slice in the store.
func PutBytes(bs []byte) {
select {
case byteStore <- bs:
default:
}
byteStoreMutex.Lock()
defer byteStoreMutex.Unlock()
byteStore = append(byteStore, bs)
}
// This is a workaround to go's broken timer implementation
func TimerStop(t *time.Timer) bool {
if !t.Stop() {
select {
case <-t.C:
default:
}
stopped := t.Stop()
select {
case <-t.C:
default:
}
return true
return stopped
}
// Run a blocking function with a timeout.

View File

@ -1,18 +0,0 @@
package yggdrasil
// Defines the minimum required struct members for an adapter type (this is
// now the base type for tunAdapter in tun.go)
type Adapter struct {
core *Core
send chan<- []byte
recv <-chan []byte
reconfigure chan chan error
}
// Initialises the adapter.
func (adapter *Adapter) init(core *Core, send chan<- []byte, recv <-chan []byte) {
adapter.core = core
adapter.send = send
adapter.recv = recv
adapter.reconfigure = make(chan chan error, 1)
}

File diff suppressed because it is too large Load Diff

540
src/yggdrasil/api.go Normal file
View File

@ -0,0 +1,540 @@
package yggdrasil
import (
"encoding/hex"
"errors"
"fmt"
"net"
"sort"
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/gologme/log"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
)
// Peer represents a single peer object. This contains information from the
// preferred switch port for this peer, although there may be more than one in
// reality.
type Peer struct {
PublicKey crypto.BoxPubKey
Endpoint string
BytesSent uint64
BytesRecvd uint64
Protocol string
Port uint64
Uptime time.Duration
}
// SwitchPeer represents a switch connection to a peer. Note that there may be
// multiple switch peers per actual peer, e.g. if there are multiple connections
// to a given node.
type SwitchPeer struct {
PublicKey crypto.BoxPubKey
Coords []byte
BytesSent uint64
BytesRecvd uint64
Port uint64
Protocol string
Endpoint string
}
// DHTEntry represents a single DHT entry that has been learned or cached from
// DHT searches.
type DHTEntry struct {
PublicKey crypto.BoxPubKey
Coords []byte
LastSeen time.Duration
}
// DHTRes represents a DHT response, as returned by DHTPing.
type DHTRes struct {
PublicKey crypto.BoxPubKey // key of the sender
Coords []byte // coords of the sender
Dest crypto.NodeID // the destination node ID
Infos []DHTEntry // response
}
// NodeInfoPayload represents a RequestNodeInfo response, in bytes.
type NodeInfoPayload []byte
// SwitchQueues represents information from the switch related to link
// congestion and a list of switch queues created in response to congestion on a
// given link.
type SwitchQueues struct {
Queues []SwitchQueue
Count uint64
Size uint64
HighestCount uint64
HighestSize uint64
MaximumSize uint64
}
// SwitchQueue represents a single switch queue, which is created in response
// to congestion on a given link.
type SwitchQueue struct {
ID string
Size uint64
Packets uint64
Port uint64
}
// Session represents an open session with another node.
type Session struct {
PublicKey crypto.BoxPubKey
Coords []byte
BytesSent uint64
BytesRecvd uint64
MTU uint16
Uptime time.Duration
WasMTUFixed bool
}
// GetPeers returns one or more Peer objects containing information about active
// peerings with other Yggdrasil nodes, where one of the responses always
// includes information about the current node (with a port number of 0). If
// there is exactly one entry then this node is not connected to any other nodes
// and is therefore isolated.
func (c *Core) GetPeers() []Peer {
ports := c.peers.ports.Load().(map[switchPort]*peer)
var peers []Peer
var ps []switchPort
for port := range ports {
ps = append(ps, port)
}
sort.Slice(ps, func(i, j int) bool { return ps[i] < ps[j] })
for _, port := range ps {
p := ports[port]
info := Peer{
Endpoint: p.intf.name,
BytesSent: atomic.LoadUint64(&p.bytesSent),
BytesRecvd: atomic.LoadUint64(&p.bytesRecvd),
Protocol: p.intf.info.linkType,
Port: uint64(port),
Uptime: time.Since(p.firstSeen),
}
copy(info.PublicKey[:], p.box[:])
peers = append(peers, info)
}
return peers
}
// GetSwitchPeers returns zero or more SwitchPeer objects containing information
// about switch port connections with other Yggdrasil nodes. Note that, unlike
// GetPeers, GetSwitchPeers does not include information about the current node,
// therefore it is possible for this to return zero elements if the node is
// isolated or not connected to any peers.
func (c *Core) GetSwitchPeers() []SwitchPeer {
var switchpeers []SwitchPeer
table := c.switchTable.table.Load().(lookupTable)
peers := c.peers.ports.Load().(map[switchPort]*peer)
for _, elem := range table.elems {
peer, isIn := peers[elem.port]
if !isIn {
continue
}
coords := elem.locator.getCoords()
info := SwitchPeer{
Coords: append([]byte{}, coords...),
BytesSent: atomic.LoadUint64(&peer.bytesSent),
BytesRecvd: atomic.LoadUint64(&peer.bytesRecvd),
Port: uint64(elem.port),
Protocol: peer.intf.info.linkType,
Endpoint: peer.intf.info.remote,
}
copy(info.PublicKey[:], peer.box[:])
switchpeers = append(switchpeers, info)
}
return switchpeers
}
// GetDHT returns zero or more entries as stored in the DHT, cached primarily
// from searches that have already taken place.
func (c *Core) GetDHT() []DHTEntry {
var dhtentries []DHTEntry
getDHT := func() {
now := time.Now()
var dhtentry []*dhtInfo
for _, v := range c.dht.table {
dhtentry = append(dhtentry, v)
}
sort.SliceStable(dhtentry, func(i, j int) bool {
return dht_ordered(&c.dht.nodeID, dhtentry[i].getNodeID(), dhtentry[j].getNodeID())
})
for _, v := range dhtentry {
info := DHTEntry{
Coords: append([]byte{}, v.coords...),
LastSeen: now.Sub(v.recv),
}
copy(info.PublicKey[:], v.key[:])
dhtentries = append(dhtentries, info)
}
}
c.router.doAdmin(getDHT)
return dhtentries
}
// GetSwitchQueues returns information about the switch queues that are
// currently in effect. These values can change within an instant.
func (c *Core) GetSwitchQueues() SwitchQueues {
var switchqueues SwitchQueues
switchTable := &c.switchTable
getSwitchQueues := func() {
switchqueues = SwitchQueues{
Count: uint64(len(switchTable.queues.bufs)),
Size: switchTable.queues.size,
HighestCount: uint64(switchTable.queues.maxbufs),
HighestSize: switchTable.queues.maxsize,
MaximumSize: switchTable.queueTotalMaxSize,
}
for k, v := range switchTable.queues.bufs {
nexthop := switchTable.bestPortForCoords([]byte(k))
queue := SwitchQueue{
ID: k,
Size: v.size,
Packets: uint64(len(v.packets)),
Port: uint64(nexthop),
}
switchqueues.Queues = append(switchqueues.Queues, queue)
}
}
c.switchTable.doAdmin(getSwitchQueues)
return switchqueues
}
// GetSessions returns a list of open sessions from this node to other nodes.
func (c *Core) GetSessions() []Session {
var sessions []Session
getSessions := func() {
for _, sinfo := range c.sessions.sinfos {
var session Session
workerFunc := func() {
session = Session{
Coords: append([]byte{}, sinfo.coords...),
MTU: sinfo.getMTU(),
BytesSent: sinfo.bytesSent,
BytesRecvd: sinfo.bytesRecvd,
Uptime: time.Now().Sub(sinfo.timeOpened),
WasMTUFixed: sinfo.wasMTUFixed,
}
copy(session.PublicKey[:], sinfo.theirPermPub[:])
}
var skip bool
func() {
defer func() {
if recover() != nil {
skip = true
}
}()
sinfo.doFunc(workerFunc)
}()
if skip {
continue
}
// TODO? skipped known but timed out sessions?
sessions = append(sessions, session)
}
}
c.router.doAdmin(getSessions)
return sessions
}
// BuildName gets the current build name. This is usually injected if built
// from git, or returns "unknown" otherwise.
func BuildName() string {
if buildName == "" {
return "yggdrasil"
}
return buildName
}
// BuildVersion gets the current build version. This is usually injected if
// built from git, or returns "unknown" otherwise.
func BuildVersion() string {
if buildVersion == "" {
return "unknown"
}
return buildVersion
}
// ConnListen returns a listener for Yggdrasil session connections.
func (c *Core) ConnListen() (*Listener, error) {
c.sessions.listenerMutex.Lock()
defer c.sessions.listenerMutex.Unlock()
if c.sessions.listener != nil {
return nil, errors.New("a listener already exists")
}
c.sessions.listener = &Listener{
core: c,
conn: make(chan *Conn),
close: make(chan interface{}),
}
return c.sessions.listener, nil
}
// ConnDialer returns a dialer for Yggdrasil session connections.
func (c *Core) ConnDialer() (*Dialer, error) {
return &Dialer{
core: c,
}, nil
}
// ListenTCP starts a new TCP listener. The input URI should match that of the
// "Listen" configuration item, e.g.
// tcp://a.b.c.d:e
func (c *Core) ListenTCP(uri string) (*TcpListener, error) {
return c.link.tcp.listen(uri)
}
// NodeID gets the node ID.
func (c *Core) NodeID() *crypto.NodeID {
return crypto.GetNodeID(&c.boxPub)
}
// TreeID gets the tree ID.
func (c *Core) TreeID() *crypto.TreeID {
return crypto.GetTreeID(&c.sigPub)
}
// SigningPublicKey gets the node's signing public key.
func (c *Core) SigningPublicKey() string {
return hex.EncodeToString(c.sigPub[:])
}
// EncryptionPublicKey gets the node's encryption public key.
func (c *Core) EncryptionPublicKey() string {
return hex.EncodeToString(c.boxPub[:])
}
// Coords returns the current coordinates of the node.
func (c *Core) Coords() []byte {
table := c.switchTable.table.Load().(lookupTable)
return table.self.getCoords()
}
// Address gets the IPv6 address of the Yggdrasil node. This is always a /128
// address.
func (c *Core) Address() net.IP {
address := net.IP(address.AddrForNodeID(c.NodeID())[:])
return address
}
// Subnet gets the routed IPv6 subnet of the Yggdrasil node. This is always a
// /64 subnet.
func (c *Core) Subnet() net.IPNet {
subnet := address.SubnetForNodeID(c.NodeID())[:]
subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0)
return net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)}
}
// MyNodeInfo gets the currently configured nodeinfo.
func (c *Core) MyNodeInfo() NodeInfoPayload {
return c.router.nodeinfo.getNodeInfo()
}
// SetNodeInfo the lcal nodeinfo. Note that nodeinfo can be any value or struct,
// it will be serialised into JSON automatically.
func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) {
c.router.nodeinfo.setNodeInfo(nodeinfo, nodeinfoprivacy)
}
// GetNodeInfo requests nodeinfo from a remote node, as specified by the public
// key and coordinates specified. The third parameter specifies whether a cached
// result is acceptable - this results in less traffic being generated than is
// necessary when, e.g. crawling the network.
func (c *Core) GetNodeInfo(keyString, coordString string, nocache bool) (NodeInfoPayload, error) {
var key crypto.BoxPubKey
if keyBytes, err := hex.DecodeString(keyString); err != nil {
return NodeInfoPayload{}, err
} else {
copy(key[:], keyBytes)
}
if !nocache {
if response, err := c.router.nodeinfo.getCachedNodeInfo(key); err == nil {
return response, nil
}
}
var coords []byte
for _, cstr := range strings.Split(strings.Trim(coordString, "[]"), " ") {
if cstr == "" {
// Special case, happens if trimmed is the empty string, e.g. this is the root
continue
}
if u64, err := strconv.ParseUint(cstr, 10, 8); err != nil {
return NodeInfoPayload{}, err
} else {
coords = append(coords, uint8(u64))
}
}
response := make(chan *NodeInfoPayload, 1)
sendNodeInfoRequest := func() {
c.router.nodeinfo.addCallback(key, func(nodeinfo *NodeInfoPayload) {
defer func() { recover() }()
select {
case response <- nodeinfo:
default:
}
})
c.router.nodeinfo.sendNodeInfo(key, coords, false)
}
c.router.doAdmin(sendNodeInfoRequest)
go func() {
time.Sleep(6 * time.Second)
close(response)
}()
for res := range response {
return *res, nil
}
return NodeInfoPayload{}, fmt.Errorf("getNodeInfo timeout: %s", keyString)
}
// SetSessionGatekeeper allows you to configure a handler function for deciding
// whether a session should be allowed or not. The default session firewall is
// implemented in this way. The function receives the public key of the remote
// side and a boolean which is true if we initiated the session or false if we
// received an incoming session request. The function should return true to
// allow the session or false to reject it.
func (c *Core) SetSessionGatekeeper(f func(pubkey *crypto.BoxPubKey, initiator bool) bool) {
c.sessions.isAllowedMutex.Lock()
defer c.sessions.isAllowedMutex.Unlock()
c.sessions.isAllowedHandler = f
}
// SetLogger sets the output logger of the Yggdrasil node after startup. This
// may be useful if you want to redirect the output later.
func (c *Core) SetLogger(log *log.Logger) {
c.log = log
}
// AddPeer adds a peer. This should be specified in the peer URI format, e.g.:
// tcp://a.b.c.d:e
// socks://a.b.c.d:e/f.g.h.i:j
// This adds the peer to the peer list, so that they will be called again if the
// connection drops.
func (c *Core) AddPeer(addr string, sintf string) error {
if err := c.CallPeer(addr, sintf); err != nil {
return err
}
c.config.Mutex.Lock()
if sintf == "" {
c.config.Current.Peers = append(c.config.Current.Peers, addr)
} else {
c.config.Current.InterfacePeers[sintf] = append(c.config.Current.InterfacePeers[sintf], addr)
}
c.config.Mutex.Unlock()
return nil
}
// RemovePeer is not implemented yet.
func (c *Core) RemovePeer(addr string, sintf string) error {
// TODO: Implement a reverse of AddPeer, where we look up the port number
// based on the addr and sintf, disconnect it and then remove it from the
// peers list so we don't reconnect to it later
return errors.New("not implemented")
}
// CallPeer calls a peer once. This should be specified in the peer URI format,
// e.g.:
// tcp://a.b.c.d:e
// socks://a.b.c.d:e/f.g.h.i:j
// This does not add the peer to the peer list, so if the connection drops, the
// peer will not be called again automatically.
func (c *Core) CallPeer(addr string, sintf string) error {
return c.link.call(addr, sintf)
}
// DisconnectPeer disconnects a peer once. This should be specified as a port
// number.
func (c *Core) DisconnectPeer(port uint64) error {
c.peers.removePeer(switchPort(port))
return nil
}
// GetAllowedEncryptionPublicKeys returns the public keys permitted for incoming
// peer connections.
func (c *Core) GetAllowedEncryptionPublicKeys() []string {
return c.peers.getAllowedEncryptionPublicKeys()
}
// AddAllowedEncryptionPublicKey whitelists a key for incoming peer connections.
func (c *Core) AddAllowedEncryptionPublicKey(bstr string) (err error) {
c.peers.addAllowedEncryptionPublicKey(bstr)
return nil
}
// RemoveAllowedEncryptionPublicKey removes a key from the whitelist for
// incoming peer connections. If none are set, an empty list permits all
// incoming connections.
func (c *Core) RemoveAllowedEncryptionPublicKey(bstr string) (err error) {
c.peers.removeAllowedEncryptionPublicKey(bstr)
return nil
}
// DHTPing sends a DHT ping to the node with the provided key and coords,
// optionally looking up the specified target NodeID.
func (c *Core) DHTPing(keyString, coordString, targetString string) (DHTRes, error) {
var key crypto.BoxPubKey
if keyBytes, err := hex.DecodeString(keyString); err != nil {
return DHTRes{}, err
} else {
copy(key[:], keyBytes)
}
var coords []byte
for _, cstr := range strings.Split(strings.Trim(coordString, "[]"), " ") {
if cstr == "" {
// Special case, happens if trimmed is the empty string, e.g. this is the root
continue
}
if u64, err := strconv.ParseUint(cstr, 10, 8); err != nil {
return DHTRes{}, err
} else {
coords = append(coords, uint8(u64))
}
}
resCh := make(chan *dhtRes, 1)
info := dhtInfo{
key: key,
coords: coords,
}
target := *info.getNodeID()
if targetString == "none" {
// Leave the default target in place
} else if targetBytes, err := hex.DecodeString(targetString); err != nil {
return DHTRes{}, err
} else if len(targetBytes) != len(target) {
return DHTRes{}, errors.New("Incorrect target NodeID length")
} else {
var target crypto.NodeID
copy(target[:], targetBytes)
}
rq := dhtReqKey{info.key, target}
sendPing := func() {
c.dht.addCallback(&rq, func(res *dhtRes) {
resCh <- res
})
c.dht.ping(&info, &target)
}
c.router.doAdmin(sendPing)
// TODO: do something better than the below...
res := <-resCh
if res != nil {
r := DHTRes{
Coords: append([]byte{}, res.Coords...),
}
copy(r.PublicKey[:], res.Key[:])
for _, i := range res.Infos {
e := DHTEntry{
Coords: append([]byte{}, i.coords...),
}
copy(e.PublicKey[:], i.key[:])
r.Infos = append(r.Infos, e)
}
return r, nil
}
return DHTRes{}, fmt.Errorf("DHT ping timeout: %s", keyString)
}

View File

@ -1,106 +0,0 @@
package yggdrasil
import (
"errors"
"io"
"sync"
)
type awdl struct {
link *link
reconfigure chan chan error
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.reconfigure = make(chan chan error, 1)
a.mutex.Unlock()
go func() {
for e := range a.reconfigure {
e <- nil
}
}()
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")
}

292
src/yggdrasil/conn.go Normal file
View File

@ -0,0 +1,292 @@
package yggdrasil
import (
"errors"
"fmt"
"sync"
"sync/atomic"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
// ConnError implements the net.Error interface
type ConnError struct {
error
timeout bool
temporary bool
closed bool
maxsize int
}
// Timeout returns true if the error relates to a timeout condition on the
// connection.
func (e *ConnError) Timeout() bool {
return e.timeout
}
// Temporary return true if the error is temporary or false if it is a permanent
// error condition.
func (e *ConnError) Temporary() bool {
return e.temporary
}
// PacketTooBig returns in response to sending a packet that is too large, and
// if so, the maximum supported packet size that should be used for the
// connection.
func (e *ConnError) PacketTooBig() bool {
return e.maxsize > 0
}
// PacketMaximumSize returns the maximum supported packet size. This will only
// return a non-zero value if ConnError.PacketTooBig() returns true.
func (e *ConnError) PacketMaximumSize() int {
if !e.PacketTooBig() {
return 0
}
return e.maxsize
}
// Closed returns if the session is already closed and is now unusable.
func (e *ConnError) Closed() bool {
return e.closed
}
type Conn struct {
core *Core
readDeadline atomic.Value // time.Time // TODO timer
writeDeadline atomic.Value // time.Time // TODO timer
cancel util.Cancellation
mutex sync.RWMutex // protects the below
nodeID *crypto.NodeID
nodeMask *crypto.NodeID
session *sessionInfo
}
// TODO func NewConn() that initializes additional fields as needed
func newConn(core *Core, nodeID *crypto.NodeID, nodeMask *crypto.NodeID, session *sessionInfo) *Conn {
conn := Conn{
core: core,
nodeID: nodeID,
nodeMask: nodeMask,
session: session,
cancel: util.NewCancellation(),
}
return &conn
}
func (c *Conn) String() string {
c.mutex.RLock()
defer c.mutex.RUnlock()
return fmt.Sprintf("conn=%p", c)
}
// This should never be called from the router goroutine
func (c *Conn) search() error {
var sinfo *searchInfo
var isIn bool
c.core.router.doAdmin(func() { sinfo, isIn = c.core.searches.searches[*c.nodeID] })
if !isIn {
done := make(chan struct{}, 1)
var sess *sessionInfo
var err error
searchCompleted := func(sinfo *sessionInfo, e error) {
sess = sinfo
err = e
// FIXME close can be called multiple times, do a non-blocking send instead
select {
case done <- struct{}{}:
default:
}
}
c.core.router.doAdmin(func() {
sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted)
sinfo.continueSearch()
})
<-done
c.session = sess
if c.session == nil && err == nil {
panic("search failed but returned no error")
}
if c.session != nil {
c.nodeID = crypto.GetNodeID(&c.session.theirPermPub)
for i := range c.nodeMask {
c.nodeMask[i] = 0xFF
}
}
return err
} else {
return errors.New("search already exists")
}
return nil
}
func (c *Conn) getDeadlineCancellation(value *atomic.Value) util.Cancellation {
if deadline, ok := value.Load().(time.Time); ok {
// A deadline is set, so return a Cancellation that uses it
return util.CancellationWithDeadline(c.cancel, deadline)
} else {
// No cancellation was set, so return a child cancellation with no timeout
return util.CancellationChild(c.cancel)
}
}
func (c *Conn) Read(b []byte) (int, error) {
// Take a copy of the session object
sinfo := c.session
cancel := c.getDeadlineCancellation(&c.readDeadline)
defer cancel.Cancel(nil)
var bs []byte
for {
// Wait for some traffic to come through from the session
select {
case <-cancel.Finished():
if cancel.Error() == util.CancellationTimeoutError {
return 0, ConnError{errors.New("read timeout"), true, false, false, 0}
} else {
return 0, ConnError{errors.New("session closed"), false, false, true, 0}
}
case p, ok := <-sinfo.recv:
// If the session is closed then do nothing
if !ok {
return 0, ConnError{errors.New("session closed"), false, false, true, 0}
}
var err error
sessionFunc := func() {
defer util.PutBytes(p.Payload)
// If the nonce is bad then drop the packet and return an error
if !sinfo.nonceIsOK(&p.Nonce) {
err = ConnError{errors.New("packet dropped due to invalid nonce"), false, true, false, 0}
return
}
// Decrypt the packet
var isOK bool
bs, isOK = crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce)
// Check if we were unable to decrypt the packet for some reason and
// return an error if we couldn't
if !isOK {
err = ConnError{errors.New("packet dropped due to decryption failure"), false, true, false, 0}
return
}
// Update the session
sinfo.updateNonce(&p.Nonce)
sinfo.time = time.Now()
sinfo.bytesRecvd += uint64(len(bs))
}
sinfo.doFunc(sessionFunc)
// Something went wrong in the session worker so abort
if err != nil {
if ce, ok := err.(*ConnError); ok && ce.Temporary() {
continue
}
return 0, err
}
// Copy results to the output slice and clean up
copy(b, bs)
util.PutBytes(bs)
// If we've reached this point then everything went to plan, return the
// number of bytes we populated back into the given slice
return len(bs), nil
}
}
}
func (c *Conn) Write(b []byte) (bytesWritten int, err error) {
sinfo := c.session
var packet []byte
written := len(b)
sessionFunc := func() {
// Does the packet exceed the permitted size for the session?
if uint16(len(b)) > sinfo.getMTU() {
written, err = 0, ConnError{errors.New("packet too big"), true, false, false, int(sinfo.getMTU())}
return
}
// Encrypt the packet
payload, nonce := crypto.BoxSeal(&sinfo.sharedSesKey, b, &sinfo.myNonce)
defer util.PutBytes(payload)
// Construct the wire packet to send to the router
p := wire_trafficPacket{
Coords: sinfo.coords,
Handle: sinfo.theirHandle,
Nonce: *nonce,
Payload: payload,
}
packet = p.encode()
sinfo.bytesSent += uint64(len(b))
// The rest of this work is session keep-alive traffic
doSearch := func() {
routerWork := func() {
// Check to see if there is a search already matching the destination
sinfo, isIn := c.core.searches.searches[*c.nodeID]
if !isIn {
// Nothing was found, so create a new search
searchCompleted := func(sinfo *sessionInfo, e error) {}
sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted)
c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo)
}
// Continue the search
sinfo.continueSearch()
}
go func() { c.core.router.admin <- routerWork }()
}
switch {
case time.Since(sinfo.time) > 6*time.Second:
if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second {
// TODO double check that the above condition is correct
doSearch()
} else {
sinfo.core.sessions.ping(sinfo)
}
case sinfo.reset && sinfo.pingTime.Before(sinfo.time):
sinfo.core.sessions.ping(sinfo)
default: // Don't do anything, to keep traffic throttled
}
}
sinfo.doFunc(sessionFunc)
// Give the packet to the router
if written > 0 {
sinfo.core.router.out(packet)
}
// Finally return the number of bytes we wrote
return written, err
}
func (c *Conn) Close() (err error) {
c.mutex.Lock()
defer c.mutex.Unlock()
if c.session != nil {
// Close the session, if it hasn't been closed already
c.core.router.doAdmin(c.session.close)
}
if e := c.cancel.Cancel(errors.New("connection closed")); e != nil {
err = ConnError{errors.New("close failed, session already closed"), false, false, true, 0}
}
return
}
func (c *Conn) LocalAddr() crypto.NodeID {
return *crypto.GetNodeID(&c.session.core.boxPub)
}
func (c *Conn) RemoteAddr() crypto.NodeID {
c.mutex.RLock()
defer c.mutex.RUnlock()
return *c.nodeID
}
func (c *Conn) SetDeadline(t time.Time) error {
c.SetReadDeadline(t)
c.SetWriteDeadline(t)
return nil
}
func (c *Conn) SetReadDeadline(t time.Time) error {
c.readDeadline.Store(t)
return nil
}
func (c *Conn) SetWriteDeadline(t time.Time) error {
c.writeDeadline.Store(t)
return nil
}

View File

@ -2,36 +2,26 @@ package yggdrasil
import (
"encoding/hex"
"errors"
"io/ioutil"
"net"
"sync"
"time"
"github.com/gologme/log"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
)
var buildName 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
// object for each Yggdrasil node you plan to run.
type Core struct {
// 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
config config.NodeState // Config
boxPub crypto.BoxPubKey
boxPriv crypto.BoxPrivKey
sigPub crypto.SigPubKey
@ -41,9 +31,7 @@ type Core struct {
sessions sessions
router router
dht dht
admin admin
searches searches
multicast multicast
link link
log *log.Logger
}
@ -57,33 +45,42 @@ func (c *Core) init() error {
c.log = log.New(ioutil.Discard, "", 0)
}
boxPubHex, err := hex.DecodeString(c.config.EncryptionPublicKey)
current := c.config.GetCurrent()
boxPrivHex, err := hex.DecodeString(current.EncryptionPrivateKey)
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
if len(boxPrivHex) < crypto.BoxPrivKeyLen {
return errors.New("EncryptionPrivateKey is incorrect length")
}
sigPrivHex, err := hex.DecodeString(current.SigningPrivateKey)
if err != nil {
return err
}
if len(sigPrivHex) < crypto.SigPrivKeyLen {
return errors.New("SigningPrivateKey is incorrect length")
}
copy(c.boxPub[:], boxPubHex)
copy(c.boxPriv[:], boxPrivHex)
copy(c.sigPub[:], sigPubHex)
copy(c.sigPriv[:], sigPrivHex)
c.admin.init(c)
boxPub, sigPub := c.boxPriv.Public(), c.sigPriv.Public()
copy(c.boxPub[:], boxPub[:])
copy(c.sigPub[:], sigPub[:])
if bp := hex.EncodeToString(c.boxPub[:]); current.EncryptionPublicKey != bp {
c.log.Warnln("EncryptionPublicKey in config is incorrect, should be", bp)
}
if sp := hex.EncodeToString(c.sigPub[:]); current.SigningPublicKey != sp {
c.log.Warnln("SigningPublicKey in config is incorrect, should be", sp)
}
c.searches.init(c)
c.dht.init(c)
c.sessions.init(c)
c.multicast.init(c)
c.peers.init(c)
c.router.init(c)
c.switchTable.init(c) // TODO move before peers? before router?
@ -96,22 +93,19 @@ func (c *Core) init() error {
// 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()
// the peers from the config - these could change!
current := c.config.GetCurrent()
// Add peers from the Peers section
for _, peer := range peers {
c.AddPeer(peer, "")
for _, peer := range current.Peers {
go c.AddPeer(peer, "")
time.Sleep(time.Second)
}
// Add peers from the InterfacePeers section
for intf, intfpeers := range interfacepeers {
for intf, intfpeers := range current.InterfacePeers {
for _, peer := range intfpeers {
c.AddPeer(peer, intf)
go c.AddPeer(peer, intf)
time.Sleep(time.Second)
}
}
@ -121,30 +115,24 @@ func (c *Core) addPeerLoop() {
}
}
// UpdateConfig updates the configuration in Core and then signals the
// various module goroutines to reconfigure themselves if needed
// UpdateConfig updates the configuration in Core with the provided
// config.NodeConfig and then signals the various module goroutines to
// reconfigure themselves if needed.
func (c *Core) UpdateConfig(config *config.NodeConfig) {
c.log.Infoln("Reloading configuration...")
c.log.Debugln("Reloading node configuration...")
c.configMutex.Lock()
c.configOld = c.config
c.config = *config
c.configMutex.Unlock()
c.config.Replace(*config)
errors := 0
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.link.reconfigure,
c.multicast.reconfigure,
}
for _, component := range components {
@ -157,196 +145,64 @@ func (c *Core) UpdateConfig(config *config.NodeConfig) {
}
if errors > 0 {
c.log.Warnln(errors, "modules reported errors during configuration reload")
c.log.Warnln(errors, "node module(s) reported errors during configuration reload")
} else {
c.log.Infoln("Configuration reloaded successfully")
c.log.Infoln("Node configuration reloaded successfully")
}
}
// GetBuildName gets the current build name. This is usually injected if built
// from git, or returns "unknown" otherwise.
func GetBuildName() string {
if buildName == "" {
return "unknown"
}
return buildName
}
// Get the current build version. This is usually injected if built from git,
// or returns "unknown" otherwise.
func GetBuildVersion() string {
if buildVersion == "" {
return "unknown"
}
return buildVersion
}
// Starts up Yggdrasil using the provided NodeConfig, and outputs debug logging
// through the provided log.Logger. The started stack will include TCP and UDP
// sockets, a multicast discovery socket, an admin socket, router, switch and
// DHT node.
func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error {
// Start starts up Yggdrasil using the provided config.NodeConfig, and outputs
// debug logging through the provided log.Logger. The started stack will include
// TCP and UDP sockets, a multicast discovery socket, an admin socket, router,
// switch and DHT node. A config.NodeState is returned which contains both the
// current and previous configurations (from reconfigures).
func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, error) {
c.log = log
if name := GetBuildName(); name != "unknown" {
c.config = config.NodeState{
Current: *nc,
Previous: *nc,
}
if name := BuildName(); name != "unknown" {
c.log.Infoln("Build name:", name)
}
if version := GetBuildVersion(); version != "unknown" {
if version := BuildVersion(); version != "unknown" {
c.log.Infoln("Build version:", version)
}
c.log.Infoln("Starting up...")
c.configMutex.Lock()
c.config = *nc
c.configOld = c.config
c.configMutex.Unlock()
c.init()
if err := c.link.init(c); err != nil {
c.log.Errorln("Failed to start link interfaces")
return err
return nil, err
}
if nc.SwitchOptions.MaxTotalQueueSize >= SwitchQueueTotalMinSize {
c.switchTable.queueTotalMaxSize = nc.SwitchOptions.MaxTotalQueueSize
c.config.Mutex.RLock()
if c.config.Current.SwitchOptions.MaxTotalQueueSize >= SwitchQueueTotalMinSize {
c.switchTable.queueTotalMaxSize = c.config.Current.SwitchOptions.MaxTotalQueueSize
}
c.config.Mutex.RUnlock()
if err := c.switchTable.start(); err != nil {
c.log.Errorln("Failed to start switch")
return err
return nil, err
}
if err := c.router.start(); err != nil {
c.log.Errorln("Failed to start router")
return err
}
if err := c.admin.start(); err != nil {
c.log.Errorln("Failed to start admin socket")
return err
}
if err := c.multicast.start(); err != nil {
c.log.Errorln("Failed to start multicast interface")
return err
}
if err := c.router.tun.start(); err != nil {
c.log.Errorln("Failed to start TUN/TAP")
return err
return nil, err
}
go c.addPeerLoop()
c.log.Infoln("Startup complete")
return nil
return &c.config, nil
}
// Stops the Yggdrasil node.
// Stop shuts down the Yggdrasil node.
func (c *Core) Stop() {
c.log.Infoln("Stopping...")
c.router.tun.close()
c.admin.close()
}
// Generates a new encryption keypair. The encryption keys are used to
// encrypt traffic and to derive the IPv6 address/subnet of the node.
func (c *Core) NewEncryptionKeys() (*crypto.BoxPubKey, *crypto.BoxPrivKey) {
return crypto.NewBoxKeys()
}
// Generates a new signing keypair. The signing keys are used to derive the
// structure of the spanning tree.
func (c *Core) NewSigningKeys() (*crypto.SigPubKey, *crypto.SigPrivKey) {
return crypto.NewSigKeys()
}
// Gets the node ID.
func (c *Core) GetNodeID() *crypto.NodeID {
return crypto.GetNodeID(&c.boxPub)
}
// Gets the tree ID.
func (c *Core) GetTreeID() *crypto.TreeID {
return crypto.GetTreeID(&c.sigPub)
}
// Gets the IPv6 address of the Yggdrasil node. This is always a /128.
func (c *Core) GetAddress() *net.IP {
address := net.IP(address.AddrForNodeID(c.GetNodeID())[:])
return &address
}
// Gets the routed IPv6 subnet of the Yggdrasil node. This is always a /64.
func (c *Core) GetSubnet() *net.IPNet {
subnet := address.SubnetForNodeID(c.GetNodeID())[:]
subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0)
return &net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)}
}
// Gets the nodeinfo.
func (c *Core) GetNodeInfo() nodeinfoPayload {
return c.router.nodeinfo.getNodeInfo()
}
// Sets the nodeinfo.
func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) {
c.router.nodeinfo.setNodeInfo(nodeinfo, nodeinfoprivacy)
}
// Sets the output logger of the Yggdrasil node after startup. This may be
// useful if you want to redirect the output later.
func (c *Core) SetLogger(log *log.Logger) {
c.log = log
}
// Adds a peer. This should be specified in the peer URI format, i.e.
// tcp://a.b.c.d:e, udp://a.b.c.d:e, socks://a.b.c.d:e/f.g.h.i:j
func (c *Core) AddPeer(addr string, sintf string) error {
return c.admin.addPeer(addr, sintf)
}
// Adds an allowed public key. This allow peerings to be restricted only to
// keys that you have selected.
func (c *Core) AddAllowedEncryptionPublicKey(boxStr string) error {
return c.admin.addAllowedEncryptionPublicKey(boxStr)
}
// Gets the default admin listen address for your platform.
func (c *Core) GetAdminDefaultListen() string {
return defaults.GetDefaults().DefaultAdminListen
}
// Gets the default TUN/TAP interface name for your platform.
func (c *Core) GetTUNDefaultIfName() string {
return defaults.GetDefaults().DefaultIfName
}
// Gets the default TUN/TAP interface MTU for your platform. This can be as high
// as 65535, depending on platform, but is never lower than 1280.
func (c *Core) GetTUNDefaultIfMTU() int {
return defaults.GetDefaults().DefaultIfMTU
}
// Gets the maximum supported TUN/TAP interface MTU for your platform. This
// can be as high as 65535, depending on platform, but is never lower than 1280.
func (c *Core) GetTUNMaximumIfMTU() int {
return defaults.GetDefaults().MaximumIfMTU
}
// Gets the default TUN/TAP interface mode for your platform.
func (c *Core) GetTUNDefaultIfTAPMode() bool {
return defaults.GetDefaults().DefaultIfTAPMode
}
// Gets the current TUN/TAP interface name.
func (c *Core) GetTUNIfName() string {
return c.router.tun.iface.Name()
}
// Gets the current TUN/TAP interface MTU.
func (c *Core) GetTUNIfMTU() int {
return c.router.tun.mtu
}

View File

@ -35,9 +35,9 @@ func init() {
hostPort := os.Getenv(envVarName)
switch {
case hostPort == "":
fmt.Printf("DEBUG: %s not set, profiler not started.\n", envVarName)
fmt.Fprintf(os.Stderr, "DEBUG: %s not set, profiler not started.\n", envVarName)
default:
fmt.Printf("DEBUG: Starting pprof on %s\n", hostPort)
fmt.Fprintf(os.Stderr, "DEBUG: Starting pprof on %s\n", hostPort)
go func() { fmt.Println(http.ListenAndServe(hostPort, nil)) }()
}
}
@ -59,13 +59,17 @@ func (c *Core) Init() {
hbpriv := hex.EncodeToString(bpriv[:])
hspub := hex.EncodeToString(spub[:])
hspriv := hex.EncodeToString(spriv[:])
c.config = config.NodeConfig{
cfg := config.NodeConfig{
EncryptionPublicKey: hbpub,
EncryptionPrivateKey: hbpriv,
SigningPublicKey: hspub,
SigningPrivateKey: hspriv,
}
c.init( /*bpub, bpriv, spub, spriv*/ )
c.config = config.NodeState{
Current: cfg,
Previous: cfg,
}
c.init()
c.switchTable.start()
c.router.start()
}
@ -82,6 +86,7 @@ func (c *Core) DEBUG_getEncryptionPublicKey() crypto.BoxPubKey {
return (crypto.BoxPubKey)(c.boxPub)
}
/*
func (c *Core) DEBUG_getSend() chan<- []byte {
return c.router.tun.send
}
@ -89,6 +94,7 @@ func (c *Core) DEBUG_getSend() chan<- []byte {
func (c *Core) DEBUG_getRecv() <-chan []byte {
return c.router.tun.recv
}
*/
// Peer
@ -317,6 +323,7 @@ func (c *Core) DEBUG_getAddr() *address.Address {
return address.AddrForNodeID(&c.dht.nodeID)
}
/*
func (c *Core) DEBUG_startTun(ifname string, iftapmode bool) {
c.DEBUG_startTunWithMTU(ifname, iftapmode, 1280)
}
@ -338,6 +345,7 @@ func (c *Core) DEBUG_startTunWithMTU(ifname string, iftapmode bool, mtu int) {
func (c *Core) DEBUG_stopTun() {
c.router.tun.close()
}
*/
////////////////////////////////////////////////////////////////////////////////
@ -382,13 +390,17 @@ func (c *Core) DEBUG_init(bpub []byte,
hbpriv := hex.EncodeToString(bpriv[:])
hspub := hex.EncodeToString(spub[:])
hspriv := hex.EncodeToString(spriv[:])
c.config = config.NodeConfig{
cfg := config.NodeConfig{
EncryptionPublicKey: hbpub,
EncryptionPrivateKey: hbpriv,
SigningPublicKey: hspub,
SigningPrivateKey: hspriv,
}
c.init( /*bpub, bpriv, spub, spriv*/ )
c.config = config.NodeState{
Current: cfg,
Previous: cfg,
}
c.init()
if err := c.router.start(); err != nil {
panic(err)
@ -427,14 +439,14 @@ func (c *Core) DEBUG_maybeSendUDPKeys(saddr string) {
*/
////////////////////////////////////////////////////////////////////////////////
/*
func (c *Core) DEBUG_addPeer(addr string) {
err := c.admin.addPeer(addr, "")
if err != nil {
panic(err)
}
}
*/
/*
func (c *Core) DEBUG_addSOCKSConn(socksaddr, peeraddr string) {
go func() {
@ -455,7 +467,7 @@ func (c *Core) DEBUG_addSOCKSConn(socksaddr, peeraddr string) {
}
*/
//*
/*
func (c *Core) DEBUG_setupAndStartGlobalTCPInterface(addrport string) {
c.config.Listen = []string{addrport}
if err := c.link.init(c); err != nil {
@ -503,10 +515,11 @@ func (c *Core) DEBUG_addKCPConn(saddr string) {
////////////////////////////////////////////////////////////////////////////////
/*
func (c *Core) DEBUG_setupAndStartAdminInterface(addrport string) {
a := admin{}
c.config.AdminListen = addrport
a.init(c /*, addrport*/)
a.init()
c.admin = a
}
@ -516,6 +529,7 @@ func (c *Core) DEBUG_setupAndStartMulticastInterface() {
c.multicast = m
m.start()
}
*/
////////////////////////////////////////////////////////////////////////////////
@ -527,13 +541,14 @@ func (c *Core) DEBUG_setIfceExpr(expr *regexp.Regexp) {
c.log.Println("DEBUG_setIfceExpr no longer implemented")
}
/*
func (c *Core) DEBUG_addAllowedEncryptionPublicKey(boxStr string) {
err := c.admin.addAllowedEncryptionPublicKey(boxStr)
if err != nil {
panic(err)
}
}
*/
////////////////////////////////////////////////////////////////////////////////
func DEBUG_simLinkPeers(p, q *peer) {
@ -579,9 +594,11 @@ func DEBUG_simLinkPeers(p, q *peer) {
q.core.switchTable.idleIn <- q.port
}
/*
func (c *Core) DEBUG_simFixMTU() {
c.router.tun.mtu = 65535
}
*/
////////////////////////////////////////////////////////////////////////////////

View File

@ -68,9 +68,9 @@ type dht struct {
core *Core
reconfigure chan chan error
nodeID crypto.NodeID
peers chan *dhtInfo // other goroutines put incoming dht updates here
reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests
callbacks map[dhtReqKey]dht_callbackInfo // Search and admin lookup callbacks
peers chan *dhtInfo // other goroutines put incoming dht updates here
reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests
callbacks map[dhtReqKey][]dht_callbackInfo // Search and admin lookup callbacks
// These next two could be replaced by a single linked list or similar...
table map[crypto.NodeID]*dhtInfo
imp []*dhtInfo
@ -86,9 +86,9 @@ func (t *dht) init(c *Core) {
e <- nil
}
}()
t.nodeID = *t.core.GetNodeID()
t.nodeID = *t.core.NodeID()
t.peers = make(chan *dhtInfo, 1024)
t.callbacks = make(map[dhtReqKey]dht_callbackInfo)
t.callbacks = make(map[dhtReqKey][]dht_callbackInfo)
t.reset()
}
@ -244,15 +244,17 @@ type dht_callbackInfo struct {
// Adds a callback and removes it after some timeout.
func (t *dht) addCallback(rq *dhtReqKey, callback func(*dhtRes)) {
info := dht_callbackInfo{callback, time.Now().Add(6 * time.Second)}
t.callbacks[*rq] = info
t.callbacks[*rq] = append(t.callbacks[*rq], info)
}
// Reads a lookup response, checks that we had sent a matching request, and processes the response info.
// This mainly consists of updating the node we asked in our DHT (they responded, so we know they're still alive), and deciding if we want to do anything with their responses
func (t *dht) handleRes(res *dhtRes) {
rq := dhtReqKey{res.Key, res.Dest}
if callback, isIn := t.callbacks[rq]; isIn {
callback.f(res)
if callbacks, isIn := t.callbacks[rq]; isIn {
for _, callback := range callbacks {
callback.f(res)
}
delete(t.callbacks, rq)
}
_, isIn := t.reqs[rq]
@ -326,10 +328,15 @@ func (t *dht) doMaintenance() {
}
}
t.reqs = newReqs
newCallbacks := make(map[dhtReqKey]dht_callbackInfo, len(t.callbacks))
for key, callback := range t.callbacks {
if now.Before(callback.time) {
newCallbacks[key] = callback
newCallbacks := make(map[dhtReqKey][]dht_callbackInfo, len(t.callbacks))
for key, cs := range t.callbacks {
for _, c := range cs {
if now.Before(c.time) {
newCallbacks[key] = append(newCallbacks[key], c)
} else {
// Signal failure
c.f(nil)
}
}
}
t.callbacks = newCallbacks

View File

@ -1,58 +0,0 @@
package yggdrasil
import (
"net"
"time"
)
// wrappedConn implements net.Conn
type wrappedConn struct {
c net.Conn
raddr net.Addr
}
// wrappedAddr implements net.Addr
type wrappedAddr struct {
network string
addr string
}
func (a *wrappedAddr) Network() string {
return a.network
}
func (a *wrappedAddr) String() string {
return a.addr
}
func (c *wrappedConn) Write(data []byte) (int, error) {
return c.c.Write(data)
}
func (c *wrappedConn) Read(data []byte) (int, error) {
return c.c.Read(data)
}
func (c *wrappedConn) SetDeadline(t time.Time) error {
return c.c.SetDeadline(t)
}
func (c *wrappedConn) SetReadDeadline(t time.Time) error {
return c.c.SetReadDeadline(t)
}
func (c *wrappedConn) SetWriteDeadline(t time.Time) error {
return c.c.SetWriteDeadline(t)
}
func (c *wrappedConn) Close() error {
return c.c.Close()
}
func (c *wrappedConn) LocalAddr() net.Addr {
return c.c.LocalAddr()
}
func (c *wrappedConn) RemoteAddr() net.Addr {
return c.raddr
}

77
src/yggdrasil/dialer.go Normal file
View File

@ -0,0 +1,77 @@
package yggdrasil
import (
"encoding/hex"
"errors"
"strconv"
"strings"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
)
// Dialer represents an Yggdrasil connection dialer.
type Dialer struct {
core *Core
}
// TODO DialContext that allows timeouts/cancellation, Dial should just call this with no timeout set in the context
// Dial opens a session to the given node. The first paramter should be "nodeid"
// and the second parameter should contain a hexadecimal representation of the
// target node ID.
func (d *Dialer) Dial(network, address string) (*Conn, error) {
var nodeID crypto.NodeID
var nodeMask crypto.NodeID
// Process
switch network {
case "nodeid":
// A node ID was provided - we don't need to do anything special with it
if tokens := strings.Split(address, "/"); len(tokens) == 2 {
len, err := strconv.Atoi(tokens[1])
if err != nil {
return nil, err
}
dest, err := hex.DecodeString(tokens[0])
if err != nil {
return nil, err
}
copy(nodeID[:], dest)
for idx := 0; idx < len; idx++ {
nodeMask[idx/8] |= 0x80 >> byte(idx%8)
}
} else {
dest, err := hex.DecodeString(tokens[0])
if err != nil {
return nil, err
}
copy(nodeID[:], dest)
for i := range nodeMask {
nodeMask[i] = 0xFF
}
}
return d.DialByNodeIDandMask(&nodeID, &nodeMask)
default:
// An unexpected address type was given, so give up
return nil, errors.New("unexpected address type")
}
}
// DialByNodeIDandMask opens a session to the given node based on raw
// NodeID parameters.
func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (*Conn, error) {
conn := newConn(d.core, nodeID, nodeMask, nil)
if err := conn.search(); err != nil {
conn.Close()
return nil, err
}
t := time.NewTimer(6 * time.Second) // TODO use a context instead
defer t.Stop()
select {
case <-conn.session.init:
return conn, nil
case <-t.C:
conn.Close()
return nil, errors.New("session handshake timeout")
}
}

View File

@ -23,8 +23,7 @@ type link struct {
reconfigure chan chan error
mutex sync.RWMutex // protects interfaces below
interfaces map[linkInfo]*linkInterface
awdl awdl // AWDL interface support
tcp tcp // TCP interface support
tcp tcp // TCP interface support
// TODO timeout (to remove from switch), read from config.ReadTimeout
}
@ -68,26 +67,15 @@ func (l *link) init(c *Core) error {
return err
}
if err := l.awdl.init(l); err != nil {
c.log.Errorln("Failed to start AWDL interface")
return err
}
go func() {
for {
e := <-l.reconfigure
tcpresponse := make(chan error)
awdlresponse := make(chan error)
l.tcp.reconfigure <- tcpresponse
if err := <-tcpresponse; err != nil {
e <- err
continue
}
l.awdl.reconfigure <- awdlresponse
if err := <-awdlresponse; err != nil {
e <- err
continue
}
e <- nil
}
}()

45
src/yggdrasil/listener.go Normal file
View File

@ -0,0 +1,45 @@
package yggdrasil
import (
"errors"
"net"
)
// Listener waits for incoming sessions
type Listener struct {
core *Core
conn chan *Conn
close chan interface{}
}
// Accept blocks until a new incoming session is received
func (l *Listener) Accept() (*Conn, error) {
select {
case c, ok := <-l.conn:
if !ok {
return nil, errors.New("listener closed")
}
return c, nil
case <-l.close:
return nil, errors.New("listener closed")
}
}
// Close will stop the listener
func (l *Listener) Close() (err error) {
defer func() {
recover()
err = errors.New("already closed")
}()
if l.core.sessions.listener == l {
l.core.sessions.listener = nil
}
close(l.close)
close(l.conn)
return nil
}
// Addr is not implemented for this type yet
func (l *Listener) Addr() net.Addr {
return nil
}

View File

@ -1,130 +0,0 @@
// +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()
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()
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()
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

@ -1,12 +0,0 @@
// +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

@ -1,62 +0,0 @@
// +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

@ -13,7 +13,7 @@ import (
type nodeinfo struct {
core *Core
myNodeInfo nodeinfoPayload
myNodeInfo NodeInfoPayload
myNodeInfoMutex sync.RWMutex
callbacks map[crypto.BoxPubKey]nodeinfoCallback
callbacksMutex sync.Mutex
@ -21,15 +21,13 @@ type nodeinfo struct {
cacheMutex sync.RWMutex
}
type nodeinfoPayload []byte
type nodeinfoCached struct {
payload nodeinfoPayload
payload NodeInfoPayload
created time.Time
}
type nodeinfoCallback struct {
call func(nodeinfo *nodeinfoPayload)
call func(nodeinfo *NodeInfoPayload)
created time.Time
}
@ -38,7 +36,7 @@ type nodeinfoReqRes struct {
SendPermPub crypto.BoxPubKey // Sender's permanent key
SendCoords []byte // Sender's coords
IsResponse bool
NodeInfo nodeinfoPayload
NodeInfo NodeInfoPayload
}
// Initialises the nodeinfo cache/callback maps, and starts a goroutine to keep
@ -70,7 +68,7 @@ func (m *nodeinfo) init(core *Core) {
}
// Add a callback for a nodeinfo lookup
func (m *nodeinfo) addCallback(sender crypto.BoxPubKey, call func(nodeinfo *nodeinfoPayload)) {
func (m *nodeinfo) addCallback(sender crypto.BoxPubKey, call func(nodeinfo *NodeInfoPayload)) {
m.callbacksMutex.Lock()
defer m.callbacksMutex.Unlock()
m.callbacks[sender] = nodeinfoCallback{
@ -80,7 +78,7 @@ func (m *nodeinfo) addCallback(sender crypto.BoxPubKey, call func(nodeinfo *node
}
// Handles the callback, if there is one
func (m *nodeinfo) callback(sender crypto.BoxPubKey, nodeinfo nodeinfoPayload) {
func (m *nodeinfo) callback(sender crypto.BoxPubKey, nodeinfo NodeInfoPayload) {
m.callbacksMutex.Lock()
defer m.callbacksMutex.Unlock()
if callback, ok := m.callbacks[sender]; ok {
@ -90,7 +88,7 @@ func (m *nodeinfo) callback(sender crypto.BoxPubKey, nodeinfo nodeinfoPayload) {
}
// Get the current node's nodeinfo
func (m *nodeinfo) getNodeInfo() nodeinfoPayload {
func (m *nodeinfo) getNodeInfo() NodeInfoPayload {
m.myNodeInfoMutex.RLock()
defer m.myNodeInfoMutex.RUnlock()
return m.myNodeInfo
@ -101,8 +99,8 @@ func (m *nodeinfo) setNodeInfo(given interface{}, privacy bool) error {
m.myNodeInfoMutex.Lock()
defer m.myNodeInfoMutex.Unlock()
defaults := map[string]interface{}{
"buildname": GetBuildName(),
"buildversion": GetBuildVersion(),
"buildname": BuildName(),
"buildversion": BuildVersion(),
"buildplatform": runtime.GOOS,
"buildarch": runtime.GOARCH,
}
@ -135,7 +133,7 @@ func (m *nodeinfo) setNodeInfo(given interface{}, privacy bool) error {
}
// Add nodeinfo into the cache for a node
func (m *nodeinfo) addCachedNodeInfo(key crypto.BoxPubKey, payload nodeinfoPayload) {
func (m *nodeinfo) addCachedNodeInfo(key crypto.BoxPubKey, payload NodeInfoPayload) {
m.cacheMutex.Lock()
defer m.cacheMutex.Unlock()
m.cache[key] = nodeinfoCached{
@ -145,13 +143,13 @@ func (m *nodeinfo) addCachedNodeInfo(key crypto.BoxPubKey, payload nodeinfoPaylo
}
// Get a nodeinfo entry from the cache
func (m *nodeinfo) getCachedNodeInfo(key crypto.BoxPubKey) (nodeinfoPayload, error) {
func (m *nodeinfo) getCachedNodeInfo(key crypto.BoxPubKey) (NodeInfoPayload, error) {
m.cacheMutex.RLock()
defer m.cacheMutex.RUnlock()
if nodeinfo, ok := m.cache[key]; ok {
return nodeinfo.payload, nil
}
return nodeinfoPayload{}, errors.New("No cache entry found")
return NodeInfoPayload{}, errors.New("No cache entry found")
}
// Handles a nodeinfo request/response - called from the router

View File

@ -44,42 +44,42 @@ func (ps *peers) init(c *Core) {
// because the key is in the whitelist or because the whitelist is empty.
func (ps *peers) isAllowedEncryptionPublicKey(box *crypto.BoxPubKey) bool {
boxstr := hex.EncodeToString(box[:])
ps.core.configMutex.RLock()
defer ps.core.configMutex.RUnlock()
for _, v := range ps.core.config.AllowedEncryptionPublicKeys {
ps.core.config.Mutex.RLock()
defer ps.core.config.Mutex.RUnlock()
for _, v := range ps.core.config.Current.AllowedEncryptionPublicKeys {
if v == boxstr {
return true
}
}
return len(ps.core.config.AllowedEncryptionPublicKeys) == 0
return len(ps.core.config.Current.AllowedEncryptionPublicKeys) == 0
}
// Adds a key to the whitelist.
func (ps *peers) addAllowedEncryptionPublicKey(box string) {
ps.core.configMutex.RLock()
defer ps.core.configMutex.RUnlock()
ps.core.config.AllowedEncryptionPublicKeys =
append(ps.core.config.AllowedEncryptionPublicKeys, box)
ps.core.config.Mutex.RLock()
defer ps.core.config.Mutex.RUnlock()
ps.core.config.Current.AllowedEncryptionPublicKeys =
append(ps.core.config.Current.AllowedEncryptionPublicKeys, box)
}
// Removes a key from the whitelist.
func (ps *peers) removeAllowedEncryptionPublicKey(box string) {
ps.core.configMutex.RLock()
defer ps.core.configMutex.RUnlock()
for k, v := range ps.core.config.AllowedEncryptionPublicKeys {
ps.core.config.Mutex.RLock()
defer ps.core.config.Mutex.RUnlock()
for k, v := range ps.core.config.Current.AllowedEncryptionPublicKeys {
if v == box {
ps.core.config.AllowedEncryptionPublicKeys =
append(ps.core.config.AllowedEncryptionPublicKeys[:k],
ps.core.config.AllowedEncryptionPublicKeys[k+1:]...)
ps.core.config.Current.AllowedEncryptionPublicKeys =
append(ps.core.config.Current.AllowedEncryptionPublicKeys[:k],
ps.core.config.Current.AllowedEncryptionPublicKeys[k+1:]...)
}
}
}
// Gets the whitelist of allowed keys for incoming connections.
func (ps *peers) getAllowedEncryptionPublicKeys() []string {
ps.core.configMutex.RLock()
defer ps.core.configMutex.RUnlock()
return ps.core.config.AllowedEncryptionPublicKeys
ps.core.config.Mutex.RLock()
defer ps.core.config.Mutex.RUnlock()
return ps.core.config.Current.AllowedEncryptionPublicKeys
}
// Atomically gets a map[switchPort]*peer of known peers.

View File

@ -5,7 +5,7 @@ package yggdrasil
// TODO clean up old/unused code, maybe improve comments on whatever is left
// Send:
// Receive a packet from the tun
// Receive a packet from the adapter
// Look up session (if none exists, trigger a search)
// Hand off to session (which encrypts, etc)
// Session will pass it back to router.out, which hands it off to the self peer
@ -20,47 +20,33 @@ package yggdrasil
// If it's dht/seach/etc. traffic, the router passes it to that part
// If it's an encapsulated IPv6 packet, the router looks up the session for it
// The packet is passed to the session, which decrypts it, router.recvPacket
// The router then runs some sanity checks before passing it to the tun
// The router then runs some sanity checks before passing it to the adapter
import (
"bytes"
"time"
//"bytes"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv6"
"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"
)
// The router struct has channels to/from the tun/tap device and a self peer (0), which is how messages are passed between this node and the peers/switch layer.
// The router struct has channels to/from the adapter device and a self peer (0), which is how messages are passed between this node and the peers/switch layer.
// The router's mainLoop goroutine is responsible for managing all information related to the dht, searches, and crypto sessions.
type router struct {
core *Core
reconfigure chan chan error
addr address.Address
subnet address.Subnet
in <-chan []byte // packets we received from the network, link to peer's "out"
out func([]byte) // packets we're sending to the network, link to peer's "in"
toRecv chan router_recvPacket // packets to handle via recvPacket()
tun tunAdapter // TUN/TAP adapter
adapters []Adapter // Other adapters
recv chan<- []byte // place where the tun pulls received packets from
send <-chan []byte // place where the tun puts outgoing packets
reset chan struct{} // signal that coords changed (re-init sessions/dht)
admin chan func() // pass a lambda for the admin socket to query stuff
cryptokey cryptokey
in <-chan []byte // packets we received from the network, link to peer's "out"
out func([]byte) // packets we're sending to the network, link to peer's "in"
reset chan struct{} // signal that coords changed (re-init sessions/dht)
admin chan func() // pass a lambda for the admin socket to query stuff
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.
type router_recvPacket struct {
bs []byte
sinfo *sessionInfo
}
// 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 adapter.
func (r *router) init(core *Core) {
r.core = core
r.reconfigure = make(chan chan error, 1)
@ -104,19 +90,12 @@ func (r *router) init(core *Core) {
}
}()
r.out = func(packet []byte) { out2 <- packet }
r.toRecv = make(chan router_recvPacket, 32)
recv := make(chan []byte, 32)
send := make(chan []byte, 32)
r.recv = recv
r.send = send
r.reset = make(chan struct{}, 1)
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.tun.init(r.core, send, recv)
r.core.config.Mutex.RLock()
r.nodeinfo.setNodeInfo(r.core.config.Current.NodeInfo, r.core.config.Current.NodeInfoPrivacy)
r.core.config.Mutex.RUnlock()
}
// Starts the mainLoop goroutine.
@ -126,7 +105,7 @@ func (r *router) start() error {
return nil
}
// Takes traffic from the tun/tap and passes it to router.send, or from r.in and handles incoming traffic.
// Takes traffic from the adapter and passes it to router.send, or from r.in and handles incoming traffic.
// Also adds new peer info to the DHT.
// Also resets the DHT and sesssions in the event of a coord change.
// Also does periodic maintenance stuff.
@ -135,16 +114,12 @@ func (r *router) mainLoop() {
defer ticker.Stop()
for {
select {
case rp := <-r.toRecv:
r.recvPacket(rp.bs, rp.sinfo)
case p := <-r.in:
r.handleIn(p)
case p := <-r.send:
r.sendPacket(p)
case info := <-r.core.dht.peers:
r.core.dht.insertPeer(info)
case <-r.reset:
r.core.sessions.resetInits()
r.core.sessions.reset()
r.core.dht.reset()
case <-ticker.C:
{
@ -157,231 +132,12 @@ func (r *router) mainLoop() {
case f := <-r.admin:
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()
current := r.core.config.GetCurrent()
e <- r.nodeinfo.setNodeInfo(current.NodeInfo, current.NodeInfoPrivacy)
}
}
}
// Checks a packet's to/from address to make sure it's in the allowed range.
// If a session to the destination exists, gets the session and passes the packet to it.
// If no session exists, it triggers (or continues) a search.
// If the session hasn't responded recently, it triggers a ping or search to keep things alive or deal with broken coords *relatively* quickly.
// It also deals with oversized packets if there are MTU issues by calling into icmpv6.go to spoof PacketTooBig traffic, or DestinationUnreachable if the other side has their tun/tap disabled.
func (r *router) sendPacket(bs []byte) {
var sourceAddr address.Address
var destAddr address.Address
var destSnet address.Subnet
var destPubKey *crypto.BoxPubKey
var destNodeID *crypto.NodeID
var addrlen int
if bs[0]&0xf0 == 0x60 {
// Check if we have a fully-sized header
if len(bs) < 40 {
panic("Tried to send a packet shorter than an IPv6 header...")
}
// IPv6 address
addrlen = 16
copy(sourceAddr[:addrlen], bs[8:])
copy(destAddr[:addrlen], bs[24:])
copy(destSnet[:addrlen/2], bs[24:])
} else if bs[0]&0xf0 == 0x40 {
// Check if we have a fully-sized header
if len(bs) < 20 {
panic("Tried to send a packet shorter than an IPv4 header...")
}
// IPv4 address
addrlen = 4
copy(sourceAddr[:addrlen], bs[12:])
copy(destAddr[:addrlen], bs[16:])
} else {
// Unknown address length
return
}
if !r.cryptokey.isValidSource(sourceAddr, addrlen) {
// The packet had a source address that doesn't belong to us or our
// configured crypto-key routing source subnets
return
}
if !destAddr.IsValid() && !destSnet.IsValid() {
// The addresses didn't match valid Yggdrasil node addresses so let's see
// whether it matches a crypto-key routing range instead
if key, err := r.cryptokey.getPublicKeyForAddress(destAddr, addrlen); err == nil {
// A public key was found, get the node ID for the search
destPubKey = &key
destNodeID = crypto.GetNodeID(destPubKey)
// Do a quick check to ensure that the node ID refers to a vaild Yggdrasil
// address or subnet - this might be superfluous
addr := *address.AddrForNodeID(destNodeID)
copy(destAddr[:], addr[:])
copy(destSnet[:], addr[:])
if !destAddr.IsValid() && !destSnet.IsValid() {
return
}
} else {
// No public key was found in the CKR table so we've exhausted our options
return
}
}
doSearch := func(packet []byte) {
var nodeID, mask *crypto.NodeID
switch {
case destNodeID != nil:
// We already know the full node ID, probably because it's from a CKR
// route in which the public key is known ahead of time
nodeID = destNodeID
var m crypto.NodeID
for i := range m {
m[i] = 0xFF
}
mask = &m
case destAddr.IsValid():
// We don't know the full node ID - try and use the address to generate
// a truncated node ID
nodeID, mask = destAddr.GetNodeIDandMask()
case destSnet.IsValid():
// We don't know the full node ID - try and use the subnet to generate
// a truncated node ID
nodeID, mask = destSnet.GetNodeIDandMask()
default:
return
}
sinfo, isIn := r.core.searches.searches[*nodeID]
if !isIn {
sinfo = r.core.searches.newIterSearch(nodeID, mask)
}
if packet != nil {
sinfo.packet = packet
}
r.core.searches.continueSearch(sinfo)
}
var sinfo *sessionInfo
var isIn bool
if destAddr.IsValid() {
sinfo, isIn = r.core.sessions.getByTheirAddr(&destAddr)
}
if destSnet.IsValid() {
sinfo, isIn = r.core.sessions.getByTheirSubnet(&destSnet)
}
switch {
case !isIn || !sinfo.init:
// No or unintiialized session, so we need to search first
doSearch(bs)
case time.Since(sinfo.time) > 6*time.Second:
if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second {
// We haven't heard from the dest in a while
// We tried pinging but didn't get a response
// They may have changed coords
// Try searching to discover new coords
// Note that search spam is throttled internally
doSearch(nil)
} else {
// We haven't heard about the dest in a while
now := time.Now()
if !sinfo.time.Before(sinfo.pingTime) {
// Update pingTime to start the clock for searches (above)
sinfo.pingTime = now
}
if time.Since(sinfo.pingSend) > time.Second {
// Send at most 1 ping per second
sinfo.pingSend = now
r.core.sessions.sendPingPong(sinfo, false)
}
}
fallthrough // Also send the packet
default:
// If we know the public key ahead of time (i.e. a CKR route) then check
// if the session perm pub key matches before we send the packet to it
if destPubKey != nil {
if !bytes.Equal((*destPubKey)[:], sinfo.theirPermPub[:]) {
return
}
}
// Drop packets if the session MTU is 0 - this means that one or other
// side probably has their TUN adapter disabled
if sinfo.getMTU() == 0 {
// Don't continue - drop the packet
return
}
// Generate an ICMPv6 Packet Too Big for packets larger than session MTU
if len(bs) > int(sinfo.getMTU()) {
// Get the size of the oversized payload, up to a max of 900 bytes
window := 900
if int(sinfo.getMTU()) < window {
window = int(sinfo.getMTU())
}
// Create the Packet Too Big response
ptb := &icmp.PacketTooBig{
MTU: int(sinfo.getMTU()),
Data: bs[:window],
}
// Create the ICMPv6 response from it
icmpv6Buf, err := r.tun.icmpv6.create_icmpv6_tun(
bs[8:24], bs[24:40],
ipv6.ICMPTypePacketTooBig, 0, ptb)
if err == nil {
r.recv <- icmpv6Buf
}
// Don't continue - drop the packet
return
}
sinfo.send <- bs
}
}
// Called for incoming traffic by the session worker for that connection.
// Checks that the IP address is correct (matches the session) and passes the packet to the tun/tap.
func (r *router) recvPacket(bs []byte, sinfo *sessionInfo) {
// Note: called directly by the session worker, not the router goroutine
if len(bs) < 24 {
util.PutBytes(bs)
return
}
var sourceAddr address.Address
var dest address.Address
var snet address.Subnet
var addrlen int
if bs[0]&0xf0 == 0x60 {
// IPv6 address
addrlen = 16
copy(sourceAddr[:addrlen], bs[8:])
copy(dest[:addrlen], bs[24:])
copy(snet[:addrlen/2], bs[8:])
} else if bs[0]&0xf0 == 0x40 {
// IPv4 address
addrlen = 4
copy(sourceAddr[:addrlen], bs[12:])
copy(dest[:addrlen], bs[16:])
} else {
// Unknown address length
return
}
// Check that the packet is destined for either our Yggdrasil address or
// subnet, or that it matches one of the crypto-key routing source routes
if !r.cryptokey.isValidSource(dest, addrlen) {
util.PutBytes(bs)
return
}
// See whether the packet they sent should have originated from this session
switch {
case sourceAddr.IsValid() && sourceAddr == sinfo.theirAddr:
case snet.IsValid() && snet == sinfo.theirSubnet:
default:
key, err := r.cryptokey.getPublicKeyForAddress(sourceAddr, addrlen)
if err != nil || key != sinfo.theirPermPub {
util.PutBytes(bs)
return
}
}
//go func() { r.recv<-bs }()
r.recv <- bs
}
// Checks incoming traffic type and passes it to the appropriate handler.
func (r *router) handleIn(packet []byte) {
pType, pTypeLen := wire_decode_uint64(packet)
@ -398,7 +154,7 @@ func (r *router) handleIn(packet []byte) {
}
// Handles incoming traffic, i.e. encapuslated ordinary IPv6 packets.
// Passes them to the crypto session worker to be decrypted and sent to the tun/tap.
// Passes them to the crypto session worker to be decrypted and sent to the adapter.
func (r *router) handleTraffic(packet []byte) {
defer util.PutBytes(packet)
p := wire_trafficPacket{}
@ -409,7 +165,11 @@ func (r *router) handleTraffic(packet []byte) {
if !isIn {
return
}
sinfo.recv <- &p
select {
case sinfo.recv <- &p: // FIXME ideally this should be front drop
default:
util.PutBytes(p.Payload)
}
}
// Handles protocol traffic by decrypting it, checking its type, and passing it to the appropriate handler for that traffic type.
@ -432,7 +192,7 @@ func (r *router) handleProto(packet []byte) {
return
}
// Now do something with the bytes in bs...
// send dht messages to dht, sessionRefresh to sessions, data to tun...
// send dht messages to dht, sessionRefresh to sessions, data to adapter...
// For data, should check that key and IP match...
bsType, bsTypeLen := wire_decode_uint64(bs)
if bsTypeLen == 0 {

View File

@ -15,6 +15,7 @@ package yggdrasil
// Some kind of max search steps, in case the node is offline, so we don't crawl through too much of the network looking for a destination that isn't there?
import (
"errors"
"sort"
"time"
@ -32,12 +33,14 @@ const search_RETRY_TIME = time.Second
// Information about an ongoing search.
// Includes the target NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited.
type searchInfo struct {
dest crypto.NodeID
mask crypto.NodeID
time time.Time
packet []byte
toVisit []*dhtInfo
visited map[crypto.NodeID]bool
core *Core
dest crypto.NodeID
mask crypto.NodeID
time time.Time
toVisit []*dhtInfo
visited map[crypto.NodeID]bool
callback func(*sessionInfo, error)
// TODO context.Context for timeout and cancellation
}
// This stores a map of active searches.
@ -47,7 +50,7 @@ type searches struct {
searches map[crypto.NodeID]*searchInfo
}
// Intializes the searches struct.
// Initializes the searches struct.
func (s *searches) init(core *Core) {
s.core = core
s.reconfigure = make(chan chan error, 1)
@ -61,17 +64,19 @@ func (s *searches) init(core *Core) {
}
// Creates a new search info, adds it to the searches struct, and returns a pointer to the info.
func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID) *searchInfo {
func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo {
now := time.Now()
for dest, sinfo := range s.searches {
if now.Sub(sinfo.time) > time.Minute {
delete(s.searches, dest)
}
}
//for dest, sinfo := range s.searches {
// if now.Sub(sinfo.time) > time.Minute {
// delete(s.searches, dest)
// }
//}
info := searchInfo{
dest: *dest,
mask: *mask,
time: now.Add(-time.Second),
core: s.core,
dest: *dest,
mask: *mask,
time: now.Add(-time.Second),
callback: callback,
}
s.searches[*dest] = &info
return &info
@ -79,31 +84,29 @@ func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID) *searc
////////////////////////////////////////////////////////////////////////////////
// Checks if there's an ongoing search relaed to a dhtRes.
// Checks if there's an ongoing search related to a dhtRes.
// If there is, it adds the response info to the search and triggers a new search step.
// If there's no ongoing search, or we if the dhtRes finished the search (it was from the target node), then don't do anything more.
func (s *searches) handleDHTRes(res *dhtRes) {
sinfo, isIn := s.searches[res.Dest]
if !isIn || s.checkDHTRes(sinfo, res) {
func (sinfo *searchInfo) handleDHTRes(res *dhtRes) {
if res == nil || sinfo.checkDHTRes(res) {
// Either we don't recognize this search, or we just finished it
return
} else {
// Add to the search and continue
s.addToSearch(sinfo, res)
s.doSearchStep(sinfo)
}
// Add to the search and continue
sinfo.addToSearch(res)
sinfo.doSearchStep()
}
// Adds the information from a dhtRes to an ongoing search.
// Info about a node that has already been visited is not re-added to the search.
// Duplicate information about nodes toVisit is deduplicated (the newest information is kept).
// The toVisit list is sorted in ascending order of keyspace distance from the destination.
func (s *searches) addToSearch(sinfo *searchInfo, res *dhtRes) {
func (sinfo *searchInfo) addToSearch(res *dhtRes) {
// Add responses to toVisit if closer to dest than the res node
from := dhtInfo{key: res.Key, coords: res.Coords}
sinfo.visited[*from.getNodeID()] = true
for _, info := range res.Infos {
if *info.getNodeID() == s.core.dht.nodeID || sinfo.visited[*info.getNodeID()] {
if *info.getNodeID() == sinfo.core.dht.nodeID || sinfo.visited[*info.getNodeID()] {
continue
}
if dht_ordered(&sinfo.dest, info.getNodeID(), from.getNodeID()) {
@ -133,85 +136,91 @@ func (s *searches) addToSearch(sinfo *searchInfo, res *dhtRes) {
// If there are no nodes left toVisit, then this cleans up the search.
// Otherwise, it pops the closest node to the destination (in keyspace) off of the toVisit list and sends a dht ping.
func (s *searches) doSearchStep(sinfo *searchInfo) {
func (sinfo *searchInfo) doSearchStep() {
if len(sinfo.toVisit) == 0 {
// Dead end, do cleanup
delete(s.searches, sinfo.dest)
delete(sinfo.core.searches.searches, sinfo.dest)
sinfo.callback(nil, errors.New("search reached dead end"))
return
} else {
// Send to the next search target
var next *dhtInfo
next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:]
rq := dhtReqKey{next.key, sinfo.dest}
s.core.dht.addCallback(&rq, s.handleDHTRes)
s.core.dht.ping(next, &sinfo.dest)
}
// Send to the next search target
var next *dhtInfo
next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:]
rq := dhtReqKey{next.key, sinfo.dest}
sinfo.core.dht.addCallback(&rq, sinfo.handleDHTRes)
sinfo.core.dht.ping(next, &sinfo.dest)
}
// If we've recenty sent a ping for this search, do nothing.
// Otherwise, doSearchStep and schedule another continueSearch to happen after search_RETRY_TIME.
func (s *searches) continueSearch(sinfo *searchInfo) {
func (sinfo *searchInfo) continueSearch() {
if time.Since(sinfo.time) < search_RETRY_TIME {
return
}
sinfo.time = time.Now()
s.doSearchStep(sinfo)
sinfo.doSearchStep()
// In case the search dies, try to spawn another thread later
// Note that this will spawn multiple parallel searches as time passes
// Any that die aren't restarted, but a new one will start later
retryLater := func() {
newSearchInfo := s.searches[sinfo.dest]
// FIXME this keeps the search alive forever if not for the searches map, fix that
newSearchInfo := sinfo.core.searches.searches[sinfo.dest]
if newSearchInfo != sinfo {
return
}
s.continueSearch(sinfo)
sinfo.continueSearch()
}
go func() {
time.Sleep(search_RETRY_TIME)
s.core.router.admin <- retryLater
sinfo.core.router.admin <- retryLater
}()
}
// Calls create search, and initializes the iterative search parts of the struct before returning it.
func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID) *searchInfo {
sinfo := s.createSearch(dest, mask)
sinfo.toVisit = s.core.dht.lookup(dest, true)
func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo {
sinfo := s.createSearch(dest, mask, callback)
sinfo.visited = make(map[crypto.NodeID]bool)
loc := s.core.switchTable.getLocator()
sinfo.toVisit = append(sinfo.toVisit, &dhtInfo{
key: s.core.boxPub,
coords: loc.getCoords(),
}) // Start the search by asking ourself, useful if we're the destination
return sinfo
}
// Checks if a dhtRes is good (called by handleDHTRes).
// If the response is from the target, get/create a session, trigger a session ping, and return true.
// Otherwise return false.
func (s *searches) checkDHTRes(info *searchInfo, res *dhtRes) bool {
func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool {
them := crypto.GetNodeID(&res.Key)
var destMasked crypto.NodeID
var themMasked crypto.NodeID
for idx := 0; idx < crypto.NodeIDLen; idx++ {
destMasked[idx] = info.dest[idx] & info.mask[idx]
themMasked[idx] = them[idx] & info.mask[idx]
destMasked[idx] = sinfo.dest[idx] & sinfo.mask[idx]
themMasked[idx] = them[idx] & sinfo.mask[idx]
}
if themMasked != destMasked {
return false
}
// They match, so create a session and send a sessionRequest
sinfo, isIn := s.core.sessions.getByTheirPerm(&res.Key)
sess, isIn := sinfo.core.sessions.getByTheirPerm(&res.Key)
if !isIn {
sinfo = s.core.sessions.createSession(&res.Key)
if sinfo == nil {
sess = sinfo.core.sessions.createSession(&res.Key)
if sess == nil {
// nil if the DHT search finished but the session wasn't allowed
sinfo.callback(nil, errors.New("session not allowed"))
return true
}
_, isIn := s.core.sessions.getByTheirPerm(&res.Key)
_, isIn := sinfo.core.sessions.getByTheirPerm(&res.Key)
if !isIn {
panic("This should never happen")
}
}
// FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)?
sinfo.coords = res.Coords
sinfo.packet = info.packet
s.core.sessions.ping(sinfo)
sess.coords = res.Coords
sinfo.core.sessions.ping(sess)
sinfo.callback(sess, nil)
// Cleanup
delete(s.searches, res.Dest)
delete(sinfo.core.searches.searches, res.Dest)
return true
}

View File

@ -6,46 +6,52 @@ package yggdrasil
import (
"bytes"
"encoding/hex"
"sync"
"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"
)
// All the information we know about an active session.
// 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 {
core *Core
reconfigure chan chan error
theirAddr address.Address
theirSubnet address.Subnet
theirPermPub crypto.BoxPubKey
theirSesPub crypto.BoxPubKey
mySesPub crypto.BoxPubKey
mySesPriv crypto.BoxPrivKey
sharedSesKey crypto.BoxSharedKey // derived from session keys
theirHandle crypto.Handle
myHandle crypto.Handle
theirNonce crypto.BoxNonce
myNonce crypto.BoxNonce
theirMTU uint16
myMTU uint16
wasMTUFixed bool // Was the MTU fixed by a receive error?
time time.Time // Time we last received a packet
coords []byte // coords of destination
packet []byte // a buffered packet, sent immediately on ping/pong
init bool // Reset if coords change
send chan []byte
recv chan *wire_trafficPacket
nonceMask uint64
tstamp int64 // tstamp from their last session ping, replay attack mitigation
mtuTime time.Time // time myMTU was last changed
pingTime time.Time // time the first ping was sent since the last received packet
pingSend time.Time // time the last ping was sent
bytesSent uint64 // Bytes of real traffic sent in this session
bytesRecvd uint64 // Bytes of real traffic received in this session
mutex sync.Mutex // Protects all of the below, use it any time you read/chance the contents of a session
core *Core //
reconfigure chan chan error //
theirAddr address.Address //
theirSubnet address.Subnet //
theirPermPub crypto.BoxPubKey //
theirSesPub crypto.BoxPubKey //
mySesPub crypto.BoxPubKey //
mySesPriv crypto.BoxPrivKey //
sharedSesKey crypto.BoxSharedKey // derived from session keys
theirHandle crypto.Handle //
myHandle crypto.Handle //
theirNonce crypto.BoxNonce //
theirNonceMask uint64 //
myNonce crypto.BoxNonce //
theirMTU uint16 //
myMTU uint16 //
wasMTUFixed bool // Was the MTU fixed by a receive error?
timeOpened time.Time // Time the sessino was opened
time time.Time // Time we last received a packet
mtuTime time.Time // time myMTU was last changed
pingTime time.Time // time the first ping was sent since the last received packet
pingSend time.Time // time the last ping was sent
coords []byte // coords of destination
reset bool // reset if coords change
tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation
bytesSent uint64 // Bytes of real traffic sent in this session
bytesRecvd uint64 // Bytes of real traffic received in this session
recv chan *wire_trafficPacket // Received packets go here, picked up by the associated Conn
init chan struct{} // Closed when the first session pong arrives, used to signal that the session is ready for initial use
}
func (sinfo *sessionInfo) doFunc(f func()) {
sinfo.mutex.Lock()
defer sinfo.mutex.Unlock()
f()
}
// Represents a session ping/pong packet, andincludes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU.
@ -53,10 +59,10 @@ type sessionPing struct {
SendPermPub crypto.BoxPubKey // Sender's permanent key
Handle crypto.Handle // Random number to ID session
SendSesPub crypto.BoxPubKey // Session key to use
Coords []byte
Tstamp int64 // unix time, but the only real requirement is that it increases
IsPong bool
MTU uint16
Coords []byte //
Tstamp int64 // unix time, but the only real requirement is that it increases
IsPong bool //
MTU uint16 //
}
// Updates session info in response to a ping, after checking that the ping is OK.
@ -76,7 +82,7 @@ func (s *sessionInfo) update(p *sessionPing) bool {
s.theirHandle = p.Handle
s.sharedSesKey = *crypto.GetSharedKey(&s.mySesPriv, &s.theirSesPub)
s.theirNonce = crypto.BoxNonce{}
s.nonceMask = 0
s.theirNonceMask = 0
}
if p.MTU >= 1280 || p.MTU == 0 {
s.theirMTU = p.MTU
@ -85,35 +91,33 @@ func (s *sessionInfo) update(p *sessionPing) bool {
// allocate enough space for additional coords
s.coords = append(make([]byte, 0, len(p.Coords)+11), p.Coords...)
}
now := time.Now()
s.time = now
s.time = time.Now()
s.tstamp = p.Tstamp
s.init = true
s.reset = false
defer func() { recover() }() // Recover if the below panics
select {
case <-s.init:
default:
// Unblock anything waiting for the session to initialize
close(s.init)
}
return true
}
// Returns true if the session has been idle for longer than the allowed timeout.
func (s *sessionInfo) timedout() bool {
return time.Since(s.time) > time.Minute
}
// Struct of all active sessions.
// Sessions are indexed by handle.
// Additionally, stores maps of address/subnet onto keys, and keys onto handles.
type sessions struct {
core *Core
reconfigure chan chan error
lastCleanup time.Time
// Maps known permanent keys to their shared key, used by DHT a lot
permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey
// Maps (secret) handle onto session info
sinfos map[crypto.Handle]*sessionInfo
// Maps mySesPub onto handle
byMySes map[crypto.BoxPubKey]*crypto.Handle
// Maps theirPermPub onto handle
byTheirPerm map[crypto.BoxPubKey]*crypto.Handle
addrToPerm map[address.Address]*crypto.BoxPubKey
subnetToPerm map[address.Subnet]*crypto.BoxPubKey
core *Core
listener *Listener
listenerMutex sync.Mutex
reconfigure chan chan error
lastCleanup time.Time
isAllowedHandler func(pubkey *crypto.BoxPubKey, initiator bool) bool // Returns true or false if session setup is allowed
isAllowedMutex sync.RWMutex // Protects the above
permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey // Maps known permanent keys to their shared key, used by DHT a lot
sinfos map[crypto.Handle]*sessionInfo // Maps handle onto session info
byTheirPerm map[crypto.BoxPubKey]*crypto.Handle // Maps theirPermPub onto handle
}
// Initializes the session struct.
@ -139,96 +143,26 @@ func (ss *sessions) init(core *Core) {
}()
ss.permShared = make(map[crypto.BoxPubKey]*crypto.BoxSharedKey)
ss.sinfos = make(map[crypto.Handle]*sessionInfo)
ss.byMySes = make(map[crypto.BoxPubKey]*crypto.Handle)
ss.byTheirPerm = make(map[crypto.BoxPubKey]*crypto.Handle)
ss.addrToPerm = make(map[address.Address]*crypto.BoxPubKey)
ss.subnetToPerm = make(map[address.Subnet]*crypto.BoxPubKey)
ss.lastCleanup = time.Now()
}
// Determines whether the session firewall is enabled.
func (ss *sessions) isSessionFirewallEnabled() bool {
ss.core.configMutex.RLock()
defer ss.core.configMutex.RUnlock()
return ss.core.config.SessionFirewall.Enable
}
// Determines whether the session with a given publickey is allowed based on
// session firewall rules.
func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) bool {
ss.core.configMutex.RLock()
defer ss.core.configMutex.RUnlock()
ss.isAllowedMutex.RLock()
defer ss.isAllowedMutex.RUnlock()
// Allow by default if the session firewall is disabled
if !ss.isSessionFirewallEnabled() {
if ss.isAllowedHandler == nil {
return true
}
// Prepare for checking whitelist/blacklist
var box crypto.BoxPubKey
// Reject blacklisted nodes
for _, b := range ss.core.config.SessionFirewall.BlacklistEncryptionPublicKeys {
key, err := hex.DecodeString(b)
if err == nil {
copy(box[:crypto.BoxPubKeyLen], key)
if box == *pubkey {
return false
}
}
}
// Allow whitelisted nodes
for _, b := range ss.core.config.SessionFirewall.WhitelistEncryptionPublicKeys {
key, err := hex.DecodeString(b)
if err == nil {
copy(box[:crypto.BoxPubKeyLen], key)
if box == *pubkey {
return true
}
}
}
// Allow outbound sessions if appropriate
if ss.core.config.SessionFirewall.AlwaysAllowOutbound {
if initiator {
return true
}
}
// Look and see if the pubkey is that of a direct peer
var isDirectPeer bool
for _, peer := range ss.core.peers.ports.Load().(map[switchPort]*peer) {
if peer.box == *pubkey {
isDirectPeer = true
break
}
}
// Allow direct peers if appropriate
if ss.core.config.SessionFirewall.AllowFromDirect && isDirectPeer {
return true
}
// Allow remote nodes if appropriate
if ss.core.config.SessionFirewall.AllowFromRemote && !isDirectPeer {
return true
}
// Finally, default-deny if not matching any of the above rules
return false
return ss.isAllowedHandler(pubkey, initiator)
}
// Gets the session corresponding to a given handle.
func (ss *sessions) getSessionForHandle(handle *crypto.Handle) (*sessionInfo, bool) {
sinfo, isIn := ss.sinfos[*handle]
if isIn && sinfo.timedout() {
// We have a session, but it has timed out
return nil, false
}
return sinfo, isIn
}
// Gets a session corresponding to an ephemeral session key used by this node.
func (ss *sessions) getByMySes(key *crypto.BoxPubKey) (*sessionInfo, bool) {
h, isIn := ss.byMySes[*key]
if !isIn {
return nil, false
}
sinfo, isIn := ss.getSessionForHandle(h)
return sinfo, isIn
}
@ -242,29 +176,11 @@ func (ss *sessions) getByTheirPerm(key *crypto.BoxPubKey) (*sessionInfo, bool) {
return sinfo, isIn
}
// Gets a session corresponding to an IPv6 address used by the remote node.
func (ss *sessions) getByTheirAddr(addr *address.Address) (*sessionInfo, bool) {
p, isIn := ss.addrToPerm[*addr]
if !isIn {
return nil, false
}
sinfo, isIn := ss.getByTheirPerm(p)
return sinfo, isIn
}
// Gets a session corresponding to an IPv6 /64 subnet used by the remote node/network.
func (ss *sessions) getByTheirSubnet(snet *address.Subnet) (*sessionInfo, bool) {
p, isIn := ss.subnetToPerm[*snet]
if !isIn {
return nil, false
}
sinfo, isIn := ss.getByTheirPerm(p)
return sinfo, isIn
}
// 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).
// Creates a new session and lazily cleans up old existing sessions. This
// includse initializing session info to sane defaults (e.g. lowest supported
// MTU).
func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo {
// TODO: this check definitely needs to be moved
if !ss.isSessionAllowed(theirPermKey, true) {
return nil
}
@ -277,12 +193,16 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo {
sinfo.mySesPriv = *priv
sinfo.myNonce = *crypto.NewBoxNonce()
sinfo.theirMTU = 1280
sinfo.myMTU = uint16(ss.core.router.tun.mtu)
ss.core.config.Mutex.RLock()
sinfo.myMTU = uint16(ss.core.config.Current.IfMTU)
ss.core.config.Mutex.RUnlock()
now := time.Now()
sinfo.timeOpened = now
sinfo.time = now
sinfo.mtuTime = now
sinfo.pingTime = now
sinfo.pingSend = now
sinfo.init = make(chan struct{})
higher := false
for idx := range ss.core.boxPub {
if ss.core.boxPub[idx] > sinfo.theirPermPub[idx] {
@ -302,14 +222,9 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo {
sinfo.myHandle = *crypto.NewHandle()
sinfo.theirAddr = *address.AddrForNodeID(crypto.GetNodeID(&sinfo.theirPermPub))
sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub))
sinfo.send = make(chan []byte, 32)
sinfo.recv = make(chan *wire_trafficPacket, 32)
go sinfo.doWorker()
ss.sinfos[sinfo.myHandle] = &sinfo
ss.byMySes[sinfo.mySesPub] = &sinfo.myHandle
ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle
ss.addrToPerm[sinfo.theirAddr] = &sinfo.theirPermPub
ss.subnetToPerm[sinfo.theirSubnet] = &sinfo.theirPermPub
return &sinfo
}
@ -323,11 +238,6 @@ func (ss *sessions) cleanup() {
if time.Since(ss.lastCleanup) < time.Minute {
return
}
for _, s := range ss.sinfos {
if s.timedout() {
s.close()
}
}
permShared := make(map[crypto.BoxPubKey]*crypto.BoxSharedKey, len(ss.permShared))
for k, v := range ss.permShared {
permShared[k] = v
@ -338,38 +248,20 @@ func (ss *sessions) cleanup() {
sinfos[k] = v
}
ss.sinfos = sinfos
byMySes := make(map[crypto.BoxPubKey]*crypto.Handle, len(ss.byMySes))
for k, v := range ss.byMySes {
byMySes[k] = v
}
ss.byMySes = byMySes
byTheirPerm := make(map[crypto.BoxPubKey]*crypto.Handle, len(ss.byTheirPerm))
for k, v := range ss.byTheirPerm {
byTheirPerm[k] = v
}
ss.byTheirPerm = byTheirPerm
addrToPerm := make(map[address.Address]*crypto.BoxPubKey, len(ss.addrToPerm))
for k, v := range ss.addrToPerm {
addrToPerm[k] = v
}
ss.addrToPerm = addrToPerm
subnetToPerm := make(map[address.Subnet]*crypto.BoxPubKey, len(ss.subnetToPerm))
for k, v := range ss.subnetToPerm {
subnetToPerm[k] = v
}
ss.subnetToPerm = subnetToPerm
ss.lastCleanup = time.Now()
}
// Closes a session, removing it from sessions maps and killing the worker goroutine.
// Closes a session, removing it from sessions maps.
func (sinfo *sessionInfo) close() {
delete(sinfo.core.sessions.sinfos, sinfo.myHandle)
delete(sinfo.core.sessions.byMySes, sinfo.mySesPub)
delete(sinfo.core.sessions.byTheirPerm, sinfo.theirPermPub)
delete(sinfo.core.sessions.addrToPerm, sinfo.theirAddr)
delete(sinfo.core.sessions.subnetToPerm, sinfo.theirSubnet)
close(sinfo.send)
close(sinfo.recv)
if s := sinfo.core.sessions.sinfos[sinfo.myHandle]; s == sinfo {
delete(sinfo.core.sessions.sinfos, sinfo.myHandle)
delete(sinfo.core.sessions.byTheirPerm, sinfo.theirPermPub)
}
}
// Returns a session ping appropriate for the given session info.
@ -393,6 +285,8 @@ func (ss *sessions) getPing(sinfo *sessionInfo) sessionPing {
// This comes up with dht req/res and session ping/pong traffic.
func (ss *sessions) getSharedKey(myPriv *crypto.BoxPrivKey,
theirPub *crypto.BoxPubKey) *crypto.BoxSharedKey {
return crypto.GetSharedKey(myPriv, theirPub)
// FIXME concurrency issues with the below, so for now we just burn the CPU every time
if skey, isIn := ss.permShared[*theirPub]; isIn {
return skey
}
@ -431,8 +325,8 @@ func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) {
}
packet := p.encode()
ss.core.router.out(packet)
if !isPong {
sinfo.pingSend = time.Now()
if sinfo.pingTime.Before(sinfo.time) {
sinfo.pingTime = time.Now()
}
}
@ -441,35 +335,39 @@ func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) {
func (ss *sessions) handlePing(ping *sessionPing) {
// Get the corresponding session (or create a new session)
sinfo, isIn := ss.getByTheirPerm(&ping.SendPermPub)
// Check the session firewall
if !isIn && ss.isSessionFirewallEnabled() {
if !ss.isSessionAllowed(&ping.SendPermPub, false) {
return
}
// Check if the session is allowed
// TODO: this check may need to be moved
if !isIn && !ss.isSessionAllowed(&ping.SendPermPub, false) {
return
}
if !isIn || sinfo.timedout() {
if isIn {
sinfo.close()
}
// Create the session if it doesn't already exist
if !isIn {
ss.createSession(&ping.SendPermPub)
sinfo, isIn = ss.getByTheirPerm(&ping.SendPermPub)
if !isIn {
panic("This should not happen")
}
ss.listenerMutex.Lock()
// Check and see if there's a Listener waiting to accept connections
// TODO: this should not block if nothing is accepting
if !ping.IsPong && ss.listener != nil {
conn := newConn(ss.core, crypto.GetNodeID(&sinfo.theirPermPub), &crypto.NodeID{}, sinfo)
for i := range conn.nodeMask {
conn.nodeMask[i] = 0xFF
}
ss.listener.conn <- conn
}
ss.listenerMutex.Unlock()
}
// Update the session
if !sinfo.update(ping) { /*panic("Should not happen in testing")*/
return
}
if !ping.IsPong {
ss.sendPingPong(sinfo, true)
}
if sinfo.packet != nil {
// send
var bs []byte
bs, sinfo.packet = sinfo.packet, nil
ss.core.router.sendPacket(bs)
}
sinfo.doFunc(func() {
// Update the session
if !sinfo.update(ping) { /*panic("Should not happen in testing")*/
return
}
if !ping.IsPong {
ss.sendPingPong(sinfo, true)
}
})
}
// Get the MTU of the session.
@ -492,7 +390,7 @@ func (sinfo *sessionInfo) nonceIsOK(theirNonce *crypto.BoxNonce) bool {
if diff > 0 {
return true
}
return ^sinfo.nonceMask&(0x01<<uint64(-diff)) != 0
return ^sinfo.theirNonceMask&(0x01<<uint64(-diff)) != 0
}
// Updates the nonce mask by (possibly) shifting the bitmask and setting the bit corresponding to this nonce to 1, and then updating the most recent nonce
@ -502,147 +400,21 @@ func (sinfo *sessionInfo) updateNonce(theirNonce *crypto.BoxNonce) {
diff := theirNonce.Minus(&sinfo.theirNonce)
if diff > 0 {
// This nonce is newer, so shift the window before setting the bit, and update theirNonce in the session info.
sinfo.nonceMask <<= uint64(diff)
sinfo.nonceMask &= 0x01
sinfo.theirNonceMask <<= uint64(diff)
sinfo.theirNonceMask &= 0x01
sinfo.theirNonce = *theirNonce
} else {
// This nonce is older, so set the bit but do not shift the window.
sinfo.nonceMask &= 0x01 << uint64(-diff)
sinfo.theirNonceMask &= 0x01 << uint64(-diff)
}
}
// Resets all sessions to an uninitialized state.
// Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change.
func (ss *sessions) resetInits() {
func (ss *sessions) reset() {
for _, sinfo := range ss.sinfos {
sinfo.init = false
sinfo.doFunc(func() {
sinfo.reset = true
})
}
}
////////////////////////////////////////////////////////////////////////////////
// This is for a per-session worker.
// It handles calling the relatively expensive crypto operations.
// It's also responsible for checking nonces and dropping out-of-date/duplicate packets, or else calling the function to update nonces if the packet is OK.
func (sinfo *sessionInfo) doWorker() {
send := make(chan []byte, 32)
defer close(send)
go func() {
for bs := range send {
sinfo.doSend(bs)
}
}()
recv := make(chan *wire_trafficPacket, 32)
defer close(recv)
go func() {
for p := range recv {
sinfo.doRecv(p)
}
}()
for {
select {
case p, ok := <-sinfo.recv:
if ok {
select {
case recv <- p:
default:
// We need something to not block, and it's best to drop it before we decrypt
util.PutBytes(p.Payload)
}
} else {
return
}
case bs, ok := <-sinfo.send:
if ok {
send <- bs
} else {
return
}
case e := <-sinfo.reconfigure:
e <- nil
}
}
}
// This encrypts a packet, creates a trafficPacket struct, encodes it, and sends it to router.out to pass it to the switch layer.
func (sinfo *sessionInfo) doSend(bs []byte) {
defer util.PutBytes(bs)
if !sinfo.init {
// To prevent using empty session keys
return
}
// code isn't multithreaded so appending to this is safe
coords := sinfo.coords
// Work out the flowkey - this is used to determine which switch queue
// traffic will be pushed to in the event of congestion
var flowkey uint64
// Get the IP protocol version from the packet
switch bs[0] & 0xf0 {
case 0x40: // IPv4 packet
// Check the packet meets minimum UDP packet length
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 {
flowkey = uint64(bs[6])<<32 /* proto */ |
uint64(bs[40])<<24 | uint64(bs[41])<<16 /* sport */ |
uint64(bs[42])<<8 | uint64(bs[43]) /* dport */
}
}
}
// If we have a flowkey, either through the IPv6 flowlabel field or through
// known TCP/UDP/SCTP proto-sport-dport triplet, then append it to the coords.
// Appending extra coords after a 0 ensures that we still target the local router
// but lets us send extra data (which is otherwise ignored) to help separate
// traffic streams into independent queues
if flowkey != 0 {
coords = append(coords, 0) // First target the local switchport
coords = wire_put_uint64(flowkey, coords) // Then variable-length encoded flowkey
}
// Prepare the payload
payload, nonce := crypto.BoxSeal(&sinfo.sharedSesKey, bs, &sinfo.myNonce)
defer util.PutBytes(payload)
p := wire_trafficPacket{
Coords: coords,
Handle: sinfo.theirHandle,
Nonce: *nonce,
Payload: payload,
}
packet := p.encode()
sinfo.bytesSent += uint64(len(bs))
sinfo.core.router.out(packet)
}
// This takes a trafficPacket and checks the nonce.
// If the nonce is OK, it decrypts the packet.
// If the decrypted packet is OK, it calls router.recvPacket to pass the packet to the tun/tap.
// If a packet does not decrypt successfully, it assumes the packet was truncated, and updates the MTU accordingly.
// TODO? remove the MTU updating part? That should never happen with TCP peers, and the old UDP code that caused it was removed (and if replaced, should be replaced with something that can reliably send messages with an arbitrary size).
func (sinfo *sessionInfo) doRecv(p *wire_trafficPacket) {
defer util.PutBytes(p.Payload)
if !sinfo.nonceIsOK(&p.Nonce) {
return
}
bs, isOK := crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce)
if !isOK {
util.PutBytes(bs)
return
}
sinfo.updateNonce(&p.Nonce)
sinfo.time = time.Now()
sinfo.bytesRecvd += uint64(len(bs))
sinfo.core.router.toRecv <- router_recvPacket{bs, sinfo}
}

View File

@ -188,9 +188,7 @@ func (t *switchTable) init(core *Core) {
now := time.Now()
t.core = core
t.reconfigure = make(chan chan error, 1)
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)
t.data = switchData{locator: locator, peers: peers}
@ -579,23 +577,28 @@ func (t *switchTable) start() error {
return nil
}
type closerInfo struct {
port switchPort
dist int
}
// Return a map of ports onto distance, keeping only ports closer to the destination than this node
// If the map is empty (or nil), then no peer is closer
func (t *switchTable) getCloser(dest []byte) map[switchPort]int {
func (t *switchTable) getCloser(dest []byte) []closerInfo {
table := t.getTable()
myDist := table.self.dist(dest)
if myDist == 0 {
// Skip the iteration step if it's impossible to be closer
return nil
}
closer := make(map[switchPort]int, len(table.elems))
t.queues.closer = t.queues.closer[:0]
for _, info := range table.elems {
dist := info.locator.dist(dest)
if dist < myDist {
closer[info.port] = dist
t.queues.closer = append(t.queues.closer, closerInfo{info.port, dist})
}
}
return closer
return t.queues.closer
}
// Returns true if the peer is closer to the destination than ourself
@ -658,9 +661,9 @@ func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) boo
var bestDist int
var bestTime time.Time
ports := t.core.peers.getPorts()
for port, dist := range closer {
to := ports[port]
thisTime, isIdle := idle[port]
for _, cinfo := range closer {
to := ports[cinfo.port]
thisTime, isIdle := idle[cinfo.port]
var update bool
switch {
case to == nil:
@ -669,9 +672,9 @@ func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) boo
//nothing
case best == nil:
update = true
case dist < bestDist:
case cinfo.dist < bestDist:
update = true
case dist > bestDist:
case cinfo.dist > bestDist:
//nothing
case thisTime.Before(bestTime):
update = true
@ -680,7 +683,7 @@ func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) boo
}
if update {
best = to
bestDist = dist
bestDist = cinfo.dist
bestTime = thisTime
}
}
@ -713,6 +716,7 @@ type switch_buffers struct {
size uint64 // Total size of all buffers, in bytes
maxbufs int
maxsize uint64
closer []closerInfo // Scratch space
}
func (b *switch_buffers) cleanup(t *switchTable) {

View File

@ -36,14 +36,18 @@ type tcp struct {
link *link
reconfigure chan chan error
mutex sync.Mutex // Protecting the below
listeners map[string]*tcpListener
listeners map[string]*TcpListener
calls map[string]struct{}
conns map[linkInfo](chan struct{})
}
type tcpListener struct {
listener net.Listener
stop chan bool
// TcpListener is a stoppable TCP listener interface. These are typically
// returned from calls to the ListenTCP() function and are also used internally
// to represent listeners created by the "Listen" configuration option and for
// multicast interfaces.
type TcpListener struct {
Listener net.Listener
Stop chan bool
}
// Wrapper function to set additional options for specific connection types.
@ -64,7 +68,7 @@ func (t *tcp) getAddr() *net.TCPAddr {
t.mutex.Lock()
defer t.mutex.Unlock()
for _, l := range t.listeners {
return l.listener.Addr().(*net.TCPAddr)
return l.Listener.Addr().(*net.TCPAddr)
}
return nil
}
@ -76,16 +80,16 @@ func (t *tcp) init(l *link) error {
t.mutex.Lock()
t.calls = make(map[string]struct{})
t.conns = make(map[linkInfo](chan struct{}))
t.listeners = make(map[string]*tcpListener)
t.listeners = make(map[string]*TcpListener)
t.mutex.Unlock()
go func() {
for {
e := <-t.reconfigure
t.link.core.configMutex.RLock()
added := util.Difference(t.link.core.config.Listen, t.link.core.configOld.Listen)
deleted := util.Difference(t.link.core.configOld.Listen, t.link.core.config.Listen)
t.link.core.configMutex.RUnlock()
t.link.core.config.Mutex.RLock()
added := util.Difference(t.link.core.config.Current.Listen, t.link.core.config.Previous.Listen)
deleted := util.Difference(t.link.core.config.Previous.Listen, t.link.core.config.Current.Listen)
t.link.core.config.Mutex.RUnlock()
if len(added) > 0 || len(deleted) > 0 {
for _, a := range added {
if a[:6] != "tcp://" {
@ -103,7 +107,7 @@ func (t *tcp) init(l *link) error {
t.mutex.Lock()
if listener, ok := t.listeners[d[6:]]; ok {
t.mutex.Unlock()
listener.stop <- true
listener.Stop <- true
} else {
t.mutex.Unlock()
}
@ -115,9 +119,9 @@ func (t *tcp) init(l *link) error {
}
}()
t.link.core.configMutex.RLock()
defer t.link.core.configMutex.RUnlock()
for _, listenaddr := range t.link.core.config.Listen {
t.link.core.config.Mutex.RLock()
defer t.link.core.config.Mutex.RUnlock()
for _, listenaddr := range t.link.core.config.Current.Listen {
if listenaddr[:6] != "tcp://" {
continue
}
@ -129,7 +133,7 @@ func (t *tcp) init(l *link) error {
return nil
}
func (t *tcp) listen(listenaddr string) (*tcpListener, error) {
func (t *tcp) listen(listenaddr string) (*TcpListener, error) {
var err error
ctx := context.Background()
@ -138,9 +142,9 @@ func (t *tcp) listen(listenaddr string) (*tcpListener, error) {
}
listener, err := lc.Listen(ctx, "tcp", listenaddr)
if err == nil {
l := tcpListener{
listener: listener,
stop: make(chan bool),
l := TcpListener{
Listener: listener,
Stop: make(chan bool),
}
go t.listener(&l, listenaddr)
return &l, nil
@ -150,7 +154,7 @@ func (t *tcp) listen(listenaddr string) (*tcpListener, error) {
}
// Runs the listener, which spawns off goroutines for incoming connections.
func (t *tcp) listener(l *tcpListener, listenaddr string) {
func (t *tcp) listener(l *TcpListener, listenaddr string) {
if l == nil {
return
}
@ -158,7 +162,7 @@ func (t *tcp) listener(l *tcpListener, listenaddr string) {
t.mutex.Lock()
if _, isIn := t.listeners[listenaddr]; isIn {
t.mutex.Unlock()
l.listener.Close()
l.Listener.Close()
return
} else {
t.listeners[listenaddr] = l
@ -167,20 +171,20 @@ func (t *tcp) listener(l *tcpListener, listenaddr string) {
// And here we go!
accepted := make(chan bool)
defer func() {
t.link.core.log.Infoln("Stopping TCP listener on:", l.listener.Addr().String())
l.listener.Close()
t.link.core.log.Infoln("Stopping TCP listener on:", l.Listener.Addr().String())
l.Listener.Close()
t.mutex.Lock()
delete(t.listeners, listenaddr)
t.mutex.Unlock()
}()
t.link.core.log.Infoln("Listening for TCP on:", l.listener.Addr().String())
t.link.core.log.Infoln("Listening for TCP on:", l.Listener.Addr().String())
for {
var sock net.Conn
var err error
// Listen in a separate goroutine, as that way it does not block us from
// receiving "stop" events
go func() {
sock, err = l.listener.Accept()
sock, err = l.Listener.Accept()
accepted <- true
}()
// Wait for either an accepted connection, or a message telling us to stop
@ -192,7 +196,7 @@ func (t *tcp) listener(l *tcpListener, listenaddr string) {
return
}
go t.handler(sock, true, nil)
case <-l.stop:
case <-l.Stop:
return
}
}
@ -251,13 +255,6 @@ func (t *tcp) call(saddr string, options interface{}, sintf string) {
if err != nil {
return
}
conn = &wrappedConn{
c: conn,
raddr: &wrappedAddr{
network: "tcp",
addr: saddr,
},
}
t.handler(conn, false, dialerdst.String())
} else {
dst, err := net.ResolveTCPAddr("tcp", saddr)

View File

@ -1,266 +0,0 @@
package yggdrasil
// This manages the tun driver to send/recv packets to/from applications
import (
"bytes"
"errors"
"fmt"
"net"
"sync"
"time"
"github.com/songgao/packets/ethernet"
"github.com/yggdrasil-network/water"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
const tun_IPv6_HEADER_LENGTH = 40
const tun_ETHER_HEADER_LENGTH = 14
// Represents a running TUN/TAP interface.
type tunAdapter struct {
Adapter
icmpv6 icmpv6
mtu int
iface *water.Interface
mutex sync.RWMutex // Protects the below
isOpen bool
}
// Gets the maximum supported MTU for the platform based on the defaults in
// defaults.GetDefaults().
func getSupportedMTU(mtu int) int {
if mtu > defaults.GetDefaults().MaximumIfMTU {
return defaults.GetDefaults().MaximumIfMTU
}
return mtu
}
// Initialises the TUN/TAP adapter.
func (tun *tunAdapter) init(core *Core, send chan<- []byte, recv <-chan []byte) {
tun.Adapter.init(core, send, recv)
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
// the read/write goroutines to handle packets on that interface.
func (tun *tunAdapter) start() error {
tun.core.configMutex.RLock()
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 {
return err
}
}
if ifname == "none" || ifname == "dummy" {
return nil
}
tun.mutex.Lock()
tun.isOpen = true
tun.mutex.Unlock()
go func() { tun.core.log.Errorln("WARNING: tun.read() exited with error:", tun.read()) }()
go func() { tun.core.log.Errorln("WARNING: tun.write() exited with error:", tun.write()) }()
if iftapmode {
go func() {
for {
if _, ok := tun.icmpv6.peermacs[tun.core.router.addr]; ok {
break
}
request, err := tun.icmpv6.create_ndp_tap(tun.core.router.addr)
if err != nil {
panic(err)
}
if _, err := tun.iface.Write(request); err != nil {
panic(err)
}
time.Sleep(time.Second)
}
}()
}
return nil
}
// Writes a packet to the TUN/TAP adapter. If the adapter is running in TAP
// mode then additional ethernet encapsulation is added for the benefit of the
// host operating system.
func (tun *tunAdapter) write() error {
for {
data := <-tun.recv
if tun.iface == nil {
continue
}
if tun.iface.IsTAP() {
var destAddr address.Address
if data[0]&0xf0 == 0x60 {
if len(data) < 40 {
panic("Tried to send a packet shorter than an IPv6 header...")
}
copy(destAddr[:16], data[24:])
} else if data[0]&0xf0 == 0x40 {
if len(data) < 20 {
panic("Tried to send a packet shorter than an IPv4 header...")
}
copy(destAddr[:4], data[16:])
} else {
return errors.New("Invalid address family")
}
sendndp := func(destAddr address.Address) {
neigh, known := tun.icmpv6.peermacs[destAddr]
known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30)
if !known {
request, err := tun.icmpv6.create_ndp_tap(destAddr)
if err != nil {
panic(err)
}
if _, err := tun.iface.Write(request); err != nil {
panic(err)
}
tun.icmpv6.peermacs[destAddr] = neighbor{
lastsolicitation: time.Now(),
}
}
}
var peermac macAddress
var peerknown bool
if data[0]&0xf0 == 0x40 {
destAddr = tun.core.router.addr
} else if data[0]&0xf0 == 0x60 {
if !bytes.Equal(tun.core.router.addr[:16], destAddr[:16]) && !bytes.Equal(tun.core.router.subnet[:8], destAddr[:8]) {
destAddr = tun.core.router.addr
}
}
if neighbor, ok := tun.icmpv6.peermacs[destAddr]; ok && neighbor.learned {
peermac = neighbor.mac
peerknown = true
} else if neighbor, ok := tun.icmpv6.peermacs[tun.core.router.addr]; ok && neighbor.learned {
peermac = neighbor.mac
peerknown = true
sendndp(destAddr)
} else {
sendndp(tun.core.router.addr)
}
if peerknown {
var proto ethernet.Ethertype
switch {
case data[0]&0xf0 == 0x60:
proto = ethernet.IPv6
case data[0]&0xf0 == 0x40:
proto = ethernet.IPv4
}
var frame ethernet.Frame
frame.Prepare(
peermac[:6], // Destination MAC address
tun.icmpv6.mymac[:6], // Source MAC address
ethernet.NotTagged, // VLAN tagging
proto, // Ethertype
len(data)) // Payload length
copy(frame[tun_ETHER_HEADER_LENGTH:], data[:])
if _, err := tun.iface.Write(frame); err != nil {
tun.mutex.RLock()
open := tun.isOpen
tun.mutex.RUnlock()
if !open {
return nil
} else {
panic(err)
}
}
}
} else {
if _, err := tun.iface.Write(data); err != nil {
tun.mutex.RLock()
open := tun.isOpen
tun.mutex.RUnlock()
if !open {
return nil
} else {
panic(err)
}
}
}
util.PutBytes(data)
}
}
// Reads any packets that are waiting on the TUN/TAP adapter. If the adapter
// is running in TAP mode then the ethernet headers will automatically be
// processed and stripped if necessary. If an ICMPv6 packet is found, then
// the relevant helper functions in icmpv6.go are called.
func (tun *tunAdapter) read() error {
mtu := tun.mtu
if tun.iface.IsTAP() {
mtu += tun_ETHER_HEADER_LENGTH
}
buf := make([]byte, mtu)
for {
n, err := tun.iface.Read(buf)
if err != nil {
tun.mutex.RLock()
open := tun.isOpen
tun.mutex.RUnlock()
if !open {
return nil
} else {
// panic(err)
return err
}
}
o := 0
if tun.iface.IsTAP() {
o = tun_ETHER_HEADER_LENGTH
}
switch {
case buf[o]&0xf0 == 0x60 && n == 256*int(buf[o+4])+int(buf[o+5])+tun_IPv6_HEADER_LENGTH+o:
case buf[o]&0xf0 == 0x40 && n == 256*int(buf[o+2])+int(buf[o+3])+o:
default:
continue
}
if buf[o+6] == 58 {
if tun.iface.IsTAP() {
// Found an ICMPv6 packet
b := make([]byte, n)
copy(b, buf)
go tun.icmpv6.parse_packet(b)
}
}
packet := append(util.GetBytes(), buf[o:n]...)
tun.send <- packet
}
}
// Closes the TUN/TAP adapter. This is only usually called when the Yggdrasil
// process stops. Typically this operation will happen quickly, but on macOS
// it can block until a read operation is completed.
func (tun *tunAdapter) close() error {
tun.mutex.Lock()
tun.isOpen = false
tun.mutex.Unlock()
if tun.iface == nil {
return nil
}
return tun.iface.Close()
}

View File

@ -1,19 +0,0 @@
// +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

@ -72,13 +72,16 @@ func wire_decode_uint64(bs []byte) (uint64, int) {
// Non-negative integers are mapped to even integers: 0 -> 0, 1 -> 2, etc.
// Negative integers are mapped to odd integers: -1 -> 1, -2 -> 3, etc.
// This means the least significant bit is a sign bit.
// This is known as zigzag encoding.
func wire_intToUint(i int64) uint64 {
return ((uint64(-(i+1))<<1)|0x01)*(uint64(i)>>63) + (uint64(i)<<1)*(^uint64(i)>>63)
// signed arithmetic shift
return uint64((i >> 63) ^ (i << 1))
}
// Converts uint64 back to int64, genreally when being read from the wire.
func wire_intFromUint(u uint64) int64 {
return int64(u&0x01)*(-int64(u>>1)-1) + int64(^u&0x01)*int64(u>>1)
// non-arithmetic shift
return int64((u >> 1) ^ -(u & 1))
}
////////////////////////////////////////////////////////////////////////////////
@ -391,7 +394,7 @@ func (p *nodeinfoReqRes) decode(bs []byte) bool {
if len(bs) == 0 {
return false
}
p.NodeInfo = make(nodeinfoPayload, len(bs))
p.NodeInfo = make(NodeInfoPayload, len(bs))
if !wire_chop_slice(p.NodeInfo[:], &bs) {
return false
}