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

Merge pull request #571 from yggdrasil-network/develop

Version 0.3.10
This commit is contained in:
Neil Alexander 2019-10-10 21:14:46 +01:00 committed by GitHub
commit 1c81e43fcd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 266 additions and 18 deletions

View File

@ -25,6 +25,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- in case of vulnerabilities. - in case of vulnerabilities.
--> -->
## [0.3.10] - 2019-10-10
### Added
- The core library now includes several unit tests for peering and `yggdrasil.Conn` connections
### Changed
- On recent Linux kernels, Yggdrasil will now set the `tcp_congestion_control` algorithm used for its own TCP sockets to [BBR](https://github.com/google/bbr), which reduces latency under load
- The systemd service configuration in `contrib` (and, by extension, some of our packages) now attemps to load the `tun` module, in case TUN/TAP support is available but not loaded, and it restricts Yggdrasil to the `CAP_NET_ADMIN` capability for managing the TUN/TAP adapter, rather than letting it do whatever the (typically `root`) user can do
### Fixed
- The `yggdrasil.Conn.RemoteAddr()` function no longer blocks, fixing a deadlock when CKR is used while under heavy load
## [0.3.9] - 2019-09-27 ## [0.3.9] - 2019-09-27
### Added ### Added
- Yggdrasil will now complain more verbosely when a peer URI is incorrectly formatted - Yggdrasil will now complain more verbosely when a peer URI is incorrectly formatted

View File

@ -8,6 +8,8 @@ Group=yggdrasil
ProtectHome=true ProtectHome=true
ProtectSystem=true ProtectSystem=true
SyslogIdentifier=yggdrasil SyslogIdentifier=yggdrasil
CapabilityBoundSet=CAP_NET_ADMIN
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; \

2
go.mod
View File

@ -1,7 +1,7 @@
module github.com/yggdrasil-network/yggdrasil-go module github.com/yggdrasil-network/yggdrasil-go
require ( require (
github.com/Arceliar/phony v0.0.0-20190907031509-af5bdbeecab6 github.com/Arceliar/phony v0.0.0-20191005181740-21679e75e3f0
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

5
go.sum
View File

@ -1,5 +1,5 @@
github.com/Arceliar/phony v0.0.0-20190907031509-af5bdbeecab6 h1:zMj5Q1V0yF4WNfV/FpXG6iXfPJ965Xc5asR2vHXanXc= github.com/Arceliar/phony v0.0.0-20191005181740-21679e75e3f0 h1:IOFsvAMFkgnKfSQHxXTeqb1+ODFeR5px1HCHU86KF30=
github.com/Arceliar/phony v0.0.0-20190907031509-af5bdbeecab6/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/Arceliar/phony v0.0.0-20191005181740-21679e75e3f0/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=
@ -31,4 +31,5 @@ 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.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

@ -31,7 +31,7 @@ func (s *tunConn) close() {
} }
func (s *tunConn) _close_from_tun() { func (s *tunConn) _close_from_tun() {
s.conn.Close() go s.conn.Close() // Just in case it blocks on actor operations
delete(s.tun.addrToConn, s.addr) delete(s.tun.addrToConn, s.addr)
delete(s.tun.subnetToConn, s.snet) delete(s.tun.subnetToConn, s.snet)
func() { func() {

View File

@ -96,7 +96,7 @@ func (c *Conn) setMTU(from phony.Actor, mtu uint16) {
c.Act(from, func() { c.mtu = mtu }) c.Act(from, func() { c.mtu = mtu })
} }
// This should never be called from the router goroutine, used in the dial functions // This should never be called from an actor, used in the dial functions
func (c *Conn) search() error { func (c *Conn) search() error {
var err error var err error
done := make(chan struct{}) done := make(chan struct{})
@ -115,9 +115,13 @@ func (c *Conn) search() error {
default: default:
if sinfo != nil { if sinfo != nil {
// Finish initializing the session // Finish initializing the session
sinfo.setConn(nil, c) c.session = sinfo
c.session.setConn(nil, c)
c.nodeID = crypto.GetNodeID(&c.session.theirPermPub)
for i := range c.nodeMask {
c.nodeMask[i] = 0xFF
}
} }
c.session = sinfo
err = e err = e
close(done) close(done)
} }
@ -133,12 +137,6 @@ func (c *Conn) search() error {
if c.session == nil && err == nil { if c.session == nil && err == nil {
panic("search failed but returned no error") 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 return err
} }
@ -294,6 +292,9 @@ func (c *Conn) writeNoCopy(msg FlowKeyMessage) error {
var cancel util.Cancellation var cancel util.Cancellation
var doCancel bool var doCancel bool
phony.Block(c, func() { cancel, doCancel = c._getDeadlineCancellation(c.writeDeadline) }) phony.Block(c, func() { cancel, doCancel = c._getDeadlineCancellation(c.writeDeadline) })
if doCancel {
defer cancel.Cancel(nil)
}
var err error var err error
select { select {
case <-cancel.Finished(): case <-cancel.Finished():
@ -353,10 +354,8 @@ func (c *Conn) LocalAddr() crypto.NodeID {
// 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() crypto.NodeID {
// TODO warn that this can block while waiting for the Conn actor to run, so don't call it from other actors... // RemoteAddr is set during the dial or accept, and isn't changed, so it's safe to access directly
var n crypto.NodeID return *c.nodeID
phony.Block(c, func() { n = *c.nodeID })
return n
} }
// SetDeadline is equivalent to calling both SetReadDeadline and // SetDeadline is equivalent to calling both SetReadDeadline and

204
src/yggdrasil/core_test.go Normal file
View File

@ -0,0 +1,204 @@
package yggdrasil
import (
"bytes"
"math/rand"
"os"
"testing"
"time"
"github.com/gologme/log"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
)
// GenerateConfig produces default configuration with suitable modifications for tests.
func GenerateConfig() *config.NodeConfig {
cfg := config.GenerateConfig()
cfg.AdminListen = "none"
cfg.Listen = []string{"tcp://127.0.0.1:0"}
cfg.IfName = "none"
return cfg
}
// GetLoggerWithPrefix creates a new logger instance wih prefix.
// If verbose is set to true, three log levels are enabled: "info", "warn", "error".
func GetLoggerWithPrefix(prefix string, verbose bool) *log.Logger {
l := log.New(os.Stderr, prefix, log.Flags())
if !verbose {
return l
}
l.EnableLevel("info")
l.EnableLevel("warn")
l.EnableLevel("error")
return l
}
// CreateAndConnectTwo creates two nodes. nodeB connects to nodeA.
// Verbosity flag is passed to logger.
func CreateAndConnectTwo(t testing.TB, verbose bool) (nodeA *Core, nodeB *Core) {
nodeA = new(Core)
_, err := nodeA.Start(GenerateConfig(), GetLoggerWithPrefix("A: ", verbose))
if err != nil {
t.Fatal(err)
}
nodeB = new(Core)
_, err = nodeB.Start(GenerateConfig(), GetLoggerWithPrefix("B: ", verbose))
if err != nil {
t.Fatal(err)
}
err = nodeB.AddPeer("tcp://"+nodeA.link.tcp.getAddr().String(), "")
if err != nil {
t.Fatal(err)
}
if l := len(nodeA.GetPeers()); l != 1 {
t.Fatal("unexpected number of peers", l)
}
if l := len(nodeB.GetPeers()); l != 1 {
t.Fatal("unexpected number of peers", l)
}
return nodeA, nodeB
}
// WaitConnected blocks until either nodes negotiated DHT or 5 seconds passed.
func WaitConnected(nodeA, nodeB *Core) bool {
// It may take up to 3 seconds, but let's wait 5.
for i := 0; i < 50; i++ {
time.Sleep(100 * time.Millisecond)
if len(nodeA.GetSwitchPeers()) > 0 && len(nodeB.GetSwitchPeers()) > 0 {
return true
}
}
return false
}
// CreateEchoListener creates a routine listening on nodeA. It expects repeats messages of length bufLen.
// It returns a channel used to synchronize the routine with caller.
func CreateEchoListener(t testing.TB, nodeA *Core, bufLen int, repeats int) chan struct{} {
// Listen. Doing it here guarantees that there will be something to try to connect when it returns.
listener, err := nodeA.ConnListen()
if err != nil {
t.Fatal(err)
}
// Start routine
done := make(chan struct{})
go func() {
defer listener.Close()
conn, err := listener.Accept()
if err != nil {
t.Error(err)
return
}
defer conn.Close()
buf := make([]byte, bufLen)
for i := 0; i < repeats; i++ {
n, err := conn.Read(buf)
if err != nil {
t.Error(err)
return
}
if n != bufLen {
t.Error("missing data")
return
}
_, err = conn.Write(buf)
if err != nil {
t.Error(err)
}
}
done <- struct{}{}
}()
return done
}
// TestCore_Start_Connect checks if two nodes can connect together.
func TestCore_Start_Connect(t *testing.T) {
CreateAndConnectTwo(t, true)
}
// TestCore_Start_Transfer checks that messages can be passed between nodes (in both directions).
func TestCore_Start_Transfer(t *testing.T) {
nodeA, nodeB := CreateAndConnectTwo(t, true)
msgLen := 1500
done := CreateEchoListener(t, nodeA, msgLen, 1)
if !WaitConnected(nodeA, nodeB) {
t.Fatal("nodes did not connect")
}
// Dial
dialer, err := nodeB.ConnDialer()
if err != nil {
t.Fatal(err)
}
conn, err := dialer.Dial("nodeid", nodeA.NodeID().String())
if err != nil {
t.Fatal(err)
}
defer conn.Close()
msg := make([]byte, msgLen)
rand.Read(msg)
conn.Write(msg)
if err != nil {
t.Fatal(err)
}
buf := make([]byte, msgLen)
_, err = conn.Read(buf)
if err != nil {
t.Fatal(err)
}
if bytes.Compare(msg, buf) != 0 {
t.Fatal("expected echo")
}
<-done
}
// BenchmarkCore_Start_Transfer estimates the possible transfer between nodes (in MB/s).
func BenchmarkCore_Start_Transfer(b *testing.B) {
nodeA, nodeB := CreateAndConnectTwo(b, false)
msgLen := 1500 // typical MTU
done := CreateEchoListener(b, nodeA, msgLen, b.N)
if !WaitConnected(nodeA, nodeB) {
b.Fatal("nodes did not connect")
}
// Dial
dialer, err := nodeB.ConnDialer()
if err != nil {
b.Fatal(err)
}
conn, err := dialer.Dial("nodeid", nodeA.NodeID().String())
if err != nil {
b.Fatal(err)
}
defer conn.Close()
msg := make([]byte, msgLen)
rand.Read(msg)
buf := make([]byte, msgLen)
b.SetBytes(int64(b.N * msgLen))
b.ResetTimer()
for i := 0; i < b.N; i++ {
conn.Write(msg)
if err != nil {
b.Fatal(err)
}
_, err = conn.Read(buf)
if err != nil {
b.Fatal(err)
}
}
<-done
}

View File

@ -0,0 +1,31 @@
// +build linux
package yggdrasil
import (
"syscall"
"golang.org/x/sys/unix"
)
// WARNING: This context is used both by net.Dialer and net.Listen in tcp.go
func (t *tcp) tcpContext(network, address string, c syscall.RawConn) error {
var control error
var bbr error
control = c.Control(func(fd uintptr) {
bbr = unix.SetsockoptString(int(fd), unix.IPPROTO_TCP, unix.TCP_CONGESTION, "bbr")
})
// Log any errors
if bbr != nil {
t.link.core.log.Debugln("Failed to set tcp_congestion_control to bbr for socket, SetsockoptString error:", bbr)
}
if control != nil {
t.link.core.log.Debugln("Failed to set tcp_congestion_control to bbr for socket, Control error:", control)
}
// Return nil because errors here are not considered fatal for the connection, it just means congestion control is suboptimal
return nil
}

View File

@ -1,4 +1,4 @@
// +build !darwin // +build !darwin,!linux
package yggdrasil package yggdrasil