Merge pull request #587 from yggdrasil-network/develop

Version 0.3.11
This commit is contained in:
Neil Alexander 2019-10-25 09:37:50 +01:00 committed by GitHub
commit 1fbab17b37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 450 additions and 153 deletions

View File

@ -5,7 +5,7 @@ version: 2.1
jobs: jobs:
build-linux: build-linux:
docker: docker:
- image: circleci/golang:1.12.7 - image: circleci/golang:1.13.3
steps: steps:
- checkout - checkout
@ -48,6 +48,7 @@ jobs:
PKGARCH=mipsel sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-mipsel && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-mipsel; PKGARCH=mipsel sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-mipsel && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-mipsel;
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=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=armhf sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-armhf && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-armhf;
PKGARCH=armel sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-armel && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-armel;
PKGARCH=arm64 sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-arm64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-arm64; PKGARCH=arm64 sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-arm64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-arm64;
mv *.deb /tmp/upload/ mv *.deb /tmp/upload/
@ -105,11 +106,11 @@ jobs:
echo -e "Host *\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config echo -e "Host *\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config
- run: - run:
name: Install Go 1.12.7 name: Install Go 1.13.3
command: | command: |
cd /tmp cd /tmp
curl -LO https://dl.google.com/go/go1.12.7.darwin-amd64.pkg curl -LO https://dl.google.com/go/go1.13.3.darwin-amd64.pkg
sudo installer -pkg /tmp/go1.12.7.darwin-amd64.pkg -target / sudo installer -pkg /tmp/go1.13.3.darwin-amd64.pkg -target /
#- run: #- run:
# name: Install Gomobile # name: Install Gomobile
@ -145,7 +146,7 @@ jobs:
build-other: build-other:
docker: docker:
- image: circleci/golang:1.12.7 - image: circleci/golang:1.13.3
steps: steps:
- checkout - checkout

View File

