Merge pull request #156 from yggdrasil-network/develop

Version 0.2.4
This commit is contained in:
Neil Alexander 2018-07-08 18:46:48 +01:00 committed by GitHub
commit 059fe24526
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 298 additions and 149 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.
-->
## [0.2.4] - 2018-07-08
### Added
- Support for UNIX domain sockets for the admin socket using `unix:///path/to/file.sock`
- Centralised platform-specific defaults
### Changed
- Backpressure tuning, including reducing resource consumption
### Fixed
- macOS local ping bug, which previously prevented you from pinging your own `utun` adapter's IPv6 address
## [0.2.3] - 2018-06-29
### Added
- Begin keeping changelog (incomplete and possibly inaccurate information before this point).

View File

@ -13,6 +13,8 @@ import (
"strings"
"sync/atomic"
"time"
"yggdrasil/defaults"
)
// TODO: Add authentication
@ -20,6 +22,7 @@ import (
type admin struct {
core *Core
listenaddr string
listener net.Listener
handlers []admin_handlerInfo
}
@ -155,15 +158,15 @@ func (a *admin) init(c *Core, listenaddr string) {
})
a.addHandler("setTunTap", []string{"name", "[tap_mode]", "[mtu]"}, func(in admin_info) (admin_info, error) {
// Set sane defaults
iftapmode := getDefaults().defaultIfTAPMode
ifmtu := getDefaults().defaultIfMTU
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 <= getDefaults().maximumIfMTU {
if mtu.(float64) >= 1280 && ifmtu <= defaults.GetDefaults().MaximumIfMTU {
ifmtu = int(in["mtu"].(float64))
}
}
@ -227,17 +230,37 @@ func (a *admin) start() error {
return nil
}
// cleans up when stopping
func (a *admin) close() error {
return a.listener.Close()
}
// listen is run by start and manages API connections.
func (a *admin) listen() {
l, err := net.Listen("tcp", a.listenaddr)
u, err := url.Parse(a.listenaddr)
if err == nil {
switch strings.ToLower(u.Scheme) {
case "unix":
a.listener, err = net.Listen("unix", a.listenaddr[7:])
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.core.log.Printf("Admin socket failed to listen: %v", err)
os.Exit(1)
}
defer l.Close()
a.core.log.Printf("Admin socket listening on %s", l.Addr().String())
a.core.log.Printf("%s admin socket listening on %s",
strings.ToUpper(a.listener.Addr().Network()),
a.listener.Addr().String())
defer a.listener.Close()
for {
conn, err := l.Accept()
conn, err := a.listener.Accept()
if err == nil {
a.handleRequest(conn)
}

View File

@ -3,7 +3,7 @@ package config
// NodeConfig defines all configuration values needed to run a signle yggdrasil node
type NodeConfig struct {
Listen string `comment:"Listen address for peer connections. Default is to listen for all\nTCP connections over IPv4 and IPv6 with a random port."`
AdminListen string `comment:"Listen address for admin connections Default is to listen for local\nconnections only on TCP port 9001."`
AdminListen string `comment:"Listen address for admin connections Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X."`
Peers []string `comment:"List of connection strings for static peers in URI format, i.e.\ntcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j"`
AllowedEncryptionPublicKeys []string `comment:"List of peer encryption public keys to allow or incoming TCP\nconnections from. If left empty/undefined then all connections\nwill be allowed by default."`
EncryptionPublicKey string `comment:"Your public encryption key. Your peers may ask you for this to put\ninto their AllowedEncryptionPublicKeys configuration."`

View File

@ -9,6 +9,7 @@ import (
"regexp"
"yggdrasil/config"
"yggdrasil/defaults"
)
// The Core object represents the Yggdrasil node. You should create a Core
@ -135,6 +136,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error {
func (c *Core) Stop() {
c.log.Println("Stopping...")
c.tun.close()
c.admin.close()
}
// Generates a new encryption keypair. The encryption keys are used to
@ -197,26 +199,31 @@ 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 getDefaults().defaultIfName
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 getDefaults().defaultIfMTU
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 getDefaults().maximumIfMTU
return defaults.GetDefaults().MaximumIfMTU
}
// Gets the default TUN/TAP interface mode for your platform.
func (c *Core) GetTUNDefaultIfTAPMode() bool {
return getDefaults().defaultIfTAPMode
return defaults.GetDefaults().DefaultIfTAPMode
}
// Gets the current TUN/TAP interface name.

View File

@ -0,0 +1,15 @@
package defaults
// Defines which parameters are expected by default for configuration on a
// specific platform. These values are populated in the relevant defaults_*.go
// for the platform being targeted. They must be set.
type platformDefaultParameters struct {
// Admin socket
DefaultAdminListen string
// TUN/TAP
MaximumIfMTU int
DefaultIfMTU int
DefaultIfName string
DefaultIfTAPMode bool
}

View File

@ -0,0 +1,18 @@
// +build darwin
package defaults
// Sane defaults for the macOS/Darwin platform. The "default" options may be
// may be replaced by the running configuration.
func GetDefaults() platformDefaultParameters {
return platformDefaultParameters{
// Admin
DefaultAdminListen: "tcp://localhost:9001",
// TUN/TAP
MaximumIfMTU: 65535,
DefaultIfMTU: 65535,
DefaultIfName: "auto",
DefaultIfTAPMode: false,
}
}

View File

@ -0,0 +1,18 @@
// +build freebsd
package defaults
// Sane defaults for the BSD platforms. The "default" options may be
// may be replaced by the running configuration.
func GetDefaults() platformDefaultParameters {
return platformDefaultParameters{
// Admin
DefaultAdminListen: "tcp://localhost:9001",
// TUN/TAP
MaximumIfMTU: 32767,
DefaultIfMTU: 32767,
DefaultIfName: "/dev/tap0",
DefaultIfTAPMode: true,
}
}

View File

@ -0,0 +1,18 @@
// +build linux
package defaults
// Sane defaults for the Linux platform. The "default" options may be
// may be replaced by the running configuration.
func GetDefaults() platformDefaultParameters {
return platformDefaultParameters{
// Admin
DefaultAdminListen: "tcp://localhost:9001",
// TUN/TAP
MaximumIfMTU: 65535,
DefaultIfMTU: 65535,
DefaultIfName: "auto",
DefaultIfTAPMode: false,
}
}

View File

@ -0,0 +1,18 @@
// +build netbsd
package defaults
// Sane defaults for the BSD platforms. The "default" options may be
// may be replaced by the running configuration.
func GetDefaults() platformDefaultParameters {
return platformDefaultParameters{
// Admin
DefaultAdminListen: "tcp://localhost:9001",
// TUN/TAP
MaximumIfMTU: 9000,
DefaultIfMTU: 9000,
DefaultIfName: "/dev/tap0",
DefaultIfTAPMode: true,
}
}

View File

@ -0,0 +1,18 @@
// +build openbsd
package defaults
// Sane defaults for the BSD platforms. The "default" options may be
// may be replaced by the running configuration.
func GetDefaults() platformDefaultParameters {
return platformDefaultParameters{
// Admin
DefaultAdminListen: "tcp://localhost:9001",
// TUN/TAP
MaximumIfMTU: 16384,
DefaultIfMTU: 16384,
DefaultIfName: "/dev/tap0",
DefaultIfTAPMode: true,
}
}

View File

@ -0,0 +1,18 @@
// +build !linux,!darwin,!windows,!openbsd,!freebsd,!netbsd
package defaults
// Sane defaults for the other platforms. The "default" options may be
// may be replaced by the running configuration.
func GetDefaults() platformDefaultParameters {
return platformDefaultParameters{
// Admin
DefaultAdminListen: "tcp://localhost:9001",
// TUN/TAP
MaximumIfMTU: 65535,
DefaultIfMTU: 65535,
DefaultIfName: "none",
DefaultIfTAPMode: false,
}
}

View File

@ -0,0 +1,18 @@
// +build windows
package defaults
// Sane defaults for the Windows platform. The "default" options may be
// may be replaced by the running configuration.
func GetDefaults() platformDefaultParameters {
return platformDefaultParameters{
// Admin
DefaultAdminListen: "tcp://localhost:9001",
// TUN/TAP
MaximumIfMTU: 65535,
DefaultIfMTU: 65535,
DefaultIfName: "auto",
DefaultIfTAPMode: true,
}
}

View File

@ -12,6 +12,7 @@ package yggdrasil
// A little annoying to do with constant changes from backpressure
import (
"math/rand"
"sync"
"sync/atomic"
"time"
@ -595,55 +596,88 @@ type switch_packetInfo struct {
// Used to keep track of buffered packets
type switch_buffer struct {
packets []switch_packetInfo // Currently buffered packets, which may be dropped if it grows too large
count uint64 // Total queue size, including dropped packets
size uint64 // Total queue size in bytes
}
func (b *switch_buffer) dropTimedOut() {
// TODO figure out what timeout makes sense
const timeout = 25 * time.Millisecond
now := time.Now()
for len(b.packets) > 0 && now.Sub(b.packets[0].time) > timeout {
util_putBytes(b.packets[0].bytes)
b.packets = b.packets[1:]
type switch_buffers struct {
bufs map[string]switch_buffer // Buffers indexed by StreamID
size uint64 // Total size of all buffers, in bytes
}
func (b *switch_buffers) cleanup(t *switchTable) {
for streamID, buf := range b.bufs {
// Remove queues for which we have no next hop
packet := buf.packets[0]
coords := switch_getPacketCoords(packet.bytes)
if t.selfIsClosest(coords) {
for _, packet := range buf.packets {
util_putBytes(packet.bytes)
}
b.size -= buf.size
delete(b.bufs, streamID)
}
}
const maxSize = 4 * 1048576 // Maximum 4 MB
for b.size > maxSize {
// Drop a random queue
target := rand.Uint64() % b.size
var size uint64 // running total
for streamID, buf := range b.bufs {
size += buf.size
if size < target {
continue
}
var packet switch_packetInfo
packet, buf.packets = buf.packets[0], buf.packets[1:]
buf.size -= uint64(len(packet.bytes))
b.size -= uint64(len(packet.bytes))
util_putBytes(packet.bytes)
if len(buf.packets) == 0 {
delete(b.bufs, streamID)
} else {
// Need to update the map, since buf was retrieved by value
b.bufs[streamID] = buf
}
break
}
}
}
// Handles incoming idle notifications
// Loops over packets and sends the newest one that's OK for this peer to send
// Returns true if the peer is no longer idle, false if it should be added to the idle list
func (t *switchTable) handleIdle(port switchPort, buffs map[string]switch_buffer) bool {
func (t *switchTable) handleIdle(port switchPort, bufs *switch_buffers) bool {
to := t.core.peers.getPorts()[port]
if to == nil {
return true
}
var best string
var bestSize uint64
for streamID, buf := range buffs {
var bestPriority float64
bufs.cleanup(t)
now := time.Now()
for streamID, buf := range bufs.bufs {
// Filter over the streams that this node is closer to
// Keep the one with the smallest queue
buf.dropTimedOut()
if len(buf.packets) == 0 {
delete(buffs, streamID)
continue
}
buffs[streamID] = buf
packet := buf.packets[0]
coords := switch_getPacketCoords(packet.bytes)
if (bestSize == 0 || buf.count < bestSize) && t.portIsCloser(coords, port) {
priority := float64(now.Sub(packet.time)) / float64(buf.size)
if priority > bestPriority && t.portIsCloser(coords, port) {
best = streamID
bestSize = buf.count
bestPriority = priority
}
}
if bestSize != 0 {
buf := buffs[best]
if bestPriority != 0 {
buf := bufs.bufs[best]
var packet switch_packetInfo
// TODO decide if this should be LIFO or FIFO
packet, buf.packets = buf.packets[0], buf.packets[1:]
buf.count--
buf.size -= uint64(len(packet.bytes))
bufs.size -= uint64(len(packet.bytes))
if len(buf.packets) == 0 {
delete(buffs, best)
delete(bufs.bufs, best)
} else {
buffs[best] = buf
// Need to update the map, since buf was retrieved by value
bufs.bufs[best] = buf
}
to.sendPacket(packet.bytes)
return true
@ -654,25 +688,27 @@ func (t *switchTable) handleIdle(port switchPort, buffs map[string]switch_buffer
// The switch worker does routing lookups and sends packets to where they need to be
func (t *switchTable) doWorker() {
buffs := make(map[string]switch_buffer) // Packets per PacketStreamID (string)
idle := make(map[switchPort]struct{}) // this is to deduplicate things
var bufs switch_buffers
bufs.bufs = make(map[string]switch_buffer) // Packets per PacketStreamID (string)
idle := make(map[switchPort]struct{}) // this is to deduplicate things
for {
select {
case packet := <-t.packetIn:
case bytes := <-t.packetIn:
// Try to send it somewhere (or drop it if it's corrupt or at a dead end)
if !t.handleIn(packet, idle) {
if !t.handleIn(bytes, idle) {
// There's nobody free to take it right now, so queue it for later
streamID := switch_getPacketStreamID(packet)
buf := buffs[streamID]
buf.dropTimedOut()
pinfo := switch_packetInfo{packet, time.Now()}
buf.packets = append(buf.packets, pinfo)
buf.count++
buffs[streamID] = buf
packet := switch_packetInfo{bytes, time.Now()}
streamID := switch_getPacketStreamID(packet.bytes)
buf := bufs.bufs[streamID]
buf.packets = append(buf.packets, packet)
buf.size += uint64(len(packet.bytes))
bufs.size += uint64(len(packet.bytes))
bufs.bufs[streamID] = buf
bufs.cleanup(t)
}
case port := <-t.idleIn:
// Try to find something to send to this peer
if !t.handleIdle(port, buffs) {
if !t.handleIdle(port, &bufs) {
// Didn't find anything ready to send yet, so stay idle
idle[port] = struct{}{}
}

View File

@ -3,6 +3,8 @@ package yggdrasil
// This manages the tun driver to send/recv packets to/from applications
import (
"yggdrasil/defaults"
"github.com/songgao/packets/ethernet"
"github.com/yggdrasil-network/water"
)
@ -20,21 +22,11 @@ type tunDevice struct {
iface *water.Interface
}
// Defines which parameters are expected by default for a TUN/TAP adapter on a
// specific platform. These values are populated in the relevant tun_*.go for
// the platform being targeted. They must be set.
type tunDefaultParameters struct {
maximumIfMTU int
defaultIfMTU int
defaultIfName string
defaultIfTAPMode bool
}
// Gets the maximum supported MTU for the platform based on the defaults in
// getDefaults().
// defaults.GetDefaults().
func getSupportedMTU(mtu int) int {
if mtu > getDefaults().maximumIfMTU {
return getDefaults().maximumIfMTU
if mtu > defaults.GetDefaults().MaximumIfMTU {
return defaults.GetDefaults().MaximumIfMTU
}
return mtu
}

View File

@ -13,17 +13,6 @@ import (
water "github.com/yggdrasil-network/water"
)
// Sane defaults for the Darwin/macOS platform. The "default" options may be
// may be replaced by the running configuration.
func getDefaults() tunDefaultParameters {
return tunDefaultParameters{
maximumIfMTU: 65535,
defaultIfMTU: 65535,
defaultIfName: "auto",
defaultIfTAPMode: false,
}
}
// Configures the "utun" adapter with the correct IPv6 address and MTU.
func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error {
if iftapmode {

View File

@ -1,12 +0,0 @@
package yggdrasil
// Sane defaults for the FreeBSD platform. The "default" options may be
// may be replaced by the running configuration.
func getDefaults() tunDefaultParameters {
return tunDefaultParameters{
maximumIfMTU: 32767,
defaultIfMTU: 32767,
defaultIfName: "/dev/tap0",
defaultIfTAPMode: true,
}
}

View File

@ -12,17 +12,6 @@ import (
water "github.com/yggdrasil-network/water"
)
// Sane defaults for the Linux platform. The "default" options may be
// may be replaced by the running configuration.
func getDefaults() tunDefaultParameters {
return tunDefaultParameters{
maximumIfMTU: 65535,
defaultIfMTU: 65535,
defaultIfName: "auto",
defaultIfTAPMode: false,
}
}
// Configures the TAP adapter with the correct IPv6 address and MTU.
func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error {
var config water.Config

View File

@ -1,12 +0,0 @@
package yggdrasil
// Sane defaults for the NetBSD platform. The "default" options may be
// may be replaced by the running configuration.
func getDefaults() tunDefaultParameters {
return tunDefaultParameters{
maximumIfMTU: 9000,
defaultIfMTU: 9000,
defaultIfName: "/dev/tap0",
defaultIfTAPMode: true,
}
}

View File

@ -1,12 +0,0 @@
package yggdrasil
// Sane defaults for the OpenBSD platform. The "default" options may be
// may be replaced by the running configuration.
func getDefaults() tunDefaultParameters {
return tunDefaultParameters{
maximumIfMTU: 16384,
defaultIfMTU: 16384,
defaultIfName: "/dev/tap0",
defaultIfTAPMode: true,
}
}

View File

@ -7,17 +7,6 @@ import water "github.com/yggdrasil-network/water"
// This is to catch unsupported platforms
// If your platform supports tun devices, you could try configuring it manually
// These are sane defaults for any platform that has not been matched by one of
// the other tun_*.go files.
func getDefaults() tunDefaultParameters {
return tunDefaultParameters{
maximumIfMTU: 65535,
defaultIfMTU: 65535,
defaultIfName: "none",
defaultIfTAPMode: false,
}
}
// 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 *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error {

View File

@ -10,17 +10,6 @@ import (
// This is to catch Windows platforms
// Sane defaults for the Windows platform. The "default" options may be
// may be replaced by the running configuration.
func getDefaults() tunDefaultParameters {
return tunDefaultParameters{
maximumIfMTU: 65535,
defaultIfMTU: 65535,
defaultIfName: "auto",
defaultIfTAPMode: true,
}
}
// 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".

View File

@ -23,6 +23,7 @@ import (
"yggdrasil"
"yggdrasil/config"
"yggdrasil/defaults"
)
type nodeConfig = config.NodeConfig
@ -53,7 +54,7 @@ func generateConfig(isAutoconf bool) *nodeConfig {
r1 := rand.New(rand.NewSource(time.Now().UnixNano()))
cfg.Listen = fmt.Sprintf("[::]:%d", r1.Intn(65534-32768)+32768)
}
cfg.AdminListen = "localhost:9001"
cfg.AdminListen = defaults.GetDefaults().DefaultAdminListen
cfg.EncryptionPublicKey = hex.EncodeToString(bpub[:])
cfg.EncryptionPrivateKey = hex.EncodeToString(bpriv[:])
cfg.SigningPublicKey = hex.EncodeToString(spub[:])
@ -61,9 +62,9 @@ func generateConfig(isAutoconf bool) *nodeConfig {
cfg.Peers = []string{}
cfg.AllowedEncryptionPublicKeys = []string{}
cfg.MulticastInterfaces = []string{".*"}
cfg.IfName = core.GetTUNDefaultIfName()
cfg.IfMTU = core.GetTUNDefaultIfMTU()
cfg.IfTAPMode = core.GetTUNDefaultIfTAPMode()
cfg.IfName = defaults.GetDefaults().DefaultIfName
cfg.IfMTU = defaults.GetDefaults().DefaultIfMTU
cfg.IfTAPMode = defaults.GetDefaults().DefaultIfTAPMode
return &cfg
}

View File

@ -1,31 +1,49 @@
package main
import "errors"
import "flag"
import "fmt"
import "strings"
import "net"
import "net/url"
import "sort"
import "encoding/json"
import "strconv"
import "os"
import "yggdrasil/defaults"
type admin_info map[string]interface{}
func main() {
server := flag.String("endpoint", "localhost:9001", "Admin socket endpoint")
server := flag.String("endpoint", defaults.GetDefaults().DefaultAdminListen, "Admin socket endpoint")
injson := flag.Bool("json", false, "Output in JSON format")
flag.Parse()
args := flag.Args()
if len(args) == 0 {
fmt.Println("usage:", os.Args[0], "[-endpoint=localhost:9001] [-json] command [key=value] [...]")
fmt.Println("usage:", os.Args[0], "[-endpoint=proto://server] [-json] command [key=value] [...]")
fmt.Println("example:", os.Args[0], "getPeers")
fmt.Println("example:", os.Args[0], "setTunTap name=auto mtu=1500 tap_mode=false")
fmt.Println("example:", os.Args[0], "-endpoint=localhost:9001 getDHT")
fmt.Println("example:", os.Args[0], "-endpoint=tcp://localhost:9001 getDHT")
fmt.Println("example:", os.Args[0], "-endpoint=unix:///var/run/ygg.sock getDHT")
return
}
conn, err := net.Dial("tcp", *server)
var conn net.Conn
u, err := url.Parse(*server)
if err == nil {
switch strings.ToLower(u.Scheme) {
case "unix":
conn, err = net.Dial("unix", (*server)[7:])
case "tcp":
conn, err = net.Dial("tcp", u.Host)
default:
err = errors.New("protocol not supported")
}
} else {
conn, err = net.Dial("tcp", *server)
}
if err != nil {
panic(err)
}