mirror of
https://github.com/cwinfo/yggdrasil-go.git
synced 2024-11-22 21:10:29 +00:00
commit
1c81e43fcd
11
CHANGELOG.md
11
CHANGELOG.md
@ -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
|
||||||
|
@ -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
2
go.mod
@ -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
5
go.sum
@ -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=
|
||||||
|
@ -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() {
|
||||||
|
@ -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 = sinfo
|
||||||
|
c.session.setConn(nil, c)
|
||||||
|
c.nodeID = crypto.GetNodeID(&c.session.theirPermPub)
|
||||||
|
for i := range c.nodeMask {
|
||||||
|
c.nodeMask[i] = 0xFF
|
||||||
|
}
|
||||||
|
}
|
||||||
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
204
src/yggdrasil/core_test.go
Normal 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
|
||||||
|
}
|
31
src/yggdrasil/tcp_linux.go
Normal file
31
src/yggdrasil/tcp_linux.go
Normal 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
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
// +build !darwin
|
// +build !darwin,!linux
|
||||||
|
|
||||||
package yggdrasil
|
package yggdrasil
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user