@ -25,6 +25,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- in case of vulnerabilities. - in case of vulnerabilities.
--> -->
## [0.3.11] - 2019-10-25
### Added
- Support for TLS listeners and peers has been added, allowing the use of `tls://host:port` in `Peers`, `InterfacePeers` and `Listen` configuration settings - this allows hiding Yggdrasil peerings inside regular TLS connections
### Changed
- Go 1.13 or later is now required for building Yggdrasil
- Some exported API functions have been updated to work with standard Go interfaces:
- `net.Conn` instead of `yggdrasil.Conn`
- `net.Dialer` (the interface it would satisfy if it wasn't a concrete type) instead of `yggdrasil.Dialer`
- `net.Listener` instead of `yggdrasil.Listener`
- Session metadata is now updated correctly when a search completes for a node to which we already have an open session
- Multicast module reloading behaviour has been improved
### Fixed
- An incorrectly held mutex in the crypto-key routing code has been fixed
- Multicast module no longer opens a listener socket if no multicast interfaces are configured
## [0.3.10] - 2019-10-10 ## [0.3.10] - 2019-10-10
### Added ### Added
- The core library now includes several unit tests for peering and `yggdrasil.Conn` connections - The core library now includes several unit tests for peering and `yggdrasil.Conn` connections

View File

@ -49,7 +49,7 @@ You may also find other platform-specific wrappers, scripts or tools in the
If you want to build from source, as opposed to installing one of the pre-built If you want to build from source, as opposed to installing one of the pre-built
packages: packages:
1. Install [Go](https://golang.org) (requires Go 1.12 or later) 1. Install [Go](https://golang.org) (requires Go 1.13 or later)
2. Clone this repository 2. Clone this repository
2. Run `./build` 2. Run `./build`

View File

@ -23,6 +23,7 @@ import (
"github.com/yggdrasil-network/yggdrasil-go/src/admin" "github.com/yggdrasil-network/yggdrasil-go/src/admin"
"github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/config"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/module"
"github.com/yggdrasil-network/yggdrasil-go/src/multicast" "github.com/yggdrasil-network/yggdrasil-go/src/multicast"
"github.com/yggdrasil-network/yggdrasil-go/src/tuntap" "github.com/yggdrasil-network/yggdrasil-go/src/tuntap"
"github.com/yggdrasil-network/yggdrasil-go/src/version" "github.com/yggdrasil-network/yggdrasil-go/src/version"
@ -32,9 +33,9 @@ import (
type node struct { type node struct {
core yggdrasil.Core core yggdrasil.Core
state *config.NodeState state *config.NodeState
tuntap tuntap.TunAdapter tuntap module.Module // tuntap.TunAdapter
multicast multicast.Multicast multicast module.Module // multicast.Multicast
admin admin.AdminSocket admin module.Module // admin.AdminSocket
} }
func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *config.NodeConfig { func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *config.NodeConfig {
@ -231,25 +232,30 @@ func main() {
} }
// Register the session firewall gatekeeper function // Register the session firewall gatekeeper function
n.core.SetSessionGatekeeper(n.sessionFirewall) n.core.SetSessionGatekeeper(n.sessionFirewall)
// Allocate our modules
n.admin = &admin.AdminSocket{}
n.multicast = &multicast.Multicast{}
n.tuntap = &tuntap.TunAdapter{}
// Start the admin socket // Start the admin socket
n.admin.Init(&n.core, n.state, logger, nil) n.admin.Init(&n.core, n.state, logger, nil)
if err := n.admin.Start(); err != nil { if err := n.admin.Start(); err != nil {
logger.Errorln("An error occurred starting admin socket:", err) logger.Errorln("An error occurred starting admin socket:", err)
} }
n.admin.SetupAdminHandlers(n.admin.(*admin.AdminSocket))
// Start the multicast interface // Start the multicast interface
n.multicast.Init(&n.core, n.state, logger, nil) n.multicast.Init(&n.core, n.state, logger, nil)
if err := n.multicast.Start(); err != nil { if err := n.multicast.Start(); err != nil {
logger.Errorln("An error occurred starting multicast:", err) logger.Errorln("An error occurred starting multicast:", err)
} }
n.multicast.SetupAdminHandlers(&n.admin) n.multicast.SetupAdminHandlers(n.admin.(*admin.AdminSocket))
// Start the TUN/TAP interface // Start the TUN/TAP interface
if listener, err := n.core.ConnListen(); err == nil { if listener, err := n.core.ConnListen(); err == nil {
if dialer, err := n.core.ConnDialer(); err == nil { if dialer, err := n.core.ConnDialer(); err == nil {
n.tuntap.Init(n.state, logger, listener, dialer) n.tuntap.Init(&n.core, n.state, logger, tuntap.TunOptions{Listener: listener, Dialer: dialer})
if err := n.tuntap.Start(); err != nil { if err := n.tuntap.Start(); err != nil {
logger.Errorln("An error occurred starting TUN/TAP:", err) logger.Errorln("An error occurred starting TUN/TAP:", err)
} }
n.tuntap.SetupAdminHandlers(&n.admin) n.tuntap.SetupAdminHandlers(n.admin.(*admin.AdminSocket))
} else { } else {
logger.Errorln("Unable to get Dialer:", err) logger.Errorln("Unable to get Dialer:", err)
} }

View File

@ -9,7 +9,7 @@ ProtectHome=true
ProtectSystem=true ProtectSystem=true
SyslogIdentifier=yggdrasil SyslogIdentifier=yggdrasil
CapabilityBoundSet=CAP_NET_ADMIN CapabilityBoundSet=CAP_NET_ADMIN
ExecStartPre=+/sbin/modprobe tun ExecStartPre=+-/sbin/modprobe tun
ExecStartPre=/bin/sh -ec "if ! test -s /etc/yggdrasil.conf; \ ExecStartPre=/bin/sh -ec "if ! test -s /etc/yggdrasil.conf; \
then umask 077; \ then umask 077; \
yggdrasil -genconf > /etc/yggdrasil.conf; \ yggdrasil -genconf > /etc/yggdrasil.conf; \

10
go.mod
View File

@ -1,7 +1,9 @@
module github.com/yggdrasil-network/yggdrasil-go module github.com/yggdrasil-network/yggdrasil-go
go 1.13
require ( require (
github.com/Arceliar/phony v0.0.0-20191005181740-21679e75e3f0 github.com/Arceliar/phony v0.0.0-20191006174943-d0c68492aca0
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8
github.com/hashicorp/go-syslog v1.0.0 github.com/hashicorp/go-syslog v1.0.0
github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible
@ -12,8 +14,8 @@ require (
github.com/vishvananda/netlink v1.0.0 github.com/vishvananda/netlink v1.0.0
github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect
github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8 github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 golang.org/x/net v0.0.0-20191021144547-ec77196f6094
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a golang.org/x/sys v0.0.0-20191024172528-b4ff53e7a1cb
golang.org/x/text v0.3.2 golang.org/x/text v0.3.2
) )

19
go.sum
View File

@ -1,5 +1,5 @@
github.com/Arceliar/phony v0.0.0-20191005181740-21679e75e3f0 h1:IOFsvAMFkgnKfSQHxXTeqb1+ODFeR5px1HCHU86KF30= github.com/Arceliar/phony v0.0.0-20191006174943-d0c68492aca0 h1:p3puK8Sl2xK+2FnnIvY/C0N1aqJo2kbEsdAzU+Tnv48=
github.com/Arceliar/phony v0.0.0-20191005181740-21679e75e3f0/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/Arceliar/phony v0.0.0-20191006174943-d0c68492aca0/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI=
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 h1:WD8iJ37bRNwvETMfVTusVSAi0WdXTpfNVGY2aHycNKY= 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/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 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=
@ -20,16 +20,17 @@ github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f h1:nBX3nTcmxEtHS
github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8 h1:YY9Pg2BEp0jeUVU60svTOaDr+fs1ySC9RbdC1Qc6wOw= github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8 h1:YY9Pg2BEp0jeUVU60svTOaDr+fs1ySC9RbdC1Qc6wOw=
github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20191021144547-ec77196f6094 h1:5O4U9trLjNpuhpynaDsqwCk+Tw6seqJz1EbqbnzHrc8=
golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191024172528-b4ff53e7a1cb h1:ZxSglHghKPYD8WDeRUzRJrUJtDF0PxsTUSxyqr9/5BI=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/sys v0.0.0-20191024172528-b4ff53e7a1cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -2,7 +2,11 @@
// Of particular importance are the functions used to derive addresses or subnets from a NodeID, or to get the NodeID and bitmask of the bits visible from an address, which is needed for DHT searches. // Of particular importance are the functions used to derive addresses or subnets from a NodeID, or to get the NodeID and bitmask of the bits visible from an address, which is needed for DHT searches.
package address package address
import "github.com/yggdrasil-network/yggdrasil-go/src/crypto" import (
"fmt"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
)
// Address represents an IPv6 address in the yggdrasil address range. // Address represents an IPv6 address in the yggdrasil address range.
type Address [16]byte type Address [16]byte
@ -128,6 +132,13 @@ func (a *Address) GetNodeIDandMask() (*crypto.NodeID, *crypto.NodeID) {
return &nid, &mask return &nid, &mask
} }
// GetNodeIDLengthString returns a string representation of the known bits of the NodeID, along with the number of known bits, for use with yggdrasil.Dialer's Dial and DialContext functions.
func (a *Address) GetNodeIDLengthString() string {
nid, mask := a.GetNodeIDandMask()
l := mask.PrefixLength()
return fmt.Sprintf("%s/%d", nid.String(), l)
}
// GetNodeIDandMask returns two *NodeID. // GetNodeIDandMask returns two *NodeID.
// The first is a NodeID with all the bits known from the Subnet set to their correct values. // The first is a NodeID with all the bits known from the Subnet set to their correct values.
// The second is a bitmask with 1 bit set for each bit that was known from the Subnet. // The second is a bitmask with 1 bit set for each bit that was known from the Subnet.
@ -156,3 +167,10 @@ func (s *Subnet) GetNodeIDandMask() (*crypto.NodeID, *crypto.NodeID) {
} }
return &nid, &mask return &nid, &mask
} }
// GetNodeIDLengthString returns a string representation of the known bits of the NodeID, along with the number of known bits, for use with yggdrasil.Dialer's Dial and DialContext functions.
func (s *Subnet) GetNodeIDLengthString() string {
nid, mask := s.GetNodeIDandMask()
l := mask.PrefixLength()
return fmt.Sprintf("%s/%d", nid.String(), l)
}

View File

@ -25,12 +25,12 @@ import (
// TODO: Add authentication // TODO: Add authentication
type AdminSocket struct { type AdminSocket struct {
core *yggdrasil.Core core *yggdrasil.Core
log *log.Logger log *log.Logger
reconfigure chan chan error listenaddr string
listenaddr string listener net.Listener
listener net.Listener handlers map[string]handler
handlers map[string]handler started bool
} }
// Info refers to information that is returned to the admin socket handler. // Info refers to information that is returned to the admin socket handler.
@ -54,23 +54,10 @@ func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc func(In
} }
// init runs the initial admin setup. // init runs the initial admin setup.
func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) { func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) error {
a.core = c a.core = c
a.log = log a.log = log
a.reconfigure = make(chan chan error, 1)
a.handlers = make(map[string]handler) 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() current := state.GetCurrent()
a.listenaddr = current.AdminListen a.listenaddr = current.AdminListen
a.AddHandler("list", []string{}, func(in Info) (Info, error) { a.AddHandler("list", []string{}, func(in Info) (Info, error) {
@ -80,16 +67,31 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log.
} }
return Info{"list": handlers}, nil return Info{"list": handlers}, nil
}) })
return nil
}
func (a *AdminSocket) UpdateConfig(config *config.NodeConfig) {
a.log.Debugln("Reloading admin configuration...")
if a.listenaddr != config.AdminListen {
a.listenaddr = config.AdminListen
if a.IsStarted() {
a.Stop()
}
a.Start()
}
}
func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) {
a.AddHandler("getSelf", []string{}, func(in Info) (Info, error) { a.AddHandler("getSelf", []string{}, func(in Info) (Info, error) {
ip := c.Address().String() ip := a.core.Address().String()
subnet := c.Subnet() subnet := a.core.Subnet()
return Info{ return Info{
"self": Info{ "self": Info{
ip: Info{ ip: Info{
"box_pub_key": c.EncryptionPublicKey(), "box_pub_key": a.core.EncryptionPublicKey(),
"build_name": version.BuildName(), "build_name": version.BuildName(),
"build_version": version.BuildVersion(), "build_version": version.BuildVersion(),
"coords": fmt.Sprintf("%v", c.Coords()), "coords": fmt.Sprintf("%v", a.core.Coords()),
"subnet": subnet.String(), "subnet": subnet.String(),
}, },
}, },
@ -312,17 +314,24 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log.
}) })
} }
// start runs the admin API socket to listen for / respond to admin API calls. // Start runs the admin API socket to listen for / respond to admin API calls.
func (a *AdminSocket) Start() error { func (a *AdminSocket) Start() error {
if a.listenaddr != "none" && a.listenaddr != "" { if a.listenaddr != "none" && a.listenaddr != "" {
go a.listen() go a.listen()
a.started = true
} }
return nil return nil
} }
// cleans up when stopping // IsStarted returns true if the module has been started.
func (a *AdminSocket) IsStarted() bool {
return a.started
}
// Stop will stop the admin API and close the socket.
func (a *AdminSocket) Stop() error { func (a *AdminSocket) Stop() error {
if a.listener != nil { if a.listener != nil {
a.started = false
return a.listener.Close() return a.listener.Close()
} else { } else {
return nil return nil

20
src/module/module.go Normal file
View File

@ -0,0 +1,20 @@
package module
import (
"github.com/gologme/log"
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
)
// Module is an interface that defines which functions must be supported by a
// given Yggdrasil module.
type Module interface {
Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) error
Start() error
Stop() error
UpdateConfig(config *config.NodeConfig)
SetupAdminHandlers(a *admin.AdminSocket)
IsStarted() bool
}

View File

@ -55,6 +55,22 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log
// listen for multicast beacons from other hosts and will advertise multicast // listen for multicast beacons from other hosts and will advertise multicast
// beacons out to the network. // beacons out to the network.
func (m *Multicast) Start() error { func (m *Multicast) Start() error {
var err error
phony.Block(m, func() {
err = m._start()
})
m.log.Debugln("Started multicast module")
return err
}
func (m *Multicast) _start() error {
if m.isOpen {
return fmt.Errorf("multicast module is already started")
}
if len(m.config.GetCurrent().MulticastInterfaces) == 0 {
return nil
}
m.log.Infoln("Starting multicast module")
addr, err := net.ResolveUDPAddr("udp", m.groupAddr) addr, err := net.ResolveUDPAddr("udp", m.groupAddr)
if err != nil { if err != nil {
return err return err
@ -80,8 +96,27 @@ func (m *Multicast) Start() error {
return nil return nil
} }
// Stop is not implemented for multicast yet. // IsStarted returns true if the module has been started.
func (m *Multicast) IsStarted() bool {
var isOpen bool
phony.Block(m, func() {
isOpen = m.isOpen
})
return isOpen
}
// Stop stops the multicast module.
func (m *Multicast) Stop() error { func (m *Multicast) Stop() error {
var err error
phony.Block(m, func() {
err = m._stop()
})
m.log.Debugln("Stopped multicast module")
return nil
}
func (m *Multicast) _stop() error {
m.log.Infoln("Stopping multicast module")
m.isOpen = false m.isOpen = false
if m.announcer != nil { if m.announcer != nil {
m.announcer.Stop() m.announcer.Stop()
@ -97,8 +132,26 @@ func (m *Multicast) Stop() error {
// and then signals the various module goroutines to reconfigure themselves if // and then signals the various module goroutines to reconfigure themselves if
// needed. // needed.
func (m *Multicast) UpdateConfig(config *config.NodeConfig) { func (m *Multicast) UpdateConfig(config *config.NodeConfig) {
m.log.Debugln("Reloading multicast configuration...") m.Act(m, func() { m._updateConfig(config) })
}
func (m *Multicast) _updateConfig(config *config.NodeConfig) {
m.log.Infoln("Reloading multicast configuration...")
if m.isOpen {
if len(config.MulticastInterfaces) == 0 || config.LinkLocalTCPPort != m.listenPort {
if err := m._stop(); err != nil {
m.log.Errorln("Error stopping multicast module:", err)
}
}
}
m.config.Replace(*config) m.config.Replace(*config)
m.listenPort = config.LinkLocalTCPPort
if !m.isOpen && len(config.MulticastInterfaces) > 0 {
if err := m._start(); err != nil {
m.log.Errorln("Error starting multicast module:", err)
}
}
m.log.Debugln("Reloaded multicast configuration successfully")
} }
// GetInterfaces returns the currently known/enabled multicast interfaces. It is // GetInterfaces returns the currently known/enabled multicast interfaces. It is
@ -271,7 +324,7 @@ func (m *Multicast) listen() {
for { for {
nBytes, rcm, fromAddr, err := m.sock.ReadFrom(bs) nBytes, rcm, fromAddr, err := m.sock.ReadFrom(bs)
if err != nil { if err != nil {
if !m.isOpen { if !m.IsStarted() {
return return
} }
panic(err) panic(err)

View File

@ -263,7 +263,6 @@ func (c *cryptokey) addRemoteSubnet(cidr string, dest string) error {
// length specified in bytes) from the crypto-key routing table. An error is // 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. // returned if the address is not suitable or no route was found.
func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (crypto.BoxPubKey, error) { 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 // Check if the address is a valid Yggdrasil address - if so it
// is exempt from all CKR checking // is exempt from all CKR checking
@ -285,11 +284,11 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c
} }
// Check if there's a cache entry for this addr // Check if there's a cache entry for this addr
c.mutexcaches.RLock()
if route, ok := (*routingcache)[addr]; ok { if route, ok := (*routingcache)[addr]; ok {
c.mutexcaches.RUnlock() c.mutexcaches.RUnlock()
return route.destination, nil return route.destination, nil
} }
c.mutexcaches.RUnlock() c.mutexcaches.RUnlock()
c.mutexremotes.RLock() c.mutexremotes.RLock()

View File

@ -93,7 +93,7 @@ func (s *tunConn) _read(bs []byte) (err error) {
skip = true skip = true
} else if key, err := s.tun.ckr.getPublicKeyForAddress(srcAddr, addrlen); err == nil { } else if key, err := s.tun.ckr.getPublicKeyForAddress(srcAddr, addrlen); err == nil {
srcNodeID := crypto.GetNodeID(&key) srcNodeID := crypto.GetNodeID(&key)
if s.conn.RemoteAddr() == *srcNodeID { if *s.conn.RemoteAddr().(*crypto.NodeID) == *srcNodeID {
// This is the one allowed CKR case, where source and destination addresses are both good // This is the one allowed CKR case, where source and destination addresses are both good
} else { } else {
// The CKR key associated with this address doesn't match the sender's NodeID // The CKR key associated with this address doesn't match the sender's NodeID
@ -170,7 +170,7 @@ func (s *tunConn) _write(bs []byte) (err error) {
skip = true skip = true
} else if key, err := s.tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { } else if key, err := s.tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil {
dstNodeID := crypto.GetNodeID(&key) dstNodeID := crypto.GetNodeID(&key)
if s.conn.RemoteAddr() == *dstNodeID { if *s.conn.RemoteAddr().(*crypto.NodeID) == *dstNodeID {
// This is the one allowed CKR case, where source and destination addresses are both good // This is the one allowed CKR case, where source and destination addresses are both good
} else { } else {
// The CKR key associated with this address doesn't match the sender's NodeID // The CKR key associated with this address doesn't match the sender's NodeID

View File

@ -9,6 +9,7 @@ import (
"github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/yggdrasil-network/yggdrasil-go/src/util"
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
"github.com/Arceliar/phony" "github.com/Arceliar/phony"
) )
@ -225,7 +226,7 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) {
return return
} }
// Do we have an active connection for this node address? // Do we have an active connection for this node address?
var dstNodeID, dstNodeIDMask *crypto.NodeID var dstString string
session, isIn := tun.addrToConn[dstAddr] session, isIn := tun.addrToConn[dstAddr]
if !isIn || session == nil { if !isIn || session == nil {
session, isIn = tun.subnetToConn[dstSnet] session, isIn = tun.subnetToConn[dstSnet]
@ -233,9 +234,9 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) {
// Neither an address nor a subnet mapping matched, therefore populate // Neither an address nor a subnet mapping matched, therefore populate
// the node ID and mask to commence a search // the node ID and mask to commence a search
if dstAddr.IsValid() { if dstAddr.IsValid() {
dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() dstString = dstAddr.GetNodeIDLengthString()
} else { } else {
dstNodeID, dstNodeIDMask = dstSnet.GetNodeIDandMask() dstString = dstSnet.GetNodeIDLengthString()
} }
} }
} }
@ -243,27 +244,27 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) {
if !isIn || session == nil { if !isIn || session == nil {
// Check we haven't been given empty node ID, really this shouldn't ever // Check we haven't been given empty node ID, really this shouldn't ever
// happen but just to be sure... // happen but just to be sure...
if dstNodeID == nil || dstNodeIDMask == nil { if dstString == "" {
panic("Given empty dstNodeID and dstNodeIDMask - this shouldn't happen") panic("Given empty dstString - this shouldn't happen")
} }
_, known := tun.dials[*dstNodeID] _, known := tun.dials[dstString]
tun.dials[*dstNodeID] = append(tun.dials[*dstNodeID], bs) tun.dials[dstString] = append(tun.dials[dstString], bs)
for len(tun.dials[*dstNodeID]) > 32 { for len(tun.dials[dstString]) > 32 {
util.PutBytes(tun.dials[*dstNodeID][0]) util.PutBytes(tun.dials[dstString][0])
tun.dials[*dstNodeID] = tun.dials[*dstNodeID][1:] tun.dials[dstString] = tun.dials[dstString][1:]
} }
if !known { if !known {
go func() { go func() {
conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask) conn, err := tun.dialer.Dial("nodeid", dstString)
tun.Act(nil, func() { tun.Act(nil, func() {
packets := tun.dials[*dstNodeID] packets := tun.dials[dstString]
delete(tun.dials, *dstNodeID) delete(tun.dials, dstString)
if err != nil { if err != nil {
return return
} }
// We've been given a connection so prepare the session wrapper // We've been given a connection so prepare the session wrapper
var tc *tunConn var tc *tunConn
if tc, err = tun._wrap(conn); err != nil { if tc, err = tun._wrap(conn.(*yggdrasil.Conn)); err != nil {
// Something went wrong when storing the connection, typically that // Something went wrong when storing the connection, typically that
// something already exists for this address or subnet // something already exists for this address or subnet
tun.log.Debugln("TUN/TAP iface wrap:", err) tun.log.Debugln("TUN/TAP iface wrap:", err)

View File

@ -52,10 +52,15 @@ type TunAdapter struct {
//mutex sync.RWMutex // Protects the below //mutex sync.RWMutex // Protects the below
addrToConn map[address.Address]*tunConn addrToConn map[address.Address]*tunConn
subnetToConn map[address.Subnet]*tunConn subnetToConn map[address.Subnet]*tunConn
dials map[crypto.NodeID][][]byte // Buffer of packets to send after dialing finishes dials map[string][][]byte // Buffer of packets to send after dialing finishes
isOpen bool isOpen bool
} }
type TunOptions struct {
Listener *yggdrasil.Listener
Dialer *yggdrasil.Dialer
}
// Gets the maximum supported MTU for the platform based on the defaults in // Gets the maximum supported MTU for the platform based on the defaults in
// defaults.GetDefaults(). // defaults.GetDefaults().
func getSupportedMTU(mtu int) int { func getSupportedMTU(mtu int) int {
@ -110,16 +115,21 @@ func MaximumMTU() int {
// Init initialises the TUN/TAP module. You must have acquired a Listener from // 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. // 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) { func (tun *TunAdapter) Init(core *yggdrasil.Core, config *config.NodeState, log *log.Logger, options interface{}) error {
tunoptions, ok := options.(TunOptions)
if !ok {
return fmt.Errorf("invalid options supplied to TunAdapter module")
}
tun.config = config tun.config = config
tun.log = log tun.log = log
tun.listener = listener tun.listener = tunoptions.Listener
tun.dialer = dialer tun.dialer = tunoptions.Dialer
tun.addrToConn = make(map[address.Address]*tunConn) tun.addrToConn = make(map[address.Address]*tunConn)
tun.subnetToConn = make(map[address.Subnet]*tunConn) tun.subnetToConn = make(map[address.Subnet]*tunConn)
tun.dials = make(map[crypto.NodeID][][]byte) tun.dials = make(map[string][][]byte)
tun.writer.tun = tun tun.writer.tun = tun
tun.reader.tun = tun tun.reader.tun = tun
return nil
} }
// Start the setup process for the TUN/TAP adapter. If successful, starts the // Start the setup process for the TUN/TAP adapter. If successful, starts the
@ -133,9 +143,12 @@ func (tun *TunAdapter) Start() error {
} }
func (tun *TunAdapter) _start() error { func (tun *TunAdapter) _start() error {
if tun.isOpen {
return errors.New("TUN/TAP module is already started")
}
current := tun.config.GetCurrent() current := tun.config.GetCurrent()
if tun.config == nil || tun.listener == nil || tun.dialer == nil { if tun.config == nil || tun.listener == nil || tun.dialer == nil {
return errors.New("No configuration available to TUN/TAP") return errors.New("no configuration available to TUN/TAP")
} }
var boxPub crypto.BoxPubKey var boxPub crypto.BoxPubKey
boxPubHex, err := hex.DecodeString(current.EncryptionPublicKey) boxPubHex, err := hex.DecodeString(current.EncryptionPublicKey)
@ -160,13 +173,6 @@ func (tun *TunAdapter) _start() error {
return nil return nil
} }
tun.isOpen = true tun.isOpen = true
tun.reconfigure = make(chan chan error)
go func() {
for {
e := <-tun.reconfigure
e <- nil
}
}()
go tun.handler() go tun.handler()
tun.reader.Act(nil, tun.reader._read) // Start the reader tun.reader.Act(nil, tun.reader._read) // Start the reader
tun.icmpv6.Init(tun) tun.icmpv6.Init(tun)
@ -177,6 +183,15 @@ func (tun *TunAdapter) _start() error {
return nil return nil
} }
// IsStarted returns true if the module has been started.
func (tun *TunAdapter) IsStarted() bool {
var isOpen bool
phony.Block(tun, func() {
isOpen = tun.isOpen
})
return isOpen
}
// Start the setup process for the TUN/TAP adapter. If successful, starts the // Start the setup process for the TUN/TAP adapter. If successful, starts the
// read/write goroutines to handle packets on that interface. // read/write goroutines to handle packets on that interface.
func (tun *TunAdapter) Stop() error { func (tun *TunAdapter) Stop() error {
@ -219,7 +234,7 @@ func (tun *TunAdapter) handler() error {
return err return err
} }
phony.Block(tun, func() { phony.Block(tun, func() {
if _, err := tun._wrap(conn); err != nil { if _, err := tun._wrap(conn.(*yggdrasil.Conn)); err != nil {
// Something went wrong when storing the connection, typically that // Something went wrong when storing the connection, typically that
// something already exists for this address or subnet // something already exists for this address or subnet
tun.log.Debugln("TUN/TAP handler wrap:", err) tun.log.Debugln("TUN/TAP handler wrap:", err)
@ -237,9 +252,9 @@ func (tun *TunAdapter) _wrap(conn *yggdrasil.Conn) (c *tunConn, err error) {
} }
c = &s c = &s
// Get the remote address and subnet of the other side // Get the remote address and subnet of the other side
remoteNodeID := conn.RemoteAddr() remoteNodeID := conn.RemoteAddr().(*crypto.NodeID)
s.addr = *address.AddrForNodeID(&remoteNodeID) s.addr = *address.AddrForNodeID(remoteNodeID)
s.snet = *address.SubnetForNodeID(&remoteNodeID) s.snet = *address.SubnetForNodeID(remoteNodeID)
// Work out if this is already a destination we already know about // Work out if this is already a destination we already know about
atc, aok := tun.addrToConn[s.addr] atc, aok := tun.addrToConn[s.addr]
stc, sok := tun.subnetToConn[s.snet] stc, sok := tun.subnetToConn[s.snet]

View File

@ -280,7 +280,14 @@ func (c *Core) ConnDialer() (*Dialer, error) {
// "Listen" configuration item, e.g. // "Listen" configuration item, e.g.
// tcp://a.b.c.d:e // tcp://a.b.c.d:e
func (c *Core) ListenTCP(uri string) (*TcpListener, error) { func (c *Core) ListenTCP(uri string) (*TcpListener, error) {
return c.link.tcp.listen(uri) return c.link.tcp.listen(uri, nil)
}
// ListenTLS starts a new TLS listener. The input URI should match that of the
// "Listen" configuration item, e.g.
// tls://a.b.c.d:e
func (c *Core) ListenTLS(uri string) (*TcpListener, error) {
return c.link.tcp.listen(uri, c.link.tcp.tls.forListener)
} }
// NodeID gets the node ID. This is derived from your router encryption keys. // NodeID gets the node ID. This is derived from your router encryption keys.

View File

@ -3,6 +3,7 @@ package yggdrasil
import ( import (
"errors" "errors"
"fmt" "fmt"
"net"
"time" "time"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
@ -348,14 +349,14 @@ func (c *Conn) Close() (err error) {
// LocalAddr returns the complete node ID of the local side of the connection. // LocalAddr returns the complete node ID of the local side of the connection.
// This is always going to return your own node's node ID. // This is always going to return your own node's node ID.
func (c *Conn) LocalAddr() crypto.NodeID { func (c *Conn) LocalAddr() net.Addr {
return *crypto.GetNodeID(&c.core.boxPub) return crypto.GetNodeID(&c.core.boxPub)
} }
// RemoteAddr returns the complete node ID of the remote side of the connection. // RemoteAddr returns the complete node ID of the remote side of the connection.
func (c *Conn) RemoteAddr() crypto.NodeID { func (c *Conn) RemoteAddr() net.Addr {
// RemoteAddr is set during the dial or accept, and isn't changed, so it's safe to access directly // RemoteAddr is set during the dial or accept, and isn't changed, so it's safe to access directly
return *c.nodeID return c.nodeID
} }
// SetDeadline is equivalent to calling both SetReadDeadline and // SetDeadline is equivalent to calling both SetReadDeadline and

View File

@ -1,8 +1,10 @@
package yggdrasil package yggdrasil
import ( import (
"context"
"encoding/hex" "encoding/hex"
"errors" "errors"
"net"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -15,12 +17,15 @@ type Dialer struct {
core *Core 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" // 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 // and the second parameter should contain a hexadecimal representation of the
// target node ID. // target node ID. It uses DialContext internally.
func (d *Dialer) Dial(network, address string) (*Conn, error) { func (d *Dialer) Dial(network, address string) (net.Conn, error) {
return d.DialContext(nil, network, address)
}
// DialContext is used internally by Dial, and should only be used with a context that includes a timeout. It uses DialByNodeIDandMask internally.
func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
var nodeID crypto.NodeID var nodeID crypto.NodeID
var nodeMask crypto.NodeID var nodeMask crypto.NodeID
// Process // Process
@ -28,7 +33,7 @@ func (d *Dialer) Dial(network, address string) (*Conn, error) {
case "nodeid": case "nodeid":
// A node ID was provided - we don't need to do anything special with it // A node ID was provided - we don't need to do anything special with it
if tokens := strings.Split(address, "/"); len(tokens) == 2 { if tokens := strings.Split(address, "/"); len(tokens) == 2 {
len, err := strconv.Atoi(tokens[1]) l, err := strconv.Atoi(tokens[1])
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -37,7 +42,7 @@ func (d *Dialer) Dial(network, address string) (*Conn, error) {
return nil, err return nil, err
} }
copy(nodeID[:], dest) copy(nodeID[:], dest)
for idx := 0; idx < len; idx++ { for idx := 0; idx < l; idx++ {
nodeMask[idx/8] |= 0x80 >> byte(idx%8) nodeMask[idx/8] |= 0x80 >> byte(idx%8)
} }
} else { } else {
@ -50,7 +55,7 @@ func (d *Dialer) Dial(network, address string) (*Conn, error) {
nodeMask[i] = 0xFF nodeMask[i] = 0xFF
} }
} }
return d.DialByNodeIDandMask(&nodeID, &nodeMask) return d.DialByNodeIDandMask(ctx, &nodeID, &nodeMask)
default: default:
// An unexpected address type was given, so give up // An unexpected address type was given, so give up
return nil, errors.New("unexpected address type") return nil, errors.New("unexpected address type")
@ -58,20 +63,25 @@ func (d *Dialer) Dial(network, address string) (*Conn, error) {
} }
// DialByNodeIDandMask opens a session to the given node based on raw // DialByNodeIDandMask opens a session to the given node based on raw
// NodeID parameters. // NodeID parameters. If ctx is nil or has no timeout, then a default timeout of 6 seconds will apply, beginning *after* the search finishes.
func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (*Conn, error) { func (d *Dialer) DialByNodeIDandMask(ctx context.Context, nodeID, nodeMask *crypto.NodeID) (net.Conn, error) {
conn := newConn(d.core, nodeID, nodeMask, nil) conn := newConn(d.core, nodeID, nodeMask, nil)
if err := conn.search(); err != nil { if err := conn.search(); err != nil {
// TODO: make searches take a context, so they can be cancelled early
conn.Close() conn.Close()
return nil, err return nil, err
} }
conn.session.setConn(nil, conn) conn.session.setConn(nil, conn)
t := time.NewTimer(6 * time.Second) // TODO use a context instead var cancel context.CancelFunc
defer t.Stop() if ctx == nil {
ctx = context.Background()
}
ctx, cancel = context.WithTimeout(ctx, 6*time.Second)
defer cancel()
select { select {
case <-conn.session.init: case <-conn.session.init:
return conn, nil return conn, nil
case <-t.C: case <-ctx.Done():
conn.Close() conn.Close()
return nil, errors.New("session handshake timeout") return nil, errors.New("session handshake timeout")
} }

View File

@ -93,9 +93,11 @@ func (l *link) call(uri string, sintf string) error {
pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/") pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/")
switch u.Scheme { switch u.Scheme {
case "tcp": case "tcp":
l.tcp.call(u.Host, nil, sintf) l.tcp.call(u.Host, nil, sintf, nil)
case "socks": case "socks":
l.tcp.call(pathtokens[0], u.Host, sintf) l.tcp.call(pathtokens[0], u.Host, sintf, nil)
case "tls":
l.tcp.call(u.Host, nil, sintf, l.tcp.tls.forDialer)
default: default:
return errors.New("unknown call scheme: " + u.Scheme) return errors.New("unknown call scheme: " + u.Scheme)
} }
@ -109,7 +111,10 @@ func (l *link) listen(uri string) error {
} }
switch u.Scheme { switch u.Scheme {
case "tcp": case "tcp":
_, err := l.tcp.listen(u.Host) _, err := l.tcp.listen(u.Host, nil)
return err
case "tls":
_, err := l.tcp.listen(u.Host, l.tcp.tls.forListener)
return err return err
default: default:
return errors.New("unknown listen scheme: " + u.Scheme) return errors.New("unknown listen scheme: " + u.Scheme)

View File

@ -13,7 +13,7 @@ type Listener struct {
} }
// Accept blocks until a new incoming session is received // Accept blocks until a new incoming session is received
func (l *Listener) Accept() (*Conn, error) { func (l *Listener) Accept() (net.Conn, error) {
select { select {
case c, ok := <-l.conn: case c, ok := <-l.conn:
if !ok { if !ok {

View File

@ -189,32 +189,34 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool {
if themMasked != destMasked { if themMasked != destMasked {
return false return false
} }
finishSearch := func(sess *sessionInfo, err error) {
if sess != nil {
// FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)?
sess.coords = res.Coords
sess.ping(sinfo.searches.router)
}
if err != nil {
sinfo.callback(nil, err)
} else {
sinfo.callback(sess, nil)
}
// Cleanup
delete(sinfo.searches.searches, res.Dest)
}
// They match, so create a session and send a sessionRequest // They match, so create a session and send a sessionRequest
var err error
sess, isIn := sinfo.searches.router.sessions.getByTheirPerm(&res.Key) sess, isIn := sinfo.searches.router.sessions.getByTheirPerm(&res.Key)
if !isIn { if !isIn {
// Don't already have a session
sess = sinfo.searches.router.sessions.createSession(&res.Key) sess = sinfo.searches.router.sessions.createSession(&res.Key)
if sess == nil { if sess == nil {
// nil if the DHT search finished but the session wasn't allowed err = errors.New("session not allowed")
sinfo.callback(nil, errors.New("session not allowed")) } else if _, isIn := sinfo.searches.router.sessions.getByTheirPerm(&res.Key); !isIn {
// Cleanup
delete(sinfo.searches.searches, res.Dest)
return true
}
_, isIn := sinfo.searches.router.sessions.getByTheirPerm(&res.Key)
if !isIn {
panic("This should never happen") panic("This should never happen")
} }
} else { } else {
sinfo.callback(nil, errors.New("session already exists")) err = errors.New("session already exists")
// Cleanup
delete(sinfo.searches.searches, res.Dest)
return true
} }
// FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)? finishSearch(sess, err)
sess.coords = res.Coords
sess.ping(sinfo.searches.router)
sinfo.callback(sess, nil)
// Cleanup
delete(sinfo.searches.searches, res.Dest)
return true return true
} }

View File

@ -39,6 +39,7 @@ type tcp struct {
listeners map[string]*TcpListener listeners map[string]*TcpListener
calls map[string]struct{} calls map[string]struct{}
conns map[linkInfo](chan struct{}) conns map[linkInfo](chan struct{})
tls tcptls
} }
// TcpListener is a stoppable TCP listener interface. These are typically // TcpListener is a stoppable TCP listener interface. These are typically
@ -47,9 +48,15 @@ type tcp struct {
// multicast interfaces. // multicast interfaces.
type TcpListener struct { type TcpListener struct {
Listener net.Listener Listener net.Listener
upgrade *TcpUpgrade
stop chan struct{} stop chan struct{}
} }
type TcpUpgrade struct {
upgrade func(c net.Conn) (net.Conn, error)
name string
}
func (l *TcpListener) Stop() { func (l *TcpListener) Stop() {
defer func() { recover() }() defer func() { recover() }()
close(l.stop) close(l.stop)
@ -81,6 +88,7 @@ func (t *tcp) getAddr() *net.TCPAddr {
// Initializes the struct. // Initializes the struct.
func (t *tcp) init(l *link) error { func (t *tcp) init(l *link) error {
t.link = l t.link = l
t.tls.init(t)
t.mutex.Lock() t.mutex.Lock()
t.calls = make(map[string]struct{}) t.calls = make(map[string]struct{})
t.conns = make(map[linkInfo](chan struct{})) t.conns = make(map[linkInfo](chan struct{}))
@ -90,12 +98,17 @@ func (t *tcp) init(l *link) error {
t.link.core.config.Mutex.RLock() t.link.core.config.Mutex.RLock()
defer t.link.core.config.Mutex.RUnlock() defer t.link.core.config.Mutex.RUnlock()
for _, listenaddr := range t.link.core.config.Current.Listen { for _, listenaddr := range t.link.core.config.Current.Listen {
if listenaddr[:6] != "tcp://" { switch listenaddr[:6] {
case "tcp://":
if _, err := t.listen(listenaddr[6:], nil); err != nil {
return err
}
case "tls://":
if _, err := t.listen(listenaddr[6:], t.tls.forListener); err != nil {
return err
}
default:
t.link.core.log.Errorln("Failed to add listener: listener", listenaddr, "is not correctly formatted, ignoring") t.link.core.log.Errorln("Failed to add listener: listener", listenaddr, "is not correctly formatted, ignoring")
continue
}
if _, err := t.listen(listenaddr[6:]); err != nil {
return err
} }
} }
@ -119,18 +132,21 @@ func (t *tcp) reconfigure() {
t.link.core.config.Mutex.RUnlock() t.link.core.config.Mutex.RUnlock()
if len(added) > 0 || len(deleted) > 0 { if len(added) > 0 || len(deleted) > 0 {
for _, a := range added { for _, a := range added {
if a[:6] != "tcp://" { switch a[:6] {
case "tcp://":
if _, err := t.listen(a[6:], nil); err != nil {
t.link.core.log.Errorln("Error adding TCP", a[6:], "listener:", err)
}
case "tls://":
if _, err := t.listen(a[6:], t.tls.forListener); err != nil {
t.link.core.log.Errorln("Error adding TLS", a[6:], "listener:", err)
}
default:
t.link.core.log.Errorln("Failed to add listener: listener", a, "is not correctly formatted, ignoring") t.link.core.log.Errorln("Failed to add listener: listener", a, "is not correctly formatted, ignoring")
continue
}
if _, err := t.listen(a[6:]); err != nil {
t.link.core.log.Errorln("Error adding TCP", a[6:], "listener:", err)
} else {
t.link.core.log.Infoln("Started TCP listener:", a[6:])
} }
} }
for _, d := range deleted { for _, d := range deleted {
if d[:6] != "tcp://" { if d[:6] != "tcp://" && d[:6] != "tls://" {
t.link.core.log.Errorln("Failed to delete listener: listener", d, "is not correctly formatted, ignoring") t.link.core.log.Errorln("Failed to delete listener: listener", d, "is not correctly formatted, ignoring")
continue continue
} }
@ -146,7 +162,7 @@ func (t *tcp) reconfigure() {
} }
} }
func (t *tcp) listen(listenaddr string) (*TcpListener, error) { func (t *tcp) listen(listenaddr string, upgrade *TcpUpgrade) (*TcpListener, error) {
var err error var err error
ctx := context.Background() ctx := context.Background()
@ -157,6 +173,7 @@ func (t *tcp) listen(listenaddr string) (*TcpListener, error) {
if err == nil { if err == nil {
l := TcpListener{ l := TcpListener{
Listener: listener, Listener: listener,
upgrade: upgrade,
stop: make(chan struct{}), stop: make(chan struct{}),
} }
t.waitgroup.Add(1) t.waitgroup.Add(1)
@ -204,7 +221,7 @@ func (t *tcp) listener(l *TcpListener, listenaddr string) {
return return
} }
t.waitgroup.Add(1) t.waitgroup.Add(1)
go t.handler(sock, true, nil) go t.handler(sock, true, nil, l.upgrade)
} }
} }
@ -222,11 +239,15 @@ func (t *tcp) startCalling(saddr string) bool {
// If the dial is successful, it launches the handler. // If the dial is successful, it launches the handler.
// When finished, it removes the outgoing call, so reconnection attempts can be made later. // When finished, it removes the outgoing call, so reconnection attempts can be made later.
// This all happens in a separate goroutine that it spawns. // This all happens in a separate goroutine that it spawns.
func (t *tcp) call(saddr string, options interface{}, sintf string) { func (t *tcp) call(saddr string, options interface{}, sintf string, upgrade *TcpUpgrade) {
go func() { go func() {
callname := saddr callname := saddr
callproto := "TCP"
if upgrade != nil {
callproto = strings.ToUpper(upgrade.name)
}
if sintf != "" { if sintf != "" {
callname = fmt.Sprintf("%s/%s", saddr, sintf) callname = fmt.Sprintf("%s/%s/%s", callproto, saddr, sintf)
} }
if !t.startCalling(callname) { if !t.startCalling(callname) {
return return
@ -261,7 +282,7 @@ func (t *tcp) call(saddr string, options interface{}, sintf string) {
return return
} }
t.waitgroup.Add(1) t.waitgroup.Add(1)
t.handler(conn, false, saddr) t.handler(conn, false, saddr, nil)
} else { } else {
dst, err := net.ResolveTCPAddr("tcp", saddr) dst, err := net.ResolveTCPAddr("tcp", saddr)
if err != nil { if err != nil {
@ -322,19 +343,29 @@ func (t *tcp) call(saddr string, options interface{}, sintf string) {
} }
conn, err = dialer.Dial("tcp", dst.String()) conn, err = dialer.Dial("tcp", dst.String())
if err != nil { if err != nil {
t.link.core.log.Debugln("Failed to dial TCP:", err) t.link.core.log.Debugf("Failed to dial %s: %s", callproto, err)
return return
} }
t.waitgroup.Add(1) t.waitgroup.Add(1)
t.handler(conn, false, nil) t.handler(conn, false, nil, upgrade)
} }
}() }()
} }
func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}) { func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}, upgrade *TcpUpgrade) {
defer t.waitgroup.Done() // Happens after sock.close defer t.waitgroup.Done() // Happens after sock.close
defer sock.Close() defer sock.Close()
t.setExtraOptions(sock) t.setExtraOptions(sock)
var upgraded bool
if upgrade != nil {
var err error
if sock, err = upgrade.upgrade(sock); err != nil {
t.link.core.log.Errorln("TCP handler upgrade failed:", err)
return
} else {
upgraded = true
}
}
stream := stream{} stream := stream{}
stream.init(sock) stream.init(sock)
var name, proto, local, remote string var name, proto, local, remote string
@ -344,8 +375,13 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}) {
local, _, _ = net.SplitHostPort(sock.LocalAddr().String()) local, _, _ = net.SplitHostPort(sock.LocalAddr().String())
remote, _, _ = net.SplitHostPort(socksaddr) remote, _, _ = net.SplitHostPort(socksaddr)
} else { } else {
name = "tcp://" + sock.RemoteAddr().String() if upgraded {
proto = "tcp" proto = upgrade.name
name = proto + "://" + sock.RemoteAddr().String()
} else {
proto = "tcp"
name = proto + "://" + sock.RemoteAddr().String()
}
local, _, _ = net.SplitHostPort(sock.LocalAddr().String()) local, _, _ = net.SplitHostPort(sock.LocalAddr().String())
remote, _, _ = net.SplitHostPort(sock.RemoteAddr().String()) remote, _, _ = net.SplitHostPort(sock.RemoteAddr().String())
} }

93
src/yggdrasil/tls.go Normal file
View File

@ -0,0 +1,93 @@
package yggdrasil
import (
"bytes"
"crypto/ed25519"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/hex"
"encoding/pem"
"log"
"math/big"
"net"
"time"
)
type tcptls struct {
tcp *tcp
config *tls.Config
forDialer *TcpUpgrade
forListener *TcpUpgrade
}
func (t *tcptls) init(tcp *tcp) {
t.tcp = tcp
t.forDialer = &TcpUpgrade{
upgrade: t.upgradeDialer,
name: "tls",
}
t.forListener = &TcpUpgrade{
upgrade: t.upgradeListener,
name: "tls",
}
edpriv := make(ed25519.PrivateKey, ed25519.PrivateKeySize)
copy(edpriv[:], tcp.link.core.sigPriv[:])
certBuf := &bytes.Buffer{}
// TODO: because NotAfter is finite, we should add some mechanism to regenerate the certificate and restart the listeners periodically for nodes with very high uptimes. Perhaps regenerate certs and restart listeners every few months or so.
pubtemp := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
CommonName: hex.EncodeToString(tcp.link.core.sigPub[:]),
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24 * 365),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
derbytes, err := x509.CreateCertificate(rand.Reader, &pubtemp, &pubtemp, edpriv.Public(), edpriv)
if err != nil {
log.Fatalf("Failed to create certificate: %s", err)
}
if err := pem.Encode(certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: derbytes}); err != nil {
panic("failed to encode certificate into PEM")
}
cpool := x509.NewCertPool()
cpool.AppendCertsFromPEM(derbytes)
t.config = &tls.Config{
RootCAs: cpool,
Certificates: []tls.Certificate{
{
Certificate: [][]byte{derbytes},
PrivateKey: edpriv,
},
},
InsecureSkipVerify: true,
MinVersion: tls.VersionTLS13,
}
}
func (t *tcptls) upgradeListener(c net.Conn) (net.Conn, error) {
conn := tls.Server(c, t.config)
if err := conn.Handshake(); err != nil {
return c, err
}
return conn, nil
}
func (t *tcptls) upgradeDialer(c net.Conn) (net.Conn, error) {
conn := tls.Client(c, t.config)
if err := conn.Handshake(); err != nil {
return c, err
}
return conn, nil
}