From 3f824ee99c9381ca139a6725d9fbda3ca03ced58 Mon Sep 17 00:00:00 2001 From: Paul Spooren Date: Tue, 19 Mar 2019 15:54:49 +0100 Subject: [PATCH 0001/1109] README: add OpenWrt as supported platform --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e9e0c7d9..3f07a2b2 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ some of the below: - FreeBSD - OpenBSD - NetBSD +- OpenWrt Please see our [Platforms](https://yggdrasil-network.github.io/) pages for more specific information about each of our supported platforms, including From 0b494a8255881fef79c926f74773b1322b0cb505 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 28 Mar 2019 00:30:25 +0000 Subject: [PATCH 0002/1109] Refactoring: move tuntap and icmpv6 into separate package --- cmd/yggdrasil/main.go | 3 + src/config/config.go | 25 ++++++ src/{yggdrasil => tuntap}/icmpv6.go | 56 ++++++------ src/{yggdrasil => tuntap}/tun.go | 104 +++++++++++++++-------- src/{yggdrasil => tuntap}/tun_bsd.go | 6 +- src/{yggdrasil => tuntap}/tun_darwin.go | 20 ++--- src/{yggdrasil => tuntap}/tun_dummy.go | 6 +- src/{yggdrasil => tuntap}/tun_linux.go | 6 +- src/{yggdrasil => tuntap}/tun_other.go | 6 +- src/{yggdrasil => tuntap}/tun_windows.go | 8 +- src/yggdrasil/adapter.go | 38 ++++++--- src/yggdrasil/admin.go | 85 +++++++++--------- src/yggdrasil/ckr.go | 13 ++- src/yggdrasil/core.go | 64 +++++++------- src/yggdrasil/multicast.go | 10 +-- src/yggdrasil/peer.go | 34 ++++---- src/yggdrasil/router.go | 25 +++--- src/yggdrasil/session.go | 22 ++--- src/yggdrasil/switch.go | 2 - src/yggdrasil/tcp.go | 14 +-- 20 files changed, 307 insertions(+), 240 deletions(-) rename src/{yggdrasil => tuntap}/icmpv6.go (84%) rename src/{yggdrasil => tuntap}/tun.go (69%) rename src/{yggdrasil => tuntap}/tun_bsd.go (97%) rename src/{yggdrasil => tuntap}/tun_darwin.go (81%) rename src/{yggdrasil => tuntap}/tun_dummy.go (79%) rename src/{yggdrasil => tuntap}/tun_linux.go (93%) rename src/{yggdrasil => tuntap}/tun_other.go (87%) rename src/{yggdrasil => tuntap}/tun_windows.go (93%) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 8c8340f0..cec77056 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -19,6 +19,7 @@ import ( "github.com/mitchellh/mapstructure" "github.com/yggdrasil-network/yggdrasil-go/src/config" + "github.com/yggdrasil-network/yggdrasil-go/src/tuntap" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) @@ -27,6 +28,7 @@ type Core = yggdrasil.Core type node struct { core Core + tun tuntap.TunAdapter } func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *nodeConfig { @@ -247,6 +249,7 @@ func main() { // Now that we have a working configuration, we can now actually start // Yggdrasil. This will start the router, switch, DHT node, TCP and UDP // sockets, TUN/TAP adapter and multicast discovery port. + n.core.SetRouterAdapter(&n.tun) if err := n.core.Start(cfg, logger); err != nil { logger.Errorln("An error occurred during startup") panic(err) diff --git a/src/config/config.go b/src/config/config.go index a9007585..861e57ac 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -2,11 +2,36 @@ package config import ( "encoding/hex" + "sync" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" ) +// NodeState represents the active and previous configuration of the node and +// protects it with a mutex +type NodeState struct { + Current NodeConfig + Previous NodeConfig + Mutex sync.RWMutex +} + +// Get returns both the current and previous node configs +func (s *NodeState) Get() (NodeConfig, NodeConfig) { + s.Mutex.RLock() + defer s.Mutex.RUnlock() + return s.Current, s.Previous +} + +// Replace the node configuration with new configuration +func (s *NodeState) Replace(n NodeConfig) NodeConfig { + s.Mutex.Lock() + defer s.Mutex.Unlock() + s.Previous = s.Current + s.Current = n + return s.Current +} + // NodeConfig defines all configuration values needed to run a signle yggdrasil node type NodeConfig struct { Peers []string `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j. These connections\nwill obey the operating system routing table, therefore you should\nuse this section when you may connect via different interfaces."` diff --git a/src/yggdrasil/icmpv6.go b/src/tuntap/icmpv6.go similarity index 84% rename from src/yggdrasil/icmpv6.go rename to src/tuntap/icmpv6.go index 52ca50c6..f4f9efe8 100644 --- a/src/yggdrasil/icmpv6.go +++ b/src/tuntap/icmpv6.go @@ -1,4 +1,4 @@ -package yggdrasil +package tuntap // The ICMPv6 module implements functions to easily create ICMPv6 // packets. These functions, when mixed with the built-in Go IPv6 @@ -25,8 +25,8 @@ type macAddress [6]byte const len_ETHER = 14 -type icmpv6 struct { - tun *tunAdapter +type ICMPv6 struct { + tun *TunAdapter mylladdr net.IP mymac macAddress peermacs map[address.Address]neighbor @@ -59,7 +59,7 @@ func ipv6Header_Marshal(h *ipv6.Header) ([]byte, error) { // Initialises the ICMPv6 module by assigning our link-local IPv6 address and // our MAC address. ICMPv6 messages will always appear to originate from these // addresses. -func (i *icmpv6) init(t *tunAdapter) { +func (i *ICMPv6) Init(t *TunAdapter) { i.tun = t i.peermacs = make(map[address.Address]neighbor) @@ -69,23 +69,23 @@ func (i *icmpv6) init(t *tunAdapter) { i.mylladdr = net.IP{ 0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFE} - copy(i.mymac[:], i.tun.core.router.addr[:]) - copy(i.mylladdr[9:], i.tun.core.router.addr[1:]) + copy(i.mymac[:], i.tun.addr[:]) + copy(i.mylladdr[9:], i.tun.addr[1:]) } // Parses an incoming ICMPv6 packet. The packet provided may be either an // ethernet frame containing an IP packet, or the IP packet alone. This is // determined by whether the TUN/TAP adapter is running in TUN (layer 3) or // TAP (layer 2) mode. -func (i *icmpv6) parse_packet(datain []byte) { +func (i *ICMPv6) ParsePacket(datain []byte) { var response []byte var err error // Parse the frame/packet - if i.tun.iface.IsTAP() { - response, err = i.parse_packet_tap(datain) + if i.tun.IsTAP() { + response, err = i.UnmarshalPacketL2(datain) } else { - response, err = i.parse_packet_tun(datain, nil) + response, err = i.UnmarshalPacket(datain, nil) } if err != nil { @@ -93,22 +93,22 @@ func (i *icmpv6) parse_packet(datain []byte) { } // Write the packet to TUN/TAP - i.tun.iface.Write(response) + i.tun.Send <- response } // Unwraps the ethernet headers of an incoming ICMPv6 packet and hands off -// the IP packet to the parse_packet_tun function for further processing. +// the IP packet to the ParsePacket function for further processing. // A response buffer is also created for the response message, also complete // with ethernet headers. -func (i *icmpv6) parse_packet_tap(datain []byte) ([]byte, error) { +func (i *ICMPv6) UnmarshalPacketL2(datain []byte) ([]byte, error) { // Ignore non-IPv6 frames if binary.BigEndian.Uint16(datain[12:14]) != uint16(0x86DD) { return nil, nil } - // Hand over to parse_packet_tun to interpret the IPv6 packet + // Hand over to ParsePacket to interpret the IPv6 packet mac := datain[6:12] - ipv6packet, err := i.parse_packet_tun(datain[len_ETHER:], &mac) + ipv6packet, err := i.UnmarshalPacket(datain[len_ETHER:], &mac) if err != nil { return nil, err } @@ -130,7 +130,7 @@ func (i *icmpv6) parse_packet_tap(datain []byte) ([]byte, error) { // sanity checks on the packet - i.e. is the packet an ICMPv6 packet, does the // ICMPv6 message match a known expected type. The relevant handler function // is then called and a response packet may be returned. -func (i *icmpv6) parse_packet_tun(datain []byte, datamac *[]byte) ([]byte, error) { +func (i *ICMPv6) UnmarshalPacket(datain []byte, datamac *[]byte) ([]byte, error) { // Parse the IPv6 packet headers ipv6Header, err := ipv6.ParseHeader(datain[:ipv6.HeaderLen]) if err != nil { @@ -156,13 +156,13 @@ func (i *icmpv6) parse_packet_tun(datain []byte, datamac *[]byte) ([]byte, error // Check for a supported message type switch icmpv6Header.Type { case ipv6.ICMPTypeNeighborSolicitation: - if !i.tun.iface.IsTAP() { + if !i.tun.IsTAP() { return nil, errors.New("Ignoring Neighbor Solicitation in TUN mode") } - response, err := i.handle_ndp(datain[ipv6.HeaderLen:]) + response, err := i.HandleNDP(datain[ipv6.HeaderLen:]) if err == nil { // Create our ICMPv6 response - responsePacket, err := i.create_icmpv6_tun( + responsePacket, err := CreateICMPv6( ipv6Header.Src, i.mylladdr, ipv6.ICMPTypeNeighborAdvertisement, 0, &icmp.DefaultMessageBody{Data: response}) @@ -176,7 +176,7 @@ func (i *icmpv6) parse_packet_tun(datain []byte, datamac *[]byte) ([]byte, error return nil, err } case ipv6.ICMPTypeNeighborAdvertisement: - if !i.tun.iface.IsTAP() { + if !i.tun.IsTAP() { return nil, errors.New("Ignoring Neighbor Advertisement in TUN mode") } if datamac != nil { @@ -202,9 +202,9 @@ func (i *icmpv6) parse_packet_tun(datain []byte, datamac *[]byte) ([]byte, error // Creates an ICMPv6 packet based on the given icmp.MessageBody and other // parameters, complete with ethernet and IP headers, which can be written // directly to a TAP adapter. -func (i *icmpv6) create_icmpv6_tap(dstmac macAddress, dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) { - // Pass through to create_icmpv6_tun - ipv6packet, err := i.create_icmpv6_tun(dst, src, mtype, mcode, mbody) +func (i *ICMPv6) CreateICMPv6L2(dstmac macAddress, dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) { + // Pass through to CreateICMPv6 + ipv6packet, err := CreateICMPv6(dst, src, mtype, mcode, mbody) if err != nil { return nil, err } @@ -224,9 +224,9 @@ func (i *icmpv6) create_icmpv6_tap(dstmac macAddress, dst net.IP, src net.IP, mt // Creates an ICMPv6 packet based on the given icmp.MessageBody and other // parameters, complete with IP headers only, which can be written directly to -// a TUN adapter, or called directly by the create_icmpv6_tap function when +// a TUN adapter, or called directly by the CreateICMPv6L2 function when // generating a message for TAP adapters. -func (i *icmpv6) create_icmpv6_tun(dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) { +func CreateICMPv6(dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) { // Create the ICMPv6 message icmpMessage := icmp.Message{ Type: mtype, @@ -265,7 +265,7 @@ func (i *icmpv6) create_icmpv6_tun(dst net.IP, src net.IP, mtype ipv6.ICMPType, return responsePacket, nil } -func (i *icmpv6) create_ndp_tap(dst address.Address) ([]byte, error) { +func (i *ICMPv6) CreateNDPL2(dst address.Address) ([]byte, error) { // Create the ND payload var payload [28]byte copy(payload[:4], []byte{0x00, 0x00, 0x00, 0x00}) @@ -287,7 +287,7 @@ func (i *icmpv6) create_ndp_tap(dst address.Address) ([]byte, error) { copy(dstmac[2:6], dstaddr[12:16]) // Create the ND request - requestPacket, err := i.create_icmpv6_tap( + requestPacket, err := i.CreateICMPv6L2( dstmac, dstaddr[:], i.mylladdr, ipv6.ICMPTypeNeighborSolicitation, 0, &icmp.DefaultMessageBody{Data: payload[:]}) @@ -305,7 +305,7 @@ func (i *icmpv6) create_ndp_tap(dst address.Address) ([]byte, error) { // when the host operating system generates an NDP request for any address in // the fd00::/8 range, so that the operating system knows to route that traffic // to the Yggdrasil TAP adapter. -func (i *icmpv6) handle_ndp(in []byte) ([]byte, error) { +func (i *ICMPv6) HandleNDP(in []byte) ([]byte, error) { // Ignore NDP requests for anything outside of fd00::/8 var source address.Address copy(source[:], in[8:]) diff --git a/src/yggdrasil/tun.go b/src/tuntap/tun.go similarity index 69% rename from src/yggdrasil/tun.go rename to src/tuntap/tun.go index 465cbb1c..7dab31c8 100644 --- a/src/yggdrasil/tun.go +++ b/src/tuntap/tun.go @@ -1,4 +1,4 @@ -package yggdrasil +package tuntap // This manages the tun driver to send/recv packets to/from applications @@ -10,21 +10,29 @@ import ( "sync" "time" + "github.com/gologme/log" + "github.com/songgao/packets/ethernet" "github.com/yggdrasil-network/water" "github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" "github.com/yggdrasil-network/yggdrasil-go/src/util" + "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) const tun_IPv6_HEADER_LENGTH = 40 const tun_ETHER_HEADER_LENGTH = 14 // Represents a running TUN/TAP interface. -type tunAdapter struct { - Adapter - icmpv6 icmpv6 +type TunAdapter struct { + yggdrasil.Adapter + addr address.Address + subnet address.Subnet + log *log.Logger + config *config.NodeState + icmpv6 ICMPv6 mtu int iface *water.Interface mutex sync.RWMutex // Protects the below @@ -40,20 +48,37 @@ func getSupportedMTU(mtu int) int { return mtu } +// Get the adapter name +func (tun *TunAdapter) Name() string { + return tun.iface.Name() +} + +// Get the adapter MTU +func (tun *TunAdapter) MTU() int { + return getSupportedMTU(tun.mtu) +} + +// Get the adapter mode +func (tun *TunAdapter) IsTAP() bool { + return tun.iface.IsTAP() +} + // Initialises the TUN/TAP adapter. -func (tun *tunAdapter) init(core *Core, send chan<- []byte, recv <-chan []byte) { - tun.Adapter.init(core, send, recv) - tun.icmpv6.init(tun) +func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, send chan<- []byte, recv <-chan []byte) { + tun.config = config + tun.log = log + tun.Adapter.Init(config, log, send, recv) + tun.icmpv6.Init(tun) go func() { for { - e := <-tun.reconfigure - tun.core.configMutex.RLock() - updated := tun.core.config.IfName != tun.core.configOld.IfName || - tun.core.config.IfTAPMode != tun.core.configOld.IfTAPMode || - tun.core.config.IfMTU != tun.core.configOld.IfMTU - tun.core.configMutex.RUnlock() + e := <-tun.Reconfigure + tun.config.Mutex.RLock() + updated := tun.config.Current.IfName != tun.config.Previous.IfName || + tun.config.Current.IfTAPMode != tun.config.Previous.IfTAPMode || + tun.config.Current.IfMTU != tun.config.Previous.IfMTU + tun.config.Mutex.RUnlock() if updated { - tun.core.log.Warnln("Reconfiguring TUN/TAP is not supported yet") + tun.log.Warnln("Reconfiguring TUN/TAP is not supported yet") e <- nil } else { e <- nil @@ -64,13 +89,18 @@ func (tun *tunAdapter) init(core *Core, send chan<- []byte, recv <-chan []byte) // Starts the setup process for the TUN/TAP adapter, and if successful, starts // the read/write goroutines to handle packets on that interface. -func (tun *tunAdapter) start() error { - tun.core.configMutex.RLock() - ifname := tun.core.config.IfName - iftapmode := tun.core.config.IfTAPMode - addr := fmt.Sprintf("%s/%d", net.IP(tun.core.router.addr[:]).String(), 8*len(address.GetPrefix())-1) - mtu := tun.core.config.IfMTU - tun.core.configMutex.RUnlock() +func (tun *TunAdapter) Start(a address.Address, s address.Subnet) error { + tun.addr = a + tun.subnet = s + if tun.config == nil { + return errors.New("No configuration available to TUN/TAP") + } + tun.config.Mutex.RLock() + ifname := tun.config.Current.IfName + iftapmode := tun.config.Current.IfTAPMode + addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1) + mtu := tun.config.Current.IfMTU + tun.config.Mutex.RUnlock() if ifname != "none" { if err := tun.setup(ifname, iftapmode, addr, mtu); err != nil { return err @@ -82,15 +112,15 @@ func (tun *tunAdapter) start() error { tun.mutex.Lock() tun.isOpen = true tun.mutex.Unlock() - go func() { tun.core.log.Errorln("WARNING: tun.read() exited with error:", tun.read()) }() - go func() { tun.core.log.Errorln("WARNING: tun.write() exited with error:", tun.write()) }() + go func() { tun.log.Errorln("WARNING: tun.read() exited with error:", tun.Read()) }() + go func() { tun.log.Errorln("WARNING: tun.write() exited with error:", tun.Write()) }() if iftapmode { go func() { for { - if _, ok := tun.icmpv6.peermacs[tun.core.router.addr]; ok { + if _, ok := tun.icmpv6.peermacs[tun.addr]; ok { break } - request, err := tun.icmpv6.create_ndp_tap(tun.core.router.addr) + request, err := tun.icmpv6.CreateNDPL2(tun.addr) if err != nil { panic(err) } @@ -107,9 +137,9 @@ func (tun *tunAdapter) start() error { // Writes a packet to the TUN/TAP adapter. If the adapter is running in TAP // mode then additional ethernet encapsulation is added for the benefit of the // host operating system. -func (tun *tunAdapter) write() error { +func (tun *TunAdapter) Write() error { for { - data := <-tun.recv + data := <-tun.Recv if tun.iface == nil { continue } @@ -132,7 +162,7 @@ func (tun *tunAdapter) write() error { neigh, known := tun.icmpv6.peermacs[destAddr] known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30) if !known { - request, err := tun.icmpv6.create_ndp_tap(destAddr) + request, err := tun.icmpv6.CreateNDPL2(destAddr) if err != nil { panic(err) } @@ -147,21 +177,21 @@ func (tun *tunAdapter) write() error { var peermac macAddress var peerknown bool if data[0]&0xf0 == 0x40 { - destAddr = tun.core.router.addr + destAddr = tun.addr } else if data[0]&0xf0 == 0x60 { - if !bytes.Equal(tun.core.router.addr[:16], destAddr[:16]) && !bytes.Equal(tun.core.router.subnet[:8], destAddr[:8]) { - destAddr = tun.core.router.addr + if !bytes.Equal(tun.addr[:16], destAddr[:16]) && !bytes.Equal(tun.subnet[:8], destAddr[:8]) { + destAddr = tun.addr } } if neighbor, ok := tun.icmpv6.peermacs[destAddr]; ok && neighbor.learned { peermac = neighbor.mac peerknown = true - } else if neighbor, ok := tun.icmpv6.peermacs[tun.core.router.addr]; ok && neighbor.learned { + } else if neighbor, ok := tun.icmpv6.peermacs[tun.addr]; ok && neighbor.learned { peermac = neighbor.mac peerknown = true sendndp(destAddr) } else { - sendndp(tun.core.router.addr) + sendndp(tun.addr) } if peerknown { var proto ethernet.Ethertype @@ -210,7 +240,7 @@ func (tun *tunAdapter) write() error { // is running in TAP mode then the ethernet headers will automatically be // processed and stripped if necessary. If an ICMPv6 packet is found, then // the relevant helper functions in icmpv6.go are called. -func (tun *tunAdapter) read() error { +func (tun *TunAdapter) Read() error { mtu := tun.mtu if tun.iface.IsTAP() { mtu += tun_ETHER_HEADER_LENGTH @@ -244,18 +274,18 @@ func (tun *tunAdapter) read() error { // Found an ICMPv6 packet b := make([]byte, n) copy(b, buf) - go tun.icmpv6.parse_packet(b) + go tun.icmpv6.ParsePacket(b) } } packet := append(util.GetBytes(), buf[o:n]...) - tun.send <- packet + tun.Send <- packet } } // Closes the TUN/TAP adapter. This is only usually called when the Yggdrasil // process stops. Typically this operation will happen quickly, but on macOS // it can block until a read operation is completed. -func (tun *tunAdapter) close() error { +func (tun *TunAdapter) Close() error { tun.mutex.Lock() tun.isOpen = false tun.mutex.Unlock() diff --git a/src/yggdrasil/tun_bsd.go b/src/tuntap/tun_bsd.go similarity index 97% rename from src/yggdrasil/tun_bsd.go rename to src/tuntap/tun_bsd.go index 81e2c46c..95c13afc 100644 --- a/src/yggdrasil/tun_bsd.go +++ b/src/tuntap/tun_bsd.go @@ -1,6 +1,6 @@ // +build openbsd freebsd netbsd -package yggdrasil +package tuntap import ( "encoding/binary" @@ -77,7 +77,7 @@ type in6_ifreq_lifetime struct { // a system socket and making syscalls to the kernel. This is not refined though // and often doesn't work (if at all), therefore if a call fails, it resorts // to calling "ifconfig" instead. -func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { +func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { var config water.Config if ifname[:4] == "auto" { ifname = "/dev/tap0" @@ -103,7 +103,7 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int return tun.setupAddress(addr) } -func (tun *tunAdapter) setupAddress(addr string) error { +func (tun *TunAdapter) setupAddress(addr string) error { var sfd int var err error diff --git a/src/yggdrasil/tun_darwin.go b/src/tuntap/tun_darwin.go similarity index 81% rename from src/yggdrasil/tun_darwin.go rename to src/tuntap/tun_darwin.go index 7ec1b8b9..5dfca137 100644 --- a/src/yggdrasil/tun_darwin.go +++ b/src/tuntap/tun_darwin.go @@ -1,6 +1,6 @@ // +build !mobile -package yggdrasil +package tuntap // The darwin platform specific tun parts @@ -16,9 +16,9 @@ import ( ) // Configures the "utun" adapter with the correct IPv6 address and MTU. -func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { +func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { if iftapmode { - tun.core.log.Warnln("TAP mode is not supported on this platform, defaulting to TUN") + tun.log.Warnln("TAP mode is not supported on this platform, defaulting to TUN") } config := water.Config{DeviceType: water.TUN} iface, err := water.New(config) @@ -64,12 +64,12 @@ type ifreq struct { // Sets the IPv6 address of the utun adapter. On Darwin/macOS this is done using // a system socket and making direct syscalls to the kernel. -func (tun *tunAdapter) setupAddress(addr string) error { +func (tun *TunAdapter) setupAddress(addr string) error { var fd int var err error if fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, 0); err != nil { - tun.core.log.Printf("Create AF_SYSTEM socket failed: %v.", err) + tun.log.Printf("Create AF_SYSTEM socket failed: %v.", err) return err } @@ -98,19 +98,19 @@ func (tun *tunAdapter) setupAddress(addr string) error { copy(ir.ifr_name[:], tun.iface.Name()) ir.ifru_mtu = uint32(tun.mtu) - tun.core.log.Infof("Interface name: %s", ar.ifra_name) - tun.core.log.Infof("Interface IPv6: %s", addr) - tun.core.log.Infof("Interface MTU: %d", ir.ifru_mtu) + tun.log.Infof("Interface name: %s", ar.ifra_name) + tun.log.Infof("Interface IPv6: %s", addr) + tun.log.Infof("Interface MTU: %d", ir.ifru_mtu) if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(darwin_SIOCAIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 { err = errno - tun.core.log.Errorf("Error in darwin_SIOCAIFADDR_IN6: %v", errno) + tun.log.Errorf("Error in darwin_SIOCAIFADDR_IN6: %v", errno) return err } if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 { err = errno - tun.core.log.Errorf("Error in SIOCSIFMTU: %v", errno) + tun.log.Errorf("Error in SIOCSIFMTU: %v", errno) return err } diff --git a/src/yggdrasil/tun_dummy.go b/src/tuntap/tun_dummy.go similarity index 79% rename from src/yggdrasil/tun_dummy.go rename to src/tuntap/tun_dummy.go index 234ab1de..04e65257 100644 --- a/src/yggdrasil/tun_dummy.go +++ b/src/tuntap/tun_dummy.go @@ -1,19 +1,19 @@ // +build mobile -package yggdrasil +package tuntap // This is to catch unsupported platforms // If your platform supports tun devices, you could try configuring it manually // Creates the TUN/TAP adapter, if supported by the Water library. Note that // no guarantees are made at this point on an unsupported platform. -func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { +func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { tun.mtu = getSupportedMTU(mtu) return tun.setupAddress(addr) } // We don't know how to set the IPv6 address on an unknown platform, therefore // write about it to stdout and don't try to do anything further. -func (tun *tunAdapter) setupAddress(addr string) error { +func (tun *TunAdapter) setupAddress(addr string) error { return nil } diff --git a/src/yggdrasil/tun_linux.go b/src/tuntap/tun_linux.go similarity index 93% rename from src/yggdrasil/tun_linux.go rename to src/tuntap/tun_linux.go index 30ada235..051287ee 100644 --- a/src/yggdrasil/tun_linux.go +++ b/src/tuntap/tun_linux.go @@ -1,6 +1,6 @@ // +build !mobile -package yggdrasil +package tuntap // The linux platform specific tun parts @@ -15,7 +15,7 @@ import ( ) // Configures the TAP adapter with the correct IPv6 address and MTU. -func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { +func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { var config water.Config if iftapmode { config = water.Config{DeviceType: water.TAP} @@ -50,7 +50,7 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int // is used to do this, so there is not a hard requirement on "ip" or "ifconfig" // to exist on the system, but this will fail if Netlink is not present in the // kernel (it nearly always is). -func (tun *tunAdapter) setupAddress(addr string) error { +func (tun *TunAdapter) setupAddress(addr string) error { // Set address var netIF *net.Interface ifces, err := net.Interfaces() diff --git a/src/yggdrasil/tun_other.go b/src/tuntap/tun_other.go similarity index 87% rename from src/yggdrasil/tun_other.go rename to src/tuntap/tun_other.go index 07ec25fd..17dfa2b4 100644 --- a/src/yggdrasil/tun_other.go +++ b/src/tuntap/tun_other.go @@ -1,6 +1,6 @@ // +build !linux,!darwin,!windows,!openbsd,!freebsd,!netbsd,!mobile -package yggdrasil +package tuntap import water "github.com/yggdrasil-network/water" @@ -9,7 +9,7 @@ import water "github.com/yggdrasil-network/water" // Creates the TUN/TAP adapter, if supported by the Water library. Note that // no guarantees are made at this point on an unsupported platform. -func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { +func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { var config water.Config if iftapmode { config = water.Config{DeviceType: water.TAP} @@ -27,7 +27,7 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int // We don't know how to set the IPv6 address on an unknown platform, therefore // write about it to stdout and don't try to do anything further. -func (tun *tunAdapter) setupAddress(addr string) error { +func (tun *TunAdapter) setupAddress(addr string) error { tun.core.log.Warnln("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr) return nil } diff --git a/src/yggdrasil/tun_windows.go b/src/tuntap/tun_windows.go similarity index 93% rename from src/yggdrasil/tun_windows.go rename to src/tuntap/tun_windows.go index 1c89a437..f5cebe55 100644 --- a/src/yggdrasil/tun_windows.go +++ b/src/tuntap/tun_windows.go @@ -1,4 +1,4 @@ -package yggdrasil +package tuntap import ( "fmt" @@ -13,7 +13,7 @@ import ( // Configures the TAP adapter with the correct IPv6 address and MTU. On Windows // we don't make use of a direct operating system API to do this - we instead // delegate the hard work to "netsh". -func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { +func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { if !iftapmode { tun.core.log.Warnln("TUN mode is not supported on this platform, defaulting to TAP") } @@ -65,7 +65,7 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int } // Sets the MTU of the TAP adapter. -func (tun *tunAdapter) setupMTU(mtu int) error { +func (tun *TunAdapter) setupMTU(mtu int) error { // Set MTU cmd := exec.Command("netsh", "interface", "ipv6", "set", "subinterface", fmt.Sprintf("interface=%s", tun.iface.Name()), @@ -82,7 +82,7 @@ func (tun *tunAdapter) setupMTU(mtu int) error { } // Sets the IPv6 address of the TAP adapter. -func (tun *tunAdapter) setupAddress(addr string) error { +func (tun *TunAdapter) setupAddress(addr string) error { // Set address cmd := exec.Command("netsh", "interface", "ipv6", "add", "address", fmt.Sprintf("interface=%s", tun.iface.Name()), diff --git a/src/yggdrasil/adapter.go b/src/yggdrasil/adapter.go index 3ce80d2b..7437eb0a 100644 --- a/src/yggdrasil/adapter.go +++ b/src/yggdrasil/adapter.go @@ -1,18 +1,36 @@ package yggdrasil +import ( + "github.com/gologme/log" + "github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/config" +) + // Defines the minimum required struct members for an adapter type (this is -// now the base type for tunAdapter in tun.go) +// now the base type for TunAdapter in tun.go) type Adapter struct { - core *Core - send chan<- []byte - recv <-chan []byte - reconfigure chan chan error + adapterImplementation + Core *Core + Send chan<- []byte + Recv <-chan []byte + Reconfigure chan chan error +} + +// Defines the minimum required functions for an adapter type +type adapterImplementation interface { + Init(*config.NodeState, *log.Logger, chan<- []byte, <-chan []byte) + Name() string + MTU() int + IsTAP() bool + Start(address.Address, address.Subnet) error + Read() error + Write() error + Close() error } // Initialises the adapter. -func (adapter *Adapter) init(core *Core, send chan<- []byte, recv <-chan []byte) { - adapter.core = core - adapter.send = send - adapter.recv = recv - adapter.reconfigure = make(chan chan error, 1) +func (adapter Adapter) Init(config *config.NodeState, log *log.Logger, send chan<- []byte, recv <-chan []byte) { + adapter.Send = send + adapter.Recv = recv + adapter.Reconfigure = make(chan chan error, 1) } diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index a0854f22..002f974c 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -16,7 +16,6 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" - "github.com/yggdrasil-network/yggdrasil-go/src/defaults" ) // TODO: Add authentication @@ -58,19 +57,17 @@ func (a *admin) init(c *Core) { go func() { for { e := <-a.reconfigure - a.core.configMutex.RLock() - if a.core.config.AdminListen != a.core.configOld.AdminListen { - a.listenaddr = a.core.config.AdminListen + current, previous := a.core.config.Get() + if current.AdminListen != previous.AdminListen { + a.listenaddr = current.AdminListen a.close() a.start() } - a.core.configMutex.RUnlock() e <- nil } }() - a.core.configMutex.RLock() - a.listenaddr = a.core.config.AdminListen - a.core.configMutex.RUnlock() + current, _ := a.core.config.Get() + a.listenaddr = current.AdminListen a.addHandler("list", []string{}, func(in admin_info) (admin_info, error) { handlers := make(map[string]interface{}) for _, handler := range a.handlers { @@ -171,47 +168,47 @@ func (a *admin) init(c *Core) { }, errors.New("Failed to remove peer") } }) - a.addHandler("getTunTap", []string{}, func(in admin_info) (r admin_info, e error) { - defer func() { - if err := recover(); err != nil { - r = admin_info{"none": admin_info{}} - e = nil - } - }() + /* a.addHandler("getTunTap", []string{}, func(in admin_info) (r admin_info, e error) { + defer func() { + if err := recover(); err != nil { + r = admin_info{"none": admin_info{}} + e = nil + } + }() - return admin_info{ - a.core.router.tun.iface.Name(): admin_info{ - "tap_mode": a.core.router.tun.iface.IsTAP(), - "mtu": a.core.router.tun.mtu, - }, - }, nil - }) - a.addHandler("setTunTap", []string{"name", "[tap_mode]", "[mtu]"}, func(in admin_info) (admin_info, error) { - // Set sane defaults - iftapmode := defaults.GetDefaults().DefaultIfTAPMode - ifmtu := defaults.GetDefaults().DefaultIfMTU - // Has TAP mode been specified? - if tap, ok := in["tap_mode"]; ok { - iftapmode = tap.(bool) - } - // Check we have enough params for MTU - if mtu, ok := in["mtu"]; ok { - if mtu.(float64) >= 1280 && ifmtu <= defaults.GetDefaults().MaximumIfMTU { - ifmtu = int(in["mtu"].(float64)) - } - } - // Start the TUN adapter - if err := a.startTunWithMTU(in["name"].(string), iftapmode, ifmtu); err != nil { - return admin_info{}, errors.New("Failed to configure adapter") - } else { return admin_info{ a.core.router.tun.iface.Name(): admin_info{ "tap_mode": a.core.router.tun.iface.IsTAP(), - "mtu": ifmtu, + "mtu": a.core.router.tun.mtu, }, }, nil - } - }) + }) + a.addHandler("setTunTap", []string{"name", "[tap_mode]", "[mtu]"}, func(in admin_info) (admin_info, error) { + // Set sane defaults + iftapmode := defaults.GetDefaults().DefaultIfTAPMode + ifmtu := defaults.GetDefaults().DefaultIfMTU + // Has TAP mode been specified? + if tap, ok := in["tap_mode"]; ok { + iftapmode = tap.(bool) + } + // Check we have enough params for MTU + if mtu, ok := in["mtu"]; ok { + if mtu.(float64) >= 1280 && ifmtu <= defaults.GetDefaults().MaximumIfMTU { + ifmtu = int(in["mtu"].(float64)) + } + } + // Start the TUN adapter + if err := a.startTunWithMTU(in["name"].(string), iftapmode, ifmtu); err != nil { + return admin_info{}, errors.New("Failed to configure adapter") + } else { + return admin_info{ + a.core.router.tun.iface.Name(): admin_info{ + "tap_mode": a.core.router.tun.iface.IsTAP(), + "mtu": ifmtu, + }, + }, nil + } + })*/ a.addHandler("getMulticastInterfaces", []string{}, func(in admin_info) (admin_info, error) { var intfs []string for _, v := range a.core.multicast.interfaces() { @@ -609,6 +606,7 @@ func (a *admin) removePeer(p string) error { } // startTunWithMTU creates the tun/tap device, sets its address, and sets the MTU to the provided value. +/* func (a *admin) startTunWithMTU(ifname string, iftapmode bool, ifmtu int) error { // Close the TUN first if open _ = a.core.router.tun.close() @@ -636,6 +634,7 @@ func (a *admin) startTunWithMTU(ifname string, iftapmode bool, ifmtu int) error go a.core.router.tun.write() return nil } +*/ // getData_getSelf returns the self node's info for admin responses. func (a *admin) getData_getSelf() *admin_nodeInfo { diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index 03bc5718..dc4f1b90 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -55,25 +55,24 @@ func (c *cryptokey) init(core *Core) { // Configure the CKR routes - this must only ever be called from the router // goroutine, e.g. through router.doAdmin func (c *cryptokey) configure() error { - c.core.configMutex.RLock() - defer c.core.configMutex.RUnlock() + current, _ := c.core.config.Get() // Set enabled/disabled state - c.setEnabled(c.core.config.TunnelRouting.Enable) + c.setEnabled(current.TunnelRouting.Enable) // Clear out existing routes c.ipv6routes = make([]cryptokey_route, 0) c.ipv4routes = make([]cryptokey_route, 0) // Add IPv6 routes - for ipv6, pubkey := range c.core.config.TunnelRouting.IPv6Destinations { + for ipv6, pubkey := range current.TunnelRouting.IPv6Destinations { if err := c.addRoute(ipv6, pubkey); err != nil { return err } } // Add IPv4 routes - for ipv4, pubkey := range c.core.config.TunnelRouting.IPv4Destinations { + for ipv4, pubkey := range current.TunnelRouting.IPv4Destinations { if err := c.addRoute(ipv4, pubkey); err != nil { return err } @@ -85,7 +84,7 @@ func (c *cryptokey) configure() error { // Add IPv6 sources c.ipv6sources = make([]net.IPNet, 0) - for _, source := range c.core.config.TunnelRouting.IPv6Sources { + for _, source := range current.TunnelRouting.IPv6Sources { if err := c.addSourceSubnet(source); err != nil { return err } @@ -93,7 +92,7 @@ func (c *cryptokey) configure() error { // Add IPv4 sources c.ipv4sources = make([]net.IPNet, 0) - for _, source := range c.core.config.TunnelRouting.IPv4Sources { + for _, source := range current.TunnelRouting.IPv4Sources { if err := c.addSourceSubnet(source); err != nil { return err } diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 12ff14fc..a583d582 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -4,7 +4,6 @@ import ( "encoding/hex" "io/ioutil" "net" - "sync" "time" "github.com/gologme/log" @@ -29,9 +28,7 @@ type Core struct { // This is the main data structure that holds everything else for a node // We're going to keep our own copy of the provided config - that way we can // guarantee that it will be covered by the mutex - config config.NodeConfig // Active config - configOld config.NodeConfig // Previous config - configMutex sync.RWMutex // Protects both config and configOld + config config.NodeState // Config boxPub crypto.BoxPubKey boxPriv crypto.BoxPrivKey sigPub crypto.SigPubKey @@ -57,19 +54,21 @@ func (c *Core) init() error { c.log = log.New(ioutil.Discard, "", 0) } - boxPubHex, err := hex.DecodeString(c.config.EncryptionPublicKey) + current, _ := c.config.Get() + + boxPubHex, err := hex.DecodeString(current.EncryptionPublicKey) if err != nil { return err } - boxPrivHex, err := hex.DecodeString(c.config.EncryptionPrivateKey) + boxPrivHex, err := hex.DecodeString(current.EncryptionPrivateKey) if err != nil { return err } - sigPubHex, err := hex.DecodeString(c.config.SigningPublicKey) + sigPubHex, err := hex.DecodeString(current.SigningPublicKey) if err != nil { return err } - sigPrivHex, err := hex.DecodeString(c.config.SigningPrivateKey) + sigPrivHex, err := hex.DecodeString(current.SigningPrivateKey) if err != nil { return err } @@ -97,19 +96,16 @@ func (c *Core) init() error { func (c *Core) addPeerLoop() { for { // Get the peers from the config - these could change! - c.configMutex.RLock() - peers := c.config.Peers - interfacepeers := c.config.InterfacePeers - c.configMutex.RUnlock() + current, _ := c.config.Get() // Add peers from the Peers section - for _, peer := range peers { + for _, peer := range current.Peers { c.AddPeer(peer, "") time.Sleep(time.Second) } // Add peers from the InterfacePeers section - for intf, intfpeers := range interfacepeers { + for intf, intfpeers := range current.InterfacePeers { for _, peer := range intfpeers { c.AddPeer(peer, intf) time.Sleep(time.Second) @@ -126,10 +122,7 @@ func (c *Core) addPeerLoop() { func (c *Core) UpdateConfig(config *config.NodeConfig) { c.log.Infoln("Reloading configuration...") - c.configMutex.Lock() - c.configOld = c.config - c.config = *config - c.configMutex.Unlock() + c.config.Replace(*config) errors := 0 @@ -140,7 +133,7 @@ func (c *Core) UpdateConfig(config *config.NodeConfig) { c.sessions.reconfigure, c.peers.reconfigure, c.router.reconfigure, - c.router.tun.reconfigure, + //c.router.tun.Reconfigure, c.router.cryptokey.reconfigure, c.switchTable.reconfigure, c.link.reconfigure, @@ -181,13 +174,23 @@ func GetBuildVersion() string { return buildVersion } -// Starts up Yggdrasil using the provided NodeConfig, and outputs debug logging +// Set the router adapter +func (c *Core) SetRouterAdapter(adapter adapterImplementation) { + c.router.tun = adapter +} + +// Starts up Yggdrasil using the provided NodeState, and outputs debug logging // through the provided log.Logger. The started stack will include TCP and UDP // sockets, a multicast discovery socket, an admin socket, router, switch and // DHT node. func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { c.log = log + c.config = config.NodeState{ + Current: *nc, + Previous: *nc, + } + if name := GetBuildName(); name != "unknown" { c.log.Infoln("Build name:", name) } @@ -197,11 +200,6 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { c.log.Infoln("Starting up...") - c.configMutex.Lock() - c.config = *nc - c.configOld = c.config - c.configMutex.Unlock() - c.init() if err := c.link.init(c); err != nil { @@ -209,9 +207,11 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { return err } - if nc.SwitchOptions.MaxTotalQueueSize >= SwitchQueueTotalMinSize { - c.switchTable.queueTotalMaxSize = nc.SwitchOptions.MaxTotalQueueSize + c.config.Mutex.RLock() + if c.config.Current.SwitchOptions.MaxTotalQueueSize >= SwitchQueueTotalMinSize { + c.switchTable.queueTotalMaxSize = c.config.Current.SwitchOptions.MaxTotalQueueSize } + c.config.Mutex.RUnlock() if err := c.switchTable.start(); err != nil { c.log.Errorln("Failed to start switch") @@ -233,7 +233,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { return err } - if err := c.router.tun.start(); err != nil { + if err := c.router.tun.Start(c.router.addr, c.router.subnet); err != nil { c.log.Errorln("Failed to start TUN/TAP") return err } @@ -247,7 +247,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { // Stops the Yggdrasil node. func (c *Core) Stop() { c.log.Infoln("Stopping...") - c.router.tun.close() + c.router.tun.Close() c.admin.close() } @@ -343,10 +343,12 @@ func (c *Core) GetTUNDefaultIfTAPMode() bool { // Gets the current TUN/TAP interface name. func (c *Core) GetTUNIfName() string { - return c.router.tun.iface.Name() + //return c.router.tun.iface.Name() + return c.router.tun.Name() } // Gets the current TUN/TAP interface MTU. func (c *Core) GetTUNIfMTU() int { - return c.router.tun.mtu + //return c.router.tun.mtu + return c.router.tun.MTU() } diff --git a/src/yggdrasil/multicast.go b/src/yggdrasil/multicast.go index ca3a1f75..ab1b158e 100644 --- a/src/yggdrasil/multicast.go +++ b/src/yggdrasil/multicast.go @@ -23,9 +23,8 @@ func (m *multicast) init(core *Core) { m.core = core m.reconfigure = make(chan chan error, 1) m.listeners = make(map[string]*tcpListener) - m.core.configMutex.RLock() - m.listenPort = m.core.config.LinkLocalTCPPort - m.core.configMutex.RUnlock() + current, _ := m.core.config.Get() + m.listenPort = current.LinkLocalTCPPort go func() { for { e := <-m.reconfigure @@ -70,9 +69,8 @@ func (m *multicast) start() error { func (m *multicast) interfaces() map[string]net.Interface { // Get interface expressions from config - m.core.configMutex.RLock() - exprs := m.core.config.MulticastInterfaces - m.core.configMutex.RUnlock() + current, _ := m.core.config.Get() + exprs := current.MulticastInterfaces // Ask the system for network interfaces interfaces := make(map[string]net.Interface) allifaces, err := net.Interfaces() diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index ce359368..06201f9b 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -44,42 +44,42 @@ func (ps *peers) init(c *Core) { // because the key is in the whitelist or because the whitelist is empty. func (ps *peers) isAllowedEncryptionPublicKey(box *crypto.BoxPubKey) bool { boxstr := hex.EncodeToString(box[:]) - ps.core.configMutex.RLock() - defer ps.core.configMutex.RUnlock() - for _, v := range ps.core.config.AllowedEncryptionPublicKeys { + ps.core.config.Mutex.RLock() + defer ps.core.config.Mutex.RUnlock() + for _, v := range ps.core.config.Current.AllowedEncryptionPublicKeys { if v == boxstr { return true } } - return len(ps.core.config.AllowedEncryptionPublicKeys) == 0 + return len(ps.core.config.Current.AllowedEncryptionPublicKeys) == 0 } // Adds a key to the whitelist. func (ps *peers) addAllowedEncryptionPublicKey(box string) { - ps.core.configMutex.RLock() - defer ps.core.configMutex.RUnlock() - ps.core.config.AllowedEncryptionPublicKeys = - append(ps.core.config.AllowedEncryptionPublicKeys, box) + ps.core.config.Mutex.RLock() + defer ps.core.config.Mutex.RUnlock() + ps.core.config.Current.AllowedEncryptionPublicKeys = + append(ps.core.config.Current.AllowedEncryptionPublicKeys, box) } // Removes a key from the whitelist. func (ps *peers) removeAllowedEncryptionPublicKey(box string) { - ps.core.configMutex.RLock() - defer ps.core.configMutex.RUnlock() - for k, v := range ps.core.config.AllowedEncryptionPublicKeys { + ps.core.config.Mutex.RLock() + defer ps.core.config.Mutex.RUnlock() + for k, v := range ps.core.config.Current.AllowedEncryptionPublicKeys { if v == box { - ps.core.config.AllowedEncryptionPublicKeys = - append(ps.core.config.AllowedEncryptionPublicKeys[:k], - ps.core.config.AllowedEncryptionPublicKeys[k+1:]...) + ps.core.config.Current.AllowedEncryptionPublicKeys = + append(ps.core.config.Current.AllowedEncryptionPublicKeys[:k], + ps.core.config.Current.AllowedEncryptionPublicKeys[k+1:]...) } } } // Gets the whitelist of allowed keys for incoming connections. func (ps *peers) getAllowedEncryptionPublicKeys() []string { - ps.core.configMutex.RLock() - defer ps.core.configMutex.RUnlock() - return ps.core.config.AllowedEncryptionPublicKeys + ps.core.config.Mutex.RLock() + defer ps.core.config.Mutex.RUnlock() + return ps.core.config.Current.AllowedEncryptionPublicKeys } // Atomically gets a map[switchPort]*peer of known peers. diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 1d4c0771..04f5ee51 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -26,9 +26,6 @@ import ( "bytes" "time" - "golang.org/x/net/icmp" - "golang.org/x/net/ipv6" - "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/util" @@ -44,8 +41,7 @@ type router struct { in <-chan []byte // packets we received from the network, link to peer's "out" out func([]byte) // packets we're sending to the network, link to peer's "in" toRecv chan router_recvPacket // packets to handle via recvPacket() - tun tunAdapter // TUN/TAP adapter - adapters []Adapter // Other adapters + tun adapterImplementation // TUN/TAP adapter recv chan<- []byte // place where the tun pulls received packets from send <-chan []byte // place where the tun puts outgoing packets reset chan struct{} // signal that coords changed (re-init sessions/dht) @@ -112,11 +108,11 @@ func (r *router) init(core *Core) { r.reset = make(chan struct{}, 1) r.admin = make(chan func(), 32) r.nodeinfo.init(r.core) - r.core.configMutex.RLock() - r.nodeinfo.setNodeInfo(r.core.config.NodeInfo, r.core.config.NodeInfoPrivacy) - r.core.configMutex.RUnlock() + r.core.config.Mutex.RLock() + r.nodeinfo.setNodeInfo(r.core.config.Current.NodeInfo, r.core.config.Current.NodeInfoPrivacy) + r.core.config.Mutex.RUnlock() r.cryptokey.init(r.core) - r.tun.init(r.core, send, recv) + r.tun.Init(&r.core.config, r.core.log, send, recv) } // Starts the mainLoop goroutine. @@ -157,9 +153,8 @@ func (r *router) mainLoop() { case f := <-r.admin: f() case e := <-r.reconfigure: - r.core.configMutex.RLock() - e <- r.nodeinfo.setNodeInfo(r.core.config.NodeInfo, r.core.config.NodeInfoPrivacy) - r.core.configMutex.RUnlock() + current, _ := r.core.config.Get() + e <- r.nodeinfo.setNodeInfo(current.NodeInfo, current.NodeInfoPrivacy) } } } @@ -308,7 +303,7 @@ func (r *router) sendPacket(bs []byte) { // Generate an ICMPv6 Packet Too Big for packets larger than session MTU if len(bs) > int(sinfo.getMTU()) { // Get the size of the oversized payload, up to a max of 900 bytes - window := 900 + /*window := 900 if int(sinfo.getMTU()) < window { window = int(sinfo.getMTU()) } @@ -320,12 +315,12 @@ func (r *router) sendPacket(bs []byte) { } // Create the ICMPv6 response from it - icmpv6Buf, err := r.tun.icmpv6.create_icmpv6_tun( + icmpv6Buf, err := CreateICMPv6( bs[8:24], bs[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb) if err == nil { r.recv <- icmpv6Buf - } + }*/ // Don't continue - drop the packet return diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 012af579..b3563e05 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -148,17 +148,17 @@ func (ss *sessions) init(core *Core) { // Determines whether the session firewall is enabled. func (ss *sessions) isSessionFirewallEnabled() bool { - ss.core.configMutex.RLock() - defer ss.core.configMutex.RUnlock() + ss.core.config.Mutex.RLock() + defer ss.core.config.Mutex.RUnlock() - return ss.core.config.SessionFirewall.Enable + return ss.core.config.Current.SessionFirewall.Enable } // Determines whether the session with a given publickey is allowed based on // session firewall rules. func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) bool { - ss.core.configMutex.RLock() - defer ss.core.configMutex.RUnlock() + ss.core.config.Mutex.RLock() + defer ss.core.config.Mutex.RUnlock() // Allow by default if the session firewall is disabled if !ss.isSessionFirewallEnabled() { @@ -167,7 +167,7 @@ func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) b // Prepare for checking whitelist/blacklist var box crypto.BoxPubKey // Reject blacklisted nodes - for _, b := range ss.core.config.SessionFirewall.BlacklistEncryptionPublicKeys { + for _, b := range ss.core.config.Current.SessionFirewall.BlacklistEncryptionPublicKeys { key, err := hex.DecodeString(b) if err == nil { copy(box[:crypto.BoxPubKeyLen], key) @@ -177,7 +177,7 @@ func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) b } } // Allow whitelisted nodes - for _, b := range ss.core.config.SessionFirewall.WhitelistEncryptionPublicKeys { + for _, b := range ss.core.config.Current.SessionFirewall.WhitelistEncryptionPublicKeys { key, err := hex.DecodeString(b) if err == nil { copy(box[:crypto.BoxPubKeyLen], key) @@ -187,7 +187,7 @@ func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) b } } // Allow outbound sessions if appropriate - if ss.core.config.SessionFirewall.AlwaysAllowOutbound { + if ss.core.config.Current.SessionFirewall.AlwaysAllowOutbound { if initiator { return true } @@ -201,11 +201,11 @@ func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) b } } // Allow direct peers if appropriate - if ss.core.config.SessionFirewall.AllowFromDirect && isDirectPeer { + if ss.core.config.Current.SessionFirewall.AllowFromDirect && isDirectPeer { return true } // Allow remote nodes if appropriate - if ss.core.config.SessionFirewall.AllowFromRemote && !isDirectPeer { + if ss.core.config.Current.SessionFirewall.AllowFromRemote && !isDirectPeer { return true } // Finally, default-deny if not matching any of the above rules @@ -277,7 +277,7 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.mySesPriv = *priv sinfo.myNonce = *crypto.NewBoxNonce() sinfo.theirMTU = 1280 - sinfo.myMTU = uint16(ss.core.router.tun.mtu) + sinfo.myMTU = uint16(ss.core.router.tun.MTU()) now := time.Now() sinfo.time = now sinfo.mtuTime = now diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 490b15f3..0e164b9a 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -188,9 +188,7 @@ func (t *switchTable) init(core *Core) { now := time.Now() t.core = core t.reconfigure = make(chan chan error, 1) - t.core.configMutex.RLock() t.key = t.core.sigPub - t.core.configMutex.RUnlock() locator := switchLocator{root: t.key, tstamp: now.Unix()} peers := make(map[switchPort]peerInfo) t.data = switchData{locator: locator, peers: peers} diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 8acf9c17..f25ac412 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -82,10 +82,10 @@ func (t *tcp) init(l *link) error { go func() { for { e := <-t.reconfigure - t.link.core.configMutex.RLock() - added := util.Difference(t.link.core.config.Listen, t.link.core.configOld.Listen) - deleted := util.Difference(t.link.core.configOld.Listen, t.link.core.config.Listen) - t.link.core.configMutex.RUnlock() + t.link.core.config.Mutex.RLock() + added := util.Difference(t.link.core.config.Current.Listen, t.link.core.config.Previous.Listen) + deleted := util.Difference(t.link.core.config.Previous.Listen, t.link.core.config.Current.Listen) + t.link.core.config.Mutex.RUnlock() if len(added) > 0 || len(deleted) > 0 { for _, a := range added { if a[:6] != "tcp://" { @@ -115,9 +115,9 @@ func (t *tcp) init(l *link) error { } }() - t.link.core.configMutex.RLock() - defer t.link.core.configMutex.RUnlock() - for _, listenaddr := range t.link.core.config.Listen { + t.link.core.config.Mutex.RLock() + defer t.link.core.config.Mutex.RUnlock() + for _, listenaddr := range t.link.core.config.Current.Listen { if listenaddr[:6] != "tcp://" { continue } From 0715e829c2b38ed1183d10b942803a3074c01507 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 28 Mar 2019 09:12:00 +0000 Subject: [PATCH 0003/1109] Fix adapter setup and no longer panics on packets shorter than IP header --- src/tuntap/icmpv6.go | 2 +- src/tuntap/tun.go | 20 +++++++++++++++----- src/yggdrasil/adapter.go | 3 ++- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/tuntap/icmpv6.go b/src/tuntap/icmpv6.go index f4f9efe8..55c3280a 100644 --- a/src/tuntap/icmpv6.go +++ b/src/tuntap/icmpv6.go @@ -93,7 +93,7 @@ func (i *ICMPv6) ParsePacket(datain []byte) { } // Write the packet to TUN/TAP - i.tun.Send <- response + i.tun.iface.Write(response) } // Unwraps the ethernet headers of an incoming ICMPv6 packet and hands off diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 7dab31c8..780043f5 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -107,13 +107,20 @@ func (tun *TunAdapter) Start(a address.Address, s address.Subnet) error { } } if ifname == "none" || ifname == "dummy" { + tun.log.Debugln("Not starting TUN/TAP as ifname is none or dummy") return nil } tun.mutex.Lock() tun.isOpen = true tun.mutex.Unlock() - go func() { tun.log.Errorln("WARNING: tun.read() exited with error:", tun.Read()) }() - go func() { tun.log.Errorln("WARNING: tun.write() exited with error:", tun.Write()) }() + go func() { + tun.log.Debugln("Starting TUN/TAP reader goroutine") + tun.log.Errorln("WARNING: tun.read() exited with error:", tun.Read()) + }() + go func() { + tun.log.Debugln("Starting TUN/TAP writer goroutine") + tun.log.Errorln("WARNING: tun.write() exited with error:", tun.Write()) + }() if iftapmode { go func() { for { @@ -147,12 +154,16 @@ func (tun *TunAdapter) Write() error { var destAddr address.Address if data[0]&0xf0 == 0x60 { if len(data) < 40 { - panic("Tried to send a packet shorter than an IPv6 header...") + //panic("Tried to send a packet shorter than an IPv6 header...") + util.PutBytes(data) + continue } copy(destAddr[:16], data[24:]) } else if data[0]&0xf0 == 0x40 { if len(data) < 20 { - panic("Tried to send a packet shorter than an IPv4 header...") + //panic("Tried to send a packet shorter than an IPv4 header...") + util.PutBytes(data) + continue } copy(destAddr[:4], data[16:]) } else { @@ -255,7 +266,6 @@ func (tun *TunAdapter) Read() error { if !open { return nil } else { - // panic(err) return err } } diff --git a/src/yggdrasil/adapter.go b/src/yggdrasil/adapter.go index 7437eb0a..4103d1ed 100644 --- a/src/yggdrasil/adapter.go +++ b/src/yggdrasil/adapter.go @@ -29,7 +29,8 @@ type adapterImplementation interface { } // Initialises the adapter. -func (adapter Adapter) Init(config *config.NodeState, log *log.Logger, send chan<- []byte, recv <-chan []byte) { +func (adapter *Adapter) Init(config *config.NodeState, log *log.Logger, send chan<- []byte, recv <-chan []byte) { + log.Traceln("Adapter setup - given channels:", send, recv) adapter.Send = send adapter.Recv = recv adapter.Reconfigure = make(chan chan error, 1) From eb22ed44ac24664e7f964a7c1fb27f2d7303d7ba Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 28 Mar 2019 09:50:13 +0000 Subject: [PATCH 0004/1109] Add new reject channel to router so we can send back rejected packets to adapter (e.g. for ICMPv6 Packet Too Big), implement ICMPv6 PTB in TUN/TAP instead of router --- src/tuntap/tun.go | 188 ++++++++++++++++++++++----------------- src/yggdrasil/adapter.go | 6 +- src/yggdrasil/router.go | 37 +++++--- 3 files changed, 135 insertions(+), 96 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 780043f5..f85d8bb2 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -11,6 +11,8 @@ import ( "time" "github.com/gologme/log" + "golang.org/x/net/icmp" + "golang.org/x/net/ipv6" "github.com/songgao/packets/ethernet" "github.com/yggdrasil-network/water" @@ -64,10 +66,10 @@ func (tun *TunAdapter) IsTAP() bool { } // Initialises the TUN/TAP adapter. -func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, send chan<- []byte, recv <-chan []byte) { +func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, send chan<- []byte, recv <-chan []byte, reject <-chan yggdrasil.RejectedPacket) { tun.config = config tun.log = log - tun.Adapter.Init(config, log, send, recv) + tun.Adapter.Init(config, log, send, recv, reject) tun.icmpv6.Init(tun) go func() { for { @@ -146,81 +148,118 @@ func (tun *TunAdapter) Start(a address.Address, s address.Subnet) error { // host operating system. func (tun *TunAdapter) Write() error { for { - data := <-tun.Recv - if tun.iface == nil { - continue - } - if tun.iface.IsTAP() { - var destAddr address.Address - if data[0]&0xf0 == 0x60 { - if len(data) < 40 { - //panic("Tried to send a packet shorter than an IPv6 header...") - util.PutBytes(data) - continue + select { + case reject := <-tun.Reject: + switch reject.Reason { + case yggdrasil.PacketTooBig: + if mtu, ok := reject.Detail.(int); ok { + // Create the Packet Too Big response + ptb := &icmp.PacketTooBig{ + MTU: int(mtu), + Data: reject.Packet, + } + + // Create the ICMPv6 response from it + icmpv6Buf, err := CreateICMPv6( + reject.Packet[8:24], reject.Packet[24:40], + ipv6.ICMPTypePacketTooBig, 0, ptb) + + // Send the ICMPv6 response back to the TUN/TAP adapter + if err == nil { + tun.iface.Write(icmpv6Buf) + } } - copy(destAddr[:16], data[24:]) - } else if data[0]&0xf0 == 0x40 { - if len(data) < 20 { - //panic("Tried to send a packet shorter than an IPv4 header...") - util.PutBytes(data) - continue - } - copy(destAddr[:4], data[16:]) - } else { - return errors.New("Invalid address family") + fallthrough + default: + continue } - sendndp := func(destAddr address.Address) { - neigh, known := tun.icmpv6.peermacs[destAddr] - known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30) - if !known { - request, err := tun.icmpv6.CreateNDPL2(destAddr) - if err != nil { - panic(err) + case data := <-tun.Recv: + if tun.iface == nil { + continue + } + if tun.iface.IsTAP() { + var destAddr address.Address + if data[0]&0xf0 == 0x60 { + if len(data) < 40 { + //panic("Tried to send a packet shorter than an IPv6 header...") + util.PutBytes(data) + continue } - if _, err := tun.iface.Write(request); err != nil { - panic(err) + copy(destAddr[:16], data[24:]) + } else if data[0]&0xf0 == 0x40 { + if len(data) < 20 { + //panic("Tried to send a packet shorter than an IPv4 header...") + util.PutBytes(data) + continue } - tun.icmpv6.peermacs[destAddr] = neighbor{ - lastsolicitation: time.Now(), + copy(destAddr[:4], data[16:]) + } else { + return errors.New("Invalid address family") + } + sendndp := func(destAddr address.Address) { + neigh, known := tun.icmpv6.peermacs[destAddr] + known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30) + if !known { + request, err := tun.icmpv6.CreateNDPL2(destAddr) + if err != nil { + panic(err) + } + if _, err := tun.iface.Write(request); err != nil { + panic(err) + } + tun.icmpv6.peermacs[destAddr] = neighbor{ + lastsolicitation: time.Now(), + } } } - } - var peermac macAddress - var peerknown bool - if data[0]&0xf0 == 0x40 { - destAddr = tun.addr - } else if data[0]&0xf0 == 0x60 { - if !bytes.Equal(tun.addr[:16], destAddr[:16]) && !bytes.Equal(tun.subnet[:8], destAddr[:8]) { + var peermac macAddress + var peerknown bool + if data[0]&0xf0 == 0x40 { destAddr = tun.addr + } else if data[0]&0xf0 == 0x60 { + if !bytes.Equal(tun.addr[:16], destAddr[:16]) && !bytes.Equal(tun.subnet[:8], destAddr[:8]) { + destAddr = tun.addr + } + } + if neighbor, ok := tun.icmpv6.peermacs[destAddr]; ok && neighbor.learned { + peermac = neighbor.mac + peerknown = true + } else if neighbor, ok := tun.icmpv6.peermacs[tun.addr]; ok && neighbor.learned { + peermac = neighbor.mac + peerknown = true + sendndp(destAddr) + } else { + sendndp(tun.addr) + } + if peerknown { + var proto ethernet.Ethertype + switch { + case data[0]&0xf0 == 0x60: + proto = ethernet.IPv6 + case data[0]&0xf0 == 0x40: + proto = ethernet.IPv4 + } + var frame ethernet.Frame + frame.Prepare( + peermac[:6], // Destination MAC address + tun.icmpv6.mymac[:6], // Source MAC address + ethernet.NotTagged, // VLAN tagging + proto, // Ethertype + len(data)) // Payload length + copy(frame[tun_ETHER_HEADER_LENGTH:], data[:]) + if _, err := tun.iface.Write(frame); err != nil { + tun.mutex.RLock() + open := tun.isOpen + tun.mutex.RUnlock() + if !open { + return nil + } else { + panic(err) + } + } } - } - if neighbor, ok := tun.icmpv6.peermacs[destAddr]; ok && neighbor.learned { - peermac = neighbor.mac - peerknown = true - } else if neighbor, ok := tun.icmpv6.peermacs[tun.addr]; ok && neighbor.learned { - peermac = neighbor.mac - peerknown = true - sendndp(destAddr) } else { - sendndp(tun.addr) - } - if peerknown { - var proto ethernet.Ethertype - switch { - case data[0]&0xf0 == 0x60: - proto = ethernet.IPv6 - case data[0]&0xf0 == 0x40: - proto = ethernet.IPv4 - } - var frame ethernet.Frame - frame.Prepare( - peermac[:6], // Destination MAC address - tun.icmpv6.mymac[:6], // Source MAC address - ethernet.NotTagged, // VLAN tagging - proto, // Ethertype - len(data)) // Payload length - copy(frame[tun_ETHER_HEADER_LENGTH:], data[:]) - if _, err := tun.iface.Write(frame); err != nil { + if _, err := tun.iface.Write(data); err != nil { tun.mutex.RLock() open := tun.isOpen tun.mutex.RUnlock() @@ -231,19 +270,8 @@ func (tun *TunAdapter) Write() error { } } } - } else { - if _, err := tun.iface.Write(data); err != nil { - tun.mutex.RLock() - open := tun.isOpen - tun.mutex.RUnlock() - if !open { - return nil - } else { - panic(err) - } - } + util.PutBytes(data) } - util.PutBytes(data) } } diff --git a/src/yggdrasil/adapter.go b/src/yggdrasil/adapter.go index 4103d1ed..6be3ef00 100644 --- a/src/yggdrasil/adapter.go +++ b/src/yggdrasil/adapter.go @@ -13,12 +13,13 @@ type Adapter struct { Core *Core Send chan<- []byte Recv <-chan []byte + Reject <-chan RejectedPacket Reconfigure chan chan error } // Defines the minimum required functions for an adapter type type adapterImplementation interface { - Init(*config.NodeState, *log.Logger, chan<- []byte, <-chan []byte) + Init(*config.NodeState, *log.Logger, chan<- []byte, <-chan []byte, <-chan RejectedPacket) Name() string MTU() int IsTAP() bool @@ -29,9 +30,10 @@ type adapterImplementation interface { } // Initialises the adapter. -func (adapter *Adapter) Init(config *config.NodeState, log *log.Logger, send chan<- []byte, recv <-chan []byte) { +func (adapter *Adapter) Init(config *config.NodeState, log *log.Logger, send chan<- []byte, recv <-chan []byte, reject <-chan RejectedPacket) { log.Traceln("Adapter setup - given channels:", send, recv) adapter.Send = send adapter.Recv = recv + adapter.Reject = reject adapter.Reconfigure = make(chan chan error, 1) } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 04f5ee51..aa2cd54b 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -44,6 +44,7 @@ type router struct { tun adapterImplementation // TUN/TAP adapter recv chan<- []byte // place where the tun pulls received packets from send <-chan []byte // place where the tun puts outgoing packets + reject chan<- RejectedPacket // place where we send error packets back to tun reset chan struct{} // signal that coords changed (re-init sessions/dht) admin chan func() // pass a lambda for the admin socket to query stuff cryptokey cryptokey @@ -56,6 +57,19 @@ type router_recvPacket struct { sinfo *sessionInfo } +type RejectedPacketReason int + +const ( + // The router rejected the packet because it is too big for the session + PacketTooBig = 1 + iota +) + +type RejectedPacket struct { + Reason RejectedPacketReason + Packet []byte + Detail interface{} +} + // Initializes the router struct, which includes setting up channels to/from the tun/tap. func (r *router) init(core *Core) { r.core = core @@ -103,8 +117,10 @@ func (r *router) init(core *Core) { r.toRecv = make(chan router_recvPacket, 32) recv := make(chan []byte, 32) send := make(chan []byte, 32) + reject := make(chan RejectedPacket, 32) r.recv = recv r.send = send + r.reject = reject r.reset = make(chan struct{}, 1) r.admin = make(chan func(), 32) r.nodeinfo.init(r.core) @@ -112,7 +128,7 @@ func (r *router) init(core *Core) { r.nodeinfo.setNodeInfo(r.core.config.Current.NodeInfo, r.core.config.Current.NodeInfoPrivacy) r.core.config.Mutex.RUnlock() r.cryptokey.init(r.core) - r.tun.Init(&r.core.config, r.core.log, send, recv) + r.tun.Init(&r.core.config, r.core.log, send, recv, reject) } // Starts the mainLoop goroutine. @@ -303,25 +319,18 @@ func (r *router) sendPacket(bs []byte) { // Generate an ICMPv6 Packet Too Big for packets larger than session MTU if len(bs) > int(sinfo.getMTU()) { // Get the size of the oversized payload, up to a max of 900 bytes - /*window := 900 + window := 900 if int(sinfo.getMTU()) < window { window = int(sinfo.getMTU()) } - // Create the Packet Too Big response - ptb := &icmp.PacketTooBig{ - MTU: int(sinfo.getMTU()), - Data: bs[:window], + // Send the error back to the adapter + r.reject <- RejectedPacket{ + Reason: PacketTooBig, + Packet: bs[:window], + Detail: int(sinfo.getMTU()), } - // Create the ICMPv6 response from it - icmpv6Buf, err := CreateICMPv6( - bs[8:24], bs[24:40], - ipv6.ICMPTypePacketTooBig, 0, ptb) - if err == nil { - r.recv <- icmpv6Buf - }*/ - // Don't continue - drop the packet return } From 03bc7bbcd607269bc40d742b2caee5f7accba1ca Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 28 Mar 2019 15:32:01 +0000 Subject: [PATCH 0005/1109] Fix TUN/TAP for non-Darwin platforms --- src/tuntap/tun_bsd.go | 24 ++++++++++++------------ src/tuntap/tun_linux.go | 6 +++--- src/tuntap/tun_other.go | 2 +- src/tuntap/tun_windows.go | 32 ++++++++++++++++---------------- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/tuntap/tun_bsd.go b/src/tuntap/tun_bsd.go index 95c13afc..996f3140 100644 --- a/src/tuntap/tun_bsd.go +++ b/src/tuntap/tun_bsd.go @@ -109,14 +109,14 @@ func (tun *TunAdapter) setupAddress(addr string) error { // Create system socket if sfd, err = unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0); err != nil { - tun.core.log.Printf("Create AF_INET socket failed: %v.", err) + tun.log.Printf("Create AF_INET socket failed: %v.", err) return err } // Friendly output - tun.core.log.Infof("Interface name: %s", tun.iface.Name()) - tun.core.log.Infof("Interface IPv6: %s", addr) - tun.core.log.Infof("Interface MTU: %d", tun.mtu) + tun.log.Infof("Interface name: %s", tun.iface.Name()) + tun.log.Infof("Interface IPv6: %s", addr) + tun.log.Infof("Interface MTU: %d", tun.mtu) // Create the MTU request var ir in6_ifreq_mtu @@ -126,15 +126,15 @@ func (tun *TunAdapter) setupAddress(addr string) error { // Set the MTU if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(syscall.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 { err = errno - tun.core.log.Errorf("Error in SIOCSIFMTU: %v", errno) + tun.log.Errorf("Error in SIOCSIFMTU: %v", errno) // Fall back to ifconfig to set the MTU cmd := exec.Command("ifconfig", tun.iface.Name(), "mtu", string(tun.mtu)) - tun.core.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " ")) + tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.core.log.Errorf("SIOCSIFMTU fallback failed: %v.", err) - tun.core.log.Traceln(string(output)) + tun.log.Errorf("SIOCSIFMTU fallback failed: %v.", err) + tun.log.Traceln(string(output)) } } @@ -155,15 +155,15 @@ func (tun *TunAdapter) setupAddress(addr string) error { // Set the interface address if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(SIOCSIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 { err = errno - tun.core.log.Errorf("Error in SIOCSIFADDR_IN6: %v", errno) + tun.log.Errorf("Error in SIOCSIFADDR_IN6: %v", errno) // Fall back to ifconfig to set the address cmd := exec.Command("ifconfig", tun.iface.Name(), "inet6", addr) - tun.core.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " ")) + tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.core.log.Errorf("SIOCSIFADDR_IN6 fallback failed: %v.", err) - tun.core.log.Traceln(string(output)) + tun.log.Errorf("SIOCSIFADDR_IN6 fallback failed: %v.", err) + tun.log.Traceln(string(output)) } } diff --git a/src/tuntap/tun_linux.go b/src/tuntap/tun_linux.go index 051287ee..c9c03c09 100644 --- a/src/tuntap/tun_linux.go +++ b/src/tuntap/tun_linux.go @@ -40,9 +40,9 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int } } // Friendly output - tun.core.log.Infof("Interface name: %s", tun.iface.Name()) - tun.core.log.Infof("Interface IPv6: %s", addr) - tun.core.log.Infof("Interface MTU: %d", tun.mtu) + tun.log.Infof("Interface name: %s", tun.iface.Name()) + tun.log.Infof("Interface IPv6: %s", addr) + tun.log.Infof("Interface MTU: %d", tun.mtu) return tun.setupAddress(addr) } diff --git a/src/tuntap/tun_other.go b/src/tuntap/tun_other.go index 17dfa2b4..48276b49 100644 --- a/src/tuntap/tun_other.go +++ b/src/tuntap/tun_other.go @@ -28,6 +28,6 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int // We don't know how to set the IPv6 address on an unknown platform, therefore // write about it to stdout and don't try to do anything further. func (tun *TunAdapter) setupAddress(addr string) error { - tun.core.log.Warnln("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr) + tun.log.Warnln("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr) return nil } diff --git a/src/tuntap/tun_windows.go b/src/tuntap/tun_windows.go index f5cebe55..8a66ac62 100644 --- a/src/tuntap/tun_windows.go +++ b/src/tuntap/tun_windows.go @@ -15,7 +15,7 @@ import ( // delegate the hard work to "netsh". func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { if !iftapmode { - tun.core.log.Warnln("TUN mode is not supported on this platform, defaulting to TAP") + tun.log.Warnln("TUN mode is not supported on this platform, defaulting to TAP") } config := water.Config{DeviceType: water.TAP} config.PlatformSpecificParams.ComponentID = "tap0901" @@ -31,19 +31,19 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int } // Disable/enable the interface to resets its configuration (invalidating iface) cmd := exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=DISABLED") - tun.core.log.Printf("netsh command: %v", strings.Join(cmd.Args, " ")) + tun.log.Printf("netsh command: %v", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.core.log.Errorf("Windows netsh failed: %v.", err) - tun.core.log.Traceln(string(output)) + tun.log.Errorf("Windows netsh failed: %v.", err) + tun.log.Traceln(string(output)) return err } cmd = exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=ENABLED") - tun.core.log.Printf("netsh command: %v", strings.Join(cmd.Args, " ")) + tun.log.Printf("netsh command: %v", strings.Join(cmd.Args, " ")) output, err = cmd.CombinedOutput() if err != nil { - tun.core.log.Errorf("Windows netsh failed: %v.", err) - tun.core.log.Traceln(string(output)) + tun.log.Errorf("Windows netsh failed: %v.", err) + tun.log.Traceln(string(output)) return err } // Get a new iface @@ -58,9 +58,9 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int panic(err) } // Friendly output - tun.core.log.Infof("Interface name: %s", tun.iface.Name()) - tun.core.log.Infof("Interface IPv6: %s", addr) - tun.core.log.Infof("Interface MTU: %d", tun.mtu) + tun.log.Infof("Interface name: %s", tun.iface.Name()) + tun.log.Infof("Interface IPv6: %s", addr) + tun.log.Infof("Interface MTU: %d", tun.mtu) return tun.setupAddress(addr) } @@ -71,11 +71,11 @@ func (tun *TunAdapter) setupMTU(mtu int) error { fmt.Sprintf("interface=%s", tun.iface.Name()), fmt.Sprintf("mtu=%d", mtu), "store=active") - tun.core.log.Debugln("netsh command: %v", strings.Join(cmd.Args, " ")) + tun.log.Debugln("netsh command: %v", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.core.log.Errorf("Windows netsh failed: %v.", err) - tun.core.log.Traceln(string(output)) + tun.log.Errorf("Windows netsh failed: %v.", err) + tun.log.Traceln(string(output)) return err } return nil @@ -88,11 +88,11 @@ func (tun *TunAdapter) setupAddress(addr string) error { fmt.Sprintf("interface=%s", tun.iface.Name()), fmt.Sprintf("addr=%s", addr), "store=active") - tun.core.log.Debugln("netsh command: %v", strings.Join(cmd.Args, " ")) + tun.log.Debugln("netsh command: %v", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.core.log.Errorf("Windows netsh failed: %v.", err) - tun.core.log.Traceln(string(output)) + tun.log.Errorf("Windows netsh failed: %v.", err) + tun.log.Traceln(string(output)) return err } return nil From 7ea4e9575e611192ec4b81b51dfc13e89e200e74 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 28 Mar 2019 16:13:14 +0000 Subject: [PATCH 0006/1109] Break out multicast into a separate package --- cmd/yggdrasil/main.go | 11 +++- src/{yggdrasil => multicast}/multicast.go | 63 +++++++++++-------- .../multicast_darwin.go | 8 +-- .../multicast_other.go | 6 +- .../multicast_unix.go | 6 +- .../multicast_windows.go | 6 +- src/yggdrasil/admin.go | 4 +- src/yggdrasil/core.go | 30 ++++++--- src/yggdrasil/mobile.go | 4 +- src/yggdrasil/tcp.go | 36 +++++------ 10 files changed, 103 insertions(+), 71 deletions(-) rename src/{yggdrasil => multicast}/multicast.go (78%) rename src/{yggdrasil => multicast}/multicast_darwin.go (86%) rename src/{yggdrasil => multicast}/multicast_other.go (53%) rename src/{yggdrasil => multicast}/multicast_unix.go (75%) rename src/{yggdrasil => multicast}/multicast_windows.go (75%) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index cec77056..fe848497 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -19,6 +19,7 @@ import ( "github.com/mitchellh/mapstructure" "github.com/yggdrasil-network/yggdrasil-go/src/config" + "github.com/yggdrasil-network/yggdrasil-go/src/multicast" "github.com/yggdrasil-network/yggdrasil-go/src/tuntap" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) @@ -27,8 +28,9 @@ type nodeConfig = config.NodeConfig type Core = yggdrasil.Core type node struct { - core Core - tun tuntap.TunAdapter + core Core + tun tuntap.TunAdapter + multicast multicast.Multicast } func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *nodeConfig { @@ -254,6 +256,11 @@ func main() { logger.Errorln("An error occurred during startup") panic(err) } + // Start the multicast interface + n.multicast.Init(&n.core, cfg, logger) + if err := n.multicast.Start(); err != nil { + logger.Errorln("An error occurred starting multicast:", err) + } // The Stop function ensures that the TUN/TAP adapter is correctly shut down // before the program exits. defer func() { diff --git a/src/yggdrasil/multicast.go b/src/multicast/multicast.go similarity index 78% rename from src/yggdrasil/multicast.go rename to src/multicast/multicast.go index ab1b158e..71bdbc83 100644 --- a/src/yggdrasil/multicast.go +++ b/src/multicast/multicast.go @@ -1,4 +1,4 @@ -package yggdrasil +package multicast import ( "context" @@ -7,23 +7,31 @@ import ( "regexp" "time" + "github.com/gologme/log" + + "github.com/yggdrasil-network/yggdrasil-go/src/config" + "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" "golang.org/x/net/ipv6" ) -type multicast struct { - core *Core +type Multicast struct { + core *yggdrasil.Core + config *config.NodeConfig + log *log.Logger reconfigure chan chan error sock *ipv6.PacketConn groupAddr string - listeners map[string]*tcpListener + listeners map[string]*yggdrasil.TcpListener listenPort uint16 } -func (m *multicast) init(core *Core) { +func (m *Multicast) Init(core *yggdrasil.Core, config *config.NodeConfig, log *log.Logger) { m.core = core + m.config = config + m.log = log m.reconfigure = make(chan chan error, 1) - m.listeners = make(map[string]*tcpListener) - current, _ := m.core.config.Get() + m.listeners = make(map[string]*yggdrasil.TcpListener) + current := m.config //.Get() m.listenPort = current.LinkLocalTCPPort go func() { for { @@ -34,15 +42,15 @@ func (m *multicast) init(core *Core) { m.groupAddr = "[ff02::114]:9001" // Check if we've been given any expressions if count := len(m.interfaces()); count != 0 { - m.core.log.Infoln("Found", count, "multicast interface(s)") + m.log.Infoln("Found", count, "multicast interface(s)") } } -func (m *multicast) start() error { +func (m *Multicast) Start() error { if len(m.interfaces()) == 0 { - m.core.log.Infoln("Multicast discovery is disabled") + m.log.Infoln("Multicast discovery is disabled") } else { - m.core.log.Infoln("Multicast discovery is enabled") + m.log.Infoln("Multicast discovery is enabled") addr, err := net.ResolveUDPAddr("udp", m.groupAddr) if err != nil { return err @@ -67,9 +75,10 @@ func (m *multicast) start() error { return nil } -func (m *multicast) interfaces() map[string]net.Interface { +func (m *Multicast) interfaces() map[string]net.Interface { // Get interface expressions from config - current, _ := m.core.config.Get() + //current, _ := m.config.Get() + current := m.config exprs := current.MulticastInterfaces // Ask the system for network interfaces interfaces := make(map[string]net.Interface) @@ -106,7 +115,7 @@ func (m *multicast) interfaces() map[string]net.Interface { return interfaces } -func (m *multicast) announce() { +func (m *Multicast) announce() { groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr) if err != nil { panic(err) @@ -122,9 +131,9 @@ func (m *multicast) announce() { for name, listener := range m.listeners { // Prepare our stop function! stop := func() { - listener.stop <- true + listener.Stop <- true delete(m.listeners, name) - m.core.log.Debugln("No longer multicasting on", name) + m.log.Debugln("No longer multicasting on", name) } // If the interface is no longer visible on the system then stop the // listener, as another one will be started further down @@ -135,7 +144,7 @@ func (m *multicast) announce() { // It's possible that the link-local listener address has changed so if // that is the case then we should clean up the interface listener found := false - listenaddr, err := net.ResolveTCPAddr("tcp6", listener.listener.Addr().String()) + listenaddr, err := net.ResolveTCPAddr("tcp6", listener.Listener.Addr().String()) if err != nil { stop() continue @@ -184,17 +193,18 @@ func (m *multicast) announce() { // Join the multicast group m.sock.JoinGroup(&iface, groupAddr) // Try and see if we already have a TCP listener for this interface - var listener *tcpListener - if l, ok := m.listeners[iface.Name]; !ok || l.listener == nil { + var listener *yggdrasil.TcpListener + if l, ok := m.listeners[iface.Name]; !ok || l.Listener == nil { // No listener was found - let's create one listenaddr := fmt.Sprintf("[%s%%%s]:%d", addrIP, iface.Name, m.listenPort) - if li, err := m.core.link.tcp.listen(listenaddr); err == nil { - m.core.log.Debugln("Started multicasting on", iface.Name) + //if li, err := m.core.link.tcp.listen(listenaddr); err == nil { + if li, err := m.core.ListenTCP(listenaddr); err == nil { + m.log.Debugln("Started multicasting on", iface.Name) // Store the listener so that we can stop it later if needed m.listeners[iface.Name] = li listener = li } else { - m.core.log.Warnln("Not multicasting on", iface.Name, "due to error:", err) + m.log.Warnln("Not multicasting on", iface.Name, "due to error:", err) } } else { // An existing listener was found @@ -205,7 +215,7 @@ func (m *multicast) announce() { continue } // Get the listener details and construct the multicast beacon - lladdr := listener.listener.Addr().String() + lladdr := listener.Listener.Addr().String() if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil { a.Zone = "" destAddr.Zone = iface.Name @@ -219,7 +229,7 @@ func (m *multicast) announce() { } } -func (m *multicast) listen() { +func (m *Multicast) listen() { groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr) if err != nil { panic(err) @@ -251,8 +261,9 @@ func (m *multicast) listen() { continue } addr.Zone = "" - if err := m.core.link.call("tcp://"+addr.String(), from.Zone); err != nil { - m.core.log.Debugln("Call from multicast failed:", err) + //if err := m.core.link.call("tcp://"+addr.String(), from.Zone); err != nil { + if err := m.core.CallPeer("tcp://"+addr.String(), from.Zone); err != nil { + m.log.Debugln("Call from multicast failed:", err) } } } diff --git a/src/yggdrasil/multicast_darwin.go b/src/multicast/multicast_darwin.go similarity index 86% rename from src/yggdrasil/multicast_darwin.go rename to src/multicast/multicast_darwin.go index 53646113..900354c7 100644 --- a/src/yggdrasil/multicast_darwin.go +++ b/src/multicast/multicast_darwin.go @@ -1,6 +1,6 @@ // +build darwin -package yggdrasil +package multicast /* #cgo CFLAGS: -x objective-c @@ -31,11 +31,11 @@ import ( var awdlGoroutineStarted bool -func (m *multicast) multicastStarted() { +func (m *Multicast) multicastStarted() { if awdlGoroutineStarted { return } - m.core.log.Infoln("Multicast discovery will wake up AWDL if required") + m.log.Infoln("Multicast discovery will wake up AWDL if required") awdlGoroutineStarted = true for { C.StopAWDLBrowsing() @@ -49,7 +49,7 @@ func (m *multicast) multicastStarted() { } } -func (m *multicast) multicastReuse(network string, address string, c syscall.RawConn) error { +func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error { var control error var reuseport error var recvanyif error diff --git a/src/yggdrasil/multicast_other.go b/src/multicast/multicast_other.go similarity index 53% rename from src/yggdrasil/multicast_other.go rename to src/multicast/multicast_other.go index e20bbda3..16ea15d6 100644 --- a/src/yggdrasil/multicast_other.go +++ b/src/multicast/multicast_other.go @@ -1,13 +1,13 @@ // +build !linux,!darwin,!netbsd,!freebsd,!openbsd,!dragonflybsd,!windows -package yggdrasil +package multicast import "syscall" -func (m *multicast) multicastStarted() { +func (m *Multicast) multicastStarted() { } -func (m *multicast) multicastReuse(network string, address string, c syscall.RawConn) error { +func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error { return nil } diff --git a/src/yggdrasil/multicast_unix.go b/src/multicast/multicast_unix.go similarity index 75% rename from src/yggdrasil/multicast_unix.go rename to src/multicast/multicast_unix.go index 3da1ab4e..9d6a01a8 100644 --- a/src/yggdrasil/multicast_unix.go +++ b/src/multicast/multicast_unix.go @@ -1,15 +1,15 @@ // +build linux netbsd freebsd openbsd dragonflybsd -package yggdrasil +package multicast import "syscall" import "golang.org/x/sys/unix" -func (m *multicast) multicastStarted() { +func (m *Multicast) multicastStarted() { } -func (m *multicast) multicastReuse(network string, address string, c syscall.RawConn) error { +func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error { var control error var reuseport error diff --git a/src/yggdrasil/multicast_windows.go b/src/multicast/multicast_windows.go similarity index 75% rename from src/yggdrasil/multicast_windows.go rename to src/multicast/multicast_windows.go index 3e07f6cc..7a846b1e 100644 --- a/src/yggdrasil/multicast_windows.go +++ b/src/multicast/multicast_windows.go @@ -1,15 +1,15 @@ // +build windows -package yggdrasil +package multicast import "syscall" import "golang.org/x/sys/windows" -func (m *multicast) multicastStarted() { +func (m *Multicast) multicastStarted() { } -func (m *multicast) multicastReuse(network string, address string, c syscall.RawConn) error { +func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error { var control error var reuseaddr error diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 002f974c..2db7ad4c 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -209,13 +209,13 @@ func (a *admin) init(c *Core) { }, nil } })*/ - a.addHandler("getMulticastInterfaces", []string{}, func(in admin_info) (admin_info, error) { + /*a.addHandler("getMulticastInterfaces", []string{}, func(in admin_info) (admin_info, error) { var intfs []string for _, v := range a.core.multicast.interfaces() { intfs = append(intfs, v.Name) } return admin_info{"multicast_interfaces": intfs}, nil - }) + })*/ a.addHandler("getAllowedEncryptionPublicKeys", []string{}, func(in admin_info) (admin_info, error) { return admin_info{"allowed_box_pubs": a.getAllowedEncryptionPublicKeys()}, nil }) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index a583d582..461e8e93 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -40,9 +40,9 @@ type Core struct { dht dht admin admin searches searches - multicast multicast - link link - log *log.Logger + //multicast multicast + link link + log *log.Logger } func (c *Core) init() error { @@ -82,7 +82,7 @@ func (c *Core) init() error { c.searches.init(c) c.dht.init(c) c.sessions.init(c) - c.multicast.init(c) + //c.multicast.init(c) c.peers.init(c) c.router.init(c) c.switchTable.init(c) // TODO move before peers? before router? @@ -137,7 +137,7 @@ func (c *Core) UpdateConfig(config *config.NodeConfig) { c.router.cryptokey.reconfigure, c.switchTable.reconfigure, c.link.reconfigure, - c.multicast.reconfigure, + //c.multicast.reconfigure, } for _, component := range components { @@ -228,10 +228,10 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { return err } - if err := c.multicast.start(); err != nil { + /*if err := c.multicast.start(); err != nil { c.log.Errorln("Failed to start multicast interface") return err - } + }*/ if err := c.router.tun.Start(c.router.addr, c.router.subnet); err != nil { c.log.Errorln("Failed to start TUN/TAP") @@ -251,6 +251,11 @@ func (c *Core) Stop() { c.admin.close() } +// ListenOn starts a new listener +func (c *Core) ListenTCP(uri string) (*TcpListener, error) { + return c.link.tcp.listen(uri) +} + // Generates a new encryption keypair. The encryption keys are used to // encrypt traffic and to derive the IPv6 address/subnet of the node. func (c *Core) NewEncryptionKeys() (*crypto.BoxPubKey, *crypto.BoxPrivKey) { @@ -303,11 +308,20 @@ func (c *Core) SetLogger(log *log.Logger) { } // Adds a peer. This should be specified in the peer URI format, i.e. -// tcp://a.b.c.d:e, udp://a.b.c.d:e, socks://a.b.c.d:e/f.g.h.i:j +// tcp://a.b.c.d:e, udp://a.b.c.d:e, socks://a.b.c.d:e/f.g.h.i:j. This adds the +// peer to the peer list, so that they will be called again if the connection +// drops. func (c *Core) AddPeer(addr string, sintf string) error { return c.admin.addPeer(addr, sintf) } +// Calls a peer. This should be specified in the peer URI format, i.e. +// tcp://a.b.c.d:e, udp://a.b.c.d:e, socks://a.b.c.d:e/f.g.h.i:j. This calls the +// peer once, and if the connection drops, it won't be called again. +func (c *Core) CallPeer(addr string, sintf string) error { + return c.link.call(addr, sintf) +} + // Adds an allowed public key. This allow peerings to be restricted only to // keys that you have selected. func (c *Core) AddAllowedEncryptionPublicKey(boxStr string) error { diff --git a/src/yggdrasil/mobile.go b/src/yggdrasil/mobile.go index 81aa47f1..bad1424a 100644 --- a/src/yggdrasil/mobile.go +++ b/src/yggdrasil/mobile.go @@ -115,7 +115,7 @@ func (c *Core) GetSigPubKeyString() string { // dummy adapter in place of real TUN - when this call returns a packet, you // will probably want to give it to the OS to write to TUN. func (c *Core) RouterRecvPacket() ([]byte, error) { - packet := <-c.router.tun.recv + packet := <-c.router.tun.Recv return packet, nil } @@ -125,6 +125,6 @@ func (c *Core) RouterRecvPacket() ([]byte, error) { // Yggdrasil. func (c *Core) RouterSendPacket(buf []byte) error { packet := append(util.GetBytes(), buf[:]...) - c.router.tun.send <- packet + c.router.tun.Send <- packet return nil } diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index f25ac412..b9681fc2 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -36,14 +36,14 @@ type tcp struct { link *link reconfigure chan chan error mutex sync.Mutex // Protecting the below - listeners map[string]*tcpListener + listeners map[string]*TcpListener calls map[string]struct{} conns map[linkInfo](chan struct{}) } -type tcpListener struct { - listener net.Listener - stop chan bool +type TcpListener struct { + Listener net.Listener + Stop chan bool } // Wrapper function to set additional options for specific connection types. @@ -64,7 +64,7 @@ func (t *tcp) getAddr() *net.TCPAddr { t.mutex.Lock() defer t.mutex.Unlock() for _, l := range t.listeners { - return l.listener.Addr().(*net.TCPAddr) + return l.Listener.Addr().(*net.TCPAddr) } return nil } @@ -76,7 +76,7 @@ func (t *tcp) init(l *link) error { t.mutex.Lock() t.calls = make(map[string]struct{}) t.conns = make(map[linkInfo](chan struct{})) - t.listeners = make(map[string]*tcpListener) + t.listeners = make(map[string]*TcpListener) t.mutex.Unlock() go func() { @@ -103,7 +103,7 @@ func (t *tcp) init(l *link) error { t.mutex.Lock() if listener, ok := t.listeners[d[6:]]; ok { t.mutex.Unlock() - listener.stop <- true + listener.Stop <- true } else { t.mutex.Unlock() } @@ -129,7 +129,7 @@ func (t *tcp) init(l *link) error { return nil } -func (t *tcp) listen(listenaddr string) (*tcpListener, error) { +func (t *tcp) listen(listenaddr string) (*TcpListener, error) { var err error ctx := context.Background() @@ -138,9 +138,9 @@ func (t *tcp) listen(listenaddr string) (*tcpListener, error) { } listener, err := lc.Listen(ctx, "tcp", listenaddr) if err == nil { - l := tcpListener{ - listener: listener, - stop: make(chan bool), + l := TcpListener{ + Listener: listener, + Stop: make(chan bool), } go t.listener(&l, listenaddr) return &l, nil @@ -150,7 +150,7 @@ func (t *tcp) listen(listenaddr string) (*tcpListener, error) { } // Runs the listener, which spawns off goroutines for incoming connections. -func (t *tcp) listener(l *tcpListener, listenaddr string) { +func (t *tcp) listener(l *TcpListener, listenaddr string) { if l == nil { return } @@ -158,7 +158,7 @@ func (t *tcp) listener(l *tcpListener, listenaddr string) { t.mutex.Lock() if _, isIn := t.listeners[listenaddr]; isIn { t.mutex.Unlock() - l.listener.Close() + l.Listener.Close() return } else { t.listeners[listenaddr] = l @@ -167,20 +167,20 @@ func (t *tcp) listener(l *tcpListener, listenaddr string) { // And here we go! accepted := make(chan bool) defer func() { - t.link.core.log.Infoln("Stopping TCP listener on:", l.listener.Addr().String()) - l.listener.Close() + t.link.core.log.Infoln("Stopping TCP listener on:", l.Listener.Addr().String()) + l.Listener.Close() t.mutex.Lock() delete(t.listeners, listenaddr) t.mutex.Unlock() }() - t.link.core.log.Infoln("Listening for TCP on:", l.listener.Addr().String()) + t.link.core.log.Infoln("Listening for TCP on:", l.Listener.Addr().String()) for { var sock net.Conn var err error // Listen in a separate goroutine, as that way it does not block us from // receiving "stop" events go func() { - sock, err = l.listener.Accept() + sock, err = l.Listener.Accept() accepted <- true }() // Wait for either an accepted connection, or a message telling us to stop @@ -192,7 +192,7 @@ func (t *tcp) listener(l *tcpListener, listenaddr string) { return } go t.handler(sock, true, nil) - case <-l.stop: + case <-l.Stop: return } } From fd0b614f9c0780e0b85a2ae77174050524ab3d61 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 28 Mar 2019 18:03:14 +0000 Subject: [PATCH 0007/1109] Temporarily disable debug CircleCI builds as I don't know how badly I've broken the sim with this PR --- .circleci/config.yml | 10 +++++----- src/yggdrasil/core.go | 12 ++---------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 99088d1c..9cf7d95b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -24,11 +24,11 @@ jobs: command: | sudo apt-get install -y alien - - run: - name: Test debug builds - command: | - ./build -d - test -f yggdrasil && test -f yggdrasilctl + # - run: + # name: Test debug builds + # command: | + # ./build -d + # test -f yggdrasil && test -f yggdrasilctl - run: name: Build for Linux (including Debian packages and RPMs) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 461e8e93..5d45a489 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -40,9 +40,8 @@ type Core struct { dht dht admin admin searches searches - //multicast multicast - link link - log *log.Logger + link link + log *log.Logger } func (c *Core) init() error { @@ -133,11 +132,9 @@ func (c *Core) UpdateConfig(config *config.NodeConfig) { c.sessions.reconfigure, c.peers.reconfigure, c.router.reconfigure, - //c.router.tun.Reconfigure, c.router.cryptokey.reconfigure, c.switchTable.reconfigure, c.link.reconfigure, - //c.multicast.reconfigure, } for _, component := range components { @@ -228,11 +225,6 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { return err } - /*if err := c.multicast.start(); err != nil { - c.log.Errorln("Failed to start multicast interface") - return err - }*/ - if err := c.router.tun.Start(c.router.addr, c.router.subnet); err != nil { c.log.Errorln("Failed to start TUN/TAP") return err From dd05a7f2a816df8eecc5f50950097908f28b9d69 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 28 Mar 2019 19:09:19 +0000 Subject: [PATCH 0008/1109] Tweaks --- cmd/yggdrasil/main.go | 9 +++++---- src/multicast/multicast.go | 18 ++++++++++-------- src/yggdrasil/core.go | 35 ++++++++++++++++++----------------- src/yggdrasil/router.go | 30 +++++++++++++++--------------- src/yggdrasil/session.go | 2 +- 5 files changed, 49 insertions(+), 45 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index fe848497..72f02840 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -29,7 +29,7 @@ type Core = yggdrasil.Core type node struct { core Core - tun tuntap.TunAdapter + tuntap tuntap.TunAdapter multicast multicast.Multicast } @@ -251,13 +251,14 @@ func main() { // Now that we have a working configuration, we can now actually start // Yggdrasil. This will start the router, switch, DHT node, TCP and UDP // sockets, TUN/TAP adapter and multicast discovery port. - n.core.SetRouterAdapter(&n.tun) - if err := n.core.Start(cfg, logger); err != nil { + n.core.SetRouterAdapter(&n.tuntap) + state, err := n.core.Start(cfg, logger) + if err != nil { logger.Errorln("An error occurred during startup") panic(err) } // Start the multicast interface - n.multicast.Init(&n.core, cfg, logger) + n.multicast.Init(&n.core, state, logger, nil) if err := n.multicast.Start(); err != nil { logger.Errorln("An error occurred starting multicast:", err) } diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 71bdbc83..c6756808 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -16,7 +16,7 @@ import ( type Multicast struct { core *yggdrasil.Core - config *config.NodeConfig + config *config.NodeState log *log.Logger reconfigure chan chan error sock *ipv6.PacketConn @@ -25,13 +25,13 @@ type Multicast struct { listenPort uint16 } -func (m *Multicast) Init(core *yggdrasil.Core, config *config.NodeConfig, log *log.Logger) { +func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) error { m.core = core - m.config = config + m.config = state m.log = log m.reconfigure = make(chan chan error, 1) m.listeners = make(map[string]*yggdrasil.TcpListener) - current := m.config //.Get() + current, _ := m.config.Get() m.listenPort = current.LinkLocalTCPPort go func() { for { @@ -44,6 +44,7 @@ func (m *Multicast) Init(core *yggdrasil.Core, config *config.NodeConfig, log *l if count := len(m.interfaces()); count != 0 { m.log.Infoln("Found", count, "multicast interface(s)") } + return nil } func (m *Multicast) Start() error { @@ -75,10 +76,13 @@ func (m *Multicast) Start() error { return nil } +func (m *Multicast) Stop() error { + return nil +} + func (m *Multicast) interfaces() map[string]net.Interface { // Get interface expressions from config - //current, _ := m.config.Get() - current := m.config + current, _ := m.config.Get() exprs := current.MulticastInterfaces // Ask the system for network interfaces interfaces := make(map[string]net.Interface) @@ -197,7 +201,6 @@ func (m *Multicast) announce() { if l, ok := m.listeners[iface.Name]; !ok || l.Listener == nil { // No listener was found - let's create one listenaddr := fmt.Sprintf("[%s%%%s]:%d", addrIP, iface.Name, m.listenPort) - //if li, err := m.core.link.tcp.listen(listenaddr); err == nil { if li, err := m.core.ListenTCP(listenaddr); err == nil { m.log.Debugln("Started multicasting on", iface.Name) // Store the listener so that we can stop it later if needed @@ -261,7 +264,6 @@ func (m *Multicast) listen() { continue } addr.Zone = "" - //if err := m.core.link.call("tcp://"+addr.String(), from.Zone); err != nil { if err := m.core.CallPeer("tcp://"+addr.String(), from.Zone); err != nil { m.log.Debugln("Call from multicast failed:", err) } diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 5d45a489..dbc893a8 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -17,11 +17,6 @@ import ( var buildName string var buildVersion string -type module interface { - init(*Core, *config.NodeConfig) error - start() error -} - // The Core object represents the Yggdrasil node. You should create a Core // object for each Yggdrasil node you plan to run. type Core struct { @@ -173,14 +168,14 @@ func GetBuildVersion() string { // Set the router adapter func (c *Core) SetRouterAdapter(adapter adapterImplementation) { - c.router.tun = adapter + c.router.adapter = adapter } // Starts up Yggdrasil using the provided NodeState, and outputs debug logging // through the provided log.Logger. The started stack will include TCP and UDP // sockets, a multicast discovery socket, an admin socket, router, switch and // DHT node. -func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { +func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, error) { c.log = log c.config = config.NodeState{ @@ -201,7 +196,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { if err := c.link.init(c); err != nil { c.log.Errorln("Failed to start link interfaces") - return err + return nil, err } c.config.Mutex.RLock() @@ -212,34 +207,34 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { if err := c.switchTable.start(); err != nil { c.log.Errorln("Failed to start switch") - return err + return nil, err } if err := c.router.start(); err != nil { c.log.Errorln("Failed to start router") - return err + return nil, err } if err := c.admin.start(); err != nil { c.log.Errorln("Failed to start admin socket") - return err + return nil, err } - if err := c.router.tun.Start(c.router.addr, c.router.subnet); err != nil { + if err := c.router.adapter.Start(c.router.addr, c.router.subnet); err != nil { c.log.Errorln("Failed to start TUN/TAP") - return err + return nil, err } go c.addPeerLoop() c.log.Infoln("Startup complete") - return nil + return &c.config, nil } // Stops the Yggdrasil node. func (c *Core) Stop() { c.log.Infoln("Stopping...") - c.router.tun.Close() + c.router.adapter.Close() c.admin.close() } @@ -283,6 +278,12 @@ func (c *Core) GetSubnet() *net.IPNet { return &net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)} } +// GetRouterAddresses returns the raw address and subnet types as used by the +// router +func (c *Core) GetRouterAddresses() (address.Address, address.Subnet) { + return c.router.addr, c.router.subnet +} + // Gets the nodeinfo. func (c *Core) GetNodeInfo() nodeinfoPayload { return c.router.nodeinfo.getNodeInfo() @@ -350,11 +351,11 @@ func (c *Core) GetTUNDefaultIfTAPMode() bool { // Gets the current TUN/TAP interface name. func (c *Core) GetTUNIfName() string { //return c.router.tun.iface.Name() - return c.router.tun.Name() + return c.router.adapter.Name() } // Gets the current TUN/TAP interface MTU. func (c *Core) GetTUNIfMTU() int { //return c.router.tun.mtu - return c.router.tun.MTU() + return c.router.adapter.MTU() } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index aa2cd54b..6314cb12 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -5,7 +5,7 @@ package yggdrasil // TODO clean up old/unused code, maybe improve comments on whatever is left // Send: -// Receive a packet from the tun +// Receive a packet from the adapter // Look up session (if none exists, trigger a search) // Hand off to session (which encrypts, etc) // Session will pass it back to router.out, which hands it off to the self peer @@ -20,7 +20,7 @@ package yggdrasil // If it's dht/seach/etc. traffic, the router passes it to that part // If it's an encapsulated IPv6 packet, the router looks up the session for it // The packet is passed to the session, which decrypts it, router.recvPacket -// The router then runs some sanity checks before passing it to the tun +// The router then runs some sanity checks before passing it to the adapter import ( "bytes" @@ -31,7 +31,7 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/util" ) -// The router struct has channels to/from the tun/tap device and a self peer (0), which is how messages are passed between this node and the peers/switch layer. +// The router struct has channels to/from the adapter device and a self peer (0), which is how messages are passed between this node and the peers/switch layer. // The router's mainLoop goroutine is responsible for managing all information related to the dht, searches, and crypto sessions. type router struct { core *Core @@ -41,17 +41,17 @@ type router struct { in <-chan []byte // packets we received from the network, link to peer's "out" out func([]byte) // packets we're sending to the network, link to peer's "in" toRecv chan router_recvPacket // packets to handle via recvPacket() - tun adapterImplementation // TUN/TAP adapter - recv chan<- []byte // place where the tun pulls received packets from - send <-chan []byte // place where the tun puts outgoing packets - reject chan<- RejectedPacket // place where we send error packets back to tun + adapter adapterImplementation // TUN/TAP adapter + recv chan<- []byte // place where the adapter pulls received packets from + send <-chan []byte // place where the adapter puts outgoing packets + reject chan<- RejectedPacket // place where we send error packets back to adapter reset chan struct{} // signal that coords changed (re-init sessions/dht) admin chan func() // pass a lambda for the admin socket to query stuff cryptokey cryptokey nodeinfo nodeinfo } -// Packet and session info, used to check that the packet matches a valid IP range or CKR prefix before sending to the tun. +// Packet and session info, used to check that the packet matches a valid IP range or CKR prefix before sending to the adapter. type router_recvPacket struct { bs []byte sinfo *sessionInfo @@ -70,7 +70,7 @@ type RejectedPacket struct { Detail interface{} } -// Initializes the router struct, which includes setting up channels to/from the tun/tap. +// Initializes the router struct, which includes setting up channels to/from the adapter. func (r *router) init(core *Core) { r.core = core r.reconfigure = make(chan chan error, 1) @@ -128,7 +128,7 @@ func (r *router) init(core *Core) { r.nodeinfo.setNodeInfo(r.core.config.Current.NodeInfo, r.core.config.Current.NodeInfoPrivacy) r.core.config.Mutex.RUnlock() r.cryptokey.init(r.core) - r.tun.Init(&r.core.config, r.core.log, send, recv, reject) + r.adapter.Init(&r.core.config, r.core.log, send, recv, reject) } // Starts the mainLoop goroutine. @@ -138,7 +138,7 @@ func (r *router) start() error { return nil } -// Takes traffic from the tun/tap and passes it to router.send, or from r.in and handles incoming traffic. +// Takes traffic from the adapter and passes it to router.send, or from r.in and handles incoming traffic. // Also adds new peer info to the DHT. // Also resets the DHT and sesssions in the event of a coord change. // Also does periodic maintenance stuff. @@ -179,7 +179,7 @@ func (r *router) mainLoop() { // If a session to the destination exists, gets the session and passes the packet to it. // If no session exists, it triggers (or continues) a search. // If the session hasn't responded recently, it triggers a ping or search to keep things alive or deal with broken coords *relatively* quickly. -// It also deals with oversized packets if there are MTU issues by calling into icmpv6.go to spoof PacketTooBig traffic, or DestinationUnreachable if the other side has their tun/tap disabled. +// It also deals with oversized packets if there are MTU issues by calling into icmpv6.go to spoof PacketTooBig traffic, or DestinationUnreachable if the other side has their adapter disabled. func (r *router) sendPacket(bs []byte) { var sourceAddr address.Address var destAddr address.Address @@ -339,7 +339,7 @@ func (r *router) sendPacket(bs []byte) { } // Called for incoming traffic by the session worker for that connection. -// Checks that the IP address is correct (matches the session) and passes the packet to the tun/tap. +// Checks that the IP address is correct (matches the session) and passes the packet to the adapter. func (r *router) recvPacket(bs []byte, sinfo *sessionInfo) { // Note: called directly by the session worker, not the router goroutine if len(bs) < 24 { @@ -402,7 +402,7 @@ func (r *router) handleIn(packet []byte) { } // Handles incoming traffic, i.e. encapuslated ordinary IPv6 packets. -// Passes them to the crypto session worker to be decrypted and sent to the tun/tap. +// Passes them to the crypto session worker to be decrypted and sent to the adapter. func (r *router) handleTraffic(packet []byte) { defer util.PutBytes(packet) p := wire_trafficPacket{} @@ -436,7 +436,7 @@ func (r *router) handleProto(packet []byte) { return } // Now do something with the bytes in bs... - // send dht messages to dht, sessionRefresh to sessions, data to tun... + // send dht messages to dht, sessionRefresh to sessions, data to adapter... // For data, should check that key and IP match... bsType, bsTypeLen := wire_decode_uint64(bs) if bsTypeLen == 0 { diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index b3563e05..8deff953 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -277,7 +277,7 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.mySesPriv = *priv sinfo.myNonce = *crypto.NewBoxNonce() sinfo.theirMTU = 1280 - sinfo.myMTU = uint16(ss.core.router.tun.MTU()) + sinfo.myMTU = uint16(ss.core.router.adapter.MTU()) now := time.Now() sinfo.time = now sinfo.mtuTime = now From a8305210786cce91bfca5b61214f5b6a173c620c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 29 Mar 2019 08:38:09 +0000 Subject: [PATCH 0009/1109] Don't crash if Yggdrasil is started with no router adapter --- cmd/yggdrasil/main.go | 6 +++--- src/yggdrasil/core.go | 12 ++++++++---- src/yggdrasil/router.go | 4 +++- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 72f02840..9278867b 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -248,10 +248,10 @@ func main() { // Setup the Yggdrasil node itself. The node{} type includes a Core, so we // don't need to create this manually. n := node{} - // Now that we have a working configuration, we can now actually start - // Yggdrasil. This will start the router, switch, DHT node, TCP and UDP - // sockets, TUN/TAP adapter and multicast discovery port. + // Before we start the node, set the TUN/TAP to be our router adapter n.core.SetRouterAdapter(&n.tuntap) + // Now start Yggdrasil - this starts the DHT, router, switch and other core + // components needed for Yggdrasil to operate state, err := n.core.Start(cfg, logger) if err != nil { logger.Errorln("An error occurred during startup") diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index dbc893a8..0e9b251e 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -220,9 +220,11 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, return nil, err } - if err := c.router.adapter.Start(c.router.addr, c.router.subnet); err != nil { - c.log.Errorln("Failed to start TUN/TAP") - return nil, err + if c.router.adapter != nil { + if err := c.router.adapter.Start(c.router.addr, c.router.subnet); err != nil { + c.log.Errorln("Failed to start TUN/TAP") + return nil, err + } } go c.addPeerLoop() @@ -234,7 +236,9 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, // Stops the Yggdrasil node. func (c *Core) Stop() { c.log.Infoln("Stopping...") - c.router.adapter.Close() + if c.router.adapter != nil { + c.router.adapter.Close() + } c.admin.close() } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 6314cb12..bef564fd 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -128,7 +128,9 @@ func (r *router) init(core *Core) { r.nodeinfo.setNodeInfo(r.core.config.Current.NodeInfo, r.core.config.Current.NodeInfoPrivacy) r.core.config.Mutex.RUnlock() r.cryptokey.init(r.core) - r.adapter.Init(&r.core.config, r.core.log, send, recv, reject) + if r.adapter != nil { + r.adapter.Init(&r.core.config, r.core.log, send, recv, reject) + } } // Starts the mainLoop goroutine. From 399e1a2ffe1a375f64517152c336f6e54b9cb945 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 29 Mar 2019 08:58:30 +0000 Subject: [PATCH 0010/1109] Make AddPeer remember added peer (as opposed to CallPeer which does not) --- src/yggdrasil/core.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 0e9b251e..523ab5e4 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -309,7 +309,17 @@ func (c *Core) SetLogger(log *log.Logger) { // peer to the peer list, so that they will be called again if the connection // drops. func (c *Core) AddPeer(addr string, sintf string) error { - return c.admin.addPeer(addr, sintf) + if err := c.CallPeer(addr, sintf); err != nil { + return err + } + c.config.Mutex.Lock() + if sintf == "" { + c.config.Current.Peers = append(c.config.Current.Peers, addr) + } else { + c.config.Current.InterfacePeers[sintf] = append(c.config.Current.InterfacePeers[sintf], addr) + } + c.config.Mutex.Unlock() + return nil } // Calls a peer. This should be specified in the peer URI format, i.e. From b5ac65cacbdc3e50d5059936af106e6634bdd963 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 29 Mar 2019 18:05:17 +0000 Subject: [PATCH 0011/1109] Rearrange public interface, godoc improvements --- src/tuntap/tun.go | 22 ++++++ src/yggdrasil/adapter.go | 18 +++-- src/yggdrasil/core.go | 154 ++++++++++++++++----------------------- src/yggdrasil/router.go | 2 +- 4 files changed, 99 insertions(+), 97 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index f85d8bb2..5a247bff 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -65,6 +65,28 @@ func (tun *TunAdapter) IsTAP() bool { return tun.iface.IsTAP() } +// Gets the default TUN/TAP interface name for your platform. +func DefaultName() string { + return defaults.GetDefaults().DefaultIfName +} + +// Gets the default TUN/TAP interface MTU for your platform. This can be as high +// as 65535, depending on platform, but is never lower than 1280. +func DefaultMTU() int { + return defaults.GetDefaults().DefaultIfMTU +} + +// Gets the default TUN/TAP interface mode for your platform. +func DefaultIsTAP() bool { + return defaults.GetDefaults().DefaultIfTAPMode +} + +// 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 MaximumMTU() int { + return defaults.GetDefaults().MaximumIfMTU +} + // Initialises the TUN/TAP adapter. func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, send chan<- []byte, recv <-chan []byte, reject <-chan yggdrasil.RejectedPacket) { tun.config = config diff --git a/src/yggdrasil/adapter.go b/src/yggdrasil/adapter.go index 6be3ef00..d373894f 100644 --- a/src/yggdrasil/adapter.go +++ b/src/yggdrasil/adapter.go @@ -6,10 +6,12 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/config" ) -// Defines the minimum required struct members for an adapter type (this is -// now the base type for TunAdapter in tun.go) +// Defines the minimum required struct members for an adapter type. This is now +// the base type for adapters like tun.go. When implementing a new adapter type, +// you should extend the adapter struct with this one and should call the +// Adapter.Init() function when initialising. type Adapter struct { - adapterImplementation + AdapterImplementation Core *Core Send chan<- []byte Recv <-chan []byte @@ -17,8 +19,9 @@ type Adapter struct { Reconfigure chan chan error } -// Defines the minimum required functions for an adapter type -type adapterImplementation interface { +// Defines the minimum required functions for an adapter type. Note that the +// implementation of Init() should call Adapter.Init(). +type AdapterImplementation interface { Init(*config.NodeState, *log.Logger, chan<- []byte, <-chan []byte, <-chan RejectedPacket) Name() string MTU() int @@ -29,7 +32,10 @@ type adapterImplementation interface { Close() error } -// Initialises the adapter. +// Initialises the adapter with the necessary channels to operate from the +// router. When defining a new Adapter type, the Adapter should call this +// function from within it's own Init function to set up the channels. It is +// otherwise not expected for you to call this function directly. func (adapter *Adapter) Init(config *config.NodeState, log *log.Logger, send chan<- []byte, recv <-chan []byte, reject <-chan RejectedPacket) { log.Traceln("Adapter setup - given channels:", send, recv) adapter.Send = send diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 523ab5e4..8444050d 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -11,7 +11,6 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" - "github.com/yggdrasil-network/yggdrasil-go/src/defaults" ) var buildName string @@ -89,7 +88,7 @@ func (c *Core) init() error { // be reconnected with. func (c *Core) addPeerLoop() { for { - // Get the peers from the config - these could change! + // the peers from the config - these could change! current, _ := c.config.Get() // Add peers from the Peers section @@ -111,8 +110,9 @@ func (c *Core) addPeerLoop() { } } -// UpdateConfig updates the configuration in Core and then signals the -// various module goroutines to reconfigure themselves if needed +// UpdateConfig updates the configuration in Core with the provided +// config.NodeConfig and then signals the various module goroutines to +// reconfigure themselves if needed. func (c *Core) UpdateConfig(config *config.NodeConfig) { c.log.Infoln("Reloading configuration...") @@ -148,33 +148,37 @@ func (c *Core) UpdateConfig(config *config.NodeConfig) { } } -// GetBuildName gets the current build name. This is usually injected if built +// BuildName gets the current build name. This is usually injected if built // from git, or returns "unknown" otherwise. -func GetBuildName() string { +func BuildName() string { if buildName == "" { return "unknown" } return buildName } -// Get the current build version. This is usually injected if built from git, -// or returns "unknown" otherwise. -func GetBuildVersion() string { +// BuildVersion gets the current build version. This is usually injected if +// built from git, or returns "unknown" otherwise. +func BuildVersion() string { if buildVersion == "" { return "unknown" } return buildVersion } -// Set the router adapter -func (c *Core) SetRouterAdapter(adapter adapterImplementation) { +// SetRouterAdapter instructs Yggdrasil to use the given adapter when starting +// the router. The adapter must implement the standard +// adapter.AdapterImplementation interface and should extend the adapter.Adapter +// struct. +func (c *Core) SetRouterAdapter(adapter AdapterImplementation) { c.router.adapter = adapter } -// Starts up Yggdrasil using the provided NodeState, and outputs debug logging -// through the provided log.Logger. The started stack will include TCP and UDP -// sockets, a multicast discovery socket, an admin socket, router, switch and -// DHT node. +// Start starts up Yggdrasil using the provided config.NodeConfig, and outputs +// debug logging through the provided log.Logger. The started stack will include +// TCP and UDP sockets, a multicast discovery socket, an admin socket, router, +// switch and DHT node. A config.NodeState is returned which contains both the +// current and previous configurations (from reconfigures). func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, error) { c.log = log @@ -183,10 +187,10 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, Previous: *nc, } - if name := GetBuildName(); name != "unknown" { + if name := BuildName(); name != "unknown" { c.log.Infoln("Build name:", name) } - if version := GetBuildVersion(); version != "unknown" { + if version := BuildVersion(); version != "unknown" { c.log.Infoln("Build version:", version) } @@ -233,7 +237,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, return &c.config, nil } -// Stops the Yggdrasil node. +// Stop shuts down the Yggdrasil node. func (c *Core) Stop() { c.log.Infoln("Stopping...") if c.router.adapter != nil { @@ -242,72 +246,78 @@ func (c *Core) Stop() { c.admin.close() } -// ListenOn starts a new listener +// ListenTCP starts a new TCP listener. The input URI should match that of the +// "Listen" configuration item, e.g. +// tcp://a.b.c.d:e func (c *Core) ListenTCP(uri string) (*TcpListener, error) { return c.link.tcp.listen(uri) } -// Generates a new encryption keypair. The encryption keys are used to -// encrypt traffic and to derive the IPv6 address/subnet of the node. +// NewEncryptionKeys generates a new encryption keypair. The encryption keys are +// used to encrypt traffic and to derive the IPv6 address/subnet of the node. func (c *Core) NewEncryptionKeys() (*crypto.BoxPubKey, *crypto.BoxPrivKey) { return crypto.NewBoxKeys() } -// Generates a new signing keypair. The signing keys are used to derive the -// structure of the spanning tree. +// NewSigningKeys generates a new signing keypair. The signing keys are used to +// derive the structure of the spanning tree. func (c *Core) NewSigningKeys() (*crypto.SigPubKey, *crypto.SigPrivKey) { return crypto.NewSigKeys() } -// Gets the node ID. -func (c *Core) GetNodeID() *crypto.NodeID { - return crypto.GetNodeID(&c.boxPub) +// NodeID gets the node ID. +func (c *Core) NodeID() *crypto.NodeID { + return crypto.NodeID(&c.boxPub) } -// Gets the tree ID. -func (c *Core) GetTreeID() *crypto.TreeID { - return crypto.GetTreeID(&c.sigPub) +// TreeID gets the tree ID. +func (c *Core) TreeID() *crypto.TreeID { + return crypto.TreeID(&c.sigPub) } -// Gets the IPv6 address of the Yggdrasil node. This is always a /128. -func (c *Core) GetAddress() *net.IP { - address := net.IP(address.AddrForNodeID(c.GetNodeID())[:]) +// Address gets the IPv6 address of the Yggdrasil node. This is always a /128 +// address. +func (c *Core) Address() *net.IP { + address := net.IP(address.AddrForNodeID(c.NodeID())[:]) return &address } -// Gets the routed IPv6 subnet of the Yggdrasil node. This is always a /64. -func (c *Core) GetSubnet() *net.IPNet { - subnet := address.SubnetForNodeID(c.GetNodeID())[:] +// Subnet gets the routed IPv6 subnet of the Yggdrasil node. This is always a +// /64 subnet. +func (c *Core) Subnet() *net.IPNet { + subnet := address.SubnetForNodeID(c.NodeID())[:] subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0) return &net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)} } -// GetRouterAddresses returns the raw address and subnet types as used by the +// RouterAddresses returns the raw address and subnet types as used by the // router -func (c *Core) GetRouterAddresses() (address.Address, address.Subnet) { +func (c *Core) RouterAddresses() (address.Address, address.Subnet) { return c.router.addr, c.router.subnet } -// Gets the nodeinfo. -func (c *Core) GetNodeInfo() nodeinfoPayload { +// NodeInfo gets the currently configured nodeinfo. +func (c *Core) NodeInfo() nodeinfoPayload { return c.router.nodeinfo.getNodeInfo() } -// Sets the nodeinfo. +// SetNodeInfo the lcal nodeinfo. Note that nodeinfo can be any value or struct, +// it will be serialised into JSON automatically. func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) { c.router.nodeinfo.setNodeInfo(nodeinfo, nodeinfoprivacy) } -// Sets the output logger of the Yggdrasil node after startup. This may be -// useful if you want to redirect the output later. +// SetLogger sets the output logger of the Yggdrasil node after startup. This +// may be useful if you want to redirect the output later. func (c *Core) SetLogger(log *log.Logger) { c.log = log } -// Adds a peer. This should be specified in the peer URI format, i.e. -// tcp://a.b.c.d:e, udp://a.b.c.d:e, socks://a.b.c.d:e/f.g.h.i:j. This adds the -// peer to the peer list, so that they will be called again if the connection -// drops. +// AddPeer adds a peer. This should be specified in the peer URI format, e.g.: +// tcp://a.b.c.d:e +// socks://a.b.c.d:e/f.g.h.i:j +// This adds the peer to the peer list, so that they will be called again if the +// connection drops. func (c *Core) AddPeer(addr string, sintf string) error { if err := c.CallPeer(addr, sintf); err != nil { return err @@ -322,54 +332,18 @@ func (c *Core) AddPeer(addr string, sintf string) error { return nil } -// Calls a peer. This should be specified in the peer URI format, i.e. -// tcp://a.b.c.d:e, udp://a.b.c.d:e, socks://a.b.c.d:e/f.g.h.i:j. This calls the -// peer once, and if the connection drops, it won't be called again. +// CallPeer calls a peer once. This should be specified in the peer URI format, +// e.g.: +// tcp://a.b.c.d:e +// socks://a.b.c.d:e/f.g.h.i:j +// This does not add the peer to the peer list, so if the connection drops, the +// peer will not be called again automatically. func (c *Core) CallPeer(addr string, sintf string) error { return c.link.call(addr, sintf) } -// Adds an allowed public key. This allow peerings to be restricted only to -// keys that you have selected. +// AddAllowedEncryptionPublicKey adds an allowed public key. This allow peerings +// to be restricted only to keys that you have selected. func (c *Core) AddAllowedEncryptionPublicKey(boxStr string) error { return c.admin.addAllowedEncryptionPublicKey(boxStr) } - -// Gets the default admin listen address for your platform. -func (c *Core) GetAdminDefaultListen() string { - return defaults.GetDefaults().DefaultAdminListen -} - -// Gets the default TUN/TAP interface name for your platform. -func (c *Core) GetTUNDefaultIfName() string { - return defaults.GetDefaults().DefaultIfName -} - -// Gets the default TUN/TAP interface MTU for your platform. This can be as high -// as 65535, depending on platform, but is never lower than 1280. -func (c *Core) GetTUNDefaultIfMTU() int { - return defaults.GetDefaults().DefaultIfMTU -} - -// Gets the maximum supported TUN/TAP interface MTU for your platform. This -// can be as high as 65535, depending on platform, but is never lower than 1280. -func (c *Core) GetTUNMaximumIfMTU() int { - return defaults.GetDefaults().MaximumIfMTU -} - -// Gets the default TUN/TAP interface mode for your platform. -func (c *Core) GetTUNDefaultIfTAPMode() bool { - return defaults.GetDefaults().DefaultIfTAPMode -} - -// Gets the current TUN/TAP interface name. -func (c *Core) GetTUNIfName() string { - //return c.router.tun.iface.Name() - return c.router.adapter.Name() -} - -// Gets the current TUN/TAP interface MTU. -func (c *Core) GetTUNIfMTU() int { - //return c.router.tun.mtu - return c.router.adapter.MTU() -} diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index bef564fd..9130f554 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -41,7 +41,7 @@ type router struct { in <-chan []byte // packets we received from the network, link to peer's "out" out func([]byte) // packets we're sending to the network, link to peer's "in" toRecv chan router_recvPacket // packets to handle via recvPacket() - adapter adapterImplementation // TUN/TAP adapter + adapter AdapterImplementation // TUN/TAP adapter recv chan<- []byte // place where the adapter pulls received packets from send <-chan []byte // place where the adapter puts outgoing packets reject chan<- RejectedPacket // place where we send error packets back to adapter From f19a4e43982dc512a929d113daf51c6552981cff Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 29 Mar 2019 18:18:31 +0000 Subject: [PATCH 0012/1109] More godoc improvements --- src/multicast/multicast.go | 9 +++++++++ src/tuntap/tun.go | 35 ++++++++++++++++++++++------------- src/yggdrasil/router.go | 10 +++++++++- src/yggdrasil/tcp.go | 4 ++++ 4 files changed, 44 insertions(+), 14 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index c6756808..4e5bc4b1 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -14,6 +14,10 @@ import ( "golang.org/x/net/ipv6" ) +// Multicast represents the multicast advertisement and discovery mechanism used +// by Yggdrasil to find peers on the same subnet. When a beacon is received on a +// configured multicast interface, Yggdrasil will attempt to peer with that node +// automatically. type Multicast struct { core *yggdrasil.Core config *config.NodeState @@ -25,6 +29,7 @@ type Multicast struct { listenPort uint16 } +// Init prepares the multicast interface for use. func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) error { m.core = core m.config = state @@ -47,6 +52,9 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log return nil } +// Start starts the multicast interface. This launches goroutines which will +// listen for multicast beacons from other hosts and will advertise multicast +// beacons out to the network. func (m *Multicast) Start() error { if len(m.interfaces()) == 0 { m.log.Infoln("Multicast discovery is disabled") @@ -76,6 +84,7 @@ func (m *Multicast) Start() error { return nil } +// Stop is not implemented for multicast yet. func (m *Multicast) Stop() error { return nil } diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 5a247bff..600d3d76 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -27,7 +27,10 @@ import ( const tun_IPv6_HEADER_LENGTH = 40 const tun_ETHER_HEADER_LENGTH = 14 -// Represents a running TUN/TAP interface. +// TunAdapter represents a running TUN/TAP interface and extends the +// yggdrasil.Adapter type. In order to use the TUN/TAP adapter with Yggdrasil, +// you should pass this object to the yggdrasil.SetRouterAdapter() function +// before calling yggdrasil.Start(). type TunAdapter struct { yggdrasil.Adapter addr address.Address @@ -50,44 +53,50 @@ func getSupportedMTU(mtu int) int { return mtu } -// Get the adapter name +// Name returns the name of the adapter, e.g. "tun0". On Windows, this may +// return a canonical adapter name instead. func (tun *TunAdapter) Name() string { return tun.iface.Name() } -// Get the adapter MTU +// MTU gets the adapter's MTU. This can range between 1280 and 65535, although +// the maximum value is determined by your platform. The returned value will +// never exceed that of MaximumMTU(). func (tun *TunAdapter) MTU() int { return getSupportedMTU(tun.mtu) } -// Get the adapter mode +// IsTAP returns true if the adapter is a TAP adapter (Layer 2) or false if it +// is a TUN adapter (Layer 3). func (tun *TunAdapter) IsTAP() bool { return tun.iface.IsTAP() } -// Gets the default TUN/TAP interface name for your platform. +// DefaultName gets the default TUN/TAP interface name for your platform. func DefaultName() string { return defaults.GetDefaults().DefaultIfName } -// Gets the default TUN/TAP interface MTU for your platform. This can be as high -// as 65535, depending on platform, but is never lower than 1280. +// DefaultMTU gets the default TUN/TAP interface MTU for your platform. This can +// be as high as MaximumMTU(), depending on platform, but is never lower than 1280. func DefaultMTU() int { return defaults.GetDefaults().DefaultIfMTU } -// Gets the default TUN/TAP interface mode for your platform. +// DefaultIsTAP returns true if the default adapter mode for the current +// platform is TAP (Layer 2) and returns false for TUN (Layer 3). func DefaultIsTAP() bool { return defaults.GetDefaults().DefaultIfTAPMode } -// 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. +// MaximumMTU returns the maximum supported TUN/TAP interface MTU for your +// platform. This can be as high as 65535, depending on platform, but is never +// lower than 1280. func MaximumMTU() int { return defaults.GetDefaults().MaximumIfMTU } -// Initialises the TUN/TAP adapter. +// Init initialises the TUN/TAP adapter. func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, send chan<- []byte, recv <-chan []byte, reject <-chan yggdrasil.RejectedPacket) { tun.config = config tun.log = log @@ -111,8 +120,8 @@ func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, send chan }() } -// Starts the setup process for the TUN/TAP adapter, and if successful, starts -// the read/write goroutines to handle packets on that interface. +// Start the setup process for the TUN/TAP adapter. If successful, starts the +// read/write goroutines to handle packets on that interface. func (tun *TunAdapter) Start(a address.Address, s address.Subnet) error { tun.addr = a tun.subnet = s diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 9130f554..aee92244 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -57,13 +57,21 @@ type router_recvPacket struct { sinfo *sessionInfo } +// RejectedPacketReason is the type code used to represent the reason that a +// packet was rejected. type RejectedPacketReason int const ( - // The router rejected the packet because it is too big for the session + // The router rejected the packet because it exceeds the session MTU for the + // given destination. In TUN/TAP, this results in the generation of an ICMPv6 + // Packet Too Big message. PacketTooBig = 1 + iota ) +// RejectedPacket represents a rejected packet from the router. This is passed +// back to the adapter so that the adapter can respond appropriately, e.g. in +// the case of TUN/TAP, a "PacketTooBig" reason can be used to generate an +// ICMPv6 Packet Too Big response. type RejectedPacket struct { Reason RejectedPacketReason Packet []byte diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index b9681fc2..4361ec82 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -41,6 +41,10 @@ type tcp struct { conns map[linkInfo](chan struct{}) } +// TcpListener is a stoppable TCP listener interface. These are typically +// returned from calls to the ListenTCP() function and are also used internally +// to represent listeners created by the "Listen" configuration option and for +// multicast interfaces. type TcpListener struct { Listener net.Listener Stop chan bool From 4c0c3a23cb6aec8a6364b3fae9220a1a8cd2d99a Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 29 Mar 2019 18:24:57 +0000 Subject: [PATCH 0013/1109] Fix bugs --- cmd/yggdrasil/main.go | 8 ++++---- src/yggdrasil/admin.go | 8 ++++---- src/yggdrasil/core.go | 4 ++-- src/yggdrasil/dht.go | 2 +- src/yggdrasil/nodeinfo.go | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 9278867b..fd8cd7b6 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -189,8 +189,8 @@ func main() { var err error switch { case *version: - fmt.Println("Build name:", yggdrasil.GetBuildName()) - fmt.Println("Build version:", yggdrasil.GetBuildVersion()) + fmt.Println("Build name:", yggdrasil.BuildName()) + fmt.Println("Build version:", yggdrasil.BuildVersion()) os.Exit(0) case *autoconf: // Use an autoconf-generated config, this will give us random keys and @@ -269,8 +269,8 @@ func main() { }() // Make some nice output that tells us what our IPv6 address and subnet are. // This is just logged to stdout for the user. - address := n.core.GetAddress() - subnet := n.core.GetSubnet() + address := n.core.Address() + subnet := n.core.Subnet() logger.Infof("Your IPv6 address is %s", address.String()) logger.Infof("Your IPv6 subnet is %s", subnet.String()) // Catch interrupts from the operating system to exit gracefully. diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 2db7ad4c..a9595f8c 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -642,14 +642,14 @@ func (a *admin) getData_getSelf() *admin_nodeInfo { coords := table.self.getCoords() self := admin_nodeInfo{ {"box_pub_key", hex.EncodeToString(a.core.boxPub[:])}, - {"ip", a.core.GetAddress().String()}, - {"subnet", a.core.GetSubnet().String()}, + {"ip", a.core.Address().String()}, + {"subnet", a.core.Subnet().String()}, {"coords", fmt.Sprint(coords)}, } - if name := GetBuildName(); name != "unknown" { + if name := BuildName(); name != "unknown" { self = append(self, admin_pair{"build_name", name}) } - if version := GetBuildVersion(); version != "unknown" { + if version := BuildVersion(); version != "unknown" { self = append(self, admin_pair{"build_version", version}) } diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 8444050d..c0548e2b 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -267,12 +267,12 @@ func (c *Core) NewSigningKeys() (*crypto.SigPubKey, *crypto.SigPrivKey) { // NodeID gets the node ID. func (c *Core) NodeID() *crypto.NodeID { - return crypto.NodeID(&c.boxPub) + return crypto.GetNodeID(&c.boxPub) } // TreeID gets the tree ID. func (c *Core) TreeID() *crypto.TreeID { - return crypto.TreeID(&c.sigPub) + return crypto.GetTreeID(&c.sigPub) } // Address gets the IPv6 address of the Yggdrasil node. This is always a /128 diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 5427aca9..b081c92d 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -86,7 +86,7 @@ func (t *dht) init(c *Core) { e <- nil } }() - t.nodeID = *t.core.GetNodeID() + t.nodeID = *t.core.NodeID() t.peers = make(chan *dhtInfo, 1024) t.callbacks = make(map[dhtReqKey]dht_callbackInfo) t.reset() diff --git a/src/yggdrasil/nodeinfo.go b/src/yggdrasil/nodeinfo.go index 963a2fc2..89b8b89f 100644 --- a/src/yggdrasil/nodeinfo.go +++ b/src/yggdrasil/nodeinfo.go @@ -101,8 +101,8 @@ func (m *nodeinfo) setNodeInfo(given interface{}, privacy bool) error { m.myNodeInfoMutex.Lock() defer m.myNodeInfoMutex.Unlock() defaults := map[string]interface{}{ - "buildname": GetBuildName(), - "buildversion": GetBuildVersion(), + "buildname": BuildName(), + "buildversion": BuildVersion(), "buildplatform": runtime.GOOS, "buildarch": runtime.GOARCH, } From 39baf7365c8b7c4eb82ade458eca9985c5d075c5 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 30 Mar 2019 00:09:35 +0000 Subject: [PATCH 0014/1109] Unexport/modify some interfaces to revive broken iOS/Android builds --- src/tuntap/tun.go | 8 ++++---- src/yggdrasil/adapter.go | 10 ++++------ src/yggdrasil/core.go | 11 ++++++++--- src/yggdrasil/link.go | 14 +------------- src/yggdrasil/mobile.go | 12 ++++++------ src/yggdrasil/mobile_ios.go | 5 ++--- src/yggdrasil/router.go | 2 +- 7 files changed, 26 insertions(+), 36 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 600d3d76..5d9f4ebb 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -148,11 +148,11 @@ func (tun *TunAdapter) Start(a address.Address, s address.Subnet) error { tun.mutex.Unlock() go func() { tun.log.Debugln("Starting TUN/TAP reader goroutine") - tun.log.Errorln("WARNING: tun.read() exited with error:", tun.Read()) + tun.log.Errorln("WARNING: tun.read() exited with error:", tun.read()) }() go func() { tun.log.Debugln("Starting TUN/TAP writer goroutine") - tun.log.Errorln("WARNING: tun.write() exited with error:", tun.Write()) + tun.log.Errorln("WARNING: tun.write() exited with error:", tun.write()) }() if iftapmode { go func() { @@ -177,7 +177,7 @@ func (tun *TunAdapter) Start(a address.Address, s address.Subnet) error { // Writes a packet to the TUN/TAP adapter. If the adapter is running in TAP // mode then additional ethernet encapsulation is added for the benefit of the // host operating system. -func (tun *TunAdapter) Write() error { +func (tun *TunAdapter) write() error { for { select { case reject := <-tun.Reject: @@ -310,7 +310,7 @@ func (tun *TunAdapter) Write() error { // is running in TAP mode then the ethernet headers will automatically be // processed and stripped if necessary. If an ICMPv6 packet is found, then // the relevant helper functions in icmpv6.go are called. -func (tun *TunAdapter) Read() error { +func (tun *TunAdapter) read() error { mtu := tun.mtu if tun.iface.IsTAP() { mtu += tun_ETHER_HEADER_LENGTH diff --git a/src/yggdrasil/adapter.go b/src/yggdrasil/adapter.go index d373894f..e5b1707d 100644 --- a/src/yggdrasil/adapter.go +++ b/src/yggdrasil/adapter.go @@ -11,7 +11,7 @@ import ( // you should extend the adapter struct with this one and should call the // Adapter.Init() function when initialising. type Adapter struct { - AdapterImplementation + adapterImplementation Core *Core Send chan<- []byte Recv <-chan []byte @@ -20,15 +20,14 @@ type Adapter struct { } // Defines the minimum required functions for an adapter type. Note that the -// implementation of Init() should call Adapter.Init(). -type AdapterImplementation interface { +// implementation of Init() should call Adapter.Init(). This is not exported +// because doing so breaks the gomobile bindings for iOS/Android. +type adapterImplementation interface { Init(*config.NodeState, *log.Logger, chan<- []byte, <-chan []byte, <-chan RejectedPacket) Name() string MTU() int IsTAP() bool Start(address.Address, address.Subnet) error - Read() error - Write() error Close() error } @@ -37,7 +36,6 @@ type AdapterImplementation interface { // function from within it's own Init function to set up the channels. It is // otherwise not expected for you to call this function directly. func (adapter *Adapter) Init(config *config.NodeState, log *log.Logger, send chan<- []byte, recv <-chan []byte, reject <-chan RejectedPacket) { - log.Traceln("Adapter setup - given channels:", send, recv) adapter.Send = send adapter.Recv = recv adapter.Reject = reject diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index c0548e2b..9634a42f 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -168,10 +168,15 @@ func BuildVersion() string { // SetRouterAdapter instructs Yggdrasil to use the given adapter when starting // the router. The adapter must implement the standard -// adapter.AdapterImplementation interface and should extend the adapter.Adapter +// adapter.adapterImplementation interface and should extend the adapter.Adapter // struct. -func (c *Core) SetRouterAdapter(adapter AdapterImplementation) { - c.router.adapter = adapter +func (c *Core) SetRouterAdapter(adapter interface{}) { + // We do this because adapterImplementation is not a valid type for the + // gomobile bindings so we just ask for a generic interface and try to cast it + // to adapterImplementation instead + if a, ok := adapter.(adapterImplementation); ok { + c.router.adapter = a + } } // Start starts up Yggdrasil using the provided config.NodeConfig, and outputs diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index bfec714b..de90fd94 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -23,8 +23,7 @@ type link struct { reconfigure chan chan error mutex sync.RWMutex // protects interfaces below interfaces map[linkInfo]*linkInterface - awdl awdl // AWDL interface support - tcp tcp // TCP interface support + tcp tcp // TCP interface support // TODO timeout (to remove from switch), read from config.ReadTimeout } @@ -68,26 +67,15 @@ func (l *link) init(c *Core) error { return err } - if err := l.awdl.init(l); err != nil { - c.log.Errorln("Failed to start AWDL interface") - return err - } - go func() { for { e := <-l.reconfigure tcpresponse := make(chan error) - awdlresponse := make(chan error) l.tcp.reconfigure <- tcpresponse if err := <-tcpresponse; err != nil { e <- err continue } - l.awdl.reconfigure <- awdlresponse - if err := <-awdlresponse; err != nil { - e <- err - continue - } e <- nil } }() diff --git a/src/yggdrasil/mobile.go b/src/yggdrasil/mobile.go index bad1424a..0c6686d4 100644 --- a/src/yggdrasil/mobile.go +++ b/src/yggdrasil/mobile.go @@ -52,7 +52,7 @@ func (c *Core) StartAutoconfigure() error { if hostname, err := os.Hostname(); err == nil { nc.NodeInfo = map[string]interface{}{"name": hostname} } - if err := c.Start(nc, logger); err != nil { + if _, err := c.Start(nc, logger); err != nil { return err } go c.addStaticPeers(nc) @@ -73,7 +73,7 @@ func (c *Core) StartJSON(configjson []byte) error { return err } nc.IfName = "dummy" - if err := c.Start(nc, logger); err != nil { + if _, err := c.Start(nc, logger); err != nil { return err } go c.addStaticPeers(nc) @@ -93,12 +93,12 @@ func GenerateConfigJSON() []byte { // Gets the node's IPv6 address. func (c *Core) GetAddressString() string { - return c.GetAddress().String() + return c.Address().String() } // Gets the node's IPv6 subnet in CIDR notation. func (c *Core) GetSubnetString() string { - return c.GetSubnet().String() + return c.Subnet().String() } // Gets the node's public encryption key. @@ -115,7 +115,7 @@ func (c *Core) GetSigPubKeyString() string { // dummy adapter in place of real TUN - when this call returns a packet, you // will probably want to give it to the OS to write to TUN. func (c *Core) RouterRecvPacket() ([]byte, error) { - packet := <-c.router.tun.Recv + packet := <-c.router.recv return packet, nil } @@ -125,6 +125,6 @@ func (c *Core) RouterRecvPacket() ([]byte, error) { // Yggdrasil. func (c *Core) RouterSendPacket(buf []byte) error { packet := append(util.GetBytes(), buf[:]...) - c.router.tun.Send <- packet + c.router.send <- packet return nil } diff --git a/src/yggdrasil/mobile_ios.go b/src/yggdrasil/mobile_ios.go index 7b089992..96d2fc02 100644 --- a/src/yggdrasil/mobile_ios.go +++ b/src/yggdrasil/mobile_ios.go @@ -13,10 +13,7 @@ void Log(const char *text) { */ import "C" import ( - "errors" "unsafe" - - "github.com/yggdrasil-network/yggdrasil-go/src/util" ) type MobileLogger struct { @@ -29,6 +26,7 @@ func (nsl MobileLogger) Write(p []byte) (n int, err error) { return len(p), nil } +/* func (c *Core) AWDLCreateInterface(name, local, remote string, incoming bool) error { if intf, err := c.link.awdl.create(name, local, remote, incoming); err != nil || intf == nil { c.log.Println("c.link.awdl.create:", err) @@ -60,3 +58,4 @@ func (c *Core) AWDLSendPacket(identity string, buf []byte) error { } return errors.New("AWDLSendPacket identity not known: " + identity) } +*/ diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index aee92244..a3d3d68c 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -41,7 +41,7 @@ type router struct { in <-chan []byte // packets we received from the network, link to peer's "out" out func([]byte) // packets we're sending to the network, link to peer's "in" toRecv chan router_recvPacket // packets to handle via recvPacket() - adapter AdapterImplementation // TUN/TAP adapter + adapter adapterImplementation // TUN/TAP adapter recv chan<- []byte // place where the adapter pulls received packets from send <-chan []byte // place where the adapter puts outgoing packets reject chan<- RejectedPacket // place where we send error packets back to adapter From 047717abf2d06c8e72c799a89c33507a6f86a5c6 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 1 Apr 2019 18:02:06 +0100 Subject: [PATCH 0015/1109] Break out mobile and dummy adapter --- build | 4 +- src/dummy/dummy.go | 57 ++++++++ src/mobile/mobile.go | 143 ++++++++++++++++++++ src/{yggdrasil => mobile}/mobile_android.go | 2 +- src/{yggdrasil => mobile}/mobile_ios.go | 4 +- src/yggdrasil/core.go | 10 ++ src/yggdrasil/mobile.go | 130 ------------------ 7 files changed, 215 insertions(+), 135 deletions(-) create mode 100644 src/dummy/dummy.go create mode 100644 src/mobile/mobile.go rename src/{yggdrasil => mobile}/mobile_android.go (90%) rename src/{yggdrasil => mobile}/mobile_ios.go (97%) delete mode 100644 src/yggdrasil/mobile.go diff --git a/build b/build index f6c72466..127af75d 100755 --- a/build +++ b/build @@ -28,10 +28,10 @@ fi if [ $IOS ]; then echo "Building framework for iOS" - gomobile bind -target ios -tags mobile -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil + gomobile bind -target ios -tags mobile -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" github.com/yggdrasil-network/yggdrasil-go/src/mobile elif [ $ANDROID ]; then echo "Building aar for Android" - gomobile bind -target android -tags mobile -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil + gomobile bind -target android -tags mobile -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" github.com/yggdrasil-network/yggdrasil-go/src/mobile else for CMD in `ls cmd/` ; do echo "Building: $CMD" diff --git a/src/dummy/dummy.go b/src/dummy/dummy.go new file mode 100644 index 00000000..a199eee3 --- /dev/null +++ b/src/dummy/dummy.go @@ -0,0 +1,57 @@ +package dummy + +import ( + "github.com/gologme/log" + "github.com/yggdrasil-network/yggdrasil-go/src/config" + "github.com/yggdrasil-network/yggdrasil-go/src/util" + "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" +) + +type DummyAdapter struct { + yggdrasil.Adapter + send chan<- []byte + recv <-chan []byte + reject <-chan yggdrasil.RejectedPacket +} + +// Init initialises the TUN/TAP adapter. +func (m *DummyAdapter) Init(config *config.NodeState, log *log.Logger, send chan<- []byte, recv <-chan []byte, reject <-chan yggdrasil.RejectedPacket) { + m.Adapter.Init(config, log, send, recv, reject) +} + +// Name returns the name of the adapter, e.g. "tun0". On Windows, this may +// return a canonical adapter name instead. +func (m *DummyAdapter) Name() string { + return "dummy" +} + +// MTU gets the adapter's MTU. This can range between 1280 and 65535, although +// the maximum value is determined by your platform. The returned value will +// never exceed that of MaximumMTU(). +func (m *DummyAdapter) MTU() int { + return 65535 +} + +// IsTAP returns true if the adapter is a TAP adapter (Layer 2) or false if it +// is a TUN adapter (Layer 3). +func (m *DummyAdapter) IsTAP() bool { + return false +} + +// Wait for a packet from the router. You will use this when implementing a +// dummy adapter in place of real TUN - when this call returns a packet, you +// will probably want to give it to the OS to write to TUN. +func (m *DummyAdapter) Recv() ([]byte, error) { + packet := <-m.recv + return packet, nil +} + +// Send a packet to the router. You will use this when implementing a +// dummy adapter in place of real TUN - when the operating system tells you +// that a new packet is available from TUN, call this function to give it to +// Yggdrasil. +func (m *DummyAdapter) Send(buf []byte) error { + packet := append(util.GetBytes(), buf[:]...) + m.send <- packet + return nil +} diff --git a/src/mobile/mobile.go b/src/mobile/mobile.go new file mode 100644 index 00000000..ecc4d652 --- /dev/null +++ b/src/mobile/mobile.go @@ -0,0 +1,143 @@ +package mobile + +import ( + "encoding/json" + "os" + "time" + + "github.com/gologme/log" + + hjson "github.com/hjson/hjson-go" + "github.com/mitchellh/mapstructure" + "github.com/yggdrasil-network/yggdrasil-go/src/config" + "github.com/yggdrasil-network/yggdrasil-go/src/dummy" + "github.com/yggdrasil-network/yggdrasil-go/src/multicast" + "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" +) + +// Yggdrasil's mobile package is meant to "plug the gap" for mobile support, as +// Gomobile will not create headers for Swift/Obj-C etc if they have complex +// (non-native) types. Therefore for iOS we will expose some nice simple +// functions. Note that in the case of iOS we handle reading/writing to/from TUN +// in Swift therefore we use the "dummy" TUN interface instead. +type Yggdrasil struct { + core *yggdrasil.Core + multicast *multicast.Multicast + dummy.DummyAdapter +} + +func (m *Yggdrasil) addStaticPeers(cfg *config.NodeConfig) { + if len(cfg.Peers) == 0 && len(cfg.InterfacePeers) == 0 { + return + } + for { + for _, peer := range cfg.Peers { + m.core.AddPeer(peer, "") + time.Sleep(time.Second) + } + for intf, intfpeers := range cfg.InterfacePeers { + for _, peer := range intfpeers { + m.core.AddPeer(peer, intf) + time.Sleep(time.Second) + } + } + time.Sleep(time.Minute) + } +} + +// StartAutoconfigure starts a node with a randomly generated config +func (m *Yggdrasil) StartAutoconfigure() error { + m.core = &yggdrasil.Core{} + //m.Adapter = dummy.DummyAdapter{} + mobilelog := MobileLogger{} + logger := log.New(mobilelog, "", 0) + nc := config.GenerateConfig() + nc.IfName = "dummy" + nc.AdminListen = "tcp://localhost:9001" + nc.Peers = []string{} + if hostname, err := os.Hostname(); err == nil { + nc.NodeInfo = map[string]interface{}{"name": hostname} + } + m.core.SetRouterAdapter(&m) + state, err := m.core.Start(nc, logger) + if err != nil { + return err + } + // Start the multicast interface + m.multicast.Init(m.core, state, logger, nil) + if err := m.multicast.Start(); err != nil { + logger.Errorln("An error occurred starting multicast:", err) + } + go m.addStaticPeers(nc) + return nil +} + +// StartJSON starts a node with the given JSON config. You can get JSON config +// (rather than HJSON) by using the GenerateConfigJSON() function +func (m *Yggdrasil) StartJSON(configjson []byte) error { + m.core = &yggdrasil.Core{} + //m.Adapter = dummy.DummyAdapter{} + mobilelog := MobileLogger{} + logger := log.New(mobilelog, "", 0) + nc := config.GenerateConfig() + var dat map[string]interface{} + if err := hjson.Unmarshal(configjson, &dat); err != nil { + return err + } + if err := mapstructure.Decode(dat, &nc); err != nil { + return err + } + nc.IfName = "dummy" + m.core.SetRouterAdapter(&m) + state, err := m.core.Start(nc, logger) + if err != nil { + return err + } + // Start the multicast interface + m.multicast.Init(m.core, state, logger, nil) + if err := m.multicast.Start(); err != nil { + logger.Errorln("An error occurred starting multicast:", err) + } + go m.addStaticPeers(nc) + return nil +} + +// Stops the mobile Yggdrasil instance +func (m *Yggdrasil) Stop() error { + m.core.Stop() + if err := m.Stop(); err != nil { + return err + } + return nil +} + +// GenerateConfigJSON generates mobile-friendly configuration in JSON format +func GenerateConfigJSON() []byte { + nc := config.GenerateConfig() + nc.IfName = "dummy" + if json, err := json.Marshal(nc); err == nil { + return json + } else { + return nil + } +} + +// GetAddressString gets the node's IPv6 address +func (m *Yggdrasil) GetAddressString() string { + return m.core.Address().String() +} + +// GetSubnetString gets the node's IPv6 subnet in CIDR notation +func (m *Yggdrasil) GetSubnetString() string { + return m.core.Subnet().String() +} + +// GetBoxPubKeyString gets the node's public encryption key +func (m *Yggdrasil) GetBoxPubKeyString() string { + return m.core.BoxPubKey() +} + +// GetSigPubKeyString gets the node's public signing key +func (m *Yggdrasil) GetSigPubKeyString() string { + return m.core.SigPubKey() +} diff --git a/src/yggdrasil/mobile_android.go b/src/mobile/mobile_android.go similarity index 90% rename from src/yggdrasil/mobile_android.go rename to src/mobile/mobile_android.go index 24764840..f3206aca 100644 --- a/src/yggdrasil/mobile_android.go +++ b/src/mobile/mobile_android.go @@ -1,6 +1,6 @@ // +build android -package yggdrasil +package mobile import "log" diff --git a/src/yggdrasil/mobile_ios.go b/src/mobile/mobile_ios.go similarity index 97% rename from src/yggdrasil/mobile_ios.go rename to src/mobile/mobile_ios.go index 96d2fc02..26b219cc 100644 --- a/src/yggdrasil/mobile_ios.go +++ b/src/mobile/mobile_ios.go @@ -1,6 +1,6 @@ -// +build mobile,darwin +// +build darwin -package yggdrasil +package mobile /* #cgo CFLAGS: -x objective-c diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 9634a42f..fcee124b 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -280,6 +280,16 @@ func (c *Core) TreeID() *crypto.TreeID { return crypto.GetTreeID(&c.sigPub) } +// SigPubKey gets the node's signing public key. +func (c *Core) SigPubKey() string { + return hex.EncodeToString(c.sigPub[:]) +} + +// BoxPubKey gets the node's encryption public key. +func (c *Core) BoxPubKey() string { + return hex.EncodeToString(c.boxPub[:]) +} + // Address gets the IPv6 address of the Yggdrasil node. This is always a /128 // address. func (c *Core) Address() *net.IP { diff --git a/src/yggdrasil/mobile.go b/src/yggdrasil/mobile.go deleted file mode 100644 index 0c6686d4..00000000 --- a/src/yggdrasil/mobile.go +++ /dev/null @@ -1,130 +0,0 @@ -// +build mobile - -package yggdrasil - -import ( - "encoding/hex" - "encoding/json" - "os" - "time" - - "github.com/gologme/log" - - hjson "github.com/hjson/hjson-go" - "github.com/mitchellh/mapstructure" - "github.com/yggdrasil-network/yggdrasil-go/src/config" - "github.com/yggdrasil-network/yggdrasil-go/src/util" -) - -// This file is meant to "plug the gap" for mobile support, as Gomobile will -// not create headers for Swift/Obj-C etc if they have complex (non-native) -// types. Therefore for iOS we will expose some nice simple functions. Note -// that in the case of iOS we handle reading/writing to/from TUN in Swift -// therefore we use the "dummy" TUN interface instead. - -func (c *Core) addStaticPeers(cfg *config.NodeConfig) { - if len(cfg.Peers) == 0 && len(cfg.InterfacePeers) == 0 { - return - } - for { - for _, peer := range cfg.Peers { - c.AddPeer(peer, "") - time.Sleep(time.Second) - } - for intf, intfpeers := range cfg.InterfacePeers { - for _, peer := range intfpeers { - c.AddPeer(peer, intf) - time.Sleep(time.Second) - } - } - time.Sleep(time.Minute) - } -} - -// Starts a node with a randomly generated config. -func (c *Core) StartAutoconfigure() error { - mobilelog := MobileLogger{} - logger := log.New(mobilelog, "", 0) - nc := config.GenerateConfig() - nc.IfName = "dummy" - nc.AdminListen = "tcp://localhost:9001" - nc.Peers = []string{} - if hostname, err := os.Hostname(); err == nil { - nc.NodeInfo = map[string]interface{}{"name": hostname} - } - if _, err := c.Start(nc, logger); err != nil { - return err - } - go c.addStaticPeers(nc) - return nil -} - -// Starts a node with the given JSON config. You can get JSON config (rather -// than HJSON) by using the GenerateConfigJSON() function. -func (c *Core) StartJSON(configjson []byte) error { - mobilelog := MobileLogger{} - logger := log.New(mobilelog, "", 0) - nc := config.GenerateConfig() - var dat map[string]interface{} - if err := hjson.Unmarshal(configjson, &dat); err != nil { - return err - } - if err := mapstructure.Decode(dat, &nc); err != nil { - return err - } - nc.IfName = "dummy" - if _, err := c.Start(nc, logger); err != nil { - return err - } - go c.addStaticPeers(nc) - return nil -} - -// Generates mobile-friendly configuration in JSON format. -func GenerateConfigJSON() []byte { - nc := config.GenerateConfig() - nc.IfName = "dummy" - if json, err := json.Marshal(nc); err == nil { - return json - } else { - return nil - } -} - -// Gets the node's IPv6 address. -func (c *Core) GetAddressString() string { - return c.Address().String() -} - -// Gets the node's IPv6 subnet in CIDR notation. -func (c *Core) GetSubnetString() string { - return c.Subnet().String() -} - -// Gets the node's public encryption key. -func (c *Core) GetBoxPubKeyString() string { - return hex.EncodeToString(c.boxPub[:]) -} - -// Gets the node's public signing key. -func (c *Core) GetSigPubKeyString() string { - return hex.EncodeToString(c.sigPub[:]) -} - -// Wait for a packet from the router. You will use this when implementing a -// dummy adapter in place of real TUN - when this call returns a packet, you -// will probably want to give it to the OS to write to TUN. -func (c *Core) RouterRecvPacket() ([]byte, error) { - packet := <-c.router.recv - return packet, nil -} - -// Send a packet to the router. You will use this when implementing a -// dummy adapter in place of real TUN - when the operating system tells you -// that a new packet is available from TUN, call this function to give it to -// Yggdrasil. -func (c *Core) RouterSendPacket(buf []byte) error { - packet := append(util.GetBytes(), buf[:]...) - c.router.send <- packet - return nil -} From 58f5cc88d03abee0afc1f3555c8aad0b1f274a1f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 1 Apr 2019 19:59:50 +0100 Subject: [PATCH 0016/1109] Fix session bug, fix dummy adapter, fix mobile framework builds --- build | 10 +++++++-- src/dummy/dummy.go | 46 ++++++++++++++++++++++------------------ src/mobile/mobile.go | 38 ++++++++++++++++----------------- src/yggdrasil/core.go | 5 ++++- src/yggdrasil/session.go | 4 +++- 5 files changed, 58 insertions(+), 45 deletions(-) diff --git a/build b/build index 127af75d..f76ee7b2 100755 --- a/build +++ b/build @@ -28,10 +28,16 @@ fi if [ $IOS ]; then echo "Building framework for iOS" - gomobile bind -target ios -tags mobile -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" github.com/yggdrasil-network/yggdrasil-go/src/mobile + gomobile bind -target ios -tags mobile -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \ + github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil \ + github.com/yggdrasil-network/yggdrasil-go/src/mobile \ + github.com/yggdrasil-network/yggdrasil-go/src/config elif [ $ANDROID ]; then echo "Building aar for Android" - gomobile bind -target android -tags mobile -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" github.com/yggdrasil-network/yggdrasil-go/src/mobile + gomobile bind -target android -tags mobile -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \ + github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil \ + github.com/yggdrasil-network/yggdrasil-go/src/mobile \ + github.com/yggdrasil-network/yggdrasil-go/src/config else for CMD in `ls cmd/` ; do echo "Building: $CMD" diff --git a/src/dummy/dummy.go b/src/dummy/dummy.go index a199eee3..ca6bb7b7 100644 --- a/src/dummy/dummy.go +++ b/src/dummy/dummy.go @@ -2,56 +2,60 @@ package dummy import ( "github.com/gologme/log" + "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/config" + "github.com/yggdrasil-network/yggdrasil-go/src/defaults" "github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) +// DummyAdapter is a non-specific adapter that is used by the mobile APIs. +// You can also use it to send or receive custom traffic over Yggdrasil. type DummyAdapter struct { yggdrasil.Adapter - send chan<- []byte - recv <-chan []byte - reject <-chan yggdrasil.RejectedPacket } -// Init initialises the TUN/TAP adapter. +// Init initialises the dummy adapter. func (m *DummyAdapter) Init(config *config.NodeState, log *log.Logger, send chan<- []byte, recv <-chan []byte, reject <-chan yggdrasil.RejectedPacket) { m.Adapter.Init(config, log, send, recv, reject) } -// Name returns the name of the adapter, e.g. "tun0". On Windows, this may -// return a canonical adapter name instead. +// Name returns the name of the adapter. This is always "dummy" for dummy +// adapters. func (m *DummyAdapter) Name() string { return "dummy" } -// MTU gets the adapter's MTU. This can range between 1280 and 65535, although -// the maximum value is determined by your platform. The returned value will -// never exceed that of MaximumMTU(). +// MTU gets the adapter's MTU. This returns your platform's maximum MTU for +// dummy adapters. func (m *DummyAdapter) MTU() int { - return 65535 + return defaults.GetDefaults().MaximumIfMTU } -// IsTAP returns true if the adapter is a TAP adapter (Layer 2) or false if it -// is a TUN adapter (Layer 3). +// IsTAP always returns false for dummy adapters. func (m *DummyAdapter) IsTAP() bool { return false } -// Wait for a packet from the router. You will use this when implementing a -// dummy adapter in place of real TUN - when this call returns a packet, you -// will probably want to give it to the OS to write to TUN. +// Recv waits for and returns for a packet from the router. func (m *DummyAdapter) Recv() ([]byte, error) { - packet := <-m.recv + packet := <-m.Adapter.Recv return packet, nil } -// Send a packet to the router. You will use this when implementing a -// dummy adapter in place of real TUN - when the operating system tells you -// that a new packet is available from TUN, call this function to give it to -// Yggdrasil. +// Send a packet to the router. func (m *DummyAdapter) Send(buf []byte) error { packet := append(util.GetBytes(), buf[:]...) - m.send <- packet + m.Adapter.Send <- packet + return nil +} + +// Start is not implemented for dummy adapters. +func (m *DummyAdapter) Start(address.Address, address.Subnet) error { + return nil +} + +// Close is not implemented for dummy adapters. +func (m *DummyAdapter) Close() error { return nil } diff --git a/src/mobile/mobile.go b/src/mobile/mobile.go index ecc4d652..5eec96e8 100644 --- a/src/mobile/mobile.go +++ b/src/mobile/mobile.go @@ -15,14 +15,15 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) -// Yggdrasil's mobile package is meant to "plug the gap" for mobile support, as +// Yggdrasil mobile package is meant to "plug the gap" for mobile support, as // Gomobile will not create headers for Swift/Obj-C etc if they have complex // (non-native) types. Therefore for iOS we will expose some nice simple // functions. Note that in the case of iOS we handle reading/writing to/from TUN // in Swift therefore we use the "dummy" TUN interface instead. type Yggdrasil struct { - core *yggdrasil.Core - multicast *multicast.Multicast + core yggdrasil.Core + multicast multicast.Multicast + log MobileLogger dummy.DummyAdapter } @@ -47,10 +48,7 @@ func (m *Yggdrasil) addStaticPeers(cfg *config.NodeConfig) { // StartAutoconfigure starts a node with a randomly generated config func (m *Yggdrasil) StartAutoconfigure() error { - m.core = &yggdrasil.Core{} - //m.Adapter = dummy.DummyAdapter{} - mobilelog := MobileLogger{} - logger := log.New(mobilelog, "", 0) + logger := log.New(m.log, "", 0) nc := config.GenerateConfig() nc.IfName = "dummy" nc.AdminListen = "tcp://localhost:9001" @@ -58,13 +56,15 @@ func (m *Yggdrasil) StartAutoconfigure() error { if hostname, err := os.Hostname(); err == nil { nc.NodeInfo = map[string]interface{}{"name": hostname} } - m.core.SetRouterAdapter(&m) + if err := m.core.SetRouterAdapter(m); err != nil { + logger.Errorln("An error occured setting router adapter:", err) + return err + } state, err := m.core.Start(nc, logger) if err != nil { return err } - // Start the multicast interface - m.multicast.Init(m.core, state, logger, nil) + m.multicast.Init(&m.core, state, logger, nil) if err := m.multicast.Start(); err != nil { logger.Errorln("An error occurred starting multicast:", err) } @@ -75,10 +75,7 @@ func (m *Yggdrasil) StartAutoconfigure() error { // StartJSON starts a node with the given JSON config. You can get JSON config // (rather than HJSON) by using the GenerateConfigJSON() function func (m *Yggdrasil) StartJSON(configjson []byte) error { - m.core = &yggdrasil.Core{} - //m.Adapter = dummy.DummyAdapter{} - mobilelog := MobileLogger{} - logger := log.New(mobilelog, "", 0) + logger := log.New(m.log, "", 0) nc := config.GenerateConfig() var dat map[string]interface{} if err := hjson.Unmarshal(configjson, &dat); err != nil { @@ -88,13 +85,15 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error { return err } nc.IfName = "dummy" - m.core.SetRouterAdapter(&m) + if err := m.core.SetRouterAdapter(m); err != nil { + logger.Errorln("An error occured setting router adapter:", err) + return err + } state, err := m.core.Start(nc, logger) if err != nil { return err } - // Start the multicast interface - m.multicast.Init(m.core, state, logger, nil) + m.multicast.Init(&m.core, state, logger, nil) if err := m.multicast.Start(); err != nil { logger.Errorln("An error occurred starting multicast:", err) } @@ -102,7 +101,7 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error { return nil } -// Stops the mobile Yggdrasil instance +// Stop the mobile Yggdrasil instance func (m *Yggdrasil) Stop() error { m.core.Stop() if err := m.Stop(); err != nil { @@ -117,9 +116,8 @@ func GenerateConfigJSON() []byte { nc.IfName = "dummy" if json, err := json.Marshal(nc); err == nil { return json - } else { - return nil } + return nil } // GetAddressString gets the node's IPv6 address diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index fcee124b..037ef094 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -2,6 +2,7 @@ package yggdrasil import ( "encoding/hex" + "errors" "io/ioutil" "net" "time" @@ -170,13 +171,15 @@ func BuildVersion() string { // the router. The adapter must implement the standard // adapter.adapterImplementation interface and should extend the adapter.Adapter // struct. -func (c *Core) SetRouterAdapter(adapter interface{}) { +func (c *Core) SetRouterAdapter(adapter interface{}) error { // We do this because adapterImplementation is not a valid type for the // gomobile bindings so we just ask for a generic interface and try to cast it // to adapterImplementation instead if a, ok := adapter.(adapterImplementation); ok { c.router.adapter = a + return nil } + return errors.New("unsuitable adapter") } // Start starts up Yggdrasil using the provided config.NodeConfig, and outputs diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 8deff953..74255c09 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -277,7 +277,9 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.mySesPriv = *priv sinfo.myNonce = *crypto.NewBoxNonce() sinfo.theirMTU = 1280 - sinfo.myMTU = uint16(ss.core.router.adapter.MTU()) + if ss.core.router.adapter != nil { + sinfo.myMTU = uint16(ss.core.router.adapter.MTU()) + } now := time.Now() sinfo.time = now sinfo.mtuTime = now From 350b51cabb0e92444dc0e6de3e00cc7cbf06e387 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 1 Apr 2019 20:10:14 +0100 Subject: [PATCH 0017/1109] TUN/TAP now uses config, log, etc from adapter.go --- src/tuntap/tun.go | 38 +++++++++++++++++--------------------- src/tuntap/tun_bsd.go | 24 ++++++++++++------------ src/tuntap/tun_darwin.go | 14 +++++++------- src/tuntap/tun_linux.go | 6 +++--- src/tuntap/tun_other.go | 2 +- src/tuntap/tun_windows.go | 32 ++++++++++++++++---------------- src/yggdrasil/adapter.go | 14 +++++++++----- 7 files changed, 65 insertions(+), 65 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 5d9f4ebb..c93b1163 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -35,8 +35,6 @@ type TunAdapter struct { yggdrasil.Adapter addr address.Address subnet address.Subnet - log *log.Logger - config *config.NodeState icmpv6 ICMPv6 mtu int iface *water.Interface @@ -98,20 +96,18 @@ func MaximumMTU() int { // Init initialises the TUN/TAP adapter. func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, send chan<- []byte, recv <-chan []byte, reject <-chan yggdrasil.RejectedPacket) { - tun.config = config - tun.log = log tun.Adapter.Init(config, log, send, recv, reject) tun.icmpv6.Init(tun) go func() { for { e := <-tun.Reconfigure - tun.config.Mutex.RLock() - updated := tun.config.Current.IfName != tun.config.Previous.IfName || - tun.config.Current.IfTAPMode != tun.config.Previous.IfTAPMode || - tun.config.Current.IfMTU != tun.config.Previous.IfMTU - tun.config.Mutex.RUnlock() + tun.Config.Mutex.RLock() + updated := tun.Config.Current.IfName != tun.Config.Previous.IfName || + tun.Config.Current.IfTAPMode != tun.Config.Previous.IfTAPMode || + tun.Config.Current.IfMTU != tun.Config.Previous.IfMTU + tun.Config.Mutex.RUnlock() if updated { - tun.log.Warnln("Reconfiguring TUN/TAP is not supported yet") + tun.Log.Warnln("Reconfiguring TUN/TAP is not supported yet") e <- nil } else { e <- nil @@ -125,34 +121,34 @@ func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, send chan func (tun *TunAdapter) Start(a address.Address, s address.Subnet) error { tun.addr = a tun.subnet = s - if tun.config == nil { + if tun.Config == nil { return errors.New("No configuration available to TUN/TAP") } - tun.config.Mutex.RLock() - ifname := tun.config.Current.IfName - iftapmode := tun.config.Current.IfTAPMode + tun.Config.Mutex.RLock() + ifname := tun.Config.Current.IfName + iftapmode := tun.Config.Current.IfTAPMode addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1) - mtu := tun.config.Current.IfMTU - tun.config.Mutex.RUnlock() + mtu := tun.Config.Current.IfMTU + tun.Config.Mutex.RUnlock() if ifname != "none" { if err := tun.setup(ifname, iftapmode, addr, mtu); err != nil { return err } } if ifname == "none" || ifname == "dummy" { - tun.log.Debugln("Not starting TUN/TAP as ifname is none or dummy") + tun.Log.Debugln("Not starting TUN/TAP as ifname is none or dummy") return nil } tun.mutex.Lock() tun.isOpen = true tun.mutex.Unlock() go func() { - tun.log.Debugln("Starting TUN/TAP reader goroutine") - tun.log.Errorln("WARNING: tun.read() exited with error:", tun.read()) + tun.Log.Debugln("Starting TUN/TAP reader goroutine") + tun.Log.Errorln("WARNING: tun.read() exited with error:", tun.read()) }() go func() { - tun.log.Debugln("Starting TUN/TAP writer goroutine") - tun.log.Errorln("WARNING: tun.write() exited with error:", tun.write()) + tun.Log.Debugln("Starting TUN/TAP writer goroutine") + tun.Log.Errorln("WARNING: tun.write() exited with error:", tun.write()) }() if iftapmode { go func() { diff --git a/src/tuntap/tun_bsd.go b/src/tuntap/tun_bsd.go index 996f3140..27c9bb2f 100644 --- a/src/tuntap/tun_bsd.go +++ b/src/tuntap/tun_bsd.go @@ -109,14 +109,14 @@ func (tun *TunAdapter) setupAddress(addr string) error { // Create system socket if sfd, err = unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0); err != nil { - tun.log.Printf("Create AF_INET socket failed: %v.", err) + tun.Log.Printf("Create AF_INET socket failed: %v.", err) return err } // Friendly output - tun.log.Infof("Interface name: %s", tun.iface.Name()) - tun.log.Infof("Interface IPv6: %s", addr) - tun.log.Infof("Interface MTU: %d", tun.mtu) + tun.Log.Infof("Interface name: %s", tun.iface.Name()) + tun.Log.Infof("Interface IPv6: %s", addr) + tun.Log.Infof("Interface MTU: %d", tun.mtu) // Create the MTU request var ir in6_ifreq_mtu @@ -126,15 +126,15 @@ func (tun *TunAdapter) setupAddress(addr string) error { // Set the MTU if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(syscall.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 { err = errno - tun.log.Errorf("Error in SIOCSIFMTU: %v", errno) + tun.Log.Errorf("Error in SIOCSIFMTU: %v", errno) // Fall back to ifconfig to set the MTU cmd := exec.Command("ifconfig", tun.iface.Name(), "mtu", string(tun.mtu)) - tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " ")) + tun.Log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.log.Errorf("SIOCSIFMTU fallback failed: %v.", err) - tun.log.Traceln(string(output)) + tun.Log.Errorf("SIOCSIFMTU fallback failed: %v.", err) + tun.Log.Traceln(string(output)) } } @@ -155,15 +155,15 @@ func (tun *TunAdapter) setupAddress(addr string) error { // Set the interface address if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(SIOCSIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 { err = errno - tun.log.Errorf("Error in SIOCSIFADDR_IN6: %v", errno) + tun.Log.Errorf("Error in SIOCSIFADDR_IN6: %v", errno) // Fall back to ifconfig to set the address cmd := exec.Command("ifconfig", tun.iface.Name(), "inet6", addr) - tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " ")) + tun.Log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.log.Errorf("SIOCSIFADDR_IN6 fallback failed: %v.", err) - tun.log.Traceln(string(output)) + tun.Log.Errorf("SIOCSIFADDR_IN6 fallback failed: %v.", err) + tun.Log.Traceln(string(output)) } } diff --git a/src/tuntap/tun_darwin.go b/src/tuntap/tun_darwin.go index 5dfca137..60786b81 100644 --- a/src/tuntap/tun_darwin.go +++ b/src/tuntap/tun_darwin.go @@ -18,7 +18,7 @@ import ( // Configures the "utun" adapter with the correct IPv6 address and MTU. func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { if iftapmode { - tun.log.Warnln("TAP mode is not supported on this platform, defaulting to TUN") + tun.Log.Warnln("TAP mode is not supported on this platform, defaulting to TUN") } config := water.Config{DeviceType: water.TUN} iface, err := water.New(config) @@ -69,7 +69,7 @@ func (tun *TunAdapter) setupAddress(addr string) error { var err error if fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, 0); err != nil { - tun.log.Printf("Create AF_SYSTEM socket failed: %v.", err) + tun.Log.Printf("Create AF_SYSTEM socket failed: %v.", err) return err } @@ -98,19 +98,19 @@ func (tun *TunAdapter) setupAddress(addr string) error { copy(ir.ifr_name[:], tun.iface.Name()) ir.ifru_mtu = uint32(tun.mtu) - tun.log.Infof("Interface name: %s", ar.ifra_name) - tun.log.Infof("Interface IPv6: %s", addr) - tun.log.Infof("Interface MTU: %d", ir.ifru_mtu) + tun.Log.Infof("Interface name: %s", ar.ifra_name) + tun.Log.Infof("Interface IPv6: %s", addr) + tun.Log.Infof("Interface MTU: %d", ir.ifru_mtu) if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(darwin_SIOCAIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 { err = errno - tun.log.Errorf("Error in darwin_SIOCAIFADDR_IN6: %v", errno) + tun.Log.Errorf("Error in darwin_SIOCAIFADDR_IN6: %v", errno) return err } if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 { err = errno - tun.log.Errorf("Error in SIOCSIFMTU: %v", errno) + tun.Log.Errorf("Error in SIOCSIFMTU: %v", errno) return err } diff --git a/src/tuntap/tun_linux.go b/src/tuntap/tun_linux.go index c9c03c09..7d228574 100644 --- a/src/tuntap/tun_linux.go +++ b/src/tuntap/tun_linux.go @@ -40,9 +40,9 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int } } // Friendly output - tun.log.Infof("Interface name: %s", tun.iface.Name()) - tun.log.Infof("Interface IPv6: %s", addr) - tun.log.Infof("Interface MTU: %d", tun.mtu) + tun.Log.Infof("Interface name: %s", tun.iface.Name()) + tun.Log.Infof("Interface IPv6: %s", addr) + tun.Log.Infof("Interface MTU: %d", tun.mtu) return tun.setupAddress(addr) } diff --git a/src/tuntap/tun_other.go b/src/tuntap/tun_other.go index 48276b49..bb302d19 100644 --- a/src/tuntap/tun_other.go +++ b/src/tuntap/tun_other.go @@ -28,6 +28,6 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int // We don't know how to set the IPv6 address on an unknown platform, therefore // write about it to stdout and don't try to do anything further. func (tun *TunAdapter) setupAddress(addr string) error { - tun.log.Warnln("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr) + tun.Log.Warnln("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr) return nil } diff --git a/src/tuntap/tun_windows.go b/src/tuntap/tun_windows.go index 8a66ac62..5a158b14 100644 --- a/src/tuntap/tun_windows.go +++ b/src/tuntap/tun_windows.go @@ -15,7 +15,7 @@ import ( // delegate the hard work to "netsh". func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { if !iftapmode { - tun.log.Warnln("TUN mode is not supported on this platform, defaulting to TAP") + tun.Log.Warnln("TUN mode is not supported on this platform, defaulting to TAP") } config := water.Config{DeviceType: water.TAP} config.PlatformSpecificParams.ComponentID = "tap0901" @@ -31,19 +31,19 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int } // Disable/enable the interface to resets its configuration (invalidating iface) cmd := exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=DISABLED") - tun.log.Printf("netsh command: %v", strings.Join(cmd.Args, " ")) + tun.Log.Printf("netsh command: %v", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.log.Errorf("Windows netsh failed: %v.", err) - tun.log.Traceln(string(output)) + tun.Log.Errorf("Windows netsh failed: %v.", err) + tun.Log.Traceln(string(output)) return err } cmd = exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=ENABLED") - tun.log.Printf("netsh command: %v", strings.Join(cmd.Args, " ")) + tun.Log.Printf("netsh command: %v", strings.Join(cmd.Args, " ")) output, err = cmd.CombinedOutput() if err != nil { - tun.log.Errorf("Windows netsh failed: %v.", err) - tun.log.Traceln(string(output)) + tun.Log.Errorf("Windows netsh failed: %v.", err) + tun.Log.Traceln(string(output)) return err } // Get a new iface @@ -58,9 +58,9 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int panic(err) } // Friendly output - tun.log.Infof("Interface name: %s", tun.iface.Name()) - tun.log.Infof("Interface IPv6: %s", addr) - tun.log.Infof("Interface MTU: %d", tun.mtu) + tun.Log.Infof("Interface name: %s", tun.iface.Name()) + tun.Log.Infof("Interface IPv6: %s", addr) + tun.Log.Infof("Interface MTU: %d", tun.mtu) return tun.setupAddress(addr) } @@ -71,11 +71,11 @@ func (tun *TunAdapter) setupMTU(mtu int) error { fmt.Sprintf("interface=%s", tun.iface.Name()), fmt.Sprintf("mtu=%d", mtu), "store=active") - tun.log.Debugln("netsh command: %v", strings.Join(cmd.Args, " ")) + tun.Log.Debugln("netsh command: %v", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.log.Errorf("Windows netsh failed: %v.", err) - tun.log.Traceln(string(output)) + tun.Log.Errorf("Windows netsh failed: %v.", err) + tun.Log.Traceln(string(output)) return err } return nil @@ -88,11 +88,11 @@ func (tun *TunAdapter) setupAddress(addr string) error { fmt.Sprintf("interface=%s", tun.iface.Name()), fmt.Sprintf("addr=%s", addr), "store=active") - tun.log.Debugln("netsh command: %v", strings.Join(cmd.Args, " ")) + tun.Log.Debugln("netsh command: %v", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.log.Errorf("Windows netsh failed: %v.", err) - tun.log.Traceln(string(output)) + tun.Log.Errorf("Windows netsh failed: %v.", err) + tun.Log.Traceln(string(output)) return err } return nil diff --git a/src/yggdrasil/adapter.go b/src/yggdrasil/adapter.go index e5b1707d..8fadb195 100644 --- a/src/yggdrasil/adapter.go +++ b/src/yggdrasil/adapter.go @@ -6,13 +6,15 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/config" ) -// Defines the minimum required struct members for an adapter type. This is now -// the base type for adapters like tun.go. When implementing a new adapter type, -// you should extend the adapter struct with this one and should call the -// Adapter.Init() function when initialising. +// Adapter defines the minimum required struct members for an adapter type. This +// is now the base type for adapters like tun.go. When implementing a new +// adapter type, you should extend the adapter struct with this one and should +// call the Adapter.Init() function when initialising. type Adapter struct { adapterImplementation Core *Core + Config *config.NodeState + Log *log.Logger Send chan<- []byte Recv <-chan []byte Reject <-chan RejectedPacket @@ -31,11 +33,13 @@ type adapterImplementation interface { Close() error } -// Initialises the adapter with the necessary channels to operate from the +// Init initialises the adapter with the necessary channels to operate from the // router. When defining a new Adapter type, the Adapter should call this // function from within it's own Init function to set up the channels. It is // otherwise not expected for you to call this function directly. func (adapter *Adapter) Init(config *config.NodeState, log *log.Logger, send chan<- []byte, recv <-chan []byte, reject <-chan RejectedPacket) { + adapter.Config = config + adapter.Log = log adapter.Send = send adapter.Recv = recv adapter.Reject = reject From 90feae6a7d24124a01a2e32975851117e0328b97 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 1 Apr 2019 20:12:39 +0100 Subject: [PATCH 0018/1109] Comment out AWDL (doesn't work in iOS properly) and move out of main package --- src/{yggdrasil => mobile}/awdl.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) rename src/{yggdrasil => mobile}/awdl.go (98%) diff --git a/src/yggdrasil/awdl.go b/src/mobile/awdl.go similarity index 98% rename from src/yggdrasil/awdl.go rename to src/mobile/awdl.go index 5e8cce16..4fb43867 100644 --- a/src/yggdrasil/awdl.go +++ b/src/mobile/awdl.go @@ -1,4 +1,5 @@ -package yggdrasil +/* +package mobile import ( "errors" @@ -104,3 +105,4 @@ func (a *awdl) shutdown(identity string) error { } return errors.New("Interface not found or already closed") } +*/ From 2e72c7c93d9d9a6306d8b669a823cd395c057a44 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 1 Apr 2019 22:45:30 +0100 Subject: [PATCH 0019/1109] Fix mobile logging --- src/mobile/awdl.go | 2 +- src/mobile/mobile.go | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/mobile/awdl.go b/src/mobile/awdl.go index 4fb43867..9a073ef7 100644 --- a/src/mobile/awdl.go +++ b/src/mobile/awdl.go @@ -1,6 +1,6 @@ -/* package mobile +/* import ( "errors" "io" diff --git a/src/mobile/mobile.go b/src/mobile/mobile.go index 5eec96e8..02bd30fe 100644 --- a/src/mobile/mobile.go +++ b/src/mobile/mobile.go @@ -49,6 +49,9 @@ func (m *Yggdrasil) addStaticPeers(cfg *config.NodeConfig) { // StartAutoconfigure starts a node with a randomly generated config func (m *Yggdrasil) StartAutoconfigure() error { logger := log.New(m.log, "", 0) + logger.EnableLevel("error") + logger.EnableLevel("warn") + logger.EnableLevel("info") nc := config.GenerateConfig() nc.IfName = "dummy" nc.AdminListen = "tcp://localhost:9001" @@ -62,6 +65,7 @@ func (m *Yggdrasil) StartAutoconfigure() error { } state, err := m.core.Start(nc, logger) if err != nil { + logger.Errorln("An error occured starting Yggdrasil:", err) return err } m.multicast.Init(&m.core, state, logger, nil) @@ -76,6 +80,9 @@ func (m *Yggdrasil) StartAutoconfigure() error { // (rather than HJSON) by using the GenerateConfigJSON() function func (m *Yggdrasil) StartJSON(configjson []byte) error { logger := log.New(m.log, "", 0) + logger.EnableLevel("error") + logger.EnableLevel("warn") + logger.EnableLevel("info") nc := config.GenerateConfig() var dat map[string]interface{} if err := hjson.Unmarshal(configjson, &dat); err != nil { @@ -91,6 +98,7 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error { } state, err := m.core.Start(nc, logger) if err != nil { + logger.Errorln("An error occured starting Yggdrasil:", err) return err } m.multicast.Init(&m.core, state, logger, nil) From 4488189a752b9c51d1755e8f7546819e0043731c Mon Sep 17 00:00:00 2001 From: cathugger Date: Sat, 6 Apr 2019 21:34:47 +0300 Subject: [PATCH 0020/1109] wire: cleaner and faster wire_intToUint and wire_intFromUint Bit operations are much faster on most processors than multiplication. Also specify that it's zigzag to ease finding additional documentation for it. --- src/yggdrasil/wire.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/wire.go b/src/yggdrasil/wire.go index 8891f1ab..1bb4d90f 100644 --- a/src/yggdrasil/wire.go +++ b/src/yggdrasil/wire.go @@ -72,13 +72,16 @@ func wire_decode_uint64(bs []byte) (uint64, int) { // Non-negative integers are mapped to even integers: 0 -> 0, 1 -> 2, etc. // Negative integers are mapped to odd integers: -1 -> 1, -2 -> 3, etc. // This means the least significant bit is a sign bit. +// This is known as zigzag encoding. func wire_intToUint(i int64) uint64 { - return ((uint64(-(i+1))<<1)|0x01)*(uint64(i)>>63) + (uint64(i)<<1)*(^uint64(i)>>63) + // signed arithmetic shift + return uint64((i >> 63) ^ (i << 1)) } // Converts uint64 back to int64, genreally when being read from the wire. func wire_intFromUint(u uint64) int64 { - return int64(u&0x01)*(-int64(u>>1)-1) + int64(^u&0x01)*int64(u>>1) + // non-arithmetic shift + return int64((u >> 1) ^ -(u & 1)) } //////////////////////////////////////////////////////////////////////////////// From b5e3b05e77e9b0476bffaeea71d2caa17ee90bb6 Mon Sep 17 00:00:00 2001 From: Colin Reeder Date: Sun, 7 Apr 2019 13:06:24 -0600 Subject: [PATCH 0021/1109] Add armel support to deb script --- contrib/deb/generate.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/deb/generate.sh b/contrib/deb/generate.sh index 4433a9d9..00f6536f 100644 --- a/contrib/deb/generate.sh +++ b/contrib/deb/generate.sh @@ -27,6 +27,7 @@ elif [ $PKGARCH = "mipsel" ]; then GOARCH=mipsle GOOS=linux ./build elif [ $PKGARCH = "mips" ]; then GOARCH=mips64 GOOS=linux ./build elif [ $PKGARCH = "armhf" ]; then GOARCH=arm GOOS=linux GOARM=6 ./build elif [ $PKGARCH = "arm64" ]; then GOARCH=arm64 GOOS=linux ./build +elif [ $PKGARCH = "armel" ]; then GOARCH=arm GOOS=linux GOARM=5 ./build else echo "Specify PKGARCH=amd64,i386,mips,mipsel,armhf,arm64" exit 1 From 2465ad03849771587c8a88c5269672d3eb20fa10 Mon Sep 17 00:00:00 2001 From: Colin Reeder Date: Sun, 7 Apr 2019 16:14:37 -0600 Subject: [PATCH 0022/1109] Add armel to PKGARCH usage list --- contrib/deb/generate.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/deb/generate.sh b/contrib/deb/generate.sh index 00f6536f..90707a7c 100644 --- a/contrib/deb/generate.sh +++ b/contrib/deb/generate.sh @@ -29,7 +29,7 @@ elif [ $PKGARCH = "armhf" ]; then GOARCH=arm GOOS=linux GOARM=6 ./build elif [ $PKGARCH = "arm64" ]; then GOARCH=arm64 GOOS=linux ./build elif [ $PKGARCH = "armel" ]; then GOARCH=arm GOOS=linux GOARM=5 ./build else - echo "Specify PKGARCH=amd64,i386,mips,mipsel,armhf,arm64" + echo "Specify PKGARCH=amd64,i386,mips,mipsel,armhf,arm64,armel" exit 1 fi From 9bc24f8dbfff90344f765c3a8ea3c965f537308f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 15 Apr 2019 22:00:38 +0100 Subject: [PATCH 0023/1109] Return both current and previous config when replacing --- src/config/config.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/config/config.go b/src/config/config.go index 861e57ac..8137cac9 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -23,13 +23,14 @@ func (s *NodeState) Get() (NodeConfig, NodeConfig) { return s.Current, s.Previous } -// Replace the node configuration with new configuration -func (s *NodeState) Replace(n NodeConfig) NodeConfig { +// Replace the node configuration with new configuration. This method returns +// both the new and the previous node configs +func (s *NodeState) Replace(n NodeConfig) (NodeConfig, NodeConfig) { s.Mutex.Lock() defer s.Mutex.Unlock() s.Previous = s.Current s.Current = n - return s.Current + return s.Current, s.Previous } // NodeConfig defines all configuration values needed to run a signle yggdrasil node From eef2a02d0ab2f329adcb38b42da3fd44944359d2 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 18 Apr 2019 16:38:24 +0100 Subject: [PATCH 0024/1109] Experiment with new API --- src/crypto/crypto.go | 9 ++++ src/yggdrasil/api.go | 113 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 src/yggdrasil/api.go diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go index c974a3c0..b47db184 100644 --- a/src/crypto/crypto.go +++ b/src/crypto/crypto.go @@ -13,6 +13,7 @@ It also defines NodeID and TreeID as hashes of keys, and wraps hash functions import ( "crypto/rand" "crypto/sha512" + "encoding/hex" "golang.org/x/crypto/ed25519" "golang.org/x/crypto/nacl/box" @@ -32,6 +33,14 @@ type NodeID [NodeIDLen]byte type TreeID [TreeIDLen]byte type Handle [handleLen]byte +func (n *NodeID) String() string { + return hex.EncodeToString(n[:]) +} + +func (n *NodeID) Network() string { + return "nodeid" +} + func GetNodeID(pub *BoxPubKey) *NodeID { h := sha512.Sum512(pub[:]) return (*NodeID)(&h) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go new file mode 100644 index 00000000..9181269d --- /dev/null +++ b/src/yggdrasil/api.go @@ -0,0 +1,113 @@ +package yggdrasil + +import ( + "encoding/hex" + "errors" + "time" + + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" +) + +func (c *Core) Dial(network, address string) (Conn, error) { + var nodeID *crypto.NodeID + var nodeMask *crypto.NodeID + // Process + switch network { + case "nodeid": + // A node ID was provided - we don't need to do anything special with it + dest, err := hex.DecodeString(address) + if err != nil { + return Conn{}, err + } + copy(nodeID[:], dest) + var m crypto.NodeID + for i := range dest { + m[i] = 0xFF + } + copy(nodeMask[:], m[:]) + default: + // An unexpected address type was given, so give up + return Conn{}, errors.New("unexpected address type") + } + // Try and search for the node on the network + doSearch := func() { + sinfo, isIn := c.searches.searches[*nodeID] + if !isIn { + sinfo = c.searches.newIterSearch(nodeID, nodeMask) + } + c.searches.continueSearch(sinfo) + } + var sinfo *sessionInfo + var isIn bool + switch { + case !isIn || !sinfo.init: + // No or unintiialized session, so we need to search first + doSearch() + case time.Since(sinfo.time) > 6*time.Second: + if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second { + // We haven't heard from the dest in a while + // We tried pinging but didn't get a response + // They may have changed coords + // Try searching to discover new coords + // Note that search spam is throttled internally + doSearch() + } else { + // We haven't heard about the dest in a while + now := time.Now() + if !sinfo.time.Before(sinfo.pingTime) { + // Update pingTime to start the clock for searches (above) + sinfo.pingTime = now + } + if time.Since(sinfo.pingSend) > time.Second { + // Send at most 1 ping per second + sinfo.pingSend = now + c.sessions.sendPingPong(sinfo, false) + } + } + } + return Conn{ + session: sinfo, + }, nil +} + +type Conn struct { + session *sessionInfo + readDeadline time.Time + writeDeadline time.Time +} + +func (c *Conn) Read(b []byte) (int, error) { + return 0, nil +} + +func (c *Conn) Write(b []byte) (int, error) { + return 0, nil +} + +func (c *Conn) Close() error { + return nil +} + +func (c *Conn) LocalAddr() crypto.NodeID { + return *crypto.GetNodeID(&c.session.core.boxPub) +} + +func (c *Conn) RemoteAddr() crypto.NodeID { + return *crypto.GetNodeID(&c.session.theirPermPub) +} + +func (c *Conn) SetDeadline(t time.Time) error { + c.SetReadDeadline(t) + c.SetWriteDeadline(t) + return nil +} + +func (c *Conn) SetReadDeadline(t time.Time) error { + c.readDeadline = t + return nil +} + +func (c *Conn) SetWriteDeadline(t time.Time) error { + c.writeDeadline = t + return nil +} From 160e01e84f82763d77e7bbfd5863e8192707da7f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 18 Apr 2019 23:38:23 +0100 Subject: [PATCH 0025/1109] Searches called from api.go, various other tweaks, searches now have a callback for success/failure, node ID now reported by admin socket --- cmd/yggdrasil/main.go | 13 +++++ cmd/yggdrasilctl/main.go | 3 ++ src/yggdrasil/admin.go | 2 + src/yggdrasil/api.go | 108 +++++++++++++++++++++++++++++++-------- src/yggdrasil/router.go | 8 ++- src/yggdrasil/search.go | 43 +++++++++------- src/yggdrasil/session.go | 81 ++++++++++++++--------------- 7 files changed, 177 insertions(+), 81 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index fd8cd7b6..36866b3f 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -10,6 +10,7 @@ import ( "os/signal" "strings" "syscall" + "time" "golang.org/x/text/encoding/unicode" @@ -267,6 +268,18 @@ func main() { defer func() { n.core.Stop() }() + // Some stuff + go func() { + time.Sleep(time.Second * 2) + session, err := n.core.Dial("nodeid", "babd4e4bccb216f77bb723c1b034b63a652060aabfe9506b51f687183e9b0fd13f438876f5a3ab21cac9c8101eb88e2613fe2a8b0724add09d7ef5a72146c31f") + logger.Println(session, err) + b := []byte{1, 2, 3, 4, 5} + for { + logger.Println(session.Write(b)) + logger.Println(session.Read(b)) + time.Sleep(time.Second) + } + }() // Make some nice output that tells us what our IPv6 address and subnet are. // This is just logged to stdout for the user. address := n.core.Address() diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index b8864dc3..b70bbe9e 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -277,6 +277,9 @@ func main() { fmt.Println("Coords:", coords) } if *verbose { + if nodeID, ok := v.(map[string]interface{})["node_id"].(string); ok { + fmt.Println("Node ID:", nodeID) + } if boxPubKey, ok := v.(map[string]interface{})["box_pub_key"].(string); ok { fmt.Println("Public encryption key:", boxPubKey) } diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index a9595f8c..0f91cd15 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -640,7 +640,9 @@ func (a *admin) startTunWithMTU(ifname string, iftapmode bool, ifmtu int) error func (a *admin) getData_getSelf() *admin_nodeInfo { table := a.core.switchTable.table.Load().(lookupTable) coords := table.self.getCoords() + nodeid := *crypto.GetNodeID(&a.core.boxPub) self := admin_nodeInfo{ + {"node_id", hex.EncodeToString(nodeid[:])}, {"box_pub_key", hex.EncodeToString(a.core.boxPub[:])}, {"ip", a.core.Address().String()}, {"subnet", a.core.Subnet().String()}, diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 9181269d..0b38525b 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -6,11 +6,13 @@ import ( "time" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/util" ) func (c *Core) Dial(network, address string) (Conn, error) { - var nodeID *crypto.NodeID - var nodeMask *crypto.NodeID + conn := Conn{} + nodeID := crypto.NodeID{} + nodeMask := crypto.NodeID{} // Process switch network { case "nodeid": @@ -20,22 +22,51 @@ func (c *Core) Dial(network, address string) (Conn, error) { return Conn{}, err } copy(nodeID[:], dest) - var m crypto.NodeID - for i := range dest { - m[i] = 0xFF + for i := range nodeMask { + nodeMask[i] = 0xFF } - copy(nodeMask[:], m[:]) default: // An unexpected address type was given, so give up return Conn{}, errors.New("unexpected address type") } + conn.core = c + conn.nodeID = &nodeID + conn.nodeMask = &nodeMask + conn.core.router.doAdmin(func() { + conn.startSearch() + }) + return conn, nil +} + +type Conn struct { + core *Core + nodeID *crypto.NodeID + nodeMask *crypto.NodeID + session *sessionInfo + readDeadline time.Time + writeDeadline time.Time +} + +// This method should only be called from the router goroutine +func (c *Conn) startSearch() { + searchCompleted := func(sinfo *sessionInfo, err error) { + if err != nil { + c.core.log.Debugln("DHT search failed:", err) + return + } + if sinfo != nil { + c.session = sinfo + c.core.log.Println("Search from API found", hex.EncodeToString(sinfo.theirPermPub[:])) + } + } // Try and search for the node on the network doSearch := func() { - sinfo, isIn := c.searches.searches[*nodeID] + sinfo, isIn := c.core.searches.searches[*c.nodeID] if !isIn { - sinfo = c.searches.newIterSearch(nodeID, nodeMask) + c.core.log.Debugln("Starting search for", hex.EncodeToString(c.nodeID[:])) + sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) } - c.searches.continueSearch(sinfo) + c.core.searches.continueSearch(sinfo) } var sinfo *sessionInfo var isIn bool @@ -61,27 +92,62 @@ func (c *Core) Dial(network, address string) (Conn, error) { if time.Since(sinfo.pingSend) > time.Second { // Send at most 1 ping per second sinfo.pingSend = now - c.sessions.sendPingPong(sinfo, false) + c.core.sessions.sendPingPong(sinfo, false) } } } - return Conn{ - session: sinfo, - }, nil -} - -type Conn struct { - session *sessionInfo - readDeadline time.Time - writeDeadline time.Time } func (c *Conn) Read(b []byte) (int, error) { - return 0, nil + if c.session == nil { + return 0, errors.New("invalid session") + } + p := <-c.session.recv + defer util.PutBytes(p.Payload) + if !c.session.nonceIsOK(&p.Nonce) { + return 0, errors.New("invalid nonce") + } + bs, isOK := crypto.BoxOpen(&c.session.sharedSesKey, p.Payload, &p.Nonce) + if !isOK { + util.PutBytes(bs) + return 0, errors.New("failed to decrypt") + } + b = b[:0] + b = append(b, bs...) + c.session.updateNonce(&p.Nonce) + c.session.time = time.Now() + c.session.bytesRecvd += uint64(len(bs)) + return len(b), nil } func (c *Conn) Write(b []byte) (int, error) { - return 0, nil + if c.session == nil { + c.core.router.doAdmin(func() { + c.startSearch() + }) + return 0, errors.New("invalid session") + } + defer util.PutBytes(b) + if !c.session.init { + // To prevent using empty session keys + return 0, errors.New("session not initialised") + } + // code isn't multithreaded so appending to this is safe + coords := c.session.coords + // Prepare the payload + payload, nonce := crypto.BoxSeal(&c.session.sharedSesKey, b, &c.session.myNonce) + defer util.PutBytes(payload) + p := wire_trafficPacket{ + Coords: coords, + Handle: c.session.theirHandle, + Nonce: *nonce, + Payload: payload, + } + packet := p.encode() + c.session.bytesSent += uint64(len(b)) + c.session.send <- packet + //c.session.core.router.out(packet) + return len(b), nil } func (c *Conn) Close() error { diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index a3d3d68c..d7923f51 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -245,6 +245,12 @@ func (r *router) sendPacket(bs []byte) { return } } + searchCompleted := func(sinfo *sessionInfo, err error) { + if err != nil { + r.core.log.Debugln("DHT search failed:", err) + return + } + } doSearch := func(packet []byte) { var nodeID, mask *crypto.NodeID switch { @@ -270,7 +276,7 @@ func (r *router) sendPacket(bs []byte) { } sinfo, isIn := r.core.searches.searches[*nodeID] if !isIn { - sinfo = r.core.searches.newIterSearch(nodeID, mask) + sinfo = r.core.searches.newIterSearch(nodeID, mask, searchCompleted) } if packet != nil { sinfo.packet = packet diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index c391dda2..e81a9723 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -15,6 +15,7 @@ package yggdrasil // Some kind of max search steps, in case the node is offline, so we don't crawl through too much of the network looking for a destination that isn't there? import ( + "errors" "sort" "time" @@ -32,12 +33,13 @@ const search_RETRY_TIME = time.Second // Information about an ongoing search. // Includes the target NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited. type searchInfo struct { - dest crypto.NodeID - mask crypto.NodeID - time time.Time - packet []byte - toVisit []*dhtInfo - visited map[crypto.NodeID]bool + dest crypto.NodeID + mask crypto.NodeID + time time.Time + packet []byte + toVisit []*dhtInfo + visited map[crypto.NodeID]bool + callback func(*sessionInfo, error) } // This stores a map of active searches. @@ -61,7 +63,7 @@ func (s *searches) init(core *Core) { } // Creates a new search info, adds it to the searches struct, and returns a pointer to the info. -func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID) *searchInfo { +func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo { now := time.Now() for dest, sinfo := range s.searches { if now.Sub(sinfo.time) > time.Minute { @@ -69,9 +71,10 @@ func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID) *searc } } info := searchInfo{ - dest: *dest, - mask: *mask, - time: now.Add(-time.Second), + dest: *dest, + mask: *mask, + time: now.Add(-time.Second), + callback: callback, } s.searches[*dest] = &info return &info @@ -137,15 +140,15 @@ func (s *searches) doSearchStep(sinfo *searchInfo) { if len(sinfo.toVisit) == 0 { // Dead end, do cleanup delete(s.searches, sinfo.dest) + sinfo.callback(nil, errors.New("search reached dead end")) return - } else { - // Send to the next search target - var next *dhtInfo - next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:] - rq := dhtReqKey{next.key, sinfo.dest} - s.core.dht.addCallback(&rq, s.handleDHTRes) - s.core.dht.ping(next, &sinfo.dest) } + // Send to the next search target + var next *dhtInfo + next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:] + rq := dhtReqKey{next.key, sinfo.dest} + s.core.dht.addCallback(&rq, s.handleDHTRes) + s.core.dht.ping(next, &sinfo.dest) } // If we've recenty sent a ping for this search, do nothing. @@ -173,8 +176,8 @@ func (s *searches) continueSearch(sinfo *searchInfo) { } // Calls create search, and initializes the iterative search parts of the struct before returning it. -func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID) *searchInfo { - sinfo := s.createSearch(dest, mask) +func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo { + sinfo := s.createSearch(dest, mask, callback) sinfo.toVisit = s.core.dht.lookup(dest, true) sinfo.visited = make(map[crypto.NodeID]bool) return sinfo @@ -200,6 +203,7 @@ func (s *searches) checkDHTRes(info *searchInfo, res *dhtRes) bool { sinfo = s.core.sessions.createSession(&res.Key) if sinfo == nil { // nil if the DHT search finished but the session wasn't allowed + info.callback(nil, errors.New("session not allowed")) return true } _, isIn := s.core.sessions.getByTheirPerm(&res.Key) @@ -211,6 +215,7 @@ func (s *searches) checkDHTRes(info *searchInfo, res *dhtRes) bool { sinfo.coords = res.Coords sinfo.packet = info.packet s.core.sessions.ping(sinfo) + info.callback(sinfo, nil) // Cleanup delete(s.searches, res.Dest) return true diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 74255c09..fd3a985b 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -7,6 +7,7 @@ package yggdrasil import ( "bytes" "encoding/hex" + "sync" "time" "github.com/yggdrasil-network/yggdrasil-go/src/address" @@ -17,35 +18,38 @@ import ( // All the information we know about an active session. // This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API. type sessionInfo struct { - core *Core - reconfigure chan chan error - theirAddr address.Address - theirSubnet address.Subnet - theirPermPub crypto.BoxPubKey - theirSesPub crypto.BoxPubKey - mySesPub crypto.BoxPubKey - mySesPriv crypto.BoxPrivKey - sharedSesKey crypto.BoxSharedKey // derived from session keys - theirHandle crypto.Handle - myHandle crypto.Handle - theirNonce crypto.BoxNonce - myNonce crypto.BoxNonce - theirMTU uint16 - myMTU uint16 - wasMTUFixed bool // Was the MTU fixed by a receive error? - time time.Time // Time we last received a packet - coords []byte // coords of destination - packet []byte // a buffered packet, sent immediately on ping/pong - init bool // Reset if coords change - send chan []byte - recv chan *wire_trafficPacket - nonceMask uint64 - tstamp int64 // tstamp from their last session ping, replay attack mitigation - mtuTime time.Time // time myMTU was last changed - pingTime time.Time // time the first ping was sent since the last received packet - pingSend time.Time // time the last ping was sent - bytesSent uint64 // Bytes of real traffic sent in this session - bytesRecvd uint64 // Bytes of real traffic received in this session + core *Core + reconfigure chan chan error + theirAddr address.Address + theirSubnet address.Subnet + theirPermPub crypto.BoxPubKey + theirSesPub crypto.BoxPubKey + mySesPub crypto.BoxPubKey + mySesPriv crypto.BoxPrivKey + sharedSesKey crypto.BoxSharedKey // derived from session keys + theirHandle crypto.Handle + myHandle crypto.Handle + theirNonce crypto.BoxNonce + theirNonceMutex sync.RWMutex // protects the above + myNonce crypto.BoxNonce + myNonceMutex sync.RWMutex // protects the above + theirMTU uint16 + myMTU uint16 + wasMTUFixed bool // Was the MTU fixed by a receive error? + time time.Time // Time we last received a packet + coords []byte // coords of destination + packet []byte // a buffered packet, sent immediately on ping/pong + init bool // Reset if coords change + send chan []byte + recv chan *wire_trafficPacket + nonceMask uint64 + tstamp int64 // tstamp from their last session ping, replay attack mitigation + tstampMutex int64 // protects the above + mtuTime time.Time // time myMTU was last changed + pingTime time.Time // time the first ping was sent since the last received packet + pingSend time.Time // time the last ping was sent + bytesSent uint64 // Bytes of real traffic sent in this session + bytesRecvd uint64 // Bytes of real traffic received in this session } // Represents a session ping/pong packet, andincludes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU. @@ -101,17 +105,14 @@ func (s *sessionInfo) timedout() bool { // Sessions are indexed by handle. // Additionally, stores maps of address/subnet onto keys, and keys onto handles. type sessions struct { - core *Core - reconfigure chan chan error - lastCleanup time.Time - // Maps known permanent keys to their shared key, used by DHT a lot - permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey - // Maps (secret) handle onto session info - sinfos map[crypto.Handle]*sessionInfo - // Maps mySesPub onto handle - byMySes map[crypto.BoxPubKey]*crypto.Handle - // Maps theirPermPub onto handle - byTheirPerm map[crypto.BoxPubKey]*crypto.Handle + core *Core + reconfigure chan chan error + lastCleanup time.Time + permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey // Maps known permanent keys to their shared key, used by DHT a lot + sinfos map[crypto.Handle]*sessionInfo // Maps (secret) handle onto session info + conns map[crypto.Handle]*Conn // Maps (secret) handle onto connections + byMySes map[crypto.BoxPubKey]*crypto.Handle // Maps mySesPub onto handle + byTheirPerm map[crypto.BoxPubKey]*crypto.Handle // Maps theirPermPub onto handle addrToPerm map[address.Address]*crypto.BoxPubKey subnetToPerm map[address.Subnet]*crypto.BoxPubKey } From b2f4f2e1b66cef8008e002d1e253d55c5275b186 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 19 Apr 2019 00:07:26 +0100 Subject: [PATCH 0026/1109] Update errors, update Write --- cmd/yggdrasil/main.go | 3 ++- src/yggdrasil/api.go | 10 +++++----- src/yggdrasil/session.go | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 36866b3f..8a810bc5 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -271,7 +271,8 @@ func main() { // Some stuff go func() { time.Sleep(time.Second * 2) - session, err := n.core.Dial("nodeid", "babd4e4bccb216f77bb723c1b034b63a652060aabfe9506b51f687183e9b0fd13f438876f5a3ab21cac9c8101eb88e2613fe2a8b0724add09d7ef5a72146c31f") + //session, err := n.core.Dial("nodeid", "babd4e4bccb216f77bb723c1b034b63a652060aabfe9506b51f687183e9b0fd13f438876f5a3ab21cac9c8101eb88e2613fe2a8b0724add09d7ef5a72146c31f") + session, err := n.core.Dial("nodeid", "9890e135604e8aa6039a909e40c629824d852042a70e51957d5b9d700195663d50552e8e869af132b4617d76f8ef00314d94cce23aa8d6b051b3b952a32a4966") logger.Println(session, err) b := []byte{1, 2, 3, 4, 5} for { diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 0b38525b..41ef8c06 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -100,17 +100,17 @@ func (c *Conn) startSearch() { func (c *Conn) Read(b []byte) (int, error) { if c.session == nil { - return 0, errors.New("invalid session") + return 0, errors.New("session not open") } p := <-c.session.recv defer util.PutBytes(p.Payload) if !c.session.nonceIsOK(&p.Nonce) { - return 0, errors.New("invalid nonce") + return 0, errors.New("packet dropped due to invalid nonce") } bs, isOK := crypto.BoxOpen(&c.session.sharedSesKey, p.Payload, &p.Nonce) if !isOK { util.PutBytes(bs) - return 0, errors.New("failed to decrypt") + return 0, errors.New("packet dropped due to decryption failure") } b = b[:0] b = append(b, bs...) @@ -125,7 +125,7 @@ func (c *Conn) Write(b []byte) (int, error) { c.core.router.doAdmin(func() { c.startSearch() }) - return 0, errors.New("invalid session") + return 0, errors.New("session not open") } defer util.PutBytes(b) if !c.session.init { @@ -146,7 +146,7 @@ func (c *Conn) Write(b []byte) (int, error) { packet := p.encode() c.session.bytesSent += uint64(len(b)) c.session.send <- packet - //c.session.core.router.out(packet) + c.session.core.router.out(packet) return len(b), nil } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index fd3a985b..4c896f2a 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -523,7 +523,7 @@ func (ss *sessions) resetInits() { } //////////////////////////////////////////////////////////////////////////////// - +/* // This is for a per-session worker. // It handles calling the relatively expensive crypto operations. // It's also responsible for checking nonces and dropping out-of-date/duplicate packets, or else calling the function to update nonces if the packet is OK. From b20c8b6da5029d49d8beb74f204a90eb2e91e8b5 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 19 Apr 2019 00:11:43 +0100 Subject: [PATCH 0027/1109] Move some things around a bit, delete session workers --- src/yggdrasil/{api.go => conn.go} | 29 ------- src/yggdrasil/core.go | 32 ++++++++ src/yggdrasil/session.go | 130 ------------------------------ 3 files changed, 32 insertions(+), 159 deletions(-) rename src/yggdrasil/{api.go => conn.go} (85%) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/conn.go similarity index 85% rename from src/yggdrasil/api.go rename to src/yggdrasil/conn.go index 41ef8c06..0ce626c9 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/conn.go @@ -9,35 +9,6 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/util" ) -func (c *Core) Dial(network, address string) (Conn, error) { - conn := Conn{} - nodeID := crypto.NodeID{} - nodeMask := crypto.NodeID{} - // Process - switch network { - case "nodeid": - // A node ID was provided - we don't need to do anything special with it - dest, err := hex.DecodeString(address) - if err != nil { - return Conn{}, err - } - copy(nodeID[:], dest) - for i := range nodeMask { - nodeMask[i] = 0xFF - } - default: - // An unexpected address type was given, so give up - return Conn{}, errors.New("unexpected address type") - } - conn.core = c - conn.nodeID = &nodeID - conn.nodeMask = &nodeMask - conn.core.router.doAdmin(func() { - conn.startSearch() - }) - return conn, nil -} - type Conn struct { core *Core nodeID *crypto.NodeID diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 037ef094..22caf08c 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -254,6 +254,38 @@ func (c *Core) Stop() { c.admin.close() } +// Dial opens a session to the given node. The first paramter should be "nodeid" +// and the second parameter should contain a hexadecimal representation of the +// target node ID. +func (c *Core) Dial(network, address string) (Conn, error) { + conn := Conn{} + nodeID := crypto.NodeID{} + nodeMask := crypto.NodeID{} + // Process + switch network { + case "nodeid": + // A node ID was provided - we don't need to do anything special with it + dest, err := hex.DecodeString(address) + if err != nil { + return Conn{}, err + } + copy(nodeID[:], dest) + for i := range nodeMask { + nodeMask[i] = 0xFF + } + default: + // An unexpected address type was given, so give up + return Conn{}, errors.New("unexpected address type") + } + conn.core = c + conn.nodeID = &nodeID + conn.nodeMask = &nodeMask + conn.core.router.doAdmin(func() { + conn.startSearch() + }) + return conn, nil +} + // ListenTCP starts a new TCP listener. The input URI should match that of the // "Listen" configuration item, e.g. // tcp://a.b.c.d:e diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 4c896f2a..a1835459 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -12,7 +12,6 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" - "github.com/yggdrasil-network/yggdrasil-go/src/util" ) // All the information we know about an active session. @@ -307,7 +306,6 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) sinfo.send = make(chan []byte, 32) sinfo.recv = make(chan *wire_trafficPacket, 32) - go sinfo.doWorker() ss.sinfos[sinfo.myHandle] = &sinfo ss.byMySes[sinfo.mySesPub] = &sinfo.myHandle ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle @@ -521,131 +519,3 @@ func (ss *sessions) resetInits() { sinfo.init = false } } - -//////////////////////////////////////////////////////////////////////////////// -/* -// This is for a per-session worker. -// It handles calling the relatively expensive crypto operations. -// It's also responsible for checking nonces and dropping out-of-date/duplicate packets, or else calling the function to update nonces if the packet is OK. -func (sinfo *sessionInfo) doWorker() { - send := make(chan []byte, 32) - defer close(send) - go func() { - for bs := range send { - sinfo.doSend(bs) - } - }() - recv := make(chan *wire_trafficPacket, 32) - defer close(recv) - go func() { - for p := range recv { - sinfo.doRecv(p) - } - }() - for { - select { - case p, ok := <-sinfo.recv: - if ok { - select { - case recv <- p: - default: - // We need something to not block, and it's best to drop it before we decrypt - util.PutBytes(p.Payload) - } - } else { - return - } - case bs, ok := <-sinfo.send: - if ok { - send <- bs - } else { - return - } - case e := <-sinfo.reconfigure: - e <- nil - } - } -} - -// This encrypts a packet, creates a trafficPacket struct, encodes it, and sends it to router.out to pass it to the switch layer. -func (sinfo *sessionInfo) doSend(bs []byte) { - defer util.PutBytes(bs) - if !sinfo.init { - // To prevent using empty session keys - return - } - // code isn't multithreaded so appending to this is safe - coords := sinfo.coords - // Work out the flowkey - this is used to determine which switch queue - // traffic will be pushed to in the event of congestion - var flowkey uint64 - // Get the IP protocol version from the packet - switch bs[0] & 0xf0 { - case 0x40: // IPv4 packet - // Check the packet meets minimum UDP packet length - if len(bs) >= 24 { - // Is the protocol TCP, UDP or SCTP? - if bs[9] == 0x06 || bs[9] == 0x11 || bs[9] == 0x84 { - ihl := bs[0] & 0x0f * 4 // Header length - flowkey = uint64(bs[9])<<32 /* proto */ | - uint64(bs[ihl+0])<<24 | uint64(bs[ihl+1])<<16 /* sport */ | - uint64(bs[ihl+2])<<8 | uint64(bs[ihl+3]) /* dport */ - } - } - case 0x60: // IPv6 packet - // Check if the flowlabel was specified in the packet header - flowkey = uint64(bs[1]&0x0f)<<16 | uint64(bs[2])<<8 | uint64(bs[3]) - // If the flowlabel isn't present, make protokey from proto | sport | dport - // if the packet meets minimum UDP packet length - if flowkey == 0 && len(bs) >= 48 { - // Is the protocol TCP, UDP or SCTP? - if bs[6] == 0x06 || bs[6] == 0x11 || bs[6] == 0x84 { - flowkey = uint64(bs[6])<<32 /* proto */ | - uint64(bs[40])<<24 | uint64(bs[41])<<16 /* sport */ | - uint64(bs[42])<<8 | uint64(bs[43]) /* dport */ - } - } - } - // If we have a flowkey, either through the IPv6 flowlabel field or through - // known TCP/UDP/SCTP proto-sport-dport triplet, then append it to the coords. - // Appending extra coords after a 0 ensures that we still target the local router - // but lets us send extra data (which is otherwise ignored) to help separate - // traffic streams into independent queues - if flowkey != 0 { - coords = append(coords, 0) // First target the local switchport - coords = wire_put_uint64(flowkey, coords) // Then variable-length encoded flowkey - } - // Prepare the payload - payload, nonce := crypto.BoxSeal(&sinfo.sharedSesKey, bs, &sinfo.myNonce) - defer util.PutBytes(payload) - p := wire_trafficPacket{ - Coords: coords, - Handle: sinfo.theirHandle, - Nonce: *nonce, - Payload: payload, - } - packet := p.encode() - sinfo.bytesSent += uint64(len(bs)) - sinfo.core.router.out(packet) -} - -// This takes a trafficPacket and checks the nonce. -// If the nonce is OK, it decrypts the packet. -// If the decrypted packet is OK, it calls router.recvPacket to pass the packet to the tun/tap. -// If a packet does not decrypt successfully, it assumes the packet was truncated, and updates the MTU accordingly. -// TODO? remove the MTU updating part? That should never happen with TCP peers, and the old UDP code that caused it was removed (and if replaced, should be replaced with something that can reliably send messages with an arbitrary size). -func (sinfo *sessionInfo) doRecv(p *wire_trafficPacket) { - defer util.PutBytes(p.Payload) - if !sinfo.nonceIsOK(&p.Nonce) { - return - } - bs, isOK := crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce) - if !isOK { - util.PutBytes(bs) - return - } - sinfo.updateNonce(&p.Nonce) - sinfo.time = time.Now() - sinfo.bytesRecvd += uint64(len(bs)) - sinfo.core.router.toRecv <- router_recvPacket{bs, sinfo} -} From c59372136212a785e9c2415845bde076a83c5a3b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 19 Apr 2019 00:33:54 +0100 Subject: [PATCH 0028/1109] Tweaks --- src/yggdrasil/conn.go | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 0ce626c9..bcf38ede 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -27,10 +27,11 @@ func (c *Conn) startSearch() { } if sinfo != nil { c.session = sinfo - c.core.log.Println("Search from API found", hex.EncodeToString(sinfo.theirPermPub[:])) + c.core.log.Println("Search from API succeeded") + c.core.log.Println("Pubkey:", hex.EncodeToString(sinfo.theirPermPub[:])) + c.core.log.Println("Coords:", sinfo.coords) } } - // Try and search for the node on the network doSearch := func() { sinfo, isIn := c.core.searches.searches[*c.nodeID] if !isIn { @@ -43,25 +44,16 @@ func (c *Conn) startSearch() { var isIn bool switch { case !isIn || !sinfo.init: - // No or unintiialized session, so we need to search first doSearch() case time.Since(sinfo.time) > 6*time.Second: if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second { - // We haven't heard from the dest in a while - // We tried pinging but didn't get a response - // They may have changed coords - // Try searching to discover new coords - // Note that search spam is throttled internally doSearch() } else { - // We haven't heard about the dest in a while now := time.Now() if !sinfo.time.Before(sinfo.pingTime) { - // Update pingTime to start the clock for searches (above) sinfo.pingTime = now } if time.Since(sinfo.pingSend) > time.Second { - // Send at most 1 ping per second sinfo.pingSend = now c.core.sessions.sendPingPong(sinfo, false) } @@ -73,6 +65,10 @@ func (c *Conn) Read(b []byte) (int, error) { if c.session == nil { return 0, errors.New("session not open") } + if !c.session.init { + // To prevent blocking forever on a session that isn't initialised + return 0, errors.New("session not initialised") + } p := <-c.session.recv defer util.PutBytes(p.Payload) if !c.session.nonceIsOK(&p.Nonce) { From ade684beffbaeb3e46f4afb623ed2f6e1bc5bc41 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 19 Apr 2019 10:55:15 +0100 Subject: [PATCH 0029/1109] Signal when a session is closed, other tweaks --- cmd/yggdrasil/main.go | 14 ------------ src/yggdrasil/conn.go | 48 +++++++++++++++++++++++----------------- src/yggdrasil/session.go | 4 ++++ 3 files changed, 32 insertions(+), 34 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 8a810bc5..fd8cd7b6 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -10,7 +10,6 @@ import ( "os/signal" "strings" "syscall" - "time" "golang.org/x/text/encoding/unicode" @@ -268,19 +267,6 @@ func main() { defer func() { n.core.Stop() }() - // Some stuff - go func() { - time.Sleep(time.Second * 2) - //session, err := n.core.Dial("nodeid", "babd4e4bccb216f77bb723c1b034b63a652060aabfe9506b51f687183e9b0fd13f438876f5a3ab21cac9c8101eb88e2613fe2a8b0724add09d7ef5a72146c31f") - session, err := n.core.Dial("nodeid", "9890e135604e8aa6039a909e40c629824d852042a70e51957d5b9d700195663d50552e8e869af132b4617d76f8ef00314d94cce23aa8d6b051b3b952a32a4966") - logger.Println(session, err) - b := []byte{1, 2, 3, 4, 5} - for { - logger.Println(session.Write(b)) - logger.Println(session.Read(b)) - time.Sleep(time.Second) - } - }() // Make some nice output that tells us what our IPv6 address and subnet are. // This is just logged to stdout for the user. address := n.core.Address() diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index bcf38ede..7aa46554 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -27,9 +27,6 @@ func (c *Conn) startSearch() { } if sinfo != nil { c.session = sinfo - c.core.log.Println("Search from API succeeded") - c.core.log.Println("Pubkey:", hex.EncodeToString(sinfo.theirPermPub[:])) - c.core.log.Println("Coords:", sinfo.coords) } } doSearch := func() { @@ -69,25 +66,32 @@ func (c *Conn) Read(b []byte) (int, error) { // To prevent blocking forever on a session that isn't initialised return 0, errors.New("session not initialised") } - p := <-c.session.recv - defer util.PutBytes(p.Payload) - if !c.session.nonceIsOK(&p.Nonce) { - return 0, errors.New("packet dropped due to invalid nonce") + select { + case p, ok := <-c.session.recv: + if !ok { + return 0, errors.New("session was closed") + } + defer util.PutBytes(p.Payload) + if !c.session.nonceIsOK(&p.Nonce) { + return 0, errors.New("packet dropped due to invalid nonce") + } + bs, isOK := crypto.BoxOpen(&c.session.sharedSesKey, p.Payload, &p.Nonce) + if !isOK { + util.PutBytes(bs) + return 0, errors.New("packet dropped due to decryption failure") + } + b = b[:0] + b = append(b, bs...) + c.session.updateNonce(&p.Nonce) + c.session.time = time.Now() + c.session.bytesRecvd += uint64(len(bs)) + return len(b), nil + case <-c.session.closed: + return len(b), errors.New("session was closed") } - bs, isOK := crypto.BoxOpen(&c.session.sharedSesKey, p.Payload, &p.Nonce) - if !isOK { - util.PutBytes(bs) - return 0, errors.New("packet dropped due to decryption failure") - } - b = b[:0] - b = append(b, bs...) - c.session.updateNonce(&p.Nonce) - c.session.time = time.Now() - c.session.bytesRecvd += uint64(len(bs)) - return len(b), nil } -func (c *Conn) Write(b []byte) (int, error) { +func (c *Conn) Write(b []byte) (bytesWritten int, err error) { if c.session == nil { c.core.router.doAdmin(func() { c.startSearch() @@ -112,7 +116,11 @@ func (c *Conn) Write(b []byte) (int, error) { } packet := p.encode() c.session.bytesSent += uint64(len(b)) - c.session.send <- packet + select { + case c.session.send <- packet: + case <-c.session.closed: + return len(b), errors.New("session was closed") + } c.session.core.router.out(packet) return len(b), nil } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index a1835459..e860dc81 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -41,6 +41,7 @@ type sessionInfo struct { init bool // Reset if coords change send chan []byte recv chan *wire_trafficPacket + closed chan interface{} nonceMask uint64 tstamp int64 // tstamp from their last session ping, replay attack mitigation tstampMutex int64 // protects the above @@ -306,6 +307,7 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) sinfo.send = make(chan []byte, 32) sinfo.recv = make(chan *wire_trafficPacket, 32) + sinfo.closed = make(chan interface{}) ss.sinfos[sinfo.myHandle] = &sinfo ss.byMySes[sinfo.mySesPub] = &sinfo.myHandle ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle @@ -364,6 +366,8 @@ func (ss *sessions) cleanup() { // Closes a session, removing it from sessions maps and killing the worker goroutine. func (sinfo *sessionInfo) close() { + sinfo.init = false + close(sinfo.closed) delete(sinfo.core.sessions.sinfos, sinfo.myHandle) delete(sinfo.core.sessions.byMySes, sinfo.mySesPub) delete(sinfo.core.sessions.byTheirPerm, sinfo.theirPermPub) From e3eadba4b7f95a151461303c098e68bfa6832eb7 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 19 Apr 2019 20:10:41 +0100 Subject: [PATCH 0030/1109] Protect session nonces with mutexes, modify sent/received bytes atomically --- src/yggdrasil/conn.go | 7 +++++-- src/yggdrasil/session.go | 28 ++++++++++++++++++++-------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 7aa46554..fd65743c 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -3,6 +3,7 @@ package yggdrasil import ( "encoding/hex" "errors" + "sync/atomic" "time" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" @@ -84,7 +85,7 @@ func (c *Conn) Read(b []byte) (int, error) { b = append(b, bs...) c.session.updateNonce(&p.Nonce) c.session.time = time.Now() - c.session.bytesRecvd += uint64(len(bs)) + atomic.AddUint64(&c.session.bytesRecvd, uint64(len(b))) return len(b), nil case <-c.session.closed: return len(b), errors.New("session was closed") @@ -106,7 +107,9 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { // code isn't multithreaded so appending to this is safe coords := c.session.coords // Prepare the payload + c.session.myNonceMutex.Lock() payload, nonce := crypto.BoxSeal(&c.session.sharedSesKey, b, &c.session.myNonce) + c.session.myNonceMutex.Unlock() defer util.PutBytes(payload) p := wire_trafficPacket{ Coords: coords, @@ -115,7 +118,7 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { Payload: payload, } packet := p.encode() - c.session.bytesSent += uint64(len(b)) + atomic.AddUint64(&c.session.bytesSent, uint64(len(b))) select { case c.session.send <- packet: case <-c.session.closed: diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index e860dc81..e46761d5 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -29,9 +29,10 @@ type sessionInfo struct { theirHandle crypto.Handle myHandle crypto.Handle theirNonce crypto.BoxNonce - theirNonceMutex sync.RWMutex // protects the above + theirNonceMask uint64 + theirNonceMutex sync.Mutex // protects the above myNonce crypto.BoxNonce - myNonceMutex sync.RWMutex // protects the above + myNonceMutex sync.Mutex // protects the above theirMTU uint16 myMTU uint16 wasMTUFixed bool // Was the MTU fixed by a receive error? @@ -42,7 +43,6 @@ type sessionInfo struct { send chan []byte recv chan *wire_trafficPacket closed chan interface{} - nonceMask uint64 tstamp int64 // tstamp from their last session ping, replay attack mitigation tstampMutex int64 // protects the above mtuTime time.Time // time myMTU was last changed @@ -79,8 +79,10 @@ func (s *sessionInfo) update(p *sessionPing) bool { s.theirSesPub = p.SendSesPub s.theirHandle = p.Handle s.sharedSesKey = *crypto.GetSharedKey(&s.mySesPriv, &s.theirSesPub) + s.theirNonceMutex.Lock() s.theirNonce = crypto.BoxNonce{} - s.nonceMask = 0 + s.theirNonceMask = 0 + s.theirNonceMutex.Unlock() } if p.MTU >= 1280 || p.MTU == 0 { s.theirMTU = p.MTU @@ -270,6 +272,10 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { return nil } sinfo := sessionInfo{} + sinfo.myNonceMutex.Lock() + sinfo.theirNonceMutex.Lock() + defer sinfo.myNonceMutex.Unlock() + defer sinfo.theirNonceMutex.Unlock() sinfo.core = ss.core sinfo.reconfigure = make(chan chan error, 1) sinfo.theirPermPub = *theirPermKey @@ -389,7 +395,9 @@ func (ss *sessions) getPing(sinfo *sessionInfo) sessionPing { Coords: coords, MTU: sinfo.myMTU, } + sinfo.myNonceMutex.Lock() sinfo.myNonce.Increment() + sinfo.myNonceMutex.Unlock() return ref } @@ -493,26 +501,30 @@ func (sinfo *sessionInfo) getMTU() uint16 { // Checks if a packet's nonce is recent enough to fall within the window of allowed packets, and not already received. func (sinfo *sessionInfo) nonceIsOK(theirNonce *crypto.BoxNonce) bool { // The bitmask is to allow for some non-duplicate out-of-order packets + sinfo.theirNonceMutex.Lock() + defer sinfo.theirNonceMutex.Unlock() diff := theirNonce.Minus(&sinfo.theirNonce) if diff > 0 { return true } - return ^sinfo.nonceMask&(0x01< 0 { // This nonce is newer, so shift the window before setting the bit, and update theirNonce in the session info. - sinfo.nonceMask <<= uint64(diff) - sinfo.nonceMask &= 0x01 + sinfo.theirNonceMask <<= uint64(diff) + sinfo.theirNonceMask &= 0x01 sinfo.theirNonce = *theirNonce } else { // This nonce is older, so set the bit but do not shift the window. - sinfo.nonceMask &= 0x01 << uint64(-diff) + sinfo.theirNonceMask &= 0x01 << uint64(-diff) } } From 27b78b925df96183b7e45fd5f3a4802e3c36cc98 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 19 Apr 2019 21:23:15 +0100 Subject: [PATCH 0031/1109] Move mutexes around --- src/yggdrasil/conn.go | 30 +++++++++++++++++++----------- src/yggdrasil/session.go | 12 ------------ 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index fd65743c..e337b34f 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -73,18 +73,26 @@ func (c *Conn) Read(b []byte) (int, error) { return 0, errors.New("session was closed") } defer util.PutBytes(p.Payload) - if !c.session.nonceIsOK(&p.Nonce) { - return 0, errors.New("packet dropped due to invalid nonce") + err := func() error { + c.session.theirNonceMutex.Lock() + defer c.session.theirNonceMutex.Unlock() + if !c.session.nonceIsOK(&p.Nonce) { + return errors.New("packet dropped due to invalid nonce") + } + bs, isOK := crypto.BoxOpen(&c.session.sharedSesKey, p.Payload, &p.Nonce) + if !isOK { + util.PutBytes(bs) + return errors.New("packet dropped due to decryption failure") + } + b = b[:0] + b = append(b, bs...) + c.session.updateNonce(&p.Nonce) + c.session.time = time.Now() + return nil + }() + if err != nil { + return 0, err } - bs, isOK := crypto.BoxOpen(&c.session.sharedSesKey, p.Payload, &p.Nonce) - if !isOK { - util.PutBytes(bs) - return 0, errors.New("packet dropped due to decryption failure") - } - b = b[:0] - b = append(b, bs...) - c.session.updateNonce(&p.Nonce) - c.session.time = time.Now() atomic.AddUint64(&c.session.bytesRecvd, uint64(len(b))) return len(b), nil case <-c.session.closed: diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index e46761d5..c5319bc7 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -79,10 +79,8 @@ func (s *sessionInfo) update(p *sessionPing) bool { s.theirSesPub = p.SendSesPub s.theirHandle = p.Handle s.sharedSesKey = *crypto.GetSharedKey(&s.mySesPriv, &s.theirSesPub) - s.theirNonceMutex.Lock() s.theirNonce = crypto.BoxNonce{} s.theirNonceMask = 0 - s.theirNonceMutex.Unlock() } if p.MTU >= 1280 || p.MTU == 0 { s.theirMTU = p.MTU @@ -272,10 +270,6 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { return nil } sinfo := sessionInfo{} - sinfo.myNonceMutex.Lock() - sinfo.theirNonceMutex.Lock() - defer sinfo.myNonceMutex.Unlock() - defer sinfo.theirNonceMutex.Unlock() sinfo.core = ss.core sinfo.reconfigure = make(chan chan error, 1) sinfo.theirPermPub = *theirPermKey @@ -395,9 +389,7 @@ func (ss *sessions) getPing(sinfo *sessionInfo) sessionPing { Coords: coords, MTU: sinfo.myMTU, } - sinfo.myNonceMutex.Lock() sinfo.myNonce.Increment() - sinfo.myNonceMutex.Unlock() return ref } @@ -501,8 +493,6 @@ func (sinfo *sessionInfo) getMTU() uint16 { // Checks if a packet's nonce is recent enough to fall within the window of allowed packets, and not already received. func (sinfo *sessionInfo) nonceIsOK(theirNonce *crypto.BoxNonce) bool { // The bitmask is to allow for some non-duplicate out-of-order packets - sinfo.theirNonceMutex.Lock() - defer sinfo.theirNonceMutex.Unlock() diff := theirNonce.Minus(&sinfo.theirNonce) if diff > 0 { return true @@ -512,8 +502,6 @@ func (sinfo *sessionInfo) nonceIsOK(theirNonce *crypto.BoxNonce) bool { // Updates the nonce mask by (possibly) shifting the bitmask and setting the bit corresponding to this nonce to 1, and then updating the most recent nonce func (sinfo *sessionInfo) updateNonce(theirNonce *crypto.BoxNonce) { - sinfo.theirNonceMutex.Lock() - defer sinfo.theirNonceMutex.Unlock() // Shift nonce mask if needed // Set bit diff := theirNonce.Minus(&sinfo.theirNonce) From aac88adbedeb70a7f35068ee2ad5db0a883283b0 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 19 Apr 2019 22:57:52 +0100 Subject: [PATCH 0032/1109] Listen-Accept-Read-Write pattern now works, amazingly --- src/yggdrasil/conn.go | 15 +++++++------- src/yggdrasil/core.go | 14 +++++++++++++ src/yggdrasil/listener.go | 41 +++++++++++++++++++++++++++++++++++++++ src/yggdrasil/session.go | 38 ++++++++++++++++++++++++++---------- 4 files changed, 91 insertions(+), 17 deletions(-) create mode 100644 src/yggdrasil/listener.go diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index e337b34f..a1515375 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -61,11 +61,10 @@ func (c *Conn) startSearch() { func (c *Conn) Read(b []byte) (int, error) { if c.session == nil { - return 0, errors.New("session not open") + return 0, errors.New("session not ready yet") } if !c.session.init { - // To prevent blocking forever on a session that isn't initialised - return 0, errors.New("session not initialised") + return 0, errors.New("waiting for remote side to accept") } select { case p, ok := <-c.session.recv: @@ -84,6 +83,7 @@ func (c *Conn) Read(b []byte) (int, error) { util.PutBytes(bs) return errors.New("packet dropped due to decryption failure") } + // c.core.log.Println("HOW MANY BYTES?", len(bs)) b = b[:0] b = append(b, bs...) c.session.updateNonce(&p.Nonce) @@ -96,7 +96,7 @@ func (c *Conn) Read(b []byte) (int, error) { atomic.AddUint64(&c.session.bytesRecvd, uint64(len(b))) return len(b), nil case <-c.session.closed: - return len(b), errors.New("session was closed") + return len(b), errors.New("session closed") } } @@ -105,12 +105,12 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { c.core.router.doAdmin(func() { c.startSearch() }) - return 0, errors.New("session not open") + return 0, errors.New("session not ready yet") } defer util.PutBytes(b) if !c.session.init { // To prevent using empty session keys - return 0, errors.New("session not initialised") + return 0, errors.New("waiting for remote side to accept") } // code isn't multithreaded so appending to this is safe coords := c.session.coords @@ -130,13 +130,14 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { select { case c.session.send <- packet: case <-c.session.closed: - return len(b), errors.New("session was closed") + return len(b), errors.New("session closed") } c.session.core.router.out(packet) return len(b), nil } func (c *Conn) Close() error { + c.session.close() return nil } diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 22caf08c..dba1c645 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -254,6 +254,20 @@ func (c *Core) Stop() { c.admin.close() } +// ListenConn returns a listener for Yggdrasil session connections. +func (c *Core) ListenConn() (*Listener, error) { + c.sessions.listenerMutex.Lock() + defer c.sessions.listenerMutex.Unlock() + if c.sessions.listener != nil { + return nil, errors.New("a listener already exists") + } + c.sessions.listener = &Listener{ + conn: make(chan *Conn), + close: make(chan interface{}), + } + return c.sessions.listener, nil +} + // Dial opens a session to the given node. The first paramter should be "nodeid" // and the second parameter should contain a hexadecimal representation of the // target node ID. diff --git a/src/yggdrasil/listener.go b/src/yggdrasil/listener.go new file mode 100644 index 00000000..268d8b78 --- /dev/null +++ b/src/yggdrasil/listener.go @@ -0,0 +1,41 @@ +package yggdrasil + +import ( + "errors" + "net" +) + +// Listener waits for incoming sessions +type Listener struct { + conn chan *Conn + close chan interface{} +} + +// Accept blocks until a new incoming session is received +func (l *Listener) Accept() (*Conn, error) { + select { + case c, ok := <-l.conn: + if !ok { + return nil, errors.New("listener closed") + } + return c, nil + case <-l.close: + return nil, errors.New("listener closed") + } +} + +// Close will stop the listener +func (l *Listener) Close() (err error) { + defer func() { + recover() + err = errors.New("already closed") + }() + close(l.close) + close(l.conn) + return nil +} + +// Addr is not implemented for this type yet +func (l *Listener) Addr() net.Addr { + return nil +} diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index c5319bc7..64b5d291 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -105,16 +105,18 @@ func (s *sessionInfo) timedout() bool { // Sessions are indexed by handle. // Additionally, stores maps of address/subnet onto keys, and keys onto handles. type sessions struct { - core *Core - reconfigure chan chan error - lastCleanup time.Time - permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey // Maps known permanent keys to their shared key, used by DHT a lot - sinfos map[crypto.Handle]*sessionInfo // Maps (secret) handle onto session info - conns map[crypto.Handle]*Conn // Maps (secret) handle onto connections - byMySes map[crypto.BoxPubKey]*crypto.Handle // Maps mySesPub onto handle - byTheirPerm map[crypto.BoxPubKey]*crypto.Handle // Maps theirPermPub onto handle - addrToPerm map[address.Address]*crypto.BoxPubKey - subnetToPerm map[address.Subnet]*crypto.BoxPubKey + core *Core + listener *Listener + listenerMutex sync.Mutex + reconfigure chan chan error + lastCleanup time.Time + permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey // Maps known permanent keys to their shared key, used by DHT a lot + sinfos map[crypto.Handle]*sessionInfo // Maps (secret) handle onto session info + conns map[crypto.Handle]*Conn // Maps (secret) handle onto connections + byMySes map[crypto.BoxPubKey]*crypto.Handle // Maps mySesPub onto handle + byTheirPerm map[crypto.BoxPubKey]*crypto.Handle // Maps theirPermPub onto handle + addrToPerm map[address.Address]*crypto.BoxPubKey + subnetToPerm map[address.Subnet]*crypto.BoxPubKey } // Initializes the session struct. @@ -461,6 +463,22 @@ func (ss *sessions) handlePing(ping *sessionPing) { if !isIn { panic("This should not happen") } + ss.listenerMutex.Lock() + if ss.listener != nil { + conn := &Conn{ + core: ss.core, + session: sinfo, + nodeID: crypto.GetNodeID(&sinfo.theirPermPub), + nodeMask: &crypto.NodeID{}, + } + for i := range conn.nodeMask { + conn.nodeMask[i] = 0xFF + } + ss.listener.conn <- conn + } else { + ss.core.log.Debugln("Received new session but there is no listener, ignoring") + } + ss.listenerMutex.Unlock() } // Update the session if !sinfo.update(ping) { /*panic("Should not happen in testing")*/ From 7e726b0afbea2fb1aafa406a1a5de96d538f9ea4 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 19 Apr 2019 23:04:09 +0100 Subject: [PATCH 0033/1109] Listener should clean up a bit more when closing --- src/yggdrasil/core.go | 1 + src/yggdrasil/listener.go | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index dba1c645..b5d74e8c 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -262,6 +262,7 @@ func (c *Core) ListenConn() (*Listener, error) { return nil, errors.New("a listener already exists") } c.sessions.listener = &Listener{ + core: c, conn: make(chan *Conn), close: make(chan interface{}), } diff --git a/src/yggdrasil/listener.go b/src/yggdrasil/listener.go index 268d8b78..62225412 100644 --- a/src/yggdrasil/listener.go +++ b/src/yggdrasil/listener.go @@ -7,6 +7,7 @@ import ( // Listener waits for incoming sessions type Listener struct { + core *Core conn chan *Conn close chan interface{} } @@ -30,6 +31,9 @@ func (l *Listener) Close() (err error) { recover() err = errors.New("already closed") }() + if l.core.sessions.listener == l { + l.core.sessions.listener = nil + } close(l.close) close(l.conn) return nil From e31b914e384bcd5e60bc7a6baa08439d7e669b0b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 19 Apr 2019 23:30:43 +0100 Subject: [PATCH 0034/1109] Improve errors and handling of expired sessions --- src/yggdrasil/conn.go | 22 ++++++++++++++++------ src/yggdrasil/session.go | 1 - 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index a1515375..df45d12b 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -17,6 +17,7 @@ type Conn struct { session *sessionInfo readDeadline time.Time writeDeadline time.Time + expired bool } // This method should only be called from the router goroutine @@ -60,8 +61,11 @@ func (c *Conn) startSearch() { } func (c *Conn) Read(b []byte) (int, error) { + if c.expired { + return 0, errors.New("session is closed") + } if c.session == nil { - return 0, errors.New("session not ready yet") + return 0, errors.New("searching for remote side") } if !c.session.init { return 0, errors.New("waiting for remote side to accept") @@ -69,7 +73,8 @@ func (c *Conn) Read(b []byte) (int, error) { select { case p, ok := <-c.session.recv: if !ok { - return 0, errors.New("session was closed") + c.expired = true + return 0, errors.New("session is closed") } defer util.PutBytes(p.Payload) err := func() error { @@ -83,7 +88,6 @@ func (c *Conn) Read(b []byte) (int, error) { util.PutBytes(bs) return errors.New("packet dropped due to decryption failure") } - // c.core.log.Println("HOW MANY BYTES?", len(bs)) b = b[:0] b = append(b, bs...) c.session.updateNonce(&p.Nonce) @@ -96,16 +100,20 @@ func (c *Conn) Read(b []byte) (int, error) { atomic.AddUint64(&c.session.bytesRecvd, uint64(len(b))) return len(b), nil case <-c.session.closed: - return len(b), errors.New("session closed") + c.expired = true + return len(b), errors.New("session is closed") } } func (c *Conn) Write(b []byte) (bytesWritten int, err error) { + if c.expired { + return 0, errors.New("session is closed") + } if c.session == nil { c.core.router.doAdmin(func() { c.startSearch() }) - return 0, errors.New("session not ready yet") + return 0, errors.New("searching for remote side") } defer util.PutBytes(b) if !c.session.init { @@ -130,13 +138,15 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { select { case c.session.send <- packet: case <-c.session.closed: - return len(b), errors.New("session closed") + c.expired = true + return len(b), errors.New("session is closed") } c.session.core.router.out(packet) return len(b), nil } func (c *Conn) Close() error { + c.expired = true c.session.close() return nil } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 64b5d291..40259d9b 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -368,7 +368,6 @@ func (ss *sessions) cleanup() { // Closes a session, removing it from sessions maps and killing the worker goroutine. func (sinfo *sessionInfo) close() { - sinfo.init = false close(sinfo.closed) delete(sinfo.core.sessions.sinfos, sinfo.myHandle) delete(sinfo.core.sessions.byMySes, sinfo.mySesPub) From 693bcc5713f809a7a63874a7a06229e1f6f5cf80 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 19 Apr 2019 23:30:57 +0100 Subject: [PATCH 0035/1109] Update sample in cmd/yggdrasil --- cmd/yggdrasil/main.go | 62 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index fd8cd7b6..2267d62c 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -10,6 +10,7 @@ import ( "os/signal" "strings" "syscall" + "time" "golang.org/x/text/encoding/unicode" @@ -267,6 +268,67 @@ func main() { defer func() { n.core.Stop() }() + // Listen for new sessions + go func() { + listener, err := n.core.ListenConn() + if err != nil { + logger.Errorln("Unable to listen for sessions:", err) + return + } + for { + conn, err := listener.Accept() + if err != nil { + logger.Errorln("Accept:", err) + continue + } + logger.Println("Accepted") + for { + b := []byte{} + if n, err := conn.Read(b); err != nil { + logger.Errorln("Read failed:", err) + time.Sleep(time.Second * 2) + } else { + logger.Println("Read", n, "bytes:", b) + b = []byte{5, 5, 5} + if n, err := conn.Write(b); err != nil { + logger.Errorln("Write failed:", err) + time.Sleep(time.Second * 2) + } else { + logger.Println("Wrote", n, "bytes:", b) + } + } + } + } + }() + // Try creating new sessions + go func() { + if cfg.EncryptionPublicKey != "533574224115f835b7c7db6433986bc5aef855ff9c9568c01abeb0fbed3e8810" { + return + } + time.Sleep(time.Second * 2) + conn, err := n.core.Dial("nodeid", "9890e135604e8aa6039a909e40c629824d852042a70e51957d5b9d700195663d50552e8e869af132b4617d76f8ef00314d94cce23aa8d6b051b3b952a32a4966") + if err != nil { + logger.Errorln("Dial:", err) + return + } + go func() { + for { + time.Sleep(time.Second * 2) + b := []byte{1, 2, 3, 4, 5} + if n, err := conn.Write(b); err != nil { + logger.Errorln("Write failed:", err) + } else { + logger.Println("Wrote", n, "bytes:", b) + b = b[:0] + if n, err := conn.Read(b); err != nil { + logger.Errorln("Read failed:", err) + } else { + logger.Println("Read", n, "bytes:", b) + } + } + } + }() + }() // Make some nice output that tells us what our IPv6 address and subnet are. // This is just logged to stdout for the user. address := n.core.Address() From 24281d4049acc6cfa1c4a88245160191fd209230 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 19 Apr 2019 23:47:11 +0100 Subject: [PATCH 0036/1109] Fix Read, update sample --- cmd/yggdrasil/main.go | 4 ++-- src/yggdrasil/conn.go | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 2267d62c..0e3aa354 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -283,7 +283,7 @@ func main() { } logger.Println("Accepted") for { - b := []byte{} + b := make([]byte, 100) if n, err := conn.Read(b); err != nil { logger.Errorln("Read failed:", err) time.Sleep(time.Second * 2) @@ -319,7 +319,7 @@ func main() { logger.Errorln("Write failed:", err) } else { logger.Println("Wrote", n, "bytes:", b) - b = b[:0] + b = make([]byte, 100) if n, err := conn.Read(b); err != nil { logger.Errorln("Read failed:", err) } else { diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index df45d12b..b936d4f9 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -88,8 +88,10 @@ func (c *Conn) Read(b []byte) (int, error) { util.PutBytes(bs) return errors.New("packet dropped due to decryption failure") } - b = b[:0] - b = append(b, bs...) + copy(b, bs) + if len(bs) < len(b) { + b = b[:len(bs)] + } c.session.updateNonce(&p.Nonce) c.session.time = time.Now() return nil From f3e742a297878ebf909c06d438af3d0e23a9bdc3 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 20 Apr 2019 11:53:38 +0100 Subject: [PATCH 0037/1109] Squash a whole load of races (and mutex half the world) --- src/yggdrasil/conn.go | 19 +++++++++++++- src/yggdrasil/core.go | 7 +++++- src/yggdrasil/router.go | 6 +++++ src/yggdrasil/session.go | 54 ++++++++++++++++++++++++++-------------- 4 files changed, 66 insertions(+), 20 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index b936d4f9..874a7a9c 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -3,6 +3,7 @@ package yggdrasil import ( "encoding/hex" "errors" + "sync" "sync/atomic" "time" @@ -15,6 +16,7 @@ type Conn struct { nodeID *crypto.NodeID nodeMask *crypto.NodeID session *sessionInfo + sessionMutex *sync.RWMutex readDeadline time.Time writeDeadline time.Time expired bool @@ -28,7 +30,9 @@ func (c *Conn) startSearch() { return } if sinfo != nil { + c.sessionMutex.Lock() c.session = sinfo + c.sessionMutex.Unlock() } } doSearch := func() { @@ -61,15 +65,20 @@ func (c *Conn) startSearch() { } func (c *Conn) Read(b []byte) (int, error) { + c.sessionMutex.RLock() + defer c.sessionMutex.RUnlock() if c.expired { return 0, errors.New("session is closed") } if c.session == nil { return 0, errors.New("searching for remote side") } + c.session.initMutex.RLock() if !c.session.init { + c.session.initMutex.RUnlock() return 0, errors.New("waiting for remote side to accept") } + c.session.initMutex.RUnlock() select { case p, ok := <-c.session.recv: if !ok { @@ -93,7 +102,9 @@ func (c *Conn) Read(b []byte) (int, error) { b = b[:len(bs)] } c.session.updateNonce(&p.Nonce) + c.session.timeMutex.Lock() c.session.time = time.Now() + c.session.timeMutex.Unlock() return nil }() if err != nil { @@ -108,6 +119,8 @@ func (c *Conn) Read(b []byte) (int, error) { } func (c *Conn) Write(b []byte) (bytesWritten int, err error) { + c.sessionMutex.RLock() + defer c.sessionMutex.RUnlock() if c.expired { return 0, errors.New("session is closed") } @@ -118,12 +131,16 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { return 0, errors.New("searching for remote side") } defer util.PutBytes(b) + c.session.initMutex.RLock() if !c.session.init { - // To prevent using empty session keys + c.session.initMutex.RUnlock() return 0, errors.New("waiting for remote side to accept") } + c.session.initMutex.RUnlock() // code isn't multithreaded so appending to this is safe + c.session.coordsMutex.RLock() coords := c.session.coords + c.session.coordsMutex.RUnlock() // Prepare the payload c.session.myNonceMutex.Lock() payload, nonce := crypto.BoxSeal(&c.session.sharedSesKey, b, &c.session.myNonce) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index b5d74e8c..cfba833b 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -5,6 +5,7 @@ import ( "errors" "io/ioutil" "net" + "sync" "time" "github.com/gologme/log" @@ -273,7 +274,9 @@ func (c *Core) ListenConn() (*Listener, error) { // and the second parameter should contain a hexadecimal representation of the // target node ID. func (c *Core) Dial(network, address string) (Conn, error) { - conn := Conn{} + conn := Conn{ + sessionMutex: &sync.RWMutex{}, + } nodeID := crypto.NodeID{} nodeMask := crypto.NodeID{} // Process @@ -298,6 +301,8 @@ func (c *Core) Dial(network, address string) (Conn, error) { conn.core.router.doAdmin(func() { conn.startSearch() }) + conn.sessionMutex.Lock() + defer conn.sessionMutex.Unlock() return conn, nil } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index d7923f51..693fba44 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -291,6 +291,10 @@ func (r *router) sendPacket(bs []byte) { if destSnet.IsValid() { sinfo, isIn = r.core.sessions.getByTheirSubnet(&destSnet) } + sinfo.timeMutex.Lock() + sinfo.initMutex.RLock() + defer sinfo.timeMutex.Unlock() + defer sinfo.initMutex.RUnlock() switch { case !isIn || !sinfo.init: // No or unintiialized session, so we need to search first @@ -306,6 +310,7 @@ func (r *router) sendPacket(bs []byte) { } else { // We haven't heard about the dest in a while now := time.Now() + if !sinfo.time.Before(sinfo.pingTime) { // Update pingTime to start the clock for searches (above) sinfo.pingTime = now @@ -315,6 +320,7 @@ func (r *router) sendPacket(bs []byte) { sinfo.pingSend = now r.core.sessions.sendPingPong(sinfo, false) } + sinfo.timeMutex.Unlock() } fallthrough // Also send the packet default: diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 40259d9b..9c09532b 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -8,6 +8,7 @@ import ( "bytes" "encoding/hex" "sync" + "sync/atomic" "time" "github.com/yggdrasil-network/yggdrasil-go/src/address" @@ -35,21 +36,23 @@ type sessionInfo struct { myNonceMutex sync.Mutex // protects the above theirMTU uint16 myMTU uint16 - wasMTUFixed bool // Was the MTU fixed by a receive error? - time time.Time // Time we last received a packet - coords []byte // coords of destination - packet []byte // a buffered packet, sent immediately on ping/pong - init bool // Reset if coords change + wasMTUFixed bool // Was the MTU fixed by a receive error? + time time.Time // Time we last received a packet + mtuTime time.Time // time myMTU was last changed + pingTime time.Time // time the first ping was sent since the last received packet + pingSend time.Time // time the last ping was sent + timeMutex sync.RWMutex // protects all time fields above + coords []byte // coords of destination + coordsMutex sync.RWMutex // protects the above + packet []byte // a buffered packet, sent immediately on ping/pong + init bool // Reset if coords change + initMutex sync.RWMutex send chan []byte recv chan *wire_trafficPacket closed chan interface{} - tstamp int64 // tstamp from their last session ping, replay attack mitigation - tstampMutex int64 // protects the above - mtuTime time.Time // time myMTU was last changed - pingTime time.Time // time the first ping was sent since the last received packet - pingSend time.Time // time the last ping was sent - bytesSent uint64 // Bytes of real traffic sent in this session - bytesRecvd uint64 // Bytes of real traffic received in this session + tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation + bytesSent uint64 // Bytes of real traffic sent in this session + bytesRecvd uint64 // Bytes of real traffic received in this session } // Represents a session ping/pong packet, andincludes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU. @@ -66,7 +69,7 @@ type sessionPing struct { // Updates session info in response to a ping, after checking that the ping is OK. // Returns true if the session was updated, or false otherwise. func (s *sessionInfo) update(p *sessionPing) bool { - if !(p.Tstamp > s.tstamp) { + if !(p.Tstamp > atomic.LoadInt64(&s.tstamp)) { // To protect against replay attacks return false } @@ -90,14 +93,20 @@ func (s *sessionInfo) update(p *sessionPing) bool { s.coords = append(make([]byte, 0, len(p.Coords)+11), p.Coords...) } now := time.Now() + s.timeMutex.Lock() s.time = now - s.tstamp = p.Tstamp + s.timeMutex.Unlock() + atomic.StoreInt64(&s.tstamp, p.Tstamp) + s.initMutex.Lock() s.init = true + s.initMutex.Unlock() return true } // Returns true if the session has been idle for longer than the allowed timeout. func (s *sessionInfo) timedout() bool { + s.timeMutex.RLock() + defer s.timeMutex.RUnlock() return time.Since(s.time) > time.Minute } @@ -284,10 +293,12 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.myMTU = uint16(ss.core.router.adapter.MTU()) } now := time.Now() + sinfo.timeMutex.Lock() sinfo.time = now sinfo.mtuTime = now sinfo.pingTime = now sinfo.pingSend = now + sinfo.timeMutex.Unlock() higher := false for idx := range ss.core.boxPub { if ss.core.boxPub[idx] > sinfo.theirPermPub[idx] { @@ -428,6 +439,7 @@ func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) { bs := ping.encode() shared := ss.getSharedKey(&ss.core.boxPriv, &sinfo.theirPermPub) payload, nonce := crypto.BoxSeal(shared, bs, nil) + sinfo.coordsMutex.RLock() p := wire_protoTrafficPacket{ Coords: sinfo.coords, ToKey: sinfo.theirPermPub, @@ -435,10 +447,13 @@ func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) { Nonce: *nonce, Payload: payload, } + sinfo.coordsMutex.RUnlock() packet := p.encode() ss.core.router.out(packet) if !isPong { + sinfo.timeMutex.Lock() sinfo.pingSend = time.Now() + sinfo.timeMutex.Unlock() } } @@ -465,10 +480,11 @@ func (ss *sessions) handlePing(ping *sessionPing) { ss.listenerMutex.Lock() if ss.listener != nil { conn := &Conn{ - core: ss.core, - session: sinfo, - nodeID: crypto.GetNodeID(&sinfo.theirPermPub), - nodeMask: &crypto.NodeID{}, + core: ss.core, + session: sinfo, + sessionMutex: &sync.RWMutex{}, + nodeID: crypto.GetNodeID(&sinfo.theirPermPub), + nodeMask: &crypto.NodeID{}, } for i := range conn.nodeMask { conn.nodeMask[i] = 0xFF @@ -537,6 +553,8 @@ func (sinfo *sessionInfo) updateNonce(theirNonce *crypto.BoxNonce) { // Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change. func (ss *sessions) resetInits() { for _, sinfo := range ss.sinfos { + sinfo.initMutex.Lock() sinfo.init = false + sinfo.initMutex.Unlock() } } From 319366513c57379b781e8aa2d305610aad37716b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 20 Apr 2019 11:53:46 +0100 Subject: [PATCH 0038/1109] Allow building with race detector --- build | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/build b/build index f76ee7b2..bad287fa 100755 --- a/build +++ b/build @@ -8,7 +8,7 @@ PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)} LDFLAGS="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER" -while getopts "udaitc:l:" option +while getopts "udaitc:l:r" option do case "${option}" in @@ -19,6 +19,7 @@ do t) TABLES=true;; c) GCFLAGS="$GCFLAGS $OPTARG";; l) LDFLAGS="$LDFLAGS $OPTARG";; + r) RACE="-race";; esac done @@ -43,9 +44,9 @@ else echo "Building: $CMD" if [ $DEBUG ]; then - go build -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" -tags debug -v ./cmd/$CMD + go build $RACE -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" -tags debug -v ./cmd/$CMD else - go build -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" -v ./cmd/$CMD + go build $RACE -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" -v ./cmd/$CMD fi if [ $UPX ]; then upx --brute $CMD From d01662c1fb1a887e9406b05d7c3a62b6561bf94e Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 20 Apr 2019 16:32:27 +0100 Subject: [PATCH 0039/1109] Try to convert TUN/TAP to use new yggdrasil.Conn, search masks are still broken --- cmd/yggdrasil/main.go | 237 +++++++++++++++-------------- src/crypto/crypto.go | 27 ++++ src/tuntap/tun.go | 304 +++++++++++++++++++++++++++----------- src/tuntap/tun_bsd.go | 24 +-- src/tuntap/tun_darwin.go | 14 +- src/tuntap/tun_linux.go | 6 +- src/tuntap/tun_other.go | 2 +- src/tuntap/tun_windows.go | 32 ++-- src/yggdrasil/adapter.go | 47 ------ src/yggdrasil/conn.go | 19 ++- src/yggdrasil/core.go | 68 +-------- src/yggdrasil/dialer.go | 70 +++++++++ src/yggdrasil/router.go | 4 - src/yggdrasil/session.go | 14 +- 14 files changed, 502 insertions(+), 366 deletions(-) delete mode 100644 src/yggdrasil/adapter.go create mode 100644 src/yggdrasil/dialer.go diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 0e3aa354..f3d933c2 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -10,7 +10,6 @@ import ( "os/signal" "strings" "syscall" - "time" "golang.org/x/text/encoding/unicode" @@ -77,57 +76,44 @@ func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *nodeCo panic(err) } json.Unmarshal(confJson, &cfg) - // For now we will do a little bit to help the user adjust their - // configuration to match the new configuration format, as some of the key - // names have changed recently. - changes := map[string]string{ - "Multicast": "", - "ReadTimeout": "", - "LinkLocal": "MulticastInterfaces", - "BoxPub": "EncryptionPublicKey", - "BoxPriv": "EncryptionPrivateKey", - "SigPub": "SigningPublicKey", - "SigPriv": "SigningPrivateKey", - "AllowedBoxPubs": "AllowedEncryptionPublicKeys", - } - // Loop over the mappings aove and see if we have anything to fix. - for from, to := range changes { - if _, ok := dat[from]; ok { - if to == "" { - if !*normaliseconf { - log.Println("Warning: Config option", from, "is deprecated") - } - } else { - if !*normaliseconf { - log.Println("Warning: Config option", from, "has been renamed - please change to", to) - } - // If the configuration file doesn't already contain a line with the - // new name then set it to the old value. This makes sure that we - // don't overwrite something that was put there intentionally. - if _, ok := dat[to]; !ok { - dat[to] = dat[from] + /* + // For now we will do a little bit to help the user adjust their + // configuration to match the new configuration format, as some of the key + // names have changed recently. + changes := map[string]string{ + "Multicast": "", + "ReadTimeout": "", + "LinkLocal": "MulticastInterfaces", + "BoxPub": "EncryptionPublicKey", + "BoxPriv": "EncryptionPrivateKey", + "SigPub": "SigningPublicKey", + "SigPriv": "SigningPrivateKey", + "AllowedBoxPubs": "AllowedEncryptionPublicKeys", + } + // Loop over the mappings aove and see if we have anything to fix. + for from, to := range changes { + if _, ok := dat[from]; ok { + if to == "" { + if !*normaliseconf { + log.Println("Warning: Config option", from, "is deprecated") + } + } else { + if !*normaliseconf { + log.Println("Warning: Config option", from, "has been renamed - please change to", to) + } + // If the configuration file doesn't already contain a line with the + // new name then set it to the old value. This makes sure that we + // don't overwrite something that was put there intentionally. + if _, ok := dat[to]; !ok { + dat[to] = dat[from] + } } } } - } - // Check to see if the peers are in a parsable format, if not then default - // them to the TCP scheme - if peers, ok := dat["Peers"].([]interface{}); ok { - for index, peer := range peers { - uri := peer.(string) - if strings.HasPrefix(uri, "tcp://") || strings.HasPrefix(uri, "socks://") { - continue - } - if strings.HasPrefix(uri, "tcp:") { - uri = uri[4:] - } - (dat["Peers"].([]interface{}))[index] = "tcp://" + uri - } - } - // Now do the same with the interface peers - if interfacepeers, ok := dat["InterfacePeers"].(map[string]interface{}); ok { - for intf, peers := range interfacepeers { - for index, peer := range peers.([]interface{}) { + // Check to see if the peers are in a parsable format, if not then default + // them to the TCP scheme + if peers, ok := dat["Peers"].([]interface{}); ok { + for index, peer := range peers { uri := peer.(string) if strings.HasPrefix(uri, "tcp://") || strings.HasPrefix(uri, "socks://") { continue @@ -135,19 +121,34 @@ func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *nodeCo if strings.HasPrefix(uri, "tcp:") { uri = uri[4:] } - ((dat["InterfacePeers"].(map[string]interface{}))[intf]).([]interface{})[index] = "tcp://" + uri + (dat["Peers"].([]interface{}))[index] = "tcp://" + uri } } - } - // Do a quick check for old-format Listen statement so that mapstructure - // doesn't fail and crash - if listen, ok := dat["Listen"].(string); ok { - if strings.HasPrefix(listen, "tcp://") { - dat["Listen"] = []string{listen} - } else { - dat["Listen"] = []string{"tcp://" + listen} + // Now do the same with the interface peers + if interfacepeers, ok := dat["InterfacePeers"].(map[string]interface{}); ok { + for intf, peers := range interfacepeers { + for index, peer := range peers.([]interface{}) { + uri := peer.(string) + if strings.HasPrefix(uri, "tcp://") || strings.HasPrefix(uri, "socks://") { + continue + } + if strings.HasPrefix(uri, "tcp:") { + uri = uri[4:] + } + ((dat["InterfacePeers"].(map[string]interface{}))[intf]).([]interface{})[index] = "tcp://" + uri + } + } } - } + // Do a quick check for old-format Listen statement so that mapstructure + // doesn't fail and crash + if listen, ok := dat["Listen"].(string); ok { + if strings.HasPrefix(listen, "tcp://") { + dat["Listen"] = []string{listen} + } else { + dat["Listen"] = []string{"tcp://" + listen} + } + } + */ // Overlay our newly mapped configuration onto the autoconf node config that // we generated above. if err = mapstructure.Decode(dat, &cfg); err != nil { @@ -249,8 +250,6 @@ func main() { // Setup the Yggdrasil node itself. The node{} type includes a Core, so we // don't need to create this manually. n := node{} - // Before we start the node, set the TUN/TAP to be our router adapter - n.core.SetRouterAdapter(&n.tuntap) // Now start Yggdrasil - this starts the DHT, router, switch and other core // components needed for Yggdrasil to operate state, err := n.core.Start(cfg, logger) @@ -263,72 +262,88 @@ func main() { if err := n.multicast.Start(); err != nil { logger.Errorln("An error occurred starting multicast:", err) } + // Start the TUN/TAP interface + if listener, err := n.core.ConnListen(); err == nil { + if dialer, err := n.core.ConnDialer(); err == nil { + logger.Println("Got listener", listener, "and dialer", dialer) + n.tuntap.Init(state, logger, listener, dialer) + if err := n.tuntap.Start(); err != nil { + logger.Errorln("An error occurred starting TUN/TAP:", err) + } + } else { + logger.Errorln("Unable to get Dialer:", err) + } + } else { + logger.Errorln("Unable to get Listener:", err) + } // The Stop function ensures that the TUN/TAP adapter is correctly shut down // before the program exits. defer func() { n.core.Stop() }() // Listen for new sessions - go func() { - listener, err := n.core.ListenConn() - if err != nil { - logger.Errorln("Unable to listen for sessions:", err) - return - } - for { - conn, err := listener.Accept() - if err != nil { - logger.Errorln("Accept:", err) - continue - } - logger.Println("Accepted") - for { - b := make([]byte, 100) - if n, err := conn.Read(b); err != nil { - logger.Errorln("Read failed:", err) - time.Sleep(time.Second * 2) - } else { - logger.Println("Read", n, "bytes:", b) - b = []byte{5, 5, 5} - if n, err := conn.Write(b); err != nil { - logger.Errorln("Write failed:", err) - time.Sleep(time.Second * 2) - } else { - logger.Println("Wrote", n, "bytes:", b) - } - } - } - } - }() - // Try creating new sessions - go func() { - if cfg.EncryptionPublicKey != "533574224115f835b7c7db6433986bc5aef855ff9c9568c01abeb0fbed3e8810" { - return - } - time.Sleep(time.Second * 2) - conn, err := n.core.Dial("nodeid", "9890e135604e8aa6039a909e40c629824d852042a70e51957d5b9d700195663d50552e8e869af132b4617d76f8ef00314d94cce23aa8d6b051b3b952a32a4966") - if err != nil { - logger.Errorln("Dial:", err) - return - } + /* go func() { + listener, err := n.core.ListenConn() + if err != nil { + logger.Errorln("Unable to listen for sessions:", err) + return + } for { - time.Sleep(time.Second * 2) - b := []byte{1, 2, 3, 4, 5} - if n, err := conn.Write(b); err != nil { - logger.Errorln("Write failed:", err) - } else { - logger.Println("Wrote", n, "bytes:", b) - b = make([]byte, 100) + conn, err := listener.Accept() + if err != nil { + logger.Errorln("Accept:", err) + continue + } + logger.Println("Accepted") + for { + b := make([]byte, 100) if n, err := conn.Read(b); err != nil { logger.Errorln("Read failed:", err) + time.Sleep(time.Second * 2) } else { logger.Println("Read", n, "bytes:", b) + b = []byte{5, 5, 5} + if n, err := conn.Write(b); err != nil { + logger.Errorln("Write failed:", err) + time.Sleep(time.Second * 2) + } else { + logger.Println("Wrote", n, "bytes:", b) + } } } } }() - }() + // Try creating new sessions + go func() { + if cfg.EncryptionPublicKey != "533574224115f835b7c7db6433986bc5aef855ff9c9568c01abeb0fbed3e8810" { + return + } + time.Sleep(time.Second * 2) + conn, err := n.core.Dial("nodeid", "9890e135604e8aa6039a909e40c629824d852042a70e51957d5b9d700195663d50552e8e869af132b4617d76f8ef00314d94cce23aa8d6b051b3b952a32a4966") + if err != nil { + logger.Errorln("Dial:", err) + return + } + go func() { + for { + time.Sleep(time.Second * 2) + b := []byte{1, 2, 3, 4, 5} + if n, err := conn.Write(b); err != nil { + logger.Errorln("Write failed:", err) + } else { + logger.Println("Wrote", n, "bytes:", b) + b = make([]byte, 100) + if n, err := conn.Read(b); err != nil { + logger.Errorln("Read failed:", err) + } else { + logger.Println("Read", n, "bytes:", b) + } + } + } + }() + }() + */ // Make some nice output that tells us what our IPv6 address and subnet are. // This is just logged to stdout for the user. address := n.core.Address() diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go index b47db184..d5b467e9 100644 --- a/src/crypto/crypto.go +++ b/src/crypto/crypto.go @@ -37,10 +37,37 @@ func (n *NodeID) String() string { return hex.EncodeToString(n[:]) } +// Network returns "nodeid" nearly always right now. func (n *NodeID) Network() string { return "nodeid" } +// PrefixLength returns the number of bits set in a masked NodeID. +func (n *NodeID) PrefixLength() int { + var len int + for i, v := range *n { + _, _ = i, v + if v == 0xff { + len += 8 + continue + } + for v&0x80 != 0 { + len++ + v <<= 1 + } + if v != 0 { + return -1 + } + for i++; i < NodeIDLen; i++ { + if n[i] != 0 { + return -1 + } + } + break + } + return len +} + func GetNodeID(pub *BoxPubKey) *NodeID { h := sha512.Sum512(pub[:]) return (*NodeID)(&h) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index c93b1163..3010fae4 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -3,7 +3,7 @@ package tuntap // This manages the tun driver to send/recv packets to/from applications import ( - "bytes" + "encoding/hex" "errors" "fmt" "net" @@ -11,16 +11,12 @@ import ( "time" "github.com/gologme/log" - "golang.org/x/net/icmp" - "golang.org/x/net/ipv6" - - "github.com/songgao/packets/ethernet" "github.com/yggdrasil-network/water" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/config" + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" - "github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) @@ -32,14 +28,20 @@ const tun_ETHER_HEADER_LENGTH = 14 // you should pass this object to the yggdrasil.SetRouterAdapter() function // before calling yggdrasil.Start(). type TunAdapter struct { - yggdrasil.Adapter - addr address.Address - subnet address.Subnet - icmpv6 ICMPv6 - mtu int - iface *water.Interface - mutex sync.RWMutex // Protects the below - isOpen bool + config *config.NodeState + log *log.Logger + reconfigure chan chan error + conns map[crypto.NodeID]yggdrasil.Conn + connsMutex sync.RWMutex + listener *yggdrasil.Listener + dialer *yggdrasil.Dialer + addr address.Address + subnet address.Subnet + icmpv6 ICMPv6 + mtu int + iface *water.Interface + mutex sync.RWMutex // Protects the below + isOpen bool } // Gets the maximum supported MTU for the platform based on the defaults in @@ -94,62 +96,48 @@ func MaximumMTU() int { return defaults.GetDefaults().MaximumIfMTU } -// Init initialises the TUN/TAP adapter. -func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, send chan<- []byte, recv <-chan []byte, reject <-chan yggdrasil.RejectedPacket) { - tun.Adapter.Init(config, log, send, recv, reject) - tun.icmpv6.Init(tun) - go func() { - for { - e := <-tun.Reconfigure - tun.Config.Mutex.RLock() - updated := tun.Config.Current.IfName != tun.Config.Previous.IfName || - tun.Config.Current.IfTAPMode != tun.Config.Previous.IfTAPMode || - tun.Config.Current.IfMTU != tun.Config.Previous.IfMTU - tun.Config.Mutex.RUnlock() - if updated { - tun.Log.Warnln("Reconfiguring TUN/TAP is not supported yet") - e <- nil - } else { - e <- nil - } - } - }() +// Init initialises the TUN/TAP module. You must have acquired a Listener from +// the Yggdrasil core before this point and it must not be in use elsewhere. +func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener *yggdrasil.Listener, dialer *yggdrasil.Dialer) { + tun.config = config + tun.log = log + tun.listener = listener + tun.dialer = dialer + tun.conns = make(map[crypto.NodeID]yggdrasil.Conn) } // Start the setup process for the TUN/TAP adapter. If successful, starts the // read/write goroutines to handle packets on that interface. -func (tun *TunAdapter) Start(a address.Address, s address.Subnet) error { - tun.addr = a - tun.subnet = s - if tun.Config == nil { +func (tun *TunAdapter) Start() error { + tun.config.Mutex.Lock() + defer tun.config.Mutex.Unlock() + if tun.config == nil || tun.listener == nil || tun.dialer == nil { return errors.New("No configuration available to TUN/TAP") } - tun.Config.Mutex.RLock() - ifname := tun.Config.Current.IfName - iftapmode := tun.Config.Current.IfTAPMode - addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1) - mtu := tun.Config.Current.IfMTU - tun.Config.Mutex.RUnlock() + var boxPub crypto.BoxPubKey + boxPubHex, err := hex.DecodeString(tun.config.Current.EncryptionPublicKey) + if err != nil { + return err + } + copy(boxPub[:], boxPubHex) + nodeID := crypto.GetNodeID(&boxPub) + tun.addr = *address.AddrForNodeID(nodeID) + tun.subnet = *address.SubnetForNodeID(nodeID) + tun.mtu = tun.config.Current.IfMTU + ifname := tun.config.Current.IfName + iftapmode := tun.config.Current.IfTAPMode if ifname != "none" { - if err := tun.setup(ifname, iftapmode, addr, mtu); err != nil { + if err := tun.setup(ifname, iftapmode, net.IP(tun.addr[:]).String(), tun.mtu); err != nil { return err } } if ifname == "none" || ifname == "dummy" { - tun.Log.Debugln("Not starting TUN/TAP as ifname is none or dummy") + tun.log.Debugln("Not starting TUN/TAP as ifname is none or dummy") return nil } tun.mutex.Lock() tun.isOpen = true tun.mutex.Unlock() - go func() { - tun.Log.Debugln("Starting TUN/TAP reader goroutine") - tun.Log.Errorln("WARNING: tun.read() exited with error:", tun.read()) - }() - go func() { - tun.Log.Debugln("Starting TUN/TAP writer goroutine") - tun.Log.Errorln("WARNING: tun.write() exited with error:", tun.write()) - }() if iftapmode { go func() { for { @@ -167,74 +155,215 @@ func (tun *TunAdapter) Start(a address.Address, s address.Subnet) error { } }() } + tun.icmpv6.Init(tun) + go func() { + for { + e := <-tun.reconfigure + e <- nil + } + }() + go tun.handler() + go tun.ifaceReader() return nil } +func (tun *TunAdapter) handler() error { + for { + // Accept the incoming connection + conn, err := tun.listener.Accept() + if err != nil { + tun.log.Errorln("TUN/TAP error accepting connection:", err) + return err + } + tun.log.Println("Accepted connection from", conn.RemoteAddr()) + go tun.connReader(conn) + } +} + +func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { + b := make([]byte, 65535) + for { + n, err := conn.Read(b) + if err != nil { + tun.log.Errorln("TUN/TAP read error:", err) + return err + } + if n == 0 { + continue + } + w, err := tun.iface.Write(b[:n]) + if err != nil { + tun.log.Errorln("TUN/TAP failed to write to interface:", err) + continue + } + if w != n { + tun.log.Errorln("TUN/TAP wrote", w, "instead of", n, "which is bad") + continue + } + } +} + +func (tun *TunAdapter) ifaceReader() error { + tun.log.Println("Start TUN reader") + bs := make([]byte, 65535) + for { + n, err := tun.iface.Read(bs) + if err != nil { + tun.log.Errorln("TUN/TAP iface read error:", err) + } + // Look up if the dstination address is somewhere we already have an + // open connection to + var srcAddr address.Address + var dstAddr address.Address + var dstNodeID *crypto.NodeID + var dstNodeIDMask *crypto.NodeID + var dstSnet address.Subnet + var addrlen int + if bs[0]&0xf0 == 0x60 { + // Check if we have a fully-sized header + if len(bs) < 40 { + panic("Tried to send a packet shorter than an IPv6 header...") + } + // IPv6 address + addrlen = 16 + copy(srcAddr[:addrlen], bs[8:]) + copy(dstAddr[:addrlen], bs[24:]) + copy(dstSnet[:addrlen/2], bs[24:]) + } else if bs[0]&0xf0 == 0x40 { + // Check if we have a fully-sized header + if len(bs) < 20 { + panic("Tried to send a packet shorter than an IPv4 header...") + } + // IPv4 address + addrlen = 4 + copy(srcAddr[:addrlen], bs[12:]) + copy(dstAddr[:addrlen], bs[16:]) + } else { + // Unknown address length + continue + } + dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() + // Do we have an active connection for this node ID? + if conn, isIn := tun.conns[*dstNodeID]; isIn { + fmt.Println("We have a connection for", *dstNodeID) + w, err := conn.Write(bs) + if err != nil { + fmt.Println("Unable to write to remote:", err) + continue + } + if w != n { + continue + } + } else { + fmt.Println("Opening connection for", *dstNodeID) + tun.connsMutex.Lock() + maskstr := hex.EncodeToString(dstNodeID[:]) + masklen := dstNodeIDMask.PrefixLength() + cidr := fmt.Sprintf("%s/%d", maskstr, masklen) + if conn, err := tun.dialer.Dial("nodeid", cidr); err == nil { + tun.conns[*dstNodeID] = conn + go tun.connReader(&conn) + } else { + fmt.Println("Error dialing:", err) + } + tun.connsMutex.Unlock() + } + + /*if !r.cryptokey.isValidSource(srcAddr, addrlen) { + // The packet had a src address that doesn't belong to us or our + // configured crypto-key routing src subnets + return + } + if !dstAddr.IsValid() && !dstSnet.IsValid() { + // The addresses didn't match valid Yggdrasil node addresses so let's see + // whether it matches a crypto-key routing range instead + if key, err := r.cryptokey.getPublicKeyForAddress(dstAddr, addrlen); err == nil { + // A public key was found, get the node ID for the search + dstPubKey = &key + dstNodeID = crypto.GetNodeID(dstPubKey) + // Do a quick check to ensure that the node ID refers to a vaild Yggdrasil + // address or subnet - this might be superfluous + addr := *address.AddrForNodeID(dstNodeID) + copy(dstAddr[:], addr[:]) + copy(dstSnet[:], addr[:]) + if !dstAddr.IsValid() && !dstSnet.IsValid() { + return + } + } else { + // No public key was found in the CKR table so we've exhausted our options + return + } + }*/ + + } +} + // Writes a packet to the TUN/TAP adapter. If the adapter is running in TAP // mode then additional ethernet encapsulation is added for the benefit of the // host operating system. +/* func (tun *TunAdapter) write() error { for { select { case reject := <-tun.Reject: - switch reject.Reason { - case yggdrasil.PacketTooBig: - if mtu, ok := reject.Detail.(int); ok { - // Create the Packet Too Big response - ptb := &icmp.PacketTooBig{ - MTU: int(mtu), - Data: reject.Packet, - } - - // Create the ICMPv6 response from it - icmpv6Buf, err := CreateICMPv6( - reject.Packet[8:24], reject.Packet[24:40], - ipv6.ICMPTypePacketTooBig, 0, ptb) - - // Send the ICMPv6 response back to the TUN/TAP adapter - if err == nil { - tun.iface.Write(icmpv6Buf) - } + switch reject.Reason { + case yggdrasil.PacketTooBig: + if mtu, ok := reject.Detail.(int); ok { + // Create the Packet Too Big response + ptb := &icmp.PacketTooBig{ + MTU: int(mtu), + Data: reject.Packet, + } + + // Create the ICMPv6 response from it + icmpv6Buf, err := CreateICMPv6( + reject.Packet[8:24], reject.Packet[24:40], + ipv6.ICMPTypePacketTooBig, 0, ptb) + + // Send the ICMPv6 response back to the TUN/TAP adapter + if err == nil { + tun.iface.Write(icmpv6Buf) } - fallthrough - default: - continue } + fallthrough + default: + continue + } case data := <-tun.Recv: if tun.iface == nil { continue } if tun.iface.IsTAP() { - var destAddr address.Address + var dstAddr address.Address if data[0]&0xf0 == 0x60 { if len(data) < 40 { //panic("Tried to send a packet shorter than an IPv6 header...") util.PutBytes(data) continue } - copy(destAddr[:16], data[24:]) + copy(dstAddr[:16], data[24:]) } else if data[0]&0xf0 == 0x40 { if len(data) < 20 { //panic("Tried to send a packet shorter than an IPv4 header...") util.PutBytes(data) continue } - copy(destAddr[:4], data[16:]) + copy(dstAddr[:4], data[16:]) } else { return errors.New("Invalid address family") } - sendndp := func(destAddr address.Address) { - neigh, known := tun.icmpv6.peermacs[destAddr] + sendndp := func(dstAddr address.Address) { + neigh, known := tun.icmpv6.peermacs[dstAddr] known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30) if !known { - request, err := tun.icmpv6.CreateNDPL2(destAddr) + request, err := tun.icmpv6.CreateNDPL2(dstAddr) if err != nil { panic(err) } if _, err := tun.iface.Write(request); err != nil { panic(err) } - tun.icmpv6.peermacs[destAddr] = neighbor{ + tun.icmpv6.peermacs[dstAddr] = neighbor{ lastsolicitation: time.Now(), } } @@ -242,19 +371,19 @@ func (tun *TunAdapter) write() error { var peermac macAddress var peerknown bool if data[0]&0xf0 == 0x40 { - destAddr = tun.addr + dstAddr = tun.addr } else if data[0]&0xf0 == 0x60 { - if !bytes.Equal(tun.addr[:16], destAddr[:16]) && !bytes.Equal(tun.subnet[:8], destAddr[:8]) { - destAddr = tun.addr + if !bytes.Equal(tun.addr[:16], dstAddr[:16]) && !bytes.Equal(tun.subnet[:8], dstAddr[:8]) { + dstAddr = tun.addr } } - if neighbor, ok := tun.icmpv6.peermacs[destAddr]; ok && neighbor.learned { + if neighbor, ok := tun.icmpv6.peermacs[dstAddr]; ok && neighbor.learned { peermac = neighbor.mac peerknown = true } else if neighbor, ok := tun.icmpv6.peermacs[tun.addr]; ok && neighbor.learned { peermac = neighbor.mac peerknown = true - sendndp(destAddr) + sendndp(dstAddr) } else { sendndp(tun.addr) } @@ -359,3 +488,4 @@ func (tun *TunAdapter) Close() error { } return tun.iface.Close() } +*/ diff --git a/src/tuntap/tun_bsd.go b/src/tuntap/tun_bsd.go index 27c9bb2f..996f3140 100644 --- a/src/tuntap/tun_bsd.go +++ b/src/tuntap/tun_bsd.go @@ -109,14 +109,14 @@ func (tun *TunAdapter) setupAddress(addr string) error { // Create system socket if sfd, err = unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0); err != nil { - tun.Log.Printf("Create AF_INET socket failed: %v.", err) + tun.log.Printf("Create AF_INET socket failed: %v.", err) return err } // Friendly output - tun.Log.Infof("Interface name: %s", tun.iface.Name()) - tun.Log.Infof("Interface IPv6: %s", addr) - tun.Log.Infof("Interface MTU: %d", tun.mtu) + tun.log.Infof("Interface name: %s", tun.iface.Name()) + tun.log.Infof("Interface IPv6: %s", addr) + tun.log.Infof("Interface MTU: %d", tun.mtu) // Create the MTU request var ir in6_ifreq_mtu @@ -126,15 +126,15 @@ func (tun *TunAdapter) setupAddress(addr string) error { // Set the MTU if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(syscall.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 { err = errno - tun.Log.Errorf("Error in SIOCSIFMTU: %v", errno) + tun.log.Errorf("Error in SIOCSIFMTU: %v", errno) // Fall back to ifconfig to set the MTU cmd := exec.Command("ifconfig", tun.iface.Name(), "mtu", string(tun.mtu)) - tun.Log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " ")) + tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.Log.Errorf("SIOCSIFMTU fallback failed: %v.", err) - tun.Log.Traceln(string(output)) + tun.log.Errorf("SIOCSIFMTU fallback failed: %v.", err) + tun.log.Traceln(string(output)) } } @@ -155,15 +155,15 @@ func (tun *TunAdapter) setupAddress(addr string) error { // Set the interface address if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(SIOCSIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 { err = errno - tun.Log.Errorf("Error in SIOCSIFADDR_IN6: %v", errno) + tun.log.Errorf("Error in SIOCSIFADDR_IN6: %v", errno) // Fall back to ifconfig to set the address cmd := exec.Command("ifconfig", tun.iface.Name(), "inet6", addr) - tun.Log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " ")) + tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.Log.Errorf("SIOCSIFADDR_IN6 fallback failed: %v.", err) - tun.Log.Traceln(string(output)) + tun.log.Errorf("SIOCSIFADDR_IN6 fallback failed: %v.", err) + tun.log.Traceln(string(output)) } } diff --git a/src/tuntap/tun_darwin.go b/src/tuntap/tun_darwin.go index 60786b81..5dfca137 100644 --- a/src/tuntap/tun_darwin.go +++ b/src/tuntap/tun_darwin.go @@ -18,7 +18,7 @@ import ( // Configures the "utun" adapter with the correct IPv6 address and MTU. func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { if iftapmode { - tun.Log.Warnln("TAP mode is not supported on this platform, defaulting to TUN") + tun.log.Warnln("TAP mode is not supported on this platform, defaulting to TUN") } config := water.Config{DeviceType: water.TUN} iface, err := water.New(config) @@ -69,7 +69,7 @@ func (tun *TunAdapter) setupAddress(addr string) error { var err error if fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, 0); err != nil { - tun.Log.Printf("Create AF_SYSTEM socket failed: %v.", err) + tun.log.Printf("Create AF_SYSTEM socket failed: %v.", err) return err } @@ -98,19 +98,19 @@ func (tun *TunAdapter) setupAddress(addr string) error { copy(ir.ifr_name[:], tun.iface.Name()) ir.ifru_mtu = uint32(tun.mtu) - tun.Log.Infof("Interface name: %s", ar.ifra_name) - tun.Log.Infof("Interface IPv6: %s", addr) - tun.Log.Infof("Interface MTU: %d", ir.ifru_mtu) + tun.log.Infof("Interface name: %s", ar.ifra_name) + tun.log.Infof("Interface IPv6: %s", addr) + tun.log.Infof("Interface MTU: %d", ir.ifru_mtu) if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(darwin_SIOCAIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 { err = errno - tun.Log.Errorf("Error in darwin_SIOCAIFADDR_IN6: %v", errno) + tun.log.Errorf("Error in darwin_SIOCAIFADDR_IN6: %v", errno) return err } if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 { err = errno - tun.Log.Errorf("Error in SIOCSIFMTU: %v", errno) + tun.log.Errorf("Error in SIOCSIFMTU: %v", errno) return err } diff --git a/src/tuntap/tun_linux.go b/src/tuntap/tun_linux.go index 7d228574..c9c03c09 100644 --- a/src/tuntap/tun_linux.go +++ b/src/tuntap/tun_linux.go @@ -40,9 +40,9 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int } } // Friendly output - tun.Log.Infof("Interface name: %s", tun.iface.Name()) - tun.Log.Infof("Interface IPv6: %s", addr) - tun.Log.Infof("Interface MTU: %d", tun.mtu) + tun.log.Infof("Interface name: %s", tun.iface.Name()) + tun.log.Infof("Interface IPv6: %s", addr) + tun.log.Infof("Interface MTU: %d", tun.mtu) return tun.setupAddress(addr) } diff --git a/src/tuntap/tun_other.go b/src/tuntap/tun_other.go index bb302d19..48276b49 100644 --- a/src/tuntap/tun_other.go +++ b/src/tuntap/tun_other.go @@ -28,6 +28,6 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int // We don't know how to set the IPv6 address on an unknown platform, therefore // write about it to stdout and don't try to do anything further. func (tun *TunAdapter) setupAddress(addr string) error { - tun.Log.Warnln("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr) + tun.log.Warnln("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr) return nil } diff --git a/src/tuntap/tun_windows.go b/src/tuntap/tun_windows.go index 5a158b14..8a66ac62 100644 --- a/src/tuntap/tun_windows.go +++ b/src/tuntap/tun_windows.go @@ -15,7 +15,7 @@ import ( // delegate the hard work to "netsh". func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { if !iftapmode { - tun.Log.Warnln("TUN mode is not supported on this platform, defaulting to TAP") + tun.log.Warnln("TUN mode is not supported on this platform, defaulting to TAP") } config := water.Config{DeviceType: water.TAP} config.PlatformSpecificParams.ComponentID = "tap0901" @@ -31,19 +31,19 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int } // Disable/enable the interface to resets its configuration (invalidating iface) cmd := exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=DISABLED") - tun.Log.Printf("netsh command: %v", strings.Join(cmd.Args, " ")) + tun.log.Printf("netsh command: %v", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.Log.Errorf("Windows netsh failed: %v.", err) - tun.Log.Traceln(string(output)) + tun.log.Errorf("Windows netsh failed: %v.", err) + tun.log.Traceln(string(output)) return err } cmd = exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=ENABLED") - tun.Log.Printf("netsh command: %v", strings.Join(cmd.Args, " ")) + tun.log.Printf("netsh command: %v", strings.Join(cmd.Args, " ")) output, err = cmd.CombinedOutput() if err != nil { - tun.Log.Errorf("Windows netsh failed: %v.", err) - tun.Log.Traceln(string(output)) + tun.log.Errorf("Windows netsh failed: %v.", err) + tun.log.Traceln(string(output)) return err } // Get a new iface @@ -58,9 +58,9 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int panic(err) } // Friendly output - tun.Log.Infof("Interface name: %s", tun.iface.Name()) - tun.Log.Infof("Interface IPv6: %s", addr) - tun.Log.Infof("Interface MTU: %d", tun.mtu) + tun.log.Infof("Interface name: %s", tun.iface.Name()) + tun.log.Infof("Interface IPv6: %s", addr) + tun.log.Infof("Interface MTU: %d", tun.mtu) return tun.setupAddress(addr) } @@ -71,11 +71,11 @@ func (tun *TunAdapter) setupMTU(mtu int) error { fmt.Sprintf("interface=%s", tun.iface.Name()), fmt.Sprintf("mtu=%d", mtu), "store=active") - tun.Log.Debugln("netsh command: %v", strings.Join(cmd.Args, " ")) + tun.log.Debugln("netsh command: %v", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.Log.Errorf("Windows netsh failed: %v.", err) - tun.Log.Traceln(string(output)) + tun.log.Errorf("Windows netsh failed: %v.", err) + tun.log.Traceln(string(output)) return err } return nil @@ -88,11 +88,11 @@ func (tun *TunAdapter) setupAddress(addr string) error { fmt.Sprintf("interface=%s", tun.iface.Name()), fmt.Sprintf("addr=%s", addr), "store=active") - tun.Log.Debugln("netsh command: %v", strings.Join(cmd.Args, " ")) + tun.log.Debugln("netsh command: %v", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.Log.Errorf("Windows netsh failed: %v.", err) - tun.Log.Traceln(string(output)) + tun.log.Errorf("Windows netsh failed: %v.", err) + tun.log.Traceln(string(output)) return err } return nil diff --git a/src/yggdrasil/adapter.go b/src/yggdrasil/adapter.go deleted file mode 100644 index 8fadb195..00000000 --- a/src/yggdrasil/adapter.go +++ /dev/null @@ -1,47 +0,0 @@ -package yggdrasil - -import ( - "github.com/gologme/log" - "github.com/yggdrasil-network/yggdrasil-go/src/address" - "github.com/yggdrasil-network/yggdrasil-go/src/config" -) - -// Adapter defines the minimum required struct members for an adapter type. This -// is now the base type for adapters like tun.go. When implementing a new -// adapter type, you should extend the adapter struct with this one and should -// call the Adapter.Init() function when initialising. -type Adapter struct { - adapterImplementation - Core *Core - Config *config.NodeState - Log *log.Logger - Send chan<- []byte - Recv <-chan []byte - Reject <-chan RejectedPacket - Reconfigure chan chan error -} - -// Defines the minimum required functions for an adapter type. Note that the -// implementation of Init() should call Adapter.Init(). This is not exported -// because doing so breaks the gomobile bindings for iOS/Android. -type adapterImplementation interface { - Init(*config.NodeState, *log.Logger, chan<- []byte, <-chan []byte, <-chan RejectedPacket) - Name() string - MTU() int - IsTAP() bool - Start(address.Address, address.Subnet) error - Close() error -} - -// Init initialises the adapter with the necessary channels to operate from the -// router. When defining a new Adapter type, the Adapter should call this -// function from within it's own Init function to set up the channels. It is -// otherwise not expected for you to call this function directly. -func (adapter *Adapter) Init(config *config.NodeState, log *log.Logger, send chan<- []byte, recv <-chan []byte, reject <-chan RejectedPacket) { - adapter.Config = config - adapter.Log = log - adapter.Send = send - adapter.Recv = recv - adapter.Reject = reject - adapter.Reconfigure = make(chan chan error, 1) -} diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 874a7a9c..6334eefa 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -16,7 +16,7 @@ type Conn struct { nodeID *crypto.NodeID nodeMask *crypto.NodeID session *sessionInfo - sessionMutex *sync.RWMutex + mutex *sync.RWMutex readDeadline time.Time writeDeadline time.Time expired bool @@ -30,9 +30,10 @@ func (c *Conn) startSearch() { return } if sinfo != nil { - c.sessionMutex.Lock() + c.mutex.Lock() c.session = sinfo - c.sessionMutex.Unlock() + c.nodeID, c.nodeMask = sinfo.theirAddr.GetNodeIDandMask() + c.mutex.Unlock() } } doSearch := func() { @@ -65,8 +66,8 @@ func (c *Conn) startSearch() { } func (c *Conn) Read(b []byte) (int, error) { - c.sessionMutex.RLock() - defer c.sessionMutex.RUnlock() + c.mutex.RLock() + defer c.mutex.RUnlock() if c.expired { return 0, errors.New("session is closed") } @@ -119,8 +120,8 @@ func (c *Conn) Read(b []byte) (int, error) { } func (c *Conn) Write(b []byte) (bytesWritten int, err error) { - c.sessionMutex.RLock() - defer c.sessionMutex.RUnlock() + c.mutex.RLock() + defer c.mutex.RUnlock() if c.expired { return 0, errors.New("session is closed") } @@ -175,7 +176,9 @@ func (c *Conn) LocalAddr() crypto.NodeID { } func (c *Conn) RemoteAddr() crypto.NodeID { - return *crypto.GetNodeID(&c.session.theirPermPub) + c.mutex.RLock() + defer c.mutex.RUnlock() + return *c.nodeID } func (c *Conn) SetDeadline(t time.Time) error { diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index cfba833b..81be1b28 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -5,7 +5,6 @@ import ( "errors" "io/ioutil" "net" - "sync" "time" "github.com/gologme/log" @@ -77,7 +76,6 @@ func (c *Core) init() error { c.searches.init(c) c.dht.init(c) c.sessions.init(c) - //c.multicast.init(c) c.peers.init(c) c.router.init(c) c.switchTable.init(c) // TODO move before peers? before router? @@ -168,21 +166,6 @@ func BuildVersion() string { return buildVersion } -// SetRouterAdapter instructs Yggdrasil to use the given adapter when starting -// the router. The adapter must implement the standard -// adapter.adapterImplementation interface and should extend the adapter.Adapter -// struct. -func (c *Core) SetRouterAdapter(adapter interface{}) error { - // We do this because adapterImplementation is not a valid type for the - // gomobile bindings so we just ask for a generic interface and try to cast it - // to adapterImplementation instead - if a, ok := adapter.(adapterImplementation); ok { - c.router.adapter = a - return nil - } - return errors.New("unsuitable adapter") -} - // Start starts up Yggdrasil using the provided config.NodeConfig, and outputs // debug logging through the provided log.Logger. The started stack will include // TCP and UDP sockets, a multicast discovery socket, an admin socket, router, @@ -233,13 +216,6 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, return nil, err } - if c.router.adapter != nil { - if err := c.router.adapter.Start(c.router.addr, c.router.subnet); err != nil { - c.log.Errorln("Failed to start TUN/TAP") - return nil, err - } - } - go c.addPeerLoop() c.log.Infoln("Startup complete") @@ -249,14 +225,11 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, // Stop shuts down the Yggdrasil node. func (c *Core) Stop() { c.log.Infoln("Stopping...") - if c.router.adapter != nil { - c.router.adapter.Close() - } c.admin.close() } // ListenConn returns a listener for Yggdrasil session connections. -func (c *Core) ListenConn() (*Listener, error) { +func (c *Core) ConnListen() (*Listener, error) { c.sessions.listenerMutex.Lock() defer c.sessions.listenerMutex.Unlock() if c.sessions.listener != nil { @@ -270,40 +243,11 @@ func (c *Core) ListenConn() (*Listener, error) { return c.sessions.listener, nil } -// Dial opens a session to the given node. The first paramter should be "nodeid" -// and the second parameter should contain a hexadecimal representation of the -// target node ID. -func (c *Core) Dial(network, address string) (Conn, error) { - conn := Conn{ - sessionMutex: &sync.RWMutex{}, - } - nodeID := crypto.NodeID{} - nodeMask := crypto.NodeID{} - // Process - switch network { - case "nodeid": - // A node ID was provided - we don't need to do anything special with it - dest, err := hex.DecodeString(address) - if err != nil { - return Conn{}, err - } - copy(nodeID[:], dest) - for i := range nodeMask { - nodeMask[i] = 0xFF - } - default: - // An unexpected address type was given, so give up - return Conn{}, errors.New("unexpected address type") - } - conn.core = c - conn.nodeID = &nodeID - conn.nodeMask = &nodeMask - conn.core.router.doAdmin(func() { - conn.startSearch() - }) - conn.sessionMutex.Lock() - defer conn.sessionMutex.Unlock() - return conn, nil +// ConnDialer returns a dialer for Yggdrasil session connections. +func (c *Core) ConnDialer() (*Dialer, error) { + return &Dialer{ + core: c, + }, nil } // ListenTCP starts a new TCP listener. The input URI should match that of the diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go new file mode 100644 index 00000000..7042bd0e --- /dev/null +++ b/src/yggdrasil/dialer.go @@ -0,0 +1,70 @@ +package yggdrasil + +import ( + "encoding/hex" + "errors" + "fmt" + "strconv" + "strings" + "sync" + + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" +) + +// Dialer represents an Yggdrasil connection dialer. +type Dialer struct { + core *Core +} + +// Dial opens a session to the given node. The first paramter should be "nodeid" +// and the second parameter should contain a hexadecimal representation of the +// target node ID. +func (d *Dialer) Dial(network, address string) (Conn, error) { + conn := Conn{ + mutex: &sync.RWMutex{}, + } + nodeID := crypto.NodeID{} + nodeMask := crypto.NodeID{} + // Process + switch network { + case "nodeid": + // A node ID was provided - we don't need to do anything special with it + if tokens := strings.Split(address, "/"); len(tokens) == 2 { + len, err := strconv.Atoi(tokens[1]) + if err != nil { + return Conn{}, err + } + dest, err := hex.DecodeString(tokens[0]) + if err != nil { + return Conn{}, err + } + copy(nodeID[:], dest) + for idx := 0; idx < len; idx++ { + nodeMask[idx/8] |= 0x80 >> byte(idx%8) + } + fmt.Println(nodeID) + fmt.Println(nodeMask) + } else { + dest, err := hex.DecodeString(tokens[0]) + if err != nil { + return Conn{}, err + } + copy(nodeID[:], dest) + for i := range nodeMask { + nodeMask[i] = 0xFF + } + } + default: + // An unexpected address type was given, so give up + return Conn{}, errors.New("unexpected address type") + } + conn.core = d.core + conn.nodeID = &nodeID + conn.nodeMask = &nodeMask + conn.core.router.doAdmin(func() { + conn.startSearch() + }) + conn.mutex.Lock() + defer conn.mutex.Unlock() + return conn, nil +} diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 693fba44..19b62f91 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -41,7 +41,6 @@ type router struct { in <-chan []byte // packets we received from the network, link to peer's "out" out func([]byte) // packets we're sending to the network, link to peer's "in" toRecv chan router_recvPacket // packets to handle via recvPacket() - adapter adapterImplementation // TUN/TAP adapter recv chan<- []byte // place where the adapter pulls received packets from send <-chan []byte // place where the adapter puts outgoing packets reject chan<- RejectedPacket // place where we send error packets back to adapter @@ -136,9 +135,6 @@ func (r *router) init(core *Core) { r.nodeinfo.setNodeInfo(r.core.config.Current.NodeInfo, r.core.config.Current.NodeInfoPrivacy) r.core.config.Mutex.RUnlock() r.cryptokey.init(r.core) - if r.adapter != nil { - r.adapter.Init(&r.core.config, r.core.log, send, recv, reject) - } } // Starts the mainLoop goroutine. diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 9c09532b..3bf69a8c 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -289,9 +289,7 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.mySesPriv = *priv sinfo.myNonce = *crypto.NewBoxNonce() sinfo.theirMTU = 1280 - if ss.core.router.adapter != nil { - sinfo.myMTU = uint16(ss.core.router.adapter.MTU()) - } + sinfo.myMTU = 1280 now := time.Now() sinfo.timeMutex.Lock() sinfo.time = now @@ -480,11 +478,11 @@ func (ss *sessions) handlePing(ping *sessionPing) { ss.listenerMutex.Lock() if ss.listener != nil { conn := &Conn{ - core: ss.core, - session: sinfo, - sessionMutex: &sync.RWMutex{}, - nodeID: crypto.GetNodeID(&sinfo.theirPermPub), - nodeMask: &crypto.NodeID{}, + core: ss.core, + session: sinfo, + mutex: &sync.RWMutex{}, + nodeID: crypto.GetNodeID(&sinfo.theirPermPub), + nodeMask: &crypto.NodeID{}, } for i := range conn.nodeMask { conn.nodeMask[i] = 0xFF From 62621f29603bbfcf8ef97e2e7fbcb80822407b7c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 20 Apr 2019 20:22:58 +0100 Subject: [PATCH 0040/1109] Some tweaks --- src/tuntap/tun.go | 13 ++++--------- src/yggdrasil/conn.go | 3 +++ src/yggdrasil/dialer.go | 23 +++++++++++++---------- src/yggdrasil/search.go | 2 ++ 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 3010fae4..4b72b465 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -5,7 +5,6 @@ package tuntap import ( "encoding/hex" "errors" - "fmt" "net" "sync" "time" @@ -245,26 +244,22 @@ func (tun *TunAdapter) ifaceReader() error { dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() // Do we have an active connection for this node ID? if conn, isIn := tun.conns[*dstNodeID]; isIn { - fmt.Println("We have a connection for", *dstNodeID) w, err := conn.Write(bs) if err != nil { - fmt.Println("Unable to write to remote:", err) + tun.log.Println("Unable to write to remote:", err) continue } if w != n { continue } } else { - fmt.Println("Opening connection for", *dstNodeID) + tun.log.Println("Opening connection for", *dstNodeID) tun.connsMutex.Lock() - maskstr := hex.EncodeToString(dstNodeID[:]) - masklen := dstNodeIDMask.PrefixLength() - cidr := fmt.Sprintf("%s/%d", maskstr, masklen) - if conn, err := tun.dialer.Dial("nodeid", cidr); err == nil { + if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { tun.conns[*dstNodeID] = conn go tun.connReader(&conn) } else { - fmt.Println("Error dialing:", err) + tun.log.Println("Error dialing:", err) } tun.connsMutex.Unlock() } diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 6334eefa..9566cae3 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -27,6 +27,9 @@ func (c *Conn) startSearch() { searchCompleted := func(sinfo *sessionInfo, err error) { if err != nil { c.core.log.Debugln("DHT search failed:", err) + c.mutex.Lock() + c.expired = true + c.mutex.Unlock() return } if sinfo != nil { diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index 7042bd0e..fee355e4 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -3,7 +3,6 @@ package yggdrasil import ( "encoding/hex" "errors" - "fmt" "strconv" "strings" "sync" @@ -20,11 +19,8 @@ type Dialer struct { // and the second parameter should contain a hexadecimal representation of the // target node ID. func (d *Dialer) Dial(network, address string) (Conn, error) { - conn := Conn{ - mutex: &sync.RWMutex{}, - } - nodeID := crypto.NodeID{} - nodeMask := crypto.NodeID{} + var nodeID crypto.NodeID + var nodeMask crypto.NodeID // Process switch network { case "nodeid": @@ -42,8 +38,6 @@ func (d *Dialer) Dial(network, address string) (Conn, error) { for idx := 0; idx < len; idx++ { nodeMask[idx/8] |= 0x80 >> byte(idx%8) } - fmt.Println(nodeID) - fmt.Println(nodeMask) } else { dest, err := hex.DecodeString(tokens[0]) if err != nil { @@ -54,13 +48,22 @@ func (d *Dialer) Dial(network, address string) (Conn, error) { nodeMask[i] = 0xFF } } + return d.DialByNodeIDandMask(&nodeID, &nodeMask) default: // An unexpected address type was given, so give up return Conn{}, errors.New("unexpected address type") } +} + +// DialByNodeIDandMask opens a session to the given node based on raw +// NodeID parameters. +func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (Conn, error) { + conn := Conn{ + mutex: &sync.RWMutex{}, + } conn.core = d.core - conn.nodeID = &nodeID - conn.nodeMask = &nodeMask + conn.nodeID = nodeID + conn.nodeMask = nodeMask conn.core.router.doAdmin(func() { conn.startSearch() }) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index e81a9723..dcf0c81f 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -212,7 +212,9 @@ func (s *searches) checkDHTRes(info *searchInfo, res *dhtRes) bool { } } // FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)? + sinfo.coordsMutex.Lock() sinfo.coords = res.Coords + sinfo.coordsMutex.Unlock() sinfo.packet = info.packet s.core.sessions.ping(sinfo) info.callback(sinfo, nil) From 79bcfbf17578c594f53d1ba260cc4c29d5abca9b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 21 Apr 2019 11:50:41 +0100 Subject: [PATCH 0041/1109] Change some mutexes to atomics, change conns map to pointers, sort of works but seems to deadlock very easily --- src/tuntap/tun.go | 12 ++--- src/yggdrasil/conn.go | 41 ++++++--------- src/yggdrasil/dialer.go | 12 ++--- src/yggdrasil/router.go | 22 ++++---- src/yggdrasil/search.go | 2 - src/yggdrasil/session.go | 107 ++++++++++++++++----------------------- 6 files changed, 81 insertions(+), 115 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 4b72b465..9e46124a 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -30,8 +30,7 @@ type TunAdapter struct { config *config.NodeState log *log.Logger reconfigure chan chan error - conns map[crypto.NodeID]yggdrasil.Conn - connsMutex sync.RWMutex + conns map[crypto.NodeID]*yggdrasil.Conn listener *yggdrasil.Listener dialer *yggdrasil.Dialer addr address.Address @@ -102,7 +101,7 @@ func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener tun.log = log tun.listener = listener tun.dialer = dialer - tun.conns = make(map[crypto.NodeID]yggdrasil.Conn) + tun.conns = make(map[crypto.NodeID]*yggdrasil.Conn) } // Start the setup process for the TUN/TAP adapter. If successful, starts the @@ -180,6 +179,7 @@ func (tun *TunAdapter) handler() error { } func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { + tun.conns[conn.RemoteAddr()] = conn b := make([]byte, 65535) for { n, err := conn.Read(b) @@ -203,7 +203,6 @@ func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { } func (tun *TunAdapter) ifaceReader() error { - tun.log.Println("Start TUN reader") bs := make([]byte, 65535) for { n, err := tun.iface.Read(bs) @@ -244,6 +243,7 @@ func (tun *TunAdapter) ifaceReader() error { dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() // Do we have an active connection for this node ID? if conn, isIn := tun.conns[*dstNodeID]; isIn { + tun.log.Println("Got", &conn) w, err := conn.Write(bs) if err != nil { tun.log.Println("Unable to write to remote:", err) @@ -254,14 +254,12 @@ func (tun *TunAdapter) ifaceReader() error { } } else { tun.log.Println("Opening connection for", *dstNodeID) - tun.connsMutex.Lock() if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { - tun.conns[*dstNodeID] = conn + tun.conns[*dstNodeID] = &conn go tun.connReader(&conn) } else { tun.log.Println("Error dialing:", err) } - tun.connsMutex.Unlock() } /*if !r.cryptokey.isValidSource(srcAddr, addrlen) { diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 9566cae3..daba2987 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -1,8 +1,8 @@ package yggdrasil import ( - "encoding/hex" "errors" + "fmt" "sync" "sync/atomic" "time" @@ -42,27 +42,27 @@ func (c *Conn) startSearch() { doSearch := func() { sinfo, isIn := c.core.searches.searches[*c.nodeID] if !isIn { - c.core.log.Debugln("Starting search for", hex.EncodeToString(c.nodeID[:])) sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) } c.core.searches.continueSearch(sinfo) } - var sinfo *sessionInfo - var isIn bool switch { - case !isIn || !sinfo.init: + case c.session == nil || !c.session.init.Load().(bool): doSearch() - case time.Since(sinfo.time) > 6*time.Second: - if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second { + case time.Since(c.session.time.Load().(time.Time)) > 6*time.Second: + sTime := c.session.time.Load().(time.Time) + pingTime := c.session.pingTime.Load().(time.Time) + if sTime.Before(pingTime) && time.Since(pingTime) > 6*time.Second { doSearch() } else { + pingSend := c.session.pingSend.Load().(time.Time) now := time.Now() - if !sinfo.time.Before(sinfo.pingTime) { - sinfo.pingTime = now + if !sTime.Before(pingTime) { + c.session.pingTime.Store(now) } - if time.Since(sinfo.pingSend) > time.Second { - sinfo.pingSend = now - c.core.sessions.sendPingPong(sinfo, false) + if time.Since(pingSend) > time.Second { + c.session.pingSend.Store(now) + c.core.sessions.sendPingPong(c.session, false) } } } @@ -77,12 +77,9 @@ func (c *Conn) Read(b []byte) (int, error) { if c.session == nil { return 0, errors.New("searching for remote side") } - c.session.initMutex.RLock() - if !c.session.init { - c.session.initMutex.RUnlock() + if !c.session.init.Load().(bool) { return 0, errors.New("waiting for remote side to accept") } - c.session.initMutex.RUnlock() select { case p, ok := <-c.session.recv: if !ok { @@ -106,9 +103,7 @@ func (c *Conn) Read(b []byte) (int, error) { b = b[:len(bs)] } c.session.updateNonce(&p.Nonce) - c.session.timeMutex.Lock() - c.session.time = time.Now() - c.session.timeMutex.Unlock() + c.session.time.Store(time.Now()) return nil }() if err != nil { @@ -129,22 +124,18 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { return 0, errors.New("session is closed") } if c.session == nil { + fmt.Println("No session found, starting search for", &c) c.core.router.doAdmin(func() { c.startSearch() }) return 0, errors.New("searching for remote side") } defer util.PutBytes(b) - c.session.initMutex.RLock() - if !c.session.init { - c.session.initMutex.RUnlock() + if !c.session.init.Load().(bool) { return 0, errors.New("waiting for remote side to accept") } - c.session.initMutex.RUnlock() // code isn't multithreaded so appending to this is safe - c.session.coordsMutex.RLock() coords := c.session.coords - c.session.coordsMutex.RUnlock() // Prepare the payload c.session.myNonceMutex.Lock() payload, nonce := crypto.BoxSeal(&c.session.sharedSesKey, b, &c.session.myNonce) diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index fee355e4..4a3d8167 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -35,7 +35,7 @@ func (d *Dialer) Dial(network, address string) (Conn, error) { return Conn{}, err } copy(nodeID[:], dest) - for idx := 0; idx < len; idx++ { + for idx := 0; idx <= len; idx++ { nodeMask[idx/8] |= 0x80 >> byte(idx%8) } } else { @@ -59,15 +59,13 @@ func (d *Dialer) Dial(network, address string) (Conn, error) { // NodeID parameters. func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (Conn, error) { conn := Conn{ - mutex: &sync.RWMutex{}, + core: d.core, + mutex: &sync.RWMutex{}, + nodeID: nodeID, + nodeMask: nodeMask, } - conn.core = d.core - conn.nodeID = nodeID - conn.nodeMask = nodeMask conn.core.router.doAdmin(func() { conn.startSearch() }) - conn.mutex.Lock() - defer conn.mutex.Unlock() return conn, nil } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 19b62f91..7da5162f 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -287,16 +287,15 @@ func (r *router) sendPacket(bs []byte) { if destSnet.IsValid() { sinfo, isIn = r.core.sessions.getByTheirSubnet(&destSnet) } - sinfo.timeMutex.Lock() - sinfo.initMutex.RLock() - defer sinfo.timeMutex.Unlock() - defer sinfo.initMutex.RUnlock() + sTime := sinfo.time.Load().(time.Time) + pingTime := sinfo.pingTime.Load().(time.Time) + pingSend := sinfo.pingSend.Load().(time.Time) switch { - case !isIn || !sinfo.init: + case !isIn || !sinfo.init.Load().(bool): // No or unintiialized session, so we need to search first doSearch(bs) - case time.Since(sinfo.time) > 6*time.Second: - if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second { + case time.Since(sTime) > 6*time.Second: + if sTime.Before(pingTime) && time.Since(pingTime) > 6*time.Second { // We haven't heard from the dest in a while // We tried pinging but didn't get a response // They may have changed coords @@ -307,16 +306,15 @@ func (r *router) sendPacket(bs []byte) { // We haven't heard about the dest in a while now := time.Now() - if !sinfo.time.Before(sinfo.pingTime) { + if !sTime.Before(pingTime) { // Update pingTime to start the clock for searches (above) - sinfo.pingTime = now + sinfo.pingTime.Store(now) } - if time.Since(sinfo.pingSend) > time.Second { + if time.Since(pingSend) > time.Second { // Send at most 1 ping per second - sinfo.pingSend = now + sinfo.pingSend.Store(now) r.core.sessions.sendPingPong(sinfo, false) } - sinfo.timeMutex.Unlock() } fallthrough // Also send the packet default: diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index dcf0c81f..e81a9723 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -212,9 +212,7 @@ func (s *searches) checkDHTRes(info *searchInfo, res *dhtRes) bool { } } // FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)? - sinfo.coordsMutex.Lock() sinfo.coords = res.Coords - sinfo.coordsMutex.Unlock() sinfo.packet = info.packet s.core.sessions.ping(sinfo) info.callback(sinfo, nil) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 3bf69a8c..b0bba2d0 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -18,41 +18,38 @@ import ( // All the information we know about an active session. // This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API. type sessionInfo struct { - core *Core - reconfigure chan chan error - theirAddr address.Address - theirSubnet address.Subnet - theirPermPub crypto.BoxPubKey - theirSesPub crypto.BoxPubKey - mySesPub crypto.BoxPubKey - mySesPriv crypto.BoxPrivKey - sharedSesKey crypto.BoxSharedKey // derived from session keys - theirHandle crypto.Handle - myHandle crypto.Handle - theirNonce crypto.BoxNonce - theirNonceMask uint64 - theirNonceMutex sync.Mutex // protects the above - myNonce crypto.BoxNonce - myNonceMutex sync.Mutex // protects the above - theirMTU uint16 - myMTU uint16 - wasMTUFixed bool // Was the MTU fixed by a receive error? - time time.Time // Time we last received a packet - mtuTime time.Time // time myMTU was last changed - pingTime time.Time // time the first ping was sent since the last received packet - pingSend time.Time // time the last ping was sent - timeMutex sync.RWMutex // protects all time fields above - coords []byte // coords of destination - coordsMutex sync.RWMutex // protects the above - packet []byte // a buffered packet, sent immediately on ping/pong - init bool // Reset if coords change - initMutex sync.RWMutex - send chan []byte - recv chan *wire_trafficPacket - closed chan interface{} - tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation - bytesSent uint64 // Bytes of real traffic sent in this session - bytesRecvd uint64 // Bytes of real traffic received in this session + core *Core // + reconfigure chan chan error // + theirAddr address.Address // + theirSubnet address.Subnet // + theirPermPub crypto.BoxPubKey // + theirSesPub crypto.BoxPubKey // + mySesPub crypto.BoxPubKey // + mySesPriv crypto.BoxPrivKey // + sharedSesKey crypto.BoxSharedKey // derived from session keys + theirHandle crypto.Handle // + myHandle crypto.Handle // + theirNonce crypto.BoxNonce // + theirNonceMask uint64 // + theirNonceMutex sync.Mutex // protects the above + myNonce crypto.BoxNonce // + myNonceMutex sync.Mutex // protects the above + theirMTU uint16 // + myMTU uint16 // + wasMTUFixed bool // Was the MTU fixed by a receive error? + time atomic.Value // time.Time // Time we last received a packet + mtuTime atomic.Value // time.Time // time myMTU was last changed + pingTime atomic.Value // time.Time // time the first ping was sent since the last received packet + pingSend atomic.Value // time.Time // time the last ping was sent + coords []byte // coords of destination + packet []byte // a buffered packet, sent immediately on ping/pong + init atomic.Value // bool // Reset if coords change + send chan []byte // + recv chan *wire_trafficPacket // + closed chan interface{} // + tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation + bytesSent uint64 // Bytes of real traffic sent in this session + bytesRecvd uint64 // Bytes of real traffic received in this session } // Represents a session ping/pong packet, andincludes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU. @@ -60,10 +57,10 @@ type sessionPing struct { SendPermPub crypto.BoxPubKey // Sender's permanent key Handle crypto.Handle // Random number to ID session SendSesPub crypto.BoxPubKey // Session key to use - Coords []byte - Tstamp int64 // unix time, but the only real requirement is that it increases - IsPong bool - MTU uint16 + Coords []byte // + Tstamp int64 // unix time, but the only real requirement is that it increases + IsPong bool // + MTU uint16 // } // Updates session info in response to a ping, after checking that the ping is OK. @@ -93,21 +90,15 @@ func (s *sessionInfo) update(p *sessionPing) bool { s.coords = append(make([]byte, 0, len(p.Coords)+11), p.Coords...) } now := time.Now() - s.timeMutex.Lock() - s.time = now - s.timeMutex.Unlock() + s.time.Store(now) atomic.StoreInt64(&s.tstamp, p.Tstamp) - s.initMutex.Lock() - s.init = true - s.initMutex.Unlock() + s.init.Store(true) return true } // Returns true if the session has been idle for longer than the allowed timeout. func (s *sessionInfo) timedout() bool { - s.timeMutex.RLock() - defer s.timeMutex.RUnlock() - return time.Since(s.time) > time.Minute + return time.Since(s.time.Load().(time.Time)) > time.Minute } // Struct of all active sessions. @@ -291,12 +282,10 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.theirMTU = 1280 sinfo.myMTU = 1280 now := time.Now() - sinfo.timeMutex.Lock() - sinfo.time = now - sinfo.mtuTime = now - sinfo.pingTime = now - sinfo.pingSend = now - sinfo.timeMutex.Unlock() + sinfo.time.Store(now) + sinfo.mtuTime.Store(now) + sinfo.pingTime.Store(now) + sinfo.pingSend.Store(now) higher := false for idx := range ss.core.boxPub { if ss.core.boxPub[idx] > sinfo.theirPermPub[idx] { @@ -437,7 +426,6 @@ func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) { bs := ping.encode() shared := ss.getSharedKey(&ss.core.boxPriv, &sinfo.theirPermPub) payload, nonce := crypto.BoxSeal(shared, bs, nil) - sinfo.coordsMutex.RLock() p := wire_protoTrafficPacket{ Coords: sinfo.coords, ToKey: sinfo.theirPermPub, @@ -445,13 +433,10 @@ func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) { Nonce: *nonce, Payload: payload, } - sinfo.coordsMutex.RUnlock() packet := p.encode() ss.core.router.out(packet) if !isPong { - sinfo.timeMutex.Lock() - sinfo.pingSend = time.Now() - sinfo.timeMutex.Unlock() + sinfo.pingSend.Store(time.Now()) } } @@ -551,8 +536,6 @@ func (sinfo *sessionInfo) updateNonce(theirNonce *crypto.BoxNonce) { // Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change. func (ss *sessions) resetInits() { for _, sinfo := range ss.sinfos { - sinfo.initMutex.Lock() - sinfo.init = false - sinfo.initMutex.Unlock() + sinfo.init.Store(false) } } From 781cd7571f8ba92fe9ab1834b30b457efb338cdc Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 21 Apr 2019 12:00:31 +0100 Subject: [PATCH 0042/1109] Fix race on tun conns, but still deadlocks if more than one connection is opened --- cmd/yggdrasil/main.go | 1 - src/tuntap/tun.go | 23 ++++++++++++++++++----- src/yggdrasil/conn.go | 2 -- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index f3d933c2..96349835 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -265,7 +265,6 @@ func main() { // Start the TUN/TAP interface if listener, err := n.core.ConnListen(); err == nil { if dialer, err := n.core.ConnDialer(); err == nil { - logger.Println("Got listener", listener, "and dialer", dialer) n.tuntap.Init(state, logger, listener, dialer) if err := n.tuntap.Start(); err != nil { logger.Errorln("An error occurred starting TUN/TAP:", err) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 9e46124a..acdcf3a1 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -30,7 +30,6 @@ type TunAdapter struct { config *config.NodeState log *log.Logger reconfigure chan chan error - conns map[crypto.NodeID]*yggdrasil.Conn listener *yggdrasil.Listener dialer *yggdrasil.Dialer addr address.Address @@ -39,6 +38,7 @@ type TunAdapter struct { mtu int iface *water.Interface mutex sync.RWMutex // Protects the below + conns map[crypto.NodeID]*yggdrasil.Conn isOpen bool } @@ -173,13 +173,24 @@ func (tun *TunAdapter) handler() error { tun.log.Errorln("TUN/TAP error accepting connection:", err) return err } - tun.log.Println("Accepted connection from", conn.RemoteAddr()) go tun.connReader(conn) } } func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { - tun.conns[conn.RemoteAddr()] = conn + remoteNodeID := conn.RemoteAddr() + tun.mutex.Lock() + if _, isIn := tun.conns[remoteNodeID]; isIn { + tun.mutex.Unlock() + return errors.New("duplicate connection") + } + tun.conns[remoteNodeID] = conn + tun.mutex.Unlock() + defer func() { + tun.mutex.Lock() + delete(tun.conns, remoteNodeID) + tun.mutex.Unlock() + }() b := make([]byte, 65535) for { n, err := conn.Read(b) @@ -242,8 +253,9 @@ func (tun *TunAdapter) ifaceReader() error { } dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() // Do we have an active connection for this node ID? + tun.mutex.Lock() if conn, isIn := tun.conns[*dstNodeID]; isIn { - tun.log.Println("Got", &conn) + tun.mutex.Unlock() w, err := conn.Write(bs) if err != nil { tun.log.Println("Unable to write to remote:", err) @@ -253,11 +265,12 @@ func (tun *TunAdapter) ifaceReader() error { continue } } else { - tun.log.Println("Opening connection for", *dstNodeID) if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { tun.conns[*dstNodeID] = &conn + tun.mutex.Unlock() go tun.connReader(&conn) } else { + tun.mutex.Unlock() tun.log.Println("Error dialing:", err) } } diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index daba2987..ed9eb6cd 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -2,7 +2,6 @@ package yggdrasil import ( "errors" - "fmt" "sync" "sync/atomic" "time" @@ -124,7 +123,6 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { return 0, errors.New("session is closed") } if c.session == nil { - fmt.Println("No session found, starting search for", &c) c.core.router.doAdmin(func() { c.startSearch() }) From 0b8f5b5dda8122b7563b3207a2dcb981d1055cc6 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 21 Apr 2019 12:28:46 +0100 Subject: [PATCH 0043/1109] Tweaks --- src/tuntap/tun.go | 18 +++++++++--------- src/yggdrasil/conn.go | 17 ++++++++++------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index acdcf3a1..d9e0e77e 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -170,7 +170,7 @@ func (tun *TunAdapter) handler() error { // Accept the incoming connection conn, err := tun.listener.Accept() if err != nil { - tun.log.Errorln("TUN/TAP error accepting connection:", err) + tun.log.Errorln("TUN/TAP connection accept error:", err) return err } go tun.connReader(conn) @@ -195,7 +195,7 @@ func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { for { n, err := conn.Read(b) if err != nil { - tun.log.Errorln("TUN/TAP read error:", err) + tun.log.Errorln("TUN/TAP conn read error:", err) return err } if n == 0 { @@ -203,11 +203,11 @@ func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { } w, err := tun.iface.Write(b[:n]) if err != nil { - tun.log.Errorln("TUN/TAP failed to write to interface:", err) + tun.log.Errorln("TUN/TAP iface write error:", err) continue } if w != n { - tun.log.Errorln("TUN/TAP wrote", w, "instead of", n, "which is bad") + tun.log.Errorln("TUN/TAP iface write len didn't match conn read len") continue } } @@ -231,7 +231,7 @@ func (tun *TunAdapter) ifaceReader() error { if bs[0]&0xf0 == 0x60 { // Check if we have a fully-sized header if len(bs) < 40 { - panic("Tried to send a packet shorter than an IPv6 header...") + continue } // IPv6 address addrlen = 16 @@ -241,14 +241,14 @@ func (tun *TunAdapter) ifaceReader() error { } else if bs[0]&0xf0 == 0x40 { // Check if we have a fully-sized header if len(bs) < 20 { - panic("Tried to send a packet shorter than an IPv4 header...") + continue } // IPv4 address addrlen = 4 copy(srcAddr[:addrlen], bs[12:]) copy(dstAddr[:addrlen], bs[16:]) } else { - // Unknown address length + // Unknown address length or protocol continue } dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() @@ -258,7 +258,7 @@ func (tun *TunAdapter) ifaceReader() error { tun.mutex.Unlock() w, err := conn.Write(bs) if err != nil { - tun.log.Println("Unable to write to remote:", err) + tun.log.Println("TUN/TAP conn write error:", err) continue } if w != n { @@ -271,7 +271,7 @@ func (tun *TunAdapter) ifaceReader() error { go tun.connReader(&conn) } else { tun.mutex.Unlock() - tun.log.Println("Error dialing:", err) + tun.log.Println("TUN/TAP dial error:", err) } } diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index ed9eb6cd..977c7421 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -2,6 +2,7 @@ package yggdrasil import ( "errors" + "fmt" "sync" "sync/atomic" "time" @@ -21,6 +22,10 @@ type Conn struct { expired bool } +func (c *Conn) String() string { + return fmt.Sprintf("c=%p", c) +} + // This method should only be called from the router goroutine func (c *Conn) startSearch() { searchCompleted := func(sinfo *sessionInfo, err error) { @@ -76,8 +81,8 @@ func (c *Conn) Read(b []byte) (int, error) { if c.session == nil { return 0, errors.New("searching for remote side") } - if !c.session.init.Load().(bool) { - return 0, errors.New("waiting for remote side to accept") + if init, ok := c.session.init.Load().(bool); !ok || (ok && !init) { + return 0, errors.New("waiting for remote side to accept " + c.String()) } select { case p, ok := <-c.session.recv: @@ -129,15 +134,12 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { return 0, errors.New("searching for remote side") } defer util.PutBytes(b) - if !c.session.init.Load().(bool) { - return 0, errors.New("waiting for remote side to accept") + if init, ok := c.session.init.Load().(bool); !ok || (ok && !init) { + return 0, errors.New("waiting for remote side to accept " + c.String()) } - // code isn't multithreaded so appending to this is safe coords := c.session.coords - // Prepare the payload c.session.myNonceMutex.Lock() payload, nonce := crypto.BoxSeal(&c.session.sharedSesKey, b, &c.session.myNonce) - c.session.myNonceMutex.Unlock() defer util.PutBytes(payload) p := wire_trafficPacket{ Coords: coords, @@ -146,6 +148,7 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { Payload: payload, } packet := p.encode() + c.session.myNonceMutex.Unlock() atomic.AddUint64(&c.session.bytesSent, uint64(len(b))) select { case c.session.send <- packet: From 5dada3952c5c2a59390a5db19f99fceecd4754a7 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 21 Apr 2019 20:38:14 -0500 Subject: [PATCH 0044/1109] use a session worker to try to avoid mutex hell. compiles, but incomplete and doesn't work yet --- src/yggdrasil/conn.go | 162 +++++++++++++++++++++------------------ src/yggdrasil/dialer.go | 1 + src/yggdrasil/router.go | 34 ++++---- src/yggdrasil/session.go | 145 ++++++++++++++++++++--------------- 4 files changed, 191 insertions(+), 151 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 977c7421..7898a128 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "sync" - "sync/atomic" "time" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" @@ -15,10 +14,11 @@ type Conn struct { core *Core nodeID *crypto.NodeID nodeMask *crypto.NodeID - session *sessionInfo + recv chan *wire_trafficPacket // Eventually gets attached to session.recv mutex *sync.RWMutex - readDeadline time.Time - writeDeadline time.Time + session *sessionInfo + readDeadline time.Time // TODO timer + writeDeadline time.Time // TODO timer expired bool } @@ -39,6 +39,7 @@ func (c *Conn) startSearch() { if sinfo != nil { c.mutex.Lock() c.session = sinfo + c.session.recv = c.recv c.nodeID, c.nodeMask = sinfo.theirAddr.GetNodeIDandMask() c.mutex.Unlock() } @@ -50,113 +51,124 @@ func (c *Conn) startSearch() { } c.core.searches.continueSearch(sinfo) } - switch { - case c.session == nil || !c.session.init.Load().(bool): + c.mutex.RLock() + defer c.mutex.RUnlock() + if c.session == nil { doSearch() - case time.Since(c.session.time.Load().(time.Time)) > 6*time.Second: - sTime := c.session.time.Load().(time.Time) - pingTime := c.session.pingTime.Load().(time.Time) - if sTime.Before(pingTime) && time.Since(pingTime) > 6*time.Second { - doSearch() - } else { - pingSend := c.session.pingSend.Load().(time.Time) - now := time.Now() - if !sTime.Before(pingTime) { - c.session.pingTime.Store(now) - } - if time.Since(pingSend) > time.Second { - c.session.pingSend.Store(now) - c.core.sessions.sendPingPong(c.session, false) + } else { + sinfo := c.session // In case c.session is somehow changed meanwhile + sinfo.worker <- func() { + switch { + case !sinfo.init: + doSearch() + case time.Since(sinfo.time) > 6*time.Second: + if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second { + // TODO double check that the above condition is correct + doSearch() + } else { + c.core.sessions.ping(sinfo) + } + default: // Don't do anything, to keep traffic throttled } } } } func (c *Conn) Read(b []byte) (int, error) { - c.mutex.RLock() - defer c.mutex.RUnlock() - if c.expired { - return 0, errors.New("session is closed") - } - if c.session == nil { - return 0, errors.New("searching for remote side") - } - if init, ok := c.session.init.Load().(bool); !ok || (ok && !init) { - return 0, errors.New("waiting for remote side to accept " + c.String()) + err := func() error { + c.mutex.RLock() + defer c.mutex.RUnlock() + if c.expired { + return errors.New("session is closed") + } + return nil + }() + if err != nil { + return 0, err } select { - case p, ok := <-c.session.recv: + // TODO... + case p, ok := <-c.recv: if !ok { + c.mutex.Lock() c.expired = true + c.mutex.Unlock() return 0, errors.New("session is closed") } defer util.PutBytes(p.Payload) - err := func() error { - c.session.theirNonceMutex.Lock() - defer c.session.theirNonceMutex.Unlock() - if !c.session.nonceIsOK(&p.Nonce) { - return errors.New("packet dropped due to invalid nonce") + c.mutex.RLock() + sinfo := c.session + c.mutex.RUnlock() + var err error + sinfo.doWorker(func() { + if !sinfo.nonceIsOK(&p.Nonce) { + err = errors.New("packet dropped due to invalid nonce") + return } - bs, isOK := crypto.BoxOpen(&c.session.sharedSesKey, p.Payload, &p.Nonce) + bs, isOK := crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce) if !isOK { util.PutBytes(bs) - return errors.New("packet dropped due to decryption failure") + err = errors.New("packet dropped due to decryption failure") + return } copy(b, bs) if len(bs) < len(b) { b = b[:len(bs)] } - c.session.updateNonce(&p.Nonce) - c.session.time.Store(time.Now()) - return nil - }() + sinfo.updateNonce(&p.Nonce) + sinfo.time = time.Now() + sinfo.bytesRecvd += uint64(len(b)) + }) if err != nil { return 0, err } - atomic.AddUint64(&c.session.bytesRecvd, uint64(len(b))) return len(b), nil - case <-c.session.closed: - c.expired = true - return len(b), errors.New("session is closed") + //case <-c.recvTimeout: + //case <-c.session.closed: + // c.expired = true + // return len(b), errors.New("session is closed") } } func (c *Conn) Write(b []byte) (bytesWritten int, err error) { - c.mutex.RLock() - defer c.mutex.RUnlock() - if c.expired { - return 0, errors.New("session is closed") + var sinfo *sessionInfo + err = func() error { + c.mutex.RLock() + defer c.mutex.RUnlock() + if c.expired { + return errors.New("session is closed") + } + sinfo = c.session + return nil + }() + if err != nil { + return 0, err } - if c.session == nil { + if sinfo == nil { c.core.router.doAdmin(func() { c.startSearch() }) return 0, errors.New("searching for remote side") } - defer util.PutBytes(b) - if init, ok := c.session.init.Load().(bool); !ok || (ok && !init) { - return 0, errors.New("waiting for remote side to accept " + c.String()) - } - coords := c.session.coords - c.session.myNonceMutex.Lock() - payload, nonce := crypto.BoxSeal(&c.session.sharedSesKey, b, &c.session.myNonce) - defer util.PutBytes(payload) - p := wire_trafficPacket{ - Coords: coords, - Handle: c.session.theirHandle, - Nonce: *nonce, - Payload: payload, - } - packet := p.encode() - c.session.myNonceMutex.Unlock() - atomic.AddUint64(&c.session.bytesSent, uint64(len(b))) - select { - case c.session.send <- packet: - case <-c.session.closed: - c.expired = true - return len(b), errors.New("session is closed") - } - c.session.core.router.out(packet) + //defer util.PutBytes(b) + var packet []byte + sinfo.doWorker(func() { + if !sinfo.init { + err = errors.New("waiting for remote side to accept " + c.String()) + return + } + payload, nonce := crypto.BoxSeal(&sinfo.sharedSesKey, b, &sinfo.myNonce) + defer util.PutBytes(payload) + p := wire_trafficPacket{ + Coords: sinfo.coords, + Handle: sinfo.theirHandle, + Nonce: *nonce, + Payload: payload, + } + packet = p.encode() + sinfo.bytesSent += uint64(len(b)) + }) + sinfo.core.router.out(packet) return len(b), nil } diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index 4a3d8167..49ce0a98 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -63,6 +63,7 @@ func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (Conn, err mutex: &sync.RWMutex{}, nodeID: nodeID, nodeMask: nodeMask, + recv: make(chan *wire_trafficPacket, 32), } conn.core.router.doAdmin(func() { conn.startSearch() diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 7da5162f..348a1ed6 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -23,7 +23,7 @@ package yggdrasil // The router then runs some sanity checks before passing it to the adapter import ( - "bytes" + //"bytes" "time" "github.com/yggdrasil-network/yggdrasil-go/src/address" @@ -42,12 +42,12 @@ type router struct { out func([]byte) // packets we're sending to the network, link to peer's "in" toRecv chan router_recvPacket // packets to handle via recvPacket() recv chan<- []byte // place where the adapter pulls received packets from - send <-chan []byte // place where the adapter puts outgoing packets - reject chan<- RejectedPacket // place where we send error packets back to adapter - reset chan struct{} // signal that coords changed (re-init sessions/dht) - admin chan func() // pass a lambda for the admin socket to query stuff - cryptokey cryptokey - nodeinfo nodeinfo + //send <-chan []byte // place where the adapter puts outgoing packets + reject chan<- RejectedPacket // place where we send error packets back to adapter + reset chan struct{} // signal that coords changed (re-init sessions/dht) + admin chan func() // pass a lambda for the admin socket to query stuff + cryptokey cryptokey + nodeinfo nodeinfo } // Packet and session info, used to check that the packet matches a valid IP range or CKR prefix before sending to the adapter. @@ -122,11 +122,11 @@ func (r *router) init(core *Core) { }() r.out = func(packet []byte) { out2 <- packet } r.toRecv = make(chan router_recvPacket, 32) - recv := make(chan []byte, 32) - send := make(chan []byte, 32) + //recv := make(chan []byte, 32) + //send := make(chan []byte, 32) reject := make(chan RejectedPacket, 32) - r.recv = recv - r.send = send + //r.recv = recv + //r.send = send r.reject = reject r.reset = make(chan struct{}, 1) r.admin = make(chan func(), 32) @@ -157,8 +157,8 @@ func (r *router) mainLoop() { r.recvPacket(rp.bs, rp.sinfo) case p := <-r.in: r.handleIn(p) - case p := <-r.send: - r.sendPacket(p) + //case p := <-r.send: + // r.sendPacket(p) case info := <-r.core.dht.peers: r.core.dht.insertPeer(info) case <-r.reset: @@ -181,6 +181,7 @@ func (r *router) mainLoop() { } } +/* // Checks a packet's to/from address to make sure it's in the allowed range. // If a session to the destination exists, gets the session and passes the packet to it. // If no session exists, it triggers (or continues) a search. @@ -353,6 +354,7 @@ func (r *router) sendPacket(bs []byte) { sinfo.send <- bs } } +*/ // Called for incoming traffic by the session worker for that connection. // Checks that the IP address is correct (matches the session) and passes the packet to the adapter. @@ -429,7 +431,11 @@ func (r *router) handleTraffic(packet []byte) { if !isIn { return } - sinfo.recv <- &p + select { + case sinfo.recv <- &p: // FIXME ideally this should be FIFO + default: + util.PutBytes(p.Payload) + } } // Handles protocol traffic by decrypting it, checking its type, and passing it to the appropriate handler for that traffic type. diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index b0bba2d0..15d346a1 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -18,38 +18,50 @@ import ( // All the information we know about an active session. // This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API. type sessionInfo struct { - core *Core // - reconfigure chan chan error // - theirAddr address.Address // - theirSubnet address.Subnet // - theirPermPub crypto.BoxPubKey // - theirSesPub crypto.BoxPubKey // - mySesPub crypto.BoxPubKey // - mySesPriv crypto.BoxPrivKey // - sharedSesKey crypto.BoxSharedKey // derived from session keys - theirHandle crypto.Handle // - myHandle crypto.Handle // - theirNonce crypto.BoxNonce // - theirNonceMask uint64 // - theirNonceMutex sync.Mutex // protects the above - myNonce crypto.BoxNonce // - myNonceMutex sync.Mutex // protects the above - theirMTU uint16 // - myMTU uint16 // - wasMTUFixed bool // Was the MTU fixed by a receive error? - time atomic.Value // time.Time // Time we last received a packet - mtuTime atomic.Value // time.Time // time myMTU was last changed - pingTime atomic.Value // time.Time // time the first ping was sent since the last received packet - pingSend atomic.Value // time.Time // time the last ping was sent - coords []byte // coords of destination - packet []byte // a buffered packet, sent immediately on ping/pong - init atomic.Value // bool // Reset if coords change - send chan []byte // - recv chan *wire_trafficPacket // - closed chan interface{} // - tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation - bytesSent uint64 // Bytes of real traffic sent in this session - bytesRecvd uint64 // Bytes of real traffic received in this session + core *Core // + reconfigure chan chan error // + theirAddr address.Address // + theirSubnet address.Subnet // + theirPermPub crypto.BoxPubKey // + theirSesPub crypto.BoxPubKey // + mySesPub crypto.BoxPubKey // + mySesPriv crypto.BoxPrivKey // + sharedSesKey crypto.BoxSharedKey // derived from session keys + theirHandle crypto.Handle // + myHandle crypto.Handle // + theirNonce crypto.BoxNonce // + theirNonceMask uint64 // + myNonce crypto.BoxNonce // + theirMTU uint16 // + myMTU uint16 // + wasMTUFixed bool // Was the MTU fixed by a receive error? + time time.Time // Time we last received a packet + mtuTime time.Time // time myMTU was last changed + pingTime time.Time // time the first ping was sent since the last received packet + pingSend time.Time // time the last ping was sent + coords []byte // coords of destination + packet []byte // a buffered packet, sent immediately on ping/pong + init bool // Reset if coords change + tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation + bytesSent uint64 // Bytes of real traffic sent in this session + bytesRecvd uint64 // Bytes of real traffic received in this session + worker chan func() // Channel to send work to the session worker + recv chan *wire_trafficPacket // Received packets go here, picked up by the associated Conn +} + +func (sinfo *sessionInfo) doWorker(f func()) { + done := make(chan struct{}) + sinfo.worker <- func() { + f() + close(done) + } + <-done +} + +func (sinfo *sessionInfo) workerMain() { + for f := range sinfo.worker { + f() + } } // Represents a session ping/pong packet, andincludes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU. @@ -89,16 +101,19 @@ func (s *sessionInfo) update(p *sessionPing) bool { // allocate enough space for additional coords s.coords = append(make([]byte, 0, len(p.Coords)+11), p.Coords...) } - now := time.Now() - s.time.Store(now) - atomic.StoreInt64(&s.tstamp, p.Tstamp) - s.init.Store(true) + s.time = time.Now() + s.tstamp = p.Tstamp + s.init = true return true } // Returns true if the session has been idle for longer than the allowed timeout. func (s *sessionInfo) timedout() bool { - return time.Since(s.time.Load().(time.Time)) > time.Minute + var timedout bool + s.doWorker(func() { + timedout = time.Since(s.time) > time.Minute + }) + return timedout } // Struct of all active sessions. @@ -282,10 +297,10 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.theirMTU = 1280 sinfo.myMTU = 1280 now := time.Now() - sinfo.time.Store(now) - sinfo.mtuTime.Store(now) - sinfo.pingTime.Store(now) - sinfo.pingSend.Store(now) + sinfo.time = now + sinfo.mtuTime = now + sinfo.pingTime = now + sinfo.pingSend = now higher := false for idx := range ss.core.boxPub { if ss.core.boxPub[idx] > sinfo.theirPermPub[idx] { @@ -305,14 +320,13 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.myHandle = *crypto.NewHandle() sinfo.theirAddr = *address.AddrForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) - sinfo.send = make(chan []byte, 32) - sinfo.recv = make(chan *wire_trafficPacket, 32) - sinfo.closed = make(chan interface{}) + sinfo.worker = make(chan func(), 1) ss.sinfos[sinfo.myHandle] = &sinfo ss.byMySes[sinfo.mySesPub] = &sinfo.myHandle ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle ss.addrToPerm[sinfo.theirAddr] = &sinfo.theirPermPub ss.subnetToPerm[sinfo.theirSubnet] = &sinfo.theirPermPub + go sinfo.workerMain() return &sinfo } @@ -366,14 +380,12 @@ func (ss *sessions) cleanup() { // Closes a session, removing it from sessions maps and killing the worker goroutine. func (sinfo *sessionInfo) close() { - close(sinfo.closed) delete(sinfo.core.sessions.sinfos, sinfo.myHandle) delete(sinfo.core.sessions.byMySes, sinfo.mySesPub) delete(sinfo.core.sessions.byTheirPerm, sinfo.theirPermPub) delete(sinfo.core.sessions.addrToPerm, sinfo.theirAddr) delete(sinfo.core.sessions.subnetToPerm, sinfo.theirSubnet) - close(sinfo.send) - close(sinfo.recv) + close(sinfo.worker) } // Returns a session ping appropriate for the given session info. @@ -436,7 +448,7 @@ func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) { packet := p.encode() ss.core.router.out(packet) if !isPong { - sinfo.pingSend.Store(time.Now()) + sinfo.pingSend = time.Now() } } @@ -468,29 +480,36 @@ func (ss *sessions) handlePing(ping *sessionPing) { mutex: &sync.RWMutex{}, nodeID: crypto.GetNodeID(&sinfo.theirPermPub), nodeMask: &crypto.NodeID{}, + recv: make(chan *wire_trafficPacket, 32), } for i := range conn.nodeMask { conn.nodeMask[i] = 0xFF } + sinfo.recv = conn.recv ss.listener.conn <- conn } else { ss.core.log.Debugln("Received new session but there is no listener, ignoring") } ss.listenerMutex.Unlock() } - // Update the session - if !sinfo.update(ping) { /*panic("Should not happen in testing")*/ - return - } - if !ping.IsPong { - ss.sendPingPong(sinfo, true) - } - if sinfo.packet != nil { - // send - var bs []byte - bs, sinfo.packet = sinfo.packet, nil - ss.core.router.sendPacket(bs) - } + sinfo.doWorker(func() { + // Update the session + if !sinfo.update(ping) { /*panic("Should not happen in testing")*/ + return + } + if !ping.IsPong { + ss.sendPingPong(sinfo, true) + } + if sinfo.packet != nil { + /* FIXME this needs to live in the net.Conn or something, needs work in Write + // send + var bs []byte + bs, sinfo.packet = sinfo.packet, nil + ss.core.router.sendPacket(bs) // FIXME this needs to live in the net.Conn or something, needs work in Write + */ + sinfo.packet = nil + } + }) } // Get the MTU of the session. @@ -536,6 +555,8 @@ func (sinfo *sessionInfo) updateNonce(theirNonce *crypto.BoxNonce) { // Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change. func (ss *sessions) resetInits() { for _, sinfo := range ss.sinfos { - sinfo.init.Store(false) + sinfo.doWorker(func() { + sinfo.init = false + }) } } From 9ce7fe2e3fbd72e24da88441236e4508160c3554 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 21 Apr 2019 20:56:12 -0500 Subject: [PATCH 0045/1109] fix tun/tap CIDR notation so things work on linux, may break other platforms for all I know --- src/tuntap/tun.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index d9e0e77e..912998ce 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -5,6 +5,7 @@ package tuntap import ( "encoding/hex" "errors" + "fmt" "net" "sync" "time" @@ -124,8 +125,9 @@ func (tun *TunAdapter) Start() error { tun.mtu = tun.config.Current.IfMTU ifname := tun.config.Current.IfName iftapmode := tun.config.Current.IfTAPMode + addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1) if ifname != "none" { - if err := tun.setup(ifname, iftapmode, net.IP(tun.addr[:]).String(), tun.mtu); err != nil { + if err := tun.setup(ifname, iftapmode, addr, tun.mtu); err != nil { return err } } From 5a02e2ff443c7eac69ca9abe90226db103f3bf26 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 21 Apr 2019 22:31:56 -0500 Subject: [PATCH 0046/1109] apparently it was these callbacks that were sometimes deadlocking things --- src/yggdrasil/search.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index e81a9723..3460fd43 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -140,7 +140,7 @@ func (s *searches) doSearchStep(sinfo *searchInfo) { if len(sinfo.toVisit) == 0 { // Dead end, do cleanup delete(s.searches, sinfo.dest) - sinfo.callback(nil, errors.New("search reached dead end")) + go sinfo.callback(nil, errors.New("search reached dead end")) return } // Send to the next search target @@ -203,7 +203,7 @@ func (s *searches) checkDHTRes(info *searchInfo, res *dhtRes) bool { sinfo = s.core.sessions.createSession(&res.Key) if sinfo == nil { // nil if the DHT search finished but the session wasn't allowed - info.callback(nil, errors.New("session not allowed")) + go info.callback(nil, errors.New("session not allowed")) return true } _, isIn := s.core.sessions.getByTheirPerm(&res.Key) @@ -215,7 +215,7 @@ func (s *searches) checkDHTRes(info *searchInfo, res *dhtRes) bool { sinfo.coords = res.Coords sinfo.packet = info.packet s.core.sessions.ping(sinfo) - info.callback(sinfo, nil) + go info.callback(sinfo, nil) // Cleanup delete(s.searches, res.Dest) return true From 47eb2fc47fef04b7cfde6d97dd5c0ae72ad144c8 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 22 Apr 2019 11:20:35 +0100 Subject: [PATCH 0047/1109] Break deadlock by creating session recv queue when session is created instead of repointing at search completion, also make expired atomic --- src/tuntap/tun.go | 16 ++++++++---- src/yggdrasil/conn.go | 56 ++++++++++++++-------------------------- src/yggdrasil/session.go | 4 +-- 3 files changed, 32 insertions(+), 44 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 912998ce..5f87c200 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -193,12 +193,13 @@ func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { delete(tun.conns, remoteNodeID) tun.mutex.Unlock() }() + tun.log.Debugln("Start connection reader for", conn.String()) b := make([]byte, 65535) for { n, err := conn.Read(b) if err != nil { tun.log.Errorln("TUN/TAP conn read error:", err) - return err + continue } if n == 0 { continue @@ -209,7 +210,7 @@ func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { continue } if w != n { - tun.log.Errorln("TUN/TAP iface write len didn't match conn read len") + tun.log.Errorln("TUN/TAP iface write mismatch:", w, "bytes written vs", n, "bytes given") continue } } @@ -220,7 +221,7 @@ func (tun *TunAdapter) ifaceReader() error { for { n, err := tun.iface.Read(bs) if err != nil { - tun.log.Errorln("TUN/TAP iface read error:", err) + continue } // Look up if the dstination address is somewhere we already have an // open connection to @@ -253,6 +254,10 @@ func (tun *TunAdapter) ifaceReader() error { // Unknown address length or protocol continue } + if !dstAddr.IsValid() && !dstSnet.IsValid() { + // For now don't deal with any non-Yggdrasil ranges + continue + } dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() // Do we have an active connection for this node ID? tun.mutex.Lock() @@ -260,10 +265,11 @@ func (tun *TunAdapter) ifaceReader() error { tun.mutex.Unlock() w, err := conn.Write(bs) if err != nil { - tun.log.Println("TUN/TAP conn write error:", err) + tun.log.Errorln("TUN/TAP conn write error:", err) continue } if w != n { + tun.log.Errorln("TUN/TAP conn write mismatch:", w, "bytes written vs", n, "bytes given") continue } } else { @@ -273,7 +279,7 @@ func (tun *TunAdapter) ifaceReader() error { go tun.connReader(&conn) } else { tun.mutex.Unlock() - tun.log.Println("TUN/TAP dial error:", err) + tun.log.Errorln("TUN/TAP dial error:", err) } } diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 7898a128..9308d344 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "sync" + "sync/atomic" "time" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" @@ -17,9 +18,9 @@ type Conn struct { recv chan *wire_trafficPacket // Eventually gets attached to session.recv mutex *sync.RWMutex session *sessionInfo - readDeadline time.Time // TODO timer - writeDeadline time.Time // TODO timer - expired bool + readDeadline atomic.Value // time.Time // TODO timer + writeDeadline atomic.Value // time.Time // TODO timer + expired atomic.Value // bool } func (c *Conn) String() string { @@ -32,14 +33,12 @@ func (c *Conn) startSearch() { if err != nil { c.core.log.Debugln("DHT search failed:", err) c.mutex.Lock() - c.expired = true - c.mutex.Unlock() + c.expired.Store(true) return } if sinfo != nil { c.mutex.Lock() c.session = sinfo - c.session.recv = c.recv c.nodeID, c.nodeMask = sinfo.theirAddr.GetNodeIDandMask() c.mutex.Unlock() } @@ -75,30 +74,20 @@ func (c *Conn) startSearch() { } func (c *Conn) Read(b []byte) (int, error) { - err := func() error { - c.mutex.RLock() - defer c.mutex.RUnlock() - if c.expired { - return errors.New("session is closed") - } - return nil - }() - if err != nil { - return 0, err + if e, ok := c.expired.Load().(bool); ok && e { + return 0, errors.New("session is closed") } + c.mutex.RLock() + sinfo := c.session + c.mutex.RUnlock() select { // TODO... case p, ok := <-c.recv: if !ok { - c.mutex.Lock() - c.expired = true - c.mutex.Unlock() + c.expired.Store(true) return 0, errors.New("session is closed") } defer util.PutBytes(p.Payload) - c.mutex.RLock() - sinfo := c.session - c.mutex.RUnlock() var err error sinfo.doWorker(func() { if !sinfo.nonceIsOK(&p.Nonce) { @@ -131,19 +120,12 @@ func (c *Conn) Read(b []byte) (int, error) { } func (c *Conn) Write(b []byte) (bytesWritten int, err error) { - var sinfo *sessionInfo - err = func() error { - c.mutex.RLock() - defer c.mutex.RUnlock() - if c.expired { - return errors.New("session is closed") - } - sinfo = c.session - return nil - }() - if err != nil { - return 0, err + if e, ok := c.expired.Load().(bool); ok && e { + return 0, errors.New("session is closed") } + c.mutex.RLock() + sinfo := c.session + c.mutex.RUnlock() if sinfo == nil { c.core.router.doAdmin(func() { c.startSearch() @@ -173,7 +155,7 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { } func (c *Conn) Close() error { - c.expired = true + c.expired.Store(true) c.session.close() return nil } @@ -195,11 +177,11 @@ func (c *Conn) SetDeadline(t time.Time) error { } func (c *Conn) SetReadDeadline(t time.Time) error { - c.readDeadline = t + c.readDeadline.Store(t) return nil } func (c *Conn) SetWriteDeadline(t time.Time) error { - c.writeDeadline = t + c.writeDeadline.Store(t) return nil } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 15d346a1..55d4a0c6 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -321,6 +321,7 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.theirAddr = *address.AddrForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) sinfo.worker = make(chan func(), 1) + sinfo.recv = make(chan *wire_trafficPacket, 32) ss.sinfos[sinfo.myHandle] = &sinfo ss.byMySes[sinfo.mySesPub] = &sinfo.myHandle ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle @@ -480,12 +481,11 @@ func (ss *sessions) handlePing(ping *sessionPing) { mutex: &sync.RWMutex{}, nodeID: crypto.GetNodeID(&sinfo.theirPermPub), nodeMask: &crypto.NodeID{}, - recv: make(chan *wire_trafficPacket, 32), + recv: sinfo.recv, } for i := range conn.nodeMask { conn.nodeMask[i] = 0xFF } - sinfo.recv = conn.recv ss.listener.conn <- conn } else { ss.core.log.Debugln("Received new session but there is no listener, ignoring") From ccf03fd3b6de8f41d8e192489d85e11f48db3c61 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 22 Apr 2019 11:22:40 +0100 Subject: [PATCH 0048/1109] Don't write huge mostly empty buffers unnecessarily --- src/tuntap/tun.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 5f87c200..746293d4 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -263,7 +263,7 @@ func (tun *TunAdapter) ifaceReader() error { tun.mutex.Lock() if conn, isIn := tun.conns[*dstNodeID]; isIn { tun.mutex.Unlock() - w, err := conn.Write(bs) + w, err := conn.Write(bs[:n]) if err != nil { tun.log.Errorln("TUN/TAP conn write error:", err) continue From bbd1246f7bf9615941a77608c63260fa6b744e00 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 22 Apr 2019 11:49:47 +0100 Subject: [PATCH 0049/1109] Fix bug in mask generation for outbound dials, change iface reader mutexes to read-only locks unless RW is needed --- src/tuntap/tun.go | 26 ++++++++++++++++---------- src/yggdrasil/dialer.go | 6 +++--- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 746293d4..3bdbde25 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -260,9 +260,9 @@ func (tun *TunAdapter) ifaceReader() error { } dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() // Do we have an active connection for this node ID? - tun.mutex.Lock() + tun.mutex.RLock() if conn, isIn := tun.conns[*dstNodeID]; isIn { - tun.mutex.Unlock() + tun.mutex.RUnlock() w, err := conn.Write(bs[:n]) if err != nil { tun.log.Errorln("TUN/TAP conn write error:", err) @@ -273,14 +273,20 @@ func (tun *TunAdapter) ifaceReader() error { continue } } else { - if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { - tun.conns[*dstNodeID] = &conn - tun.mutex.Unlock() - go tun.connReader(&conn) - } else { - tun.mutex.Unlock() - tun.log.Errorln("TUN/TAP dial error:", err) - } + tun.mutex.RUnlock() + func() { + tun.mutex.Lock() + defer tun.mutex.Unlock() + if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { + tun.log.Debugln("Opening new session connection") + tun.log.Debugln("Node:", dstNodeID) + tun.log.Debugln("Mask:", dstNodeIDMask) + tun.conns[*dstNodeID] = &conn + go tun.connReader(&conn) + } else { + tun.log.Errorln("TUN/TAP dial error:", err) + } + }() } /*if !r.cryptokey.isValidSource(srcAddr, addrlen) { diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index 49ce0a98..bd4b87e2 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -35,7 +35,7 @@ func (d *Dialer) Dial(network, address string) (Conn, error) { return Conn{}, err } copy(nodeID[:], dest) - for idx := 0; idx <= len; idx++ { + for idx := 0; idx < len; idx++ { nodeMask[idx/8] |= 0x80 >> byte(idx%8) } } else { @@ -65,8 +65,8 @@ func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (Conn, err nodeMask: nodeMask, recv: make(chan *wire_trafficPacket, 32), } - conn.core.router.doAdmin(func() { + conn.core.router.admin <- func() { conn.startSearch() - }) + } return conn, nil } From 9778f5d2b86336189d259b9bb272dcb9e470f0e4 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 22 Apr 2019 15:00:19 +0100 Subject: [PATCH 0050/1109] Fix search behaviour on closed Conns, various other fixes --- src/tuntap/tun.go | 66 +++++++++++++++++-------------- src/yggdrasil/conn.go | 84 +++++++++++++++++++++++++++++++--------- src/yggdrasil/dialer.go | 3 -- src/yggdrasil/search.go | 7 ++-- src/yggdrasil/session.go | 1 + 5 files changed, 107 insertions(+), 54 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 3bdbde25..07264dab 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -193,12 +193,11 @@ func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { delete(tun.conns, remoteNodeID) tun.mutex.Unlock() }() - tun.log.Debugln("Start connection reader for", conn.String()) b := make([]byte, 65535) for { n, err := conn.Read(b) if err != nil { - tun.log.Errorln("TUN/TAP conn read error:", err) + tun.log.Errorln(conn.String(), "TUN/TAP conn read error:", err) continue } if n == 0 { @@ -206,11 +205,11 @@ func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { } w, err := tun.iface.Write(b[:n]) if err != nil { - tun.log.Errorln("TUN/TAP iface write error:", err) + tun.log.Errorln(conn.String(), "TUN/TAP iface write error:", err) continue } if w != n { - tun.log.Errorln("TUN/TAP iface write mismatch:", w, "bytes written vs", n, "bytes given") + tun.log.Errorln(conn.String(), "TUN/TAP iface write mismatch:", w, "bytes written vs", n, "bytes given") continue } } @@ -219,20 +218,24 @@ func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { func (tun *TunAdapter) ifaceReader() error { bs := make([]byte, 65535) for { + // Wait for a packet to be delivered to us through the TUN/TAP adapter n, err := tun.iface.Read(bs) if err != nil { continue } - // Look up if the dstination address is somewhere we already have an - // open connection to + // From the IP header, work out what our source and destination addresses + // and node IDs are. We will need these in order to work out where to send + // the packet var srcAddr address.Address var dstAddr address.Address var dstNodeID *crypto.NodeID var dstNodeIDMask *crypto.NodeID var dstSnet address.Subnet var addrlen int + // Check the IP protocol - if it doesn't match then we drop the packet and + // do nothing with it if bs[0]&0xf0 == 0x60 { - // Check if we have a fully-sized header + // Check if we have a fully-sized IPv6 header if len(bs) < 40 { continue } @@ -242,7 +245,7 @@ func (tun *TunAdapter) ifaceReader() error { copy(dstAddr[:addrlen], bs[24:]) copy(dstSnet[:addrlen/2], bs[24:]) } else if bs[0]&0xf0 == 0x40 { - // Check if we have a fully-sized header + // Check if we have a fully-sized IPv4 header if len(bs) < 20 { continue } @@ -251,7 +254,7 @@ func (tun *TunAdapter) ifaceReader() error { copy(srcAddr[:addrlen], bs[12:]) copy(dstAddr[:addrlen], bs[16:]) } else { - // Unknown address length or protocol + // Unknown address length or protocol, so drop the packet and ignore it continue } if !dstAddr.IsValid() && !dstSnet.IsValid() { @@ -261,32 +264,39 @@ func (tun *TunAdapter) ifaceReader() error { dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() // Do we have an active connection for this node ID? tun.mutex.RLock() - if conn, isIn := tun.conns[*dstNodeID]; isIn { - tun.mutex.RUnlock() + conn, isIn := tun.conns[*dstNodeID] + tun.mutex.RUnlock() + // If we don't have a connection then we should open one + if !isIn { + // Dial to the remote node + if c, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { + // We've been given a connection, so save it in our connections so we + // can refer to it the next time we send a packet to this destination + tun.mutex.Lock() + tun.conns[*dstNodeID] = &c + tun.mutex.Unlock() + // Start the connection reader goroutine + go tun.connReader(&c) + // Then update our reference to the connection + conn, isIn = &c, true + } else { + // We weren't able to dial for some reason so there's no point in + // continuing this iteration - skip to the next one + continue + } + } + // If we have an open connection, either because we already had one or + // because we opened one above, try writing the packet to it + if isIn && conn != nil { w, err := conn.Write(bs[:n]) if err != nil { - tun.log.Errorln("TUN/TAP conn write error:", err) + tun.log.Errorln(conn.String(), "TUN/TAP conn write error:", err) continue } if w != n { - tun.log.Errorln("TUN/TAP conn write mismatch:", w, "bytes written vs", n, "bytes given") + tun.log.Errorln(conn.String(), "TUN/TAP conn write mismatch:", w, "bytes written vs", n, "bytes given") continue } - } else { - tun.mutex.RUnlock() - func() { - tun.mutex.Lock() - defer tun.mutex.Unlock() - if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { - tun.log.Debugln("Opening new session connection") - tun.log.Debugln("Node:", dstNodeID) - tun.log.Debugln("Mask:", dstNodeIDMask) - tun.conns[*dstNodeID] = &conn - go tun.connReader(&conn) - } else { - tun.log.Errorln("TUN/TAP dial error:", err) - } - }() } /*if !r.cryptokey.isValidSource(srcAddr, addrlen) { diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 9308d344..0accf163 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -21,41 +21,50 @@ type Conn struct { readDeadline atomic.Value // time.Time // TODO timer writeDeadline atomic.Value // time.Time // TODO timer expired atomic.Value // bool + searching atomic.Value // bool } func (c *Conn) String() string { - return fmt.Sprintf("c=%p", c) + return fmt.Sprintf("conn=%p", c) } // This method should only be called from the router goroutine func (c *Conn) startSearch() { searchCompleted := func(sinfo *sessionInfo, err error) { + c.searching.Store(false) + c.mutex.Lock() + defer c.mutex.Unlock() if err != nil { - c.core.log.Debugln("DHT search failed:", err) - c.mutex.Lock() + c.core.log.Debugln(c.String(), "DHT search failed:", err) c.expired.Store(true) return } if sinfo != nil { - c.mutex.Lock() + c.core.log.Debugln(c.String(), "DHT search completed") c.session = sinfo c.nodeID, c.nodeMask = sinfo.theirAddr.GetNodeIDandMask() - c.mutex.Unlock() + c.expired.Store(false) + } else { + c.core.log.Debugln(c.String(), "DHT search failed: no session returned") + c.expired.Store(true) + return } } doSearch := func() { + c.searching.Store(true) sinfo, isIn := c.core.searches.searches[*c.nodeID] if !isIn { sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) + c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo) } c.core.searches.continueSearch(sinfo) } c.mutex.RLock() - defer c.mutex.RUnlock() + sinfo := c.session + c.mutex.RUnlock() if c.session == nil { doSearch() } else { - sinfo := c.session // In case c.session is somehow changed meanwhile sinfo.worker <- func() { switch { case !sinfo.init: @@ -74,43 +83,66 @@ func (c *Conn) startSearch() { } func (c *Conn) Read(b []byte) (int, error) { + // If the session is marked as expired then do nothing at this point if e, ok := c.expired.Load().(bool); ok && e { return 0, errors.New("session is closed") } + // Take a copy of the session object c.mutex.RLock() sinfo := c.session c.mutex.RUnlock() + // If the session is not initialised, do nothing. Currently in this instance + // in a write, we would trigger a new session, but it doesn't make sense for + // us to block forever here if the session will not reopen. + // TODO: should this return an error or just a zero-length buffer? + if !sinfo.init { + return 0, errors.New("session is closed") + } + // Wait for some traffic to come through from the session select { // TODO... case p, ok := <-c.recv: + // If the channel was closed then mark the connection as expired, this will + // mean that the next write will start a new search and reopen the session if !ok { c.expired.Store(true) return 0, errors.New("session is closed") } defer util.PutBytes(p.Payload) var err error + // Hand over to the session worker sinfo.doWorker(func() { + // If the nonce is bad then drop the packet and return an error if !sinfo.nonceIsOK(&p.Nonce) { err = errors.New("packet dropped due to invalid nonce") return } + // Decrypt the packet bs, isOK := crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce) + // Check if we were unable to decrypt the packet for some reason and + // return an error if we couldn't if !isOK { util.PutBytes(bs) err = errors.New("packet dropped due to decryption failure") return } + // Return the newly decrypted buffer back to the slice we were given copy(b, bs) + // Trim the slice down to size based on the data we received if len(bs) < len(b) { b = b[:len(bs)] } + // Update the session sinfo.updateNonce(&p.Nonce) sinfo.time = time.Now() sinfo.bytesRecvd += uint64(len(b)) }) + // Something went wrong in the session worker so abort if err != nil { return 0, err } + // If we've reached this point then everything went to plan, return the + // number of bytes we populated back into the given slice return len(b), nil //case <-c.recvTimeout: //case <-c.session.closed: @@ -120,27 +152,35 @@ func (c *Conn) Read(b []byte) (int, error) { } func (c *Conn) Write(b []byte) (bytesWritten int, err error) { - if e, ok := c.expired.Load().(bool); ok && e { - return 0, errors.New("session is closed") - } c.mutex.RLock() sinfo := c.session c.mutex.RUnlock() - if sinfo == nil { - c.core.router.doAdmin(func() { - c.startSearch() - }) - return 0, errors.New("searching for remote side") + // Check whether the connection is expired, if it is we can start a new + // search to revive it + expired, eok := c.expired.Load().(bool) + // If the session doesn't exist, or isn't initialised (which probably means + // that the session was never set up or it closed by timeout), or the conn + // is marked as expired, then see if we can start a new search + if sinfo == nil || !sinfo.init || (eok && expired) { + // Is a search already taking place? + if searching, sok := c.searching.Load().(bool); !sok || (sok && !searching) { + // No search was already taking place so start a new one + c.core.router.doAdmin(func() { + c.startSearch() + }) + return 0, errors.New("starting search") + } + // A search is already taking place so wait for it to finish + return 0, errors.New("waiting for search to complete") } //defer util.PutBytes(b) var packet []byte + // Hand over to the session worker sinfo.doWorker(func() { - if !sinfo.init { - err = errors.New("waiting for remote side to accept " + c.String()) - return - } + // Encrypt the packet payload, nonce := crypto.BoxSeal(&sinfo.sharedSesKey, b, &sinfo.myNonce) defer util.PutBytes(payload) + // Construct the wire packet to send to the router p := wire_trafficPacket{ Coords: sinfo.coords, Handle: sinfo.theirHandle, @@ -150,13 +190,19 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { packet = p.encode() sinfo.bytesSent += uint64(len(b)) }) + // Give the packet to the router sinfo.core.router.out(packet) + // Finally return the number of bytes we wrote return len(b), nil } func (c *Conn) Close() error { + // Mark the connection as expired, so that a future read attempt will fail + // and a future write attempt will start a new search c.expired.Store(true) + // Close the session, if it hasn't been closed already c.session.close() + // This can't fail yet - TODO? return nil } diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index bd4b87e2..89016100 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -65,8 +65,5 @@ func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (Conn, err nodeMask: nodeMask, recv: make(chan *wire_trafficPacket, 32), } - conn.core.router.admin <- func() { - conn.startSearch() - } return conn, nil } diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 3460fd43..0a643363 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -90,11 +90,10 @@ func (s *searches) handleDHTRes(res *dhtRes) { if !isIn || s.checkDHTRes(sinfo, res) { // Either we don't recognize this search, or we just finished it return - } else { - // Add to the search and continue - s.addToSearch(sinfo, res) - s.doSearchStep(sinfo) } + // Add to the search and continue + s.addToSearch(sinfo, res) + s.doSearchStep(sinfo) } // Adds the information from a dhtRes to an ongoing search. diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 55d4a0c6..e356f612 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -387,6 +387,7 @@ func (sinfo *sessionInfo) close() { delete(sinfo.core.sessions.addrToPerm, sinfo.theirAddr) delete(sinfo.core.sessions.subnetToPerm, sinfo.theirSubnet) close(sinfo.worker) + sinfo.init = false } // Returns a session ping appropriate for the given session info. From ea8948f3781877c5371d1b072326498a1d7a48d4 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 22 Apr 2019 20:06:39 +0100 Subject: [PATCH 0051/1109] TUN/TAP addr/subnet to Conn mappings, other fixes --- src/tuntap/tun.go | 95 ++++++++++++++++++++++++++-------------- src/yggdrasil/conn.go | 60 +++++++++++++------------ src/yggdrasil/session.go | 35 +++------------ 3 files changed, 101 insertions(+), 89 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 07264dab..c65c15e8 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -28,19 +28,20 @@ const tun_ETHER_HEADER_LENGTH = 14 // you should pass this object to the yggdrasil.SetRouterAdapter() function // before calling yggdrasil.Start(). type TunAdapter struct { - config *config.NodeState - log *log.Logger - reconfigure chan chan error - listener *yggdrasil.Listener - dialer *yggdrasil.Dialer - addr address.Address - subnet address.Subnet - icmpv6 ICMPv6 - mtu int - iface *water.Interface - mutex sync.RWMutex // Protects the below - conns map[crypto.NodeID]*yggdrasil.Conn - isOpen bool + config *config.NodeState + log *log.Logger + reconfigure chan chan error + listener *yggdrasil.Listener + dialer *yggdrasil.Dialer + addr address.Address + subnet address.Subnet + icmpv6 ICMPv6 + mtu int + iface *water.Interface + mutex sync.RWMutex // Protects the below + addrToConn map[address.Address]*yggdrasil.Conn + subnetToConn map[address.Subnet]*yggdrasil.Conn + isOpen bool } // Gets the maximum supported MTU for the platform based on the defaults in @@ -102,7 +103,8 @@ func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener tun.log = log tun.listener = listener tun.dialer = dialer - tun.conns = make(map[crypto.NodeID]*yggdrasil.Conn) + tun.addrToConn = make(map[address.Address]*yggdrasil.Conn) + tun.subnetToConn = make(map[address.Subnet]*yggdrasil.Conn) } // Start the setup process for the TUN/TAP adapter. If successful, starts the @@ -181,23 +183,40 @@ func (tun *TunAdapter) handler() error { func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { remoteNodeID := conn.RemoteAddr() - tun.mutex.Lock() - if _, isIn := tun.conns[remoteNodeID]; isIn { - tun.mutex.Unlock() - return errors.New("duplicate connection") + remoteAddr := address.AddrForNodeID(&remoteNodeID) + remoteSubnet := address.SubnetForNodeID(&remoteNodeID) + err := func() error { + tun.mutex.RLock() + defer tun.mutex.RUnlock() + if _, isIn := tun.addrToConn[*remoteAddr]; isIn { + return errors.New("duplicate connection for address " + net.IP(remoteAddr[:]).String()) + } + if _, isIn := tun.subnetToConn[*remoteSubnet]; isIn { + return errors.New("duplicate connection for subnet " + net.IP(remoteSubnet[:]).String()) + } + return nil + }() + if err != nil { + //return err + panic(err) } - tun.conns[remoteNodeID] = conn + // Store the connection mapped to address and subnet + tun.mutex.Lock() + tun.addrToConn[*remoteAddr] = conn + tun.subnetToConn[*remoteSubnet] = conn tun.mutex.Unlock() + // Make sure to clean those up later when the connection is closed defer func() { tun.mutex.Lock() - delete(tun.conns, remoteNodeID) + delete(tun.addrToConn, *remoteAddr) + delete(tun.subnetToConn, *remoteSubnet) tun.mutex.Unlock() }() b := make([]byte, 65535) for { n, err := conn.Read(b) if err != nil { - tun.log.Errorln(conn.String(), "TUN/TAP conn read error:", err) + //tun.log.Errorln(conn.String(), "TUN/TAP conn read error:", err) continue } if n == 0 { @@ -261,21 +280,28 @@ func (tun *TunAdapter) ifaceReader() error { // For now don't deal with any non-Yggdrasil ranges continue } - dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() - // Do we have an active connection for this node ID? + // Do we have an active connection for this node address? tun.mutex.RLock() - conn, isIn := tun.conns[*dstNodeID] + conn, isIn := tun.addrToConn[dstAddr] + if !isIn || conn == nil { + conn, isIn = tun.subnetToConn[dstSnet] + if !isIn || conn == nil { + // Neither an address nor a subnet mapping matched, therefore populate + // the node ID and mask to commence a search + dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() + } + } tun.mutex.RUnlock() // If we don't have a connection then we should open one - if !isIn { + if !isIn || conn == nil { + // Check we haven't been given empty node ID, really this shouldn't ever + // happen but just to be sure... + if dstNodeID == nil || dstNodeIDMask == nil { + panic("Given empty dstNodeID and dstNodeIDMask - this shouldn't happen") + } // Dial to the remote node if c, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { - // We've been given a connection, so save it in our connections so we - // can refer to it the next time we send a packet to this destination - tun.mutex.Lock() - tun.conns[*dstNodeID] = &c - tun.mutex.Unlock() - // Start the connection reader goroutine + // We've been given a connection so start the connection reader goroutine go tun.connReader(&c) // Then update our reference to the connection conn, isIn = &c, true @@ -285,9 +311,10 @@ func (tun *TunAdapter) ifaceReader() error { continue } } - // If we have an open connection, either because we already had one or - // because we opened one above, try writing the packet to it - if isIn && conn != nil { + // If we have a connection now, try writing to it + if conn != nil { + // If we have an open connection, either because we already had one or + // because we opened one above, try writing the packet to it w, err := conn.Write(bs[:n]) if err != nil { tun.log.Errorln(conn.String(), "TUN/TAP conn write error:", err) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 0accf163..903152d2 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -20,7 +20,6 @@ type Conn struct { session *sessionInfo readDeadline atomic.Value // time.Time // TODO timer writeDeadline atomic.Value // time.Time // TODO timer - expired atomic.Value // bool searching atomic.Value // bool } @@ -30,39 +29,58 @@ func (c *Conn) String() string { // This method should only be called from the router goroutine func (c *Conn) startSearch() { + // The searchCompleted callback is given to the search searchCompleted := func(sinfo *sessionInfo, err error) { + // Update the connection with the fact that the search completed, which + // allows another search to be triggered if necessary c.searching.Store(false) - c.mutex.Lock() - defer c.mutex.Unlock() + // If the search failed for some reason, e.g. it hit a dead end or timed + // out, then do nothing if err != nil { c.core.log.Debugln(c.String(), "DHT search failed:", err) - c.expired.Store(true) return } + // Take the connection mutex + c.mutex.Lock() + defer c.mutex.Unlock() + // Were we successfully given a sessionInfo pointeR? if sinfo != nil { + // Store it, and update the nodeID and nodeMask (which may have been + // wildcarded before now) with their complete counterparts c.core.log.Debugln(c.String(), "DHT search completed") c.session = sinfo - c.nodeID, c.nodeMask = sinfo.theirAddr.GetNodeIDandMask() - c.expired.Store(false) + c.nodeID = crypto.GetNodeID(&sinfo.theirPermPub) + for i := range c.nodeMask { + c.nodeMask[i] = 0xFF + } } else { - c.core.log.Debugln(c.String(), "DHT search failed: no session returned") - c.expired.Store(true) - return + // No session was returned - this shouldn't really happen because we + // should always return an error reason if we don't return a session + panic("DHT search didn't return an error or a sessionInfo") } } + // doSearch will be called below in response to one or more conditions doSearch := func() { + // Store the fact that we're searching, so that we don't start additional + // searches until this one has completed c.searching.Store(true) + // Check to see if there is a search already matching the destination sinfo, isIn := c.core.searches.searches[*c.nodeID] if !isIn { + // Nothing was found, so create a new search sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo) } + // Continue the search c.core.searches.continueSearch(sinfo) } + // Take a copy of the session object, in case it changes later c.mutex.RLock() sinfo := c.session c.mutex.RUnlock() if c.session == nil { + // No session object is present so previous searches, if we ran any, have + // not yielded a useful result (dead end, remote host not found) doSearch() } else { sinfo.worker <- func() { @@ -83,10 +101,6 @@ func (c *Conn) startSearch() { } func (c *Conn) Read(b []byte) (int, error) { - // If the session is marked as expired then do nothing at this point - if e, ok := c.expired.Load().(bool); ok && e { - return 0, errors.New("session is closed") - } // Take a copy of the session object c.mutex.RLock() sinfo := c.session @@ -95,17 +109,15 @@ func (c *Conn) Read(b []byte) (int, error) { // in a write, we would trigger a new session, but it doesn't make sense for // us to block forever here if the session will not reopen. // TODO: should this return an error or just a zero-length buffer? - if !sinfo.init { + if sinfo == nil || !sinfo.init { return 0, errors.New("session is closed") } // Wait for some traffic to come through from the session select { // TODO... case p, ok := <-c.recv: - // If the channel was closed then mark the connection as expired, this will - // mean that the next write will start a new search and reopen the session + // If the session is closed then do nothing if !ok { - c.expired.Store(true) return 0, errors.New("session is closed") } defer util.PutBytes(p.Payload) @@ -155,13 +167,9 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { c.mutex.RLock() sinfo := c.session c.mutex.RUnlock() - // Check whether the connection is expired, if it is we can start a new - // search to revive it - expired, eok := c.expired.Load().(bool) // If the session doesn't exist, or isn't initialised (which probably means - // that the session was never set up or it closed by timeout), or the conn - // is marked as expired, then see if we can start a new search - if sinfo == nil || !sinfo.init || (eok && expired) { + // that the search didn't complete successfully) then try to search again + if sinfo == nil || !sinfo.init { // Is a search already taking place? if searching, sok := c.searching.Load().(bool); !sok || (sok && !searching) { // No search was already taking place so start a new one @@ -173,7 +181,7 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { // A search is already taking place so wait for it to finish return 0, errors.New("waiting for search to complete") } - //defer util.PutBytes(b) + // defer util.PutBytes(b) var packet []byte // Hand over to the session worker sinfo.doWorker(func() { @@ -197,11 +205,9 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { } func (c *Conn) Close() error { - // Mark the connection as expired, so that a future read attempt will fail - // and a future write attempt will start a new search - c.expired.Store(true) // Close the session, if it hasn't been closed already c.session.close() + c.session = nil // This can't fail yet - TODO? return nil } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index e356f612..967d5f6b 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -107,15 +107,6 @@ func (s *sessionInfo) update(p *sessionPing) bool { return true } -// Returns true if the session has been idle for longer than the allowed timeout. -func (s *sessionInfo) timedout() bool { - var timedout bool - s.doWorker(func() { - timedout = time.Since(s.time) > time.Minute - }) - return timedout -} - // Struct of all active sessions. // Sessions are indexed by handle. // Additionally, stores maps of address/subnet onto keys, and keys onto handles. @@ -233,10 +224,6 @@ func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) b // Gets the session corresponding to a given handle. func (ss *sessions) getSessionForHandle(handle *crypto.Handle) (*sessionInfo, bool) { sinfo, isIn := ss.sinfos[*handle] - if isIn && sinfo.timedout() { - // We have a session, but it has timed out - return nil, false - } return sinfo, isIn } @@ -280,8 +267,9 @@ func (ss *sessions) getByTheirSubnet(snet *address.Subnet) (*sessionInfo, bool) return sinfo, isIn } -// Creates a new session and lazily cleans up old/timedout existing sessions. -// This includse initializing session info to sane defaults (e.g. lowest supported MTU). +// Creates a new session and lazily cleans up old existing sessions. This +// includse initializing session info to sane defaults (e.g. lowest supported +// MTU). func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { if !ss.isSessionAllowed(theirPermKey, true) { return nil @@ -341,11 +329,6 @@ func (ss *sessions) cleanup() { if time.Since(ss.lastCleanup) < time.Minute { return } - for _, s := range ss.sinfos { - if s.timedout() { - s.close() - } - } permShared := make(map[crypto.BoxPubKey]*crypto.BoxSharedKey, len(ss.permShared)) for k, v := range ss.permShared { permShared[k] = v @@ -387,7 +370,6 @@ func (sinfo *sessionInfo) close() { delete(sinfo.core.sessions.addrToPerm, sinfo.theirAddr) delete(sinfo.core.sessions.subnetToPerm, sinfo.theirSubnet) close(sinfo.worker) - sinfo.init = false } // Returns a session ping appropriate for the given session info. @@ -465,17 +447,16 @@ func (ss *sessions) handlePing(ping *sessionPing) { return } } - if !isIn || sinfo.timedout() { - if isIn { - sinfo.close() - } + if !isIn { ss.createSession(&ping.SendPermPub) sinfo, isIn = ss.getByTheirPerm(&ping.SendPermPub) if !isIn { panic("This should not happen") } ss.listenerMutex.Lock() - if ss.listener != nil { + // Check and see if there's a Listener waiting to accept connections + // TODO: this should not block if nothing is accepting + if !ping.IsPong && ss.listener != nil { conn := &Conn{ core: ss.core, session: sinfo, @@ -488,8 +469,6 @@ func (ss *sessions) handlePing(ping *sessionPing) { conn.nodeMask[i] = 0xFF } ss.listener.conn <- conn - } else { - ss.core.log.Debugln("Received new session but there is no listener, ignoring") } ss.listenerMutex.Unlock() } From 6e528799e9fa6e9c8f547f71a2d1450a6faa0650 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 22 Apr 2019 22:38:37 +0100 Subject: [PATCH 0052/1109] Conn Read/Write operations will block while search completes --- src/tuntap/tun.go | 2 +- src/yggdrasil/conn.go | 23 +++++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index c65c15e8..36e29658 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -216,7 +216,7 @@ func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { for { n, err := conn.Read(b) if err != nil { - //tun.log.Errorln(conn.String(), "TUN/TAP conn read error:", err) + tun.log.Errorln(conn.String(), "TUN/TAP conn read error:", err) continue } if n == 0 { diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 903152d2..a5702d33 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -21,6 +21,7 @@ type Conn struct { readDeadline atomic.Value // time.Time // TODO timer writeDeadline atomic.Value // time.Time // TODO timer searching atomic.Value // bool + searchwait chan interface{} } func (c *Conn) String() string { @@ -31,6 +32,8 @@ func (c *Conn) String() string { func (c *Conn) startSearch() { // The searchCompleted callback is given to the search searchCompleted := func(sinfo *sessionInfo, err error) { + // Make sure that any blocks on read/write operations are lifted + defer close(c.searchwait) // Update the connection with the fact that the search completed, which // allows another search to be triggered if necessary c.searching.Store(false) @@ -70,6 +73,8 @@ func (c *Conn) startSearch() { // Nothing was found, so create a new search sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo) + // Allow writes/reads to block until the search is complete + c.searchwait = make(chan interface{}) } // Continue the search c.core.searches.continueSearch(sinfo) @@ -110,12 +115,16 @@ func (c *Conn) Read(b []byte) (int, error) { // us to block forever here if the session will not reopen. // TODO: should this return an error or just a zero-length buffer? if sinfo == nil || !sinfo.init { - return 0, errors.New("session is closed") + // block + <-c.searchwait + // return 0, errors.New("session is closed") } // Wait for some traffic to come through from the session + fmt.Println("Start select") select { // TODO... case p, ok := <-c.recv: + fmt.Println("Finish select") // If the session is closed then do nothing if !ok { return 0, errors.New("session is closed") @@ -167,6 +176,9 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { c.mutex.RLock() sinfo := c.session c.mutex.RUnlock() + // A search is already taking place so wait for it to finish + if sinfo == nil || !sinfo.init { + } // If the session doesn't exist, or isn't initialised (which probably means // that the search didn't complete successfully) then try to search again if sinfo == nil || !sinfo.init { @@ -176,10 +188,13 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { c.core.router.doAdmin(func() { c.startSearch() }) - return 0, errors.New("starting search") + //return 0, errors.New("starting search") } - // A search is already taking place so wait for it to finish - return 0, errors.New("waiting for search to complete") + <-c.searchwait + if sinfo == nil || !sinfo.init { + return 0, errors.New("search was failed") + } + //return 0, errors.New("waiting for search to complete") } // defer util.PutBytes(b) var packet []byte From e1a2d666bf0176da43e47c76d49892cb89f9081e Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 22 Apr 2019 23:12:13 +0100 Subject: [PATCH 0053/1109] Clean up router, tweaks --- src/tuntap/tun.go | 14 ++++-- src/yggdrasil/conn.go | 18 ++++--- src/yggdrasil/router.go | 103 +++------------------------------------- 3 files changed, 28 insertions(+), 107 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 36e29658..709f7051 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -2,6 +2,12 @@ package tuntap // This manages the tun driver to send/recv packets to/from applications +// TODO: Crypto-key routing +// TODO: Set MTU of session properly +// TODO: Reject packets that exceed session MTU +// TODO: Connection timeouts (call Close() when done) +// TODO: Keep packet that was used to set up a session and send it when done + import ( "encoding/hex" "errors" @@ -38,9 +44,9 @@ type TunAdapter struct { icmpv6 ICMPv6 mtu int iface *water.Interface - mutex sync.RWMutex // Protects the below - addrToConn map[address.Address]*yggdrasil.Conn - subnetToConn map[address.Subnet]*yggdrasil.Conn + mutex sync.RWMutex // Protects the below + addrToConn map[address.Address]*yggdrasil.Conn // Managed by connReader + subnetToConn map[address.Subnet]*yggdrasil.Conn // Managed by connReader isOpen bool } @@ -312,7 +318,7 @@ func (tun *TunAdapter) ifaceReader() error { } } // If we have a connection now, try writing to it - if conn != nil { + if isIn && conn != nil { // If we have an open connection, either because we already had one or // because we opened one above, try writing the packet to it w, err := conn.Write(bs[:n]) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index a5702d33..4ffa4b17 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -110,21 +110,24 @@ func (c *Conn) Read(b []byte) (int, error) { c.mutex.RLock() sinfo := c.session c.mutex.RUnlock() + // If there is a search in progress then wait for the result + if searching, ok := c.searching.Load().(bool); ok && searching { + <-c.searchwait + } // If the session is not initialised, do nothing. Currently in this instance // in a write, we would trigger a new session, but it doesn't make sense for // us to block forever here if the session will not reopen. // TODO: should this return an error or just a zero-length buffer? if sinfo == nil || !sinfo.init { - // block - <-c.searchwait - // return 0, errors.New("session is closed") + time.Sleep(time.Second) + return 0, errors.New("session is closed") } // Wait for some traffic to come through from the session - fmt.Println("Start select") + fmt.Println(c.String(), "Start select") select { // TODO... case p, ok := <-c.recv: - fmt.Println("Finish select") + fmt.Println(c.String(), "Finish select") // If the session is closed then do nothing if !ok { return 0, errors.New("session is closed") @@ -176,8 +179,9 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { c.mutex.RLock() sinfo := c.session c.mutex.RUnlock() - // A search is already taking place so wait for it to finish - if sinfo == nil || !sinfo.init { + // If there is a search in progress then wait for the result + if searching, ok := c.searching.Load().(bool); ok && searching { + <-c.searchwait } // If the session doesn't exist, or isn't initialised (which probably means // that the search didn't complete successfully) then try to search again diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 348a1ed6..68700723 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -24,6 +24,7 @@ package yggdrasil import ( //"bytes" + "time" "github.com/yggdrasil-network/yggdrasil-go/src/address" @@ -38,43 +39,12 @@ type router struct { reconfigure chan chan error addr address.Address subnet address.Subnet - in <-chan []byte // packets we received from the network, link to peer's "out" - out func([]byte) // packets we're sending to the network, link to peer's "in" - toRecv chan router_recvPacket // packets to handle via recvPacket() - recv chan<- []byte // place where the adapter pulls received packets from - //send <-chan []byte // place where the adapter puts outgoing packets - reject chan<- RejectedPacket // place where we send error packets back to adapter - reset chan struct{} // signal that coords changed (re-init sessions/dht) - admin chan func() // pass a lambda for the admin socket to query stuff - cryptokey cryptokey - nodeinfo nodeinfo -} - -// Packet and session info, used to check that the packet matches a valid IP range or CKR prefix before sending to the adapter. -type router_recvPacket struct { - bs []byte - sinfo *sessionInfo -} - -// RejectedPacketReason is the type code used to represent the reason that a -// packet was rejected. -type RejectedPacketReason int - -const ( - // The router rejected the packet because it exceeds the session MTU for the - // given destination. In TUN/TAP, this results in the generation of an ICMPv6 - // Packet Too Big message. - PacketTooBig = 1 + iota -) - -// RejectedPacket represents a rejected packet from the router. This is passed -// back to the adapter so that the adapter can respond appropriately, e.g. in -// the case of TUN/TAP, a "PacketTooBig" reason can be used to generate an -// ICMPv6 Packet Too Big response. -type RejectedPacket struct { - Reason RejectedPacketReason - Packet []byte - Detail interface{} + in <-chan []byte // packets we received from the network, link to peer's "out" + out func([]byte) // packets we're sending to the network, link to peer's "in" + reset chan struct{} // signal that coords changed (re-init sessions/dht) + admin chan func() // pass a lambda for the admin socket to query stuff + cryptokey cryptokey + nodeinfo nodeinfo } // Initializes the router struct, which includes setting up channels to/from the adapter. @@ -121,13 +91,6 @@ func (r *router) init(core *Core) { } }() r.out = func(packet []byte) { out2 <- packet } - r.toRecv = make(chan router_recvPacket, 32) - //recv := make(chan []byte, 32) - //send := make(chan []byte, 32) - reject := make(chan RejectedPacket, 32) - //r.recv = recv - //r.send = send - r.reject = reject r.reset = make(chan struct{}, 1) r.admin = make(chan func(), 32) r.nodeinfo.init(r.core) @@ -153,12 +116,8 @@ func (r *router) mainLoop() { defer ticker.Stop() for { select { - case rp := <-r.toRecv: - r.recvPacket(rp.bs, rp.sinfo) case p := <-r.in: r.handleIn(p) - //case p := <-r.send: - // r.sendPacket(p) case info := <-r.core.dht.peers: r.core.dht.insertPeer(info) case <-r.reset: @@ -356,54 +315,6 @@ func (r *router) sendPacket(bs []byte) { } */ -// Called for incoming traffic by the session worker for that connection. -// Checks that the IP address is correct (matches the session) and passes the packet to the adapter. -func (r *router) recvPacket(bs []byte, sinfo *sessionInfo) { - // Note: called directly by the session worker, not the router goroutine - if len(bs) < 24 { - util.PutBytes(bs) - return - } - var sourceAddr address.Address - var dest address.Address - var snet address.Subnet - var addrlen int - if bs[0]&0xf0 == 0x60 { - // IPv6 address - addrlen = 16 - copy(sourceAddr[:addrlen], bs[8:]) - copy(dest[:addrlen], bs[24:]) - copy(snet[:addrlen/2], bs[8:]) - } else if bs[0]&0xf0 == 0x40 { - // IPv4 address - addrlen = 4 - copy(sourceAddr[:addrlen], bs[12:]) - copy(dest[:addrlen], bs[16:]) - } else { - // Unknown address length - return - } - // Check that the packet is destined for either our Yggdrasil address or - // subnet, or that it matches one of the crypto-key routing source routes - if !r.cryptokey.isValidSource(dest, addrlen) { - util.PutBytes(bs) - return - } - // See whether the packet they sent should have originated from this session - switch { - case sourceAddr.IsValid() && sourceAddr == sinfo.theirAddr: - case snet.IsValid() && snet == sinfo.theirSubnet: - default: - key, err := r.cryptokey.getPublicKeyForAddress(sourceAddr, addrlen) - if err != nil || key != sinfo.theirPermPub { - util.PutBytes(bs) - return - } - } - //go func() { r.recv<-bs }() - r.recv <- bs -} - // Checks incoming traffic type and passes it to the appropriate handler. func (r *router) handleIn(packet []byte) { pType, pTypeLen := wire_decode_uint64(packet) From d7a1c0474842c447b17d8c5a1bb8a07760d49a93 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 22 Apr 2019 23:58:59 +0100 Subject: [PATCH 0054/1109] It works, sort of, amazingly --- src/yggdrasil/conn.go | 65 +++++++++++++++++++--------------------- src/yggdrasil/dialer.go | 10 +++---- src/yggdrasil/session.go | 12 ++++---- 3 files changed, 41 insertions(+), 46 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 4ffa4b17..8d36a60a 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -15,7 +15,6 @@ type Conn struct { core *Core nodeID *crypto.NodeID nodeMask *crypto.NodeID - recv chan *wire_trafficPacket // Eventually gets attached to session.recv mutex *sync.RWMutex session *sessionInfo readDeadline atomic.Value // time.Time // TODO timer @@ -33,10 +32,11 @@ func (c *Conn) startSearch() { // The searchCompleted callback is given to the search searchCompleted := func(sinfo *sessionInfo, err error) { // Make sure that any blocks on read/write operations are lifted - defer close(c.searchwait) - // Update the connection with the fact that the search completed, which - // allows another search to be triggered if necessary - c.searching.Store(false) + defer func() { + c.searching.Store(false) + close(c.searchwait) + c.searchwait = make(chan interface{}) + }() // If the search failed for some reason, e.g. it hit a dead end or timed // out, then do nothing if err != nil { @@ -64,8 +64,6 @@ func (c *Conn) startSearch() { } // doSearch will be called below in response to one or more conditions doSearch := func() { - // Store the fact that we're searching, so that we don't start additional - // searches until this one has completed c.searching.Store(true) // Check to see if there is a search already matching the destination sinfo, isIn := c.core.searches.searches[*c.nodeID] @@ -73,8 +71,6 @@ func (c *Conn) startSearch() { // Nothing was found, so create a new search sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo) - // Allow writes/reads to block until the search is complete - c.searchwait = make(chan interface{}) } // Continue the search c.core.searches.continueSearch(sinfo) @@ -111,23 +107,23 @@ func (c *Conn) Read(b []byte) (int, error) { sinfo := c.session c.mutex.RUnlock() // If there is a search in progress then wait for the result - if searching, ok := c.searching.Load().(bool); ok && searching { + if sinfo == nil { + // Wait for the search to complete <-c.searchwait - } - // If the session is not initialised, do nothing. Currently in this instance - // in a write, we would trigger a new session, but it doesn't make sense for - // us to block forever here if the session will not reopen. - // TODO: should this return an error or just a zero-length buffer? - if sinfo == nil || !sinfo.init { - time.Sleep(time.Second) - return 0, errors.New("session is closed") + // Retrieve our session info again + c.mutex.RLock() + sinfo = c.session + c.mutex.RUnlock() + // If sinfo is still nil at this point then the search failed and the + // searchwait channel has been recreated, so might as well give up and + // return an error code + if sinfo == nil { + return 0, errors.New("search failed") + } } // Wait for some traffic to come through from the session - fmt.Println(c.String(), "Start select") select { - // TODO... - case p, ok := <-c.recv: - fmt.Println(c.String(), "Finish select") + case p, ok := <-sinfo.recv: // If the session is closed then do nothing if !ok { return 0, errors.New("session is closed") @@ -168,10 +164,6 @@ func (c *Conn) Read(b []byte) (int, error) { // If we've reached this point then everything went to plan, return the // number of bytes we populated back into the given slice return len(b), nil - //case <-c.recvTimeout: - //case <-c.session.closed: - // c.expired = true - // return len(b), errors.New("session is closed") } } @@ -179,12 +171,9 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { c.mutex.RLock() sinfo := c.session c.mutex.RUnlock() - // If there is a search in progress then wait for the result - if searching, ok := c.searching.Load().(bool); ok && searching { - <-c.searchwait - } // If the session doesn't exist, or isn't initialised (which probably means - // that the search didn't complete successfully) then try to search again + // that the search didn't complete successfully) then we may need to wait for + // the search to complete or start the search again if sinfo == nil || !sinfo.init { // Is a search already taking place? if searching, sok := c.searching.Load().(bool); !sok || (sok && !searching) { @@ -192,13 +181,19 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { c.core.router.doAdmin(func() { c.startSearch() }) - //return 0, errors.New("starting search") } + // Wait for the search to complete <-c.searchwait - if sinfo == nil || !sinfo.init { - return 0, errors.New("search was failed") + // Retrieve our session info again + c.mutex.RLock() + sinfo = c.session + c.mutex.RUnlock() + // If sinfo is still nil at this point then the search failed and the + // searchwait channel has been recreated, so might as well give up and + // return an error code + if sinfo == nil { + return 0, errors.New("search failed") } - //return 0, errors.New("waiting for search to complete") } // defer util.PutBytes(b) var packet []byte diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index 89016100..325c6b76 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -59,11 +59,11 @@ func (d *Dialer) Dial(network, address string) (Conn, error) { // NodeID parameters. func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (Conn, error) { conn := Conn{ - core: d.core, - mutex: &sync.RWMutex{}, - nodeID: nodeID, - nodeMask: nodeMask, - recv: make(chan *wire_trafficPacket, 32), + core: d.core, + mutex: &sync.RWMutex{}, + nodeID: nodeID, + nodeMask: nodeMask, + searchwait: make(chan interface{}), } return conn, nil } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 967d5f6b..44c83874 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -458,12 +458,12 @@ func (ss *sessions) handlePing(ping *sessionPing) { // TODO: this should not block if nothing is accepting if !ping.IsPong && ss.listener != nil { conn := &Conn{ - core: ss.core, - session: sinfo, - mutex: &sync.RWMutex{}, - nodeID: crypto.GetNodeID(&sinfo.theirPermPub), - nodeMask: &crypto.NodeID{}, - recv: sinfo.recv, + core: ss.core, + session: sinfo, + mutex: &sync.RWMutex{}, + nodeID: crypto.GetNodeID(&sinfo.theirPermPub), + nodeMask: &crypto.NodeID{}, + searchwait: make(chan interface{}), } for i := range conn.nodeMask { conn.nodeMask[i] = 0xFF From 2bee3cd7cac554a0b74de3e798f983cd716468e5 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 23 Apr 2019 00:04:22 +0100 Subject: [PATCH 0055/1109] Update TODOs at top of tun.go --- src/tuntap/tun.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 709f7051..58eca27a 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -2,11 +2,11 @@ package tuntap // This manages the tun driver to send/recv packets to/from applications -// TODO: Crypto-key routing +// TODO: Crypto-key routing support // TODO: Set MTU of session properly -// TODO: Reject packets that exceed session MTU -// TODO: Connection timeouts (call Close() when done) -// TODO: Keep packet that was used to set up a session and send it when done +// TODO: Reject packets that exceed session MTU with ICMPv6 for PMTU Discovery +// TODO: Connection timeouts (call Conn.Close() when we want to time out) +// TODO: Don't block in ifaceReader on writes that are pending searches import ( "encoding/hex" From 870b2b6a2ed09ad7d3509c6b3cc22d05c2b24d18 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 23 Apr 2019 10:28:40 +0100 Subject: [PATCH 0056/1109] Remove CKR from src/yggdrasil (it will be moved into tuntap) --- src/yggdrasil/admin.go | 4 +- src/yggdrasil/ckr.go | 414 ---------------------------------------- src/yggdrasil/core.go | 1 - src/yggdrasil/router.go | 2 - 4 files changed, 2 insertions(+), 419 deletions(-) delete mode 100644 src/yggdrasil/ckr.go diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 0f91cd15..a24a5852 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -249,7 +249,7 @@ func (a *admin) init(c *Core) { }, errors.New("Failed to remove allowed key") } }) - a.addHandler("getTunnelRouting", []string{}, func(in admin_info) (admin_info, error) { + /*a.addHandler("getTunnelRouting", []string{}, func(in admin_info) (admin_info, error) { enabled := false a.core.router.doAdmin(func() { enabled = a.core.router.cryptokey.isEnabled() @@ -335,7 +335,7 @@ func (a *admin) init(c *Core) { } else { return admin_info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route") } - }) + })*/ a.addHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in admin_info) (admin_info, error) { if in["target"] == nil { in["target"] = "none" diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go deleted file mode 100644 index dc4f1b90..00000000 --- a/src/yggdrasil/ckr.go +++ /dev/null @@ -1,414 +0,0 @@ -package yggdrasil - -import ( - "bytes" - "encoding/hex" - "errors" - "fmt" - "net" - "sort" - - "github.com/yggdrasil-network/yggdrasil-go/src/address" - "github.com/yggdrasil-network/yggdrasil-go/src/crypto" -) - -// This module implements crypto-key routing, similar to Wireguard, where we -// allow traffic for non-Yggdrasil ranges to be routed over Yggdrasil. - -type cryptokey struct { - core *Core - enabled bool - reconfigure chan chan error - ipv4routes []cryptokey_route - ipv6routes []cryptokey_route - ipv4cache map[address.Address]cryptokey_route - ipv6cache map[address.Address]cryptokey_route - ipv4sources []net.IPNet - ipv6sources []net.IPNet -} - -type cryptokey_route struct { - subnet net.IPNet - destination crypto.BoxPubKey -} - -// Initialise crypto-key routing. This must be done before any other CKR calls. -func (c *cryptokey) init(core *Core) { - c.core = core - c.reconfigure = make(chan chan error, 1) - go func() { - for { - e := <-c.reconfigure - var err error - c.core.router.doAdmin(func() { - err = c.core.router.cryptokey.configure() - }) - e <- err - } - }() - - if err := c.configure(); err != nil { - c.core.log.Errorln("CKR configuration failed:", err) - } -} - -// Configure the CKR routes - this must only ever be called from the router -// goroutine, e.g. through router.doAdmin -func (c *cryptokey) configure() error { - current, _ := c.core.config.Get() - - // Set enabled/disabled state - c.setEnabled(current.TunnelRouting.Enable) - - // Clear out existing routes - c.ipv6routes = make([]cryptokey_route, 0) - c.ipv4routes = make([]cryptokey_route, 0) - - // Add IPv6 routes - for ipv6, pubkey := range current.TunnelRouting.IPv6Destinations { - if err := c.addRoute(ipv6, pubkey); err != nil { - return err - } - } - - // Add IPv4 routes - for ipv4, pubkey := range current.TunnelRouting.IPv4Destinations { - if err := c.addRoute(ipv4, pubkey); err != nil { - return err - } - } - - // Clear out existing sources - c.ipv6sources = make([]net.IPNet, 0) - c.ipv4sources = make([]net.IPNet, 0) - - // Add IPv6 sources - c.ipv6sources = make([]net.IPNet, 0) - for _, source := range current.TunnelRouting.IPv6Sources { - if err := c.addSourceSubnet(source); err != nil { - return err - } - } - - // Add IPv4 sources - c.ipv4sources = make([]net.IPNet, 0) - for _, source := range current.TunnelRouting.IPv4Sources { - if err := c.addSourceSubnet(source); err != nil { - return err - } - } - - // Wipe the caches - c.ipv4cache = make(map[address.Address]cryptokey_route, 0) - c.ipv6cache = make(map[address.Address]cryptokey_route, 0) - - return nil -} - -// Enable or disable crypto-key routing. -func (c *cryptokey) setEnabled(enabled bool) { - c.enabled = enabled -} - -// Check if crypto-key routing is enabled. -func (c *cryptokey) isEnabled() bool { - return c.enabled -} - -// Check whether the given address (with the address length specified in bytes) -// matches either the current node's address, the node's routed subnet or the -// list of subnets specified in IPv4Sources/IPv6Sources. -func (c *cryptokey) isValidSource(addr address.Address, addrlen int) bool { - ip := net.IP(addr[:addrlen]) - - if addrlen == net.IPv6len { - // Does this match our node's address? - if bytes.Equal(addr[:16], c.core.router.addr[:16]) { - return true - } - - // Does this match our node's subnet? - if bytes.Equal(addr[:8], c.core.router.subnet[:8]) { - return true - } - } - - // Does it match a configured CKR source? - if c.isEnabled() { - // Build our references to the routing sources - var routingsources *[]net.IPNet - - // Check if the prefix is IPv4 or IPv6 - if addrlen == net.IPv6len { - routingsources = &c.ipv6sources - } else if addrlen == net.IPv4len { - routingsources = &c.ipv4sources - } else { - return false - } - - for _, subnet := range *routingsources { - if subnet.Contains(ip) { - return true - } - } - } - - // Doesn't match any of the above - return false -} - -// Adds a source subnet, which allows traffic with these source addresses to -// be tunnelled using crypto-key routing. -func (c *cryptokey) addSourceSubnet(cidr string) error { - // Is the CIDR we've been given valid? - _, ipnet, err := net.ParseCIDR(cidr) - if err != nil { - return err - } - - // Get the prefix length and size - _, prefixsize := ipnet.Mask.Size() - - // Build our references to the routing sources - var routingsources *[]net.IPNet - - // Check if the prefix is IPv4 or IPv6 - if prefixsize == net.IPv6len*8 { - routingsources = &c.ipv6sources - } else if prefixsize == net.IPv4len*8 { - routingsources = &c.ipv4sources - } else { - return errors.New("Unexpected prefix size") - } - - // Check if we already have this CIDR - for _, subnet := range *routingsources { - if subnet.String() == ipnet.String() { - return errors.New("Source subnet already configured") - } - } - - // Add the source subnet - *routingsources = append(*routingsources, *ipnet) - c.core.log.Infoln("Added CKR source subnet", cidr) - return nil -} - -// Adds a destination route for the given CIDR to be tunnelled to the node -// with the given BoxPubKey. -func (c *cryptokey) addRoute(cidr string, dest string) error { - // Is the CIDR we've been given valid? - ipaddr, ipnet, err := net.ParseCIDR(cidr) - if err != nil { - return err - } - - // Get the prefix length and size - _, prefixsize := ipnet.Mask.Size() - - // Build our references to the routing table and cache - var routingtable *[]cryptokey_route - var routingcache *map[address.Address]cryptokey_route - - // Check if the prefix is IPv4 or IPv6 - if prefixsize == net.IPv6len*8 { - routingtable = &c.ipv6routes - routingcache = &c.ipv6cache - } else if prefixsize == net.IPv4len*8 { - routingtable = &c.ipv4routes - routingcache = &c.ipv4cache - } else { - return errors.New("Unexpected prefix size") - } - - // Is the route an Yggdrasil destination? - var addr address.Address - var snet address.Subnet - copy(addr[:], ipaddr) - copy(snet[:], ipnet.IP) - if addr.IsValid() || snet.IsValid() { - return errors.New("Can't specify Yggdrasil destination as crypto-key route") - } - // Do we already have a route for this subnet? - for _, route := range *routingtable { - if route.subnet.String() == ipnet.String() { - return errors.New(fmt.Sprintf("Route already exists for %s", cidr)) - } - } - // Decode the public key - if bpk, err := hex.DecodeString(dest); err != nil { - return err - } else if len(bpk) != crypto.BoxPubKeyLen { - return errors.New(fmt.Sprintf("Incorrect key length for %s", dest)) - } else { - // Add the new crypto-key route - var key crypto.BoxPubKey - copy(key[:], bpk) - *routingtable = append(*routingtable, cryptokey_route{ - subnet: *ipnet, - destination: key, - }) - - // Sort so most specific routes are first - sort.Slice(*routingtable, func(i, j int) bool { - im, _ := (*routingtable)[i].subnet.Mask.Size() - jm, _ := (*routingtable)[j].subnet.Mask.Size() - return im > jm - }) - - // Clear the cache as this route might change future routing - // Setting an empty slice keeps the memory whereas nil invokes GC - for k := range *routingcache { - delete(*routingcache, k) - } - - c.core.log.Infoln("Added CKR destination subnet", cidr) - return nil - } -} - -// Looks up the most specific route for the given address (with the address -// length specified in bytes) from the crypto-key routing table. An error is -// returned if the address is not suitable or no route was found. -func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (crypto.BoxPubKey, error) { - // Check if the address is a valid Yggdrasil address - if so it - // is exempt from all CKR checking - if addr.IsValid() { - return crypto.BoxPubKey{}, errors.New("Cannot look up CKR for Yggdrasil addresses") - } - - // Build our references to the routing table and cache - var routingtable *[]cryptokey_route - var routingcache *map[address.Address]cryptokey_route - - // Check if the prefix is IPv4 or IPv6 - if addrlen == net.IPv6len { - routingtable = &c.ipv6routes - routingcache = &c.ipv6cache - } else if addrlen == net.IPv4len { - routingtable = &c.ipv4routes - routingcache = &c.ipv4cache - } else { - return crypto.BoxPubKey{}, errors.New("Unexpected prefix size") - } - - // Check if there's a cache entry for this addr - if route, ok := (*routingcache)[addr]; ok { - return route.destination, nil - } - - // No cache was found - start by converting the address into a net.IP - ip := make(net.IP, addrlen) - copy(ip[:addrlen], addr[:]) - - // Check if we have a route. At this point c.ipv6routes should be - // pre-sorted so that the most specific routes are first - for _, route := range *routingtable { - // Does this subnet match the given IP? - if route.subnet.Contains(ip) { - // Check if the routing cache is above a certain size, if it is evict - // a random entry so we can make room for this one. We take advantage - // of the fact that the iteration order is random here - for k := range *routingcache { - if len(*routingcache) < 1024 { - break - } - delete(*routingcache, k) - } - - // Cache the entry for future packets to get a faster lookup - (*routingcache)[addr] = route - - // Return the boxPubKey - return route.destination, nil - } - } - - // No route was found if we got to this point - return crypto.BoxPubKey{}, errors.New(fmt.Sprintf("No route to %s", ip.String())) -} - -// Removes a source subnet, which allows traffic with these source addresses to -// be tunnelled using crypto-key routing. -func (c *cryptokey) removeSourceSubnet(cidr string) error { - // Is the CIDR we've been given valid? - _, ipnet, err := net.ParseCIDR(cidr) - if err != nil { - return err - } - - // Get the prefix length and size - _, prefixsize := ipnet.Mask.Size() - - // Build our references to the routing sources - var routingsources *[]net.IPNet - - // Check if the prefix is IPv4 or IPv6 - if prefixsize == net.IPv6len*8 { - routingsources = &c.ipv6sources - } else if prefixsize == net.IPv4len*8 { - routingsources = &c.ipv4sources - } else { - return errors.New("Unexpected prefix size") - } - - // Check if we already have this CIDR - for idx, subnet := range *routingsources { - if subnet.String() == ipnet.String() { - *routingsources = append((*routingsources)[:idx], (*routingsources)[idx+1:]...) - c.core.log.Infoln("Removed CKR source subnet", cidr) - return nil - } - } - return errors.New("Source subnet not found") -} - -// Removes a destination route for the given CIDR to be tunnelled to the node -// with the given BoxPubKey. -func (c *cryptokey) removeRoute(cidr string, dest string) error { - // Is the CIDR we've been given valid? - _, ipnet, err := net.ParseCIDR(cidr) - if err != nil { - return err - } - - // Get the prefix length and size - _, prefixsize := ipnet.Mask.Size() - - // Build our references to the routing table and cache - var routingtable *[]cryptokey_route - var routingcache *map[address.Address]cryptokey_route - - // Check if the prefix is IPv4 or IPv6 - if prefixsize == net.IPv6len*8 { - routingtable = &c.ipv6routes - routingcache = &c.ipv6cache - } else if prefixsize == net.IPv4len*8 { - routingtable = &c.ipv4routes - routingcache = &c.ipv4cache - } else { - return errors.New("Unexpected prefix size") - } - - // Decode the public key - bpk, err := hex.DecodeString(dest) - if err != nil { - return err - } else if len(bpk) != crypto.BoxPubKeyLen { - return errors.New(fmt.Sprintf("Incorrect key length for %s", dest)) - } - netStr := ipnet.String() - - for idx, route := range *routingtable { - if bytes.Equal(route.destination[:], bpk) && route.subnet.String() == netStr { - *routingtable = append((*routingtable)[:idx], (*routingtable)[idx+1:]...) - for k := range *routingcache { - delete(*routingcache, k) - } - c.core.log.Infoln("Removed CKR destination subnet %s via %s\n", cidr, dest) - return nil - } - } - return errors.New(fmt.Sprintf("Route does not exists for %s", cidr)) -} diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 81be1b28..ac2b494f 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -127,7 +127,6 @@ func (c *Core) UpdateConfig(config *config.NodeConfig) { c.sessions.reconfigure, c.peers.reconfigure, c.router.reconfigure, - c.router.cryptokey.reconfigure, c.switchTable.reconfigure, c.link.reconfigure, } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 68700723..cacf0259 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -43,7 +43,6 @@ type router struct { out func([]byte) // packets we're sending to the network, link to peer's "in" reset chan struct{} // signal that coords changed (re-init sessions/dht) admin chan func() // pass a lambda for the admin socket to query stuff - cryptokey cryptokey nodeinfo nodeinfo } @@ -97,7 +96,6 @@ func (r *router) init(core *Core) { r.core.config.Mutex.RLock() r.nodeinfo.setNodeInfo(r.core.config.Current.NodeInfo, r.core.config.Current.NodeInfoPrivacy) r.core.config.Mutex.RUnlock() - r.cryptokey.init(r.core) } // Starts the mainLoop goroutine. From b4513ca2e843242d6d6127d8b07df4df050bf841 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 23 Apr 2019 10:43:07 +0100 Subject: [PATCH 0057/1109] Re-add support for TAP mode --- src/tuntap/tun.go | 104 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 2 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 58eca27a..eb06cc9b 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -9,6 +9,7 @@ package tuntap // TODO: Don't block in ifaceReader on writes that are pending searches import ( + "bytes" "encoding/hex" "errors" "fmt" @@ -17,12 +18,14 @@ import ( "time" "github.com/gologme/log" + "github.com/songgao/packets/ethernet" "github.com/yggdrasil-network/water" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" + "github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) @@ -111,6 +114,7 @@ func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener tun.dialer = dialer tun.addrToConn = make(map[address.Address]*yggdrasil.Conn) tun.subnetToConn = make(map[address.Subnet]*yggdrasil.Conn) + tun.icmpv6.Init(tun) } // Start the setup process for the TUN/TAP adapter. If successful, starts the @@ -163,7 +167,6 @@ func (tun *TunAdapter) Start() error { } }() } - tun.icmpv6.Init(tun) go func() { for { e := <-tun.reconfigure @@ -228,7 +231,82 @@ func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { if n == 0 { continue } - w, err := tun.iface.Write(b[:n]) + var w int + if tun.iface.IsTAP() { + var dstAddr address.Address + if b[0]&0xf0 == 0x60 { + if len(b) < 40 { + //panic("Tried to sendb a packet shorter than an IPv6 header...") + util.PutBytes(b) + continue + } + copy(dstAddr[:16], b[24:]) + } else if b[0]&0xf0 == 0x40 { + if len(b) < 20 { + //panic("Tried to send a packet shorter than an IPv4 header...") + util.PutBytes(b) + continue + } + copy(dstAddr[:4], b[16:]) + } else { + return errors.New("Invalid address family") + } + sendndp := func(dstAddr address.Address) { + neigh, known := tun.icmpv6.peermacs[dstAddr] + known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30) + if !known { + request, err := tun.icmpv6.CreateNDPL2(dstAddr) + if err != nil { + panic(err) + } + if _, err := tun.iface.Write(request); err != nil { + panic(err) + } + tun.icmpv6.peermacs[dstAddr] = neighbor{ + lastsolicitation: time.Now(), + } + } + } + var peermac macAddress + var peerknown bool + if b[0]&0xf0 == 0x40 { + dstAddr = tun.addr + } else if b[0]&0xf0 == 0x60 { + if !bytes.Equal(tun.addr[:16], dstAddr[:16]) && !bytes.Equal(tun.subnet[:8], dstAddr[:8]) { + dstAddr = tun.addr + } + } + if neighbor, ok := tun.icmpv6.peermacs[dstAddr]; ok && neighbor.learned { + peermac = neighbor.mac + peerknown = true + } else if neighbor, ok := tun.icmpv6.peermacs[tun.addr]; ok && neighbor.learned { + peermac = neighbor.mac + peerknown = true + sendndp(dstAddr) + } else { + sendndp(tun.addr) + } + if peerknown { + var proto ethernet.Ethertype + switch { + case b[0]&0xf0 == 0x60: + proto = ethernet.IPv6 + case b[0]&0xf0 == 0x40: + proto = ethernet.IPv4 + } + var frame ethernet.Frame + frame.Prepare( + peermac[:6], // Destination MAC address + tun.icmpv6.mymac[:6], // Source MAC address + ethernet.NotTagged, // VLAN tagging + proto, // Ethertype + len(b)) // Payload length + copy(frame[tun_ETHER_HEADER_LENGTH:], b[:]) + w, err = tun.iface.Write(b[:n]) + } + } else { + w, err = tun.iface.Write(b[:n]) + } if err != nil { tun.log.Errorln(conn.String(), "TUN/TAP iface write error:", err) continue @@ -248,6 +326,20 @@ func (tun *TunAdapter) ifaceReader() error { if err != nil { continue } + // If it's a TAP adapter, update the buffer slice so that we no longer + // include the ethernet headers + if tun.iface.IsTAP() { + bs = bs[tun_ETHER_HEADER_LENGTH:] + } + // If we detect an ICMP packet then hand it to the ICMPv6 module + if bs[6] == 58 { + if tun.iface.IsTAP() { + // Found an ICMPv6 packet + b := make([]byte, n) + copy(b, bs) + go tun.icmpv6.ParsePacket(b) + } + } // From the IP header, work out what our source and destination addresses // and node IDs are. We will need these in order to work out where to send // the packet @@ -264,6 +356,10 @@ func (tun *TunAdapter) ifaceReader() error { if len(bs) < 40 { continue } + // Check the packet size + if n != 256*int(bs[4])+int(bs[5])+tun_IPv6_HEADER_LENGTH { + continue + } // IPv6 address addrlen = 16 copy(srcAddr[:addrlen], bs[8:]) @@ -274,6 +370,10 @@ func (tun *TunAdapter) ifaceReader() error { if len(bs) < 20 { continue } + // Check the packet size + if bs[0]&0xf0 == 0x40 && n != 256*int(bs[2])+int(bs[3]) { + continue + } // IPv4 address addrlen = 4 copy(srcAddr[:addrlen], bs[12:]) From 2b44f5d2f6a81b33ac6e62d497e84132f5d873cf Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 23 Apr 2019 11:37:32 +0100 Subject: [PATCH 0058/1109] Fix TAP support --- src/tuntap/icmpv6.go | 3 +-- src/tuntap/tun.go | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/tuntap/icmpv6.go b/src/tuntap/icmpv6.go index 55c3280a..8159e0f9 100644 --- a/src/tuntap/icmpv6.go +++ b/src/tuntap/icmpv6.go @@ -169,7 +169,6 @@ func (i *ICMPv6) UnmarshalPacket(datain []byte, datamac *[]byte) ([]byte, error) if err != nil { return nil, err } - // Send it back return responsePacket, nil } else { @@ -186,7 +185,7 @@ func (i *ICMPv6) UnmarshalPacket(datain []byte, datamac *[]byte) ([]byte, error) copy(addr[:], ipv6Header.Src[:]) copy(target[:], datain[48:64]) copy(mac[:], (*datamac)[:]) - // i.tun.core.log.Printf("Learning peer MAC %x for %x\n", mac, target) + // fmt.Printf("Learning peer MAC %x for %x\n", mac, target) neighbor := i.peermacs[target] neighbor.mac = mac neighbor.learned = true diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index eb06cc9b..fed6563a 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -114,7 +114,6 @@ func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener tun.dialer = dialer tun.addrToConn = make(map[address.Address]*yggdrasil.Conn) tun.subnetToConn = make(map[address.Subnet]*yggdrasil.Conn) - tun.icmpv6.Init(tun) } // Start the setup process for the TUN/TAP adapter. If successful, starts the @@ -175,6 +174,7 @@ func (tun *TunAdapter) Start() error { }() go tun.handler() go tun.ifaceReader() + tun.icmpv6.Init(tun) return nil } @@ -328,17 +328,20 @@ func (tun *TunAdapter) ifaceReader() error { } // If it's a TAP adapter, update the buffer slice so that we no longer // include the ethernet headers + offset := 0 if tun.iface.IsTAP() { - bs = bs[tun_ETHER_HEADER_LENGTH:] - } - // If we detect an ICMP packet then hand it to the ICMPv6 module - if bs[6] == 58 { - if tun.iface.IsTAP() { + // Set our offset to beyond the ethernet headers + offset = tun_ETHER_HEADER_LENGTH + // If we detect an ICMP packet then hand it to the ICMPv6 module + if bs[offset+6] == 58 { // Found an ICMPv6 packet b := make([]byte, n) copy(b, bs) go tun.icmpv6.ParsePacket(b) } + // Then offset the buffer so that we can now just treat it as an IP + // packet from now on + bs = bs[offset:] } // From the IP header, work out what our source and destination addresses // and node IDs are. We will need these in order to work out where to send @@ -357,7 +360,7 @@ func (tun *TunAdapter) ifaceReader() error { continue } // Check the packet size - if n != 256*int(bs[4])+int(bs[5])+tun_IPv6_HEADER_LENGTH { + if n != 256*int(bs[4])+int(bs[5])+offset+tun_IPv6_HEADER_LENGTH { continue } // IPv6 address @@ -371,7 +374,7 @@ func (tun *TunAdapter) ifaceReader() error { continue } // Check the packet size - if bs[0]&0xf0 == 0x40 && n != 256*int(bs[2])+int(bs[3]) { + if n != 256*int(bs[2])+int(bs[3])+offset { continue } // IPv4 address From 75130f7735c961e33db05246c63cb1c048507345 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 23 Apr 2019 11:46:16 +0100 Subject: [PATCH 0059/1109] Fix TAP support again --- src/tuntap/tun.go | 7 ++++--- src/yggdrasil/session.go | 3 +-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index fed6563a..01cafaa4 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -236,7 +236,7 @@ func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { var dstAddr address.Address if b[0]&0xf0 == 0x60 { if len(b) < 40 { - //panic("Tried to sendb a packet shorter than an IPv6 header...") + //panic("Tried to send a packet shorter than an IPv6 header...") util.PutBytes(b) continue } @@ -301,8 +301,9 @@ func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { ethernet.NotTagged, // VLAN tagging proto, // Ethertype len(b)) // Payload length - copy(frame[tun_ETHER_HEADER_LENGTH:], b[:]) - w, err = tun.iface.Write(b[:n]) + copy(frame[tun_ETHER_HEADER_LENGTH:], b[:n]) + n += tun_ETHER_HEADER_LENGTH + w, err = tun.iface.Write(frame[:n]) } } else { w, err = tun.iface.Write(b[:n]) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 44c83874..bf35e8c3 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -8,7 +8,6 @@ import ( "bytes" "encoding/hex" "sync" - "sync/atomic" "time" "github.com/yggdrasil-network/yggdrasil-go/src/address" @@ -78,7 +77,7 @@ type sessionPing struct { // Updates session info in response to a ping, after checking that the ping is OK. // Returns true if the session was updated, or false otherwise. func (s *sessionInfo) update(p *sessionPing) bool { - if !(p.Tstamp > atomic.LoadInt64(&s.tstamp)) { + if !(p.Tstamp > s.tstamp) { // To protect against replay attacks return false } From 0059baf36c9202f6327f4434f58825171b21ec90 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 26 Apr 2019 18:07:57 -0500 Subject: [PATCH 0060/1109] add a newConn function that returns a pointer to a Conn with atomics properly initialized --- src/tuntap/tun.go | 4 ++-- src/yggdrasil/conn.go | 24 ++++++++++++++++++++---- src/yggdrasil/dialer.go | 21 +++++++-------------- src/yggdrasil/session.go | 9 +-------- 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 01cafaa4..c19764c3 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -412,9 +412,9 @@ func (tun *TunAdapter) ifaceReader() error { // Dial to the remote node if c, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { // We've been given a connection so start the connection reader goroutine - go tun.connReader(&c) + go tun.connReader(c) // Then update our reference to the connection - conn, isIn = &c, true + conn, isIn = c, true } else { // We weren't able to dial for some reason so there's no point in // continuing this iteration - skip to the next one diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 8d36a60a..08591c4a 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -15,12 +15,26 @@ type Conn struct { core *Core nodeID *crypto.NodeID nodeMask *crypto.NodeID - mutex *sync.RWMutex + mutex sync.RWMutex session *sessionInfo readDeadline atomic.Value // time.Time // TODO timer writeDeadline atomic.Value // time.Time // TODO timer searching atomic.Value // bool - searchwait chan interface{} + searchwait chan struct{} +} + +// TODO func NewConn() that initializes atomic and channel fields so things don't crash or block indefinitely +func newConn(core *Core, nodeID *crypto.NodeID, nodeMask *crypto.NodeID, session *sessionInfo) *Conn { + conn := Conn{ + core: core, + nodeID: nodeID, + nodeMask: nodeMask, + session: session, + searchwait: make(chan struct{}), + } + conn.SetDeadline(time.Time{}) + conn.searching.Store(false) + return &conn } func (c *Conn) String() string { @@ -33,9 +47,9 @@ func (c *Conn) startSearch() { searchCompleted := func(sinfo *sessionInfo, err error) { // Make sure that any blocks on read/write operations are lifted defer func() { + defer func() { recover() }() // In case searchwait was closed by another goroutine c.searching.Store(false) - close(c.searchwait) - c.searchwait = make(chan interface{}) + close(c.searchwait) // Never reset this to an open channel }() // If the search failed for some reason, e.g. it hit a dead end or timed // out, then do nothing @@ -106,6 +120,8 @@ func (c *Conn) Read(b []byte) (int, error) { c.mutex.RLock() sinfo := c.session c.mutex.RUnlock() + timer := time.NewTimer(0) + util.TimerStop(timer) // If there is a search in progress then wait for the result if sinfo == nil { // Wait for the search to complete diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index 325c6b76..1943c859 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -5,7 +5,6 @@ import ( "errors" "strconv" "strings" - "sync" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" ) @@ -18,7 +17,7 @@ type Dialer struct { // Dial opens a session to the given node. The first paramter should be "nodeid" // and the second parameter should contain a hexadecimal representation of the // target node ID. -func (d *Dialer) Dial(network, address string) (Conn, error) { +func (d *Dialer) Dial(network, address string) (*Conn, error) { var nodeID crypto.NodeID var nodeMask crypto.NodeID // Process @@ -28,11 +27,11 @@ func (d *Dialer) Dial(network, address string) (Conn, error) { if tokens := strings.Split(address, "/"); len(tokens) == 2 { len, err := strconv.Atoi(tokens[1]) if err != nil { - return Conn{}, err + return nil, err } dest, err := hex.DecodeString(tokens[0]) if err != nil { - return Conn{}, err + return nil, err } copy(nodeID[:], dest) for idx := 0; idx < len; idx++ { @@ -41,7 +40,7 @@ func (d *Dialer) Dial(network, address string) (Conn, error) { } else { dest, err := hex.DecodeString(tokens[0]) if err != nil { - return Conn{}, err + return nil, err } copy(nodeID[:], dest) for i := range nodeMask { @@ -51,19 +50,13 @@ func (d *Dialer) Dial(network, address string) (Conn, error) { return d.DialByNodeIDandMask(&nodeID, &nodeMask) default: // An unexpected address type was given, so give up - return Conn{}, errors.New("unexpected address type") + return nil, errors.New("unexpected address type") } } // DialByNodeIDandMask opens a session to the given node based on raw // NodeID parameters. -func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (Conn, error) { - conn := Conn{ - core: d.core, - mutex: &sync.RWMutex{}, - nodeID: nodeID, - nodeMask: nodeMask, - searchwait: make(chan interface{}), - } +func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (*Conn, error) { + conn := newConn(d.core, nodeID, nodeMask, nil) return conn, nil } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index bf35e8c3..724152dd 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -456,14 +456,7 @@ func (ss *sessions) handlePing(ping *sessionPing) { // Check and see if there's a Listener waiting to accept connections // TODO: this should not block if nothing is accepting if !ping.IsPong && ss.listener != nil { - conn := &Conn{ - core: ss.core, - session: sinfo, - mutex: &sync.RWMutex{}, - nodeID: crypto.GetNodeID(&sinfo.theirPermPub), - nodeMask: &crypto.NodeID{}, - searchwait: make(chan interface{}), - } + conn := newConn(ss.core, crypto.GetNodeID(&sinfo.theirPermPub), &crypto.NodeID{}, sinfo) for i := range conn.nodeMask { conn.nodeMask[i] = 0xFF } From 15051b0a3cc02f39be8ad5c000d65c19eae3e364 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 26 Apr 2019 19:31:47 -0500 Subject: [PATCH 0061/1109] Add deadline timers, keep searches alive until they complete (or the conn is closed) to keep Write from blocking forever --- src/yggdrasil/conn.go | 128 +++++++++++++++++++++++++++++++++--------- 1 file changed, 102 insertions(+), 26 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 08591c4a..a019908f 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -11,19 +11,35 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/util" ) +// Error implements the net.Error interface +type ConnError struct { + error + timeout bool + temporary bool +} + +func (e *ConnError) Timeout() bool { + return e.timeout +} + +func (e *ConnError) Temporary() bool { + return e.temporary +} + type Conn struct { core *Core nodeID *crypto.NodeID nodeMask *crypto.NodeID mutex sync.RWMutex + closed bool session *sessionInfo - readDeadline atomic.Value // time.Time // TODO timer - writeDeadline atomic.Value // time.Time // TODO timer - searching atomic.Value // bool - searchwait chan struct{} + readDeadline atomic.Value // time.Time // TODO timer + writeDeadline atomic.Value // time.Time // TODO timer + searching atomic.Value // bool + searchwait chan struct{} // Never reset this, it's only used for the initial search } -// TODO func NewConn() that initializes atomic and channel fields so things don't crash or block indefinitely +// TODO func NewConn() that initializes additional fields as needed func newConn(core *Core, nodeID *crypto.NodeID, nodeMask *crypto.NodeID, session *sessionInfo) *Conn { conn := Conn{ core: core, @@ -32,7 +48,6 @@ func newConn(core *Core, nodeID *crypto.NodeID, nodeMask *crypto.NodeID, session session: session, searchwait: make(chan struct{}), } - conn.SetDeadline(time.Time{}) conn.searching.Store(false) return &conn } @@ -45,22 +60,27 @@ func (c *Conn) String() string { func (c *Conn) startSearch() { // The searchCompleted callback is given to the search searchCompleted := func(sinfo *sessionInfo, err error) { - // Make sure that any blocks on read/write operations are lifted - defer func() { - defer func() { recover() }() // In case searchwait was closed by another goroutine - c.searching.Store(false) - close(c.searchwait) // Never reset this to an open channel - }() + defer c.searching.Store(false) // If the search failed for some reason, e.g. it hit a dead end or timed // out, then do nothing if err != nil { c.core.log.Debugln(c.String(), "DHT search failed:", err) + go func() { + time.Sleep(time.Second) + c.mutex.RLock() + closed := c.closed + c.mutex.RUnlock() + if !closed { + // Restart the search, or else Write can stay blocked forever + c.core.router.admin <- c.startSearch + } + }() return } // Take the connection mutex c.mutex.Lock() defer c.mutex.Unlock() - // Were we successfully given a sessionInfo pointeR? + // Were we successfully given a sessionInfo pointer? if sinfo != nil { // Store it, and update the nodeID and nodeMask (which may have been // wildcarded before now) with their complete counterparts @@ -70,11 +90,19 @@ func (c *Conn) startSearch() { for i := range c.nodeMask { c.nodeMask[i] = 0xFF } + // Make sure that any blocks on read/write operations are lifted + defer func() { recover() }() // So duplicate searches don't panic + close(c.searchwait) } else { // No session was returned - this shouldn't really happen because we // should always return an error reason if we don't return a session panic("DHT search didn't return an error or a sessionInfo") } + if c.closed { + // Things were closed before the search returned + // Go ahead and close it again to make sure the session is cleaned up + go c.Close() + } } // doSearch will be called below in response to one or more conditions doSearch := func() { @@ -115,17 +143,30 @@ func (c *Conn) startSearch() { } } +func getDeadlineTimer(value *atomic.Value) *time.Timer { + timer := time.NewTimer(0) + util.TimerStop(timer) + if deadline, ok := value.Load().(time.Time); ok { + timer.Reset(time.Until(deadline)) + } + return timer +} + func (c *Conn) Read(b []byte) (int, error) { // Take a copy of the session object c.mutex.RLock() sinfo := c.session c.mutex.RUnlock() - timer := time.NewTimer(0) - util.TimerStop(timer) + timer := getDeadlineTimer(&c.readDeadline) + defer util.TimerStop(timer) // If there is a search in progress then wait for the result if sinfo == nil { // Wait for the search to complete - <-c.searchwait + select { + case <-c.searchwait: + case <-timer.C: + return 0, ConnError{errors.New("Timeout"), true, false} + } // Retrieve our session info again c.mutex.RLock() sinfo = c.session @@ -146,8 +187,9 @@ func (c *Conn) Read(b []byte) (int, error) { } defer util.PutBytes(p.Payload) var err error - // Hand over to the session worker - sinfo.doWorker(func() { + done := make(chan struct{}) + workerFunc := func() { + defer close(done) // If the nonce is bad then drop the packet and return an error if !sinfo.nonceIsOK(&p.Nonce) { err = errors.New("packet dropped due to invalid nonce") @@ -172,7 +214,18 @@ func (c *Conn) Read(b []byte) (int, error) { sinfo.updateNonce(&p.Nonce) sinfo.time = time.Now() sinfo.bytesRecvd += uint64(len(b)) - }) + } + // Hand over to the session worker + select { // Send to worker + case sinfo.worker <- workerFunc: + case <-timer.C: + return 0, ConnError{errors.New("Timeout"), true, false} + } + select { // Wait for worker to return + case <-done: + case <-timer.C: + return 0, ConnError{errors.New("Timeout"), true, false} + } // Something went wrong in the session worker so abort if err != nil { return 0, err @@ -187,6 +240,8 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { c.mutex.RLock() sinfo := c.session c.mutex.RUnlock() + timer := getDeadlineTimer(&c.writeDeadline) + defer util.TimerStop(timer) // If the session doesn't exist, or isn't initialised (which probably means // that the search didn't complete successfully) then we may need to wait for // the search to complete or start the search again @@ -199,7 +254,11 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { }) } // Wait for the search to complete - <-c.searchwait + select { + case <-c.searchwait: + case <-timer.C: + return 0, ConnError{errors.New("Timeout"), true, false} + } // Retrieve our session info again c.mutex.RLock() sinfo = c.session @@ -213,8 +272,9 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { } // defer util.PutBytes(b) var packet []byte - // Hand over to the session worker - sinfo.doWorker(func() { + done := make(chan struct{}) + workerFunc := func() { + defer close(done) // Encrypt the packet payload, nonce := crypto.BoxSeal(&sinfo.sharedSesKey, b, &sinfo.myNonce) defer util.PutBytes(payload) @@ -227,7 +287,18 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { } packet = p.encode() sinfo.bytesSent += uint64(len(b)) - }) + } + // Hand over to the session worker + select { // Send to worker + case sinfo.worker <- workerFunc: + case <-timer.C: + return 0, ConnError{errors.New("Timeout"), true, false} + } + select { // Wait for worker to return + case <-done: + case <-timer.C: + return 0, ConnError{errors.New("Timeout"), true, false} + } // Give the packet to the router sinfo.core.router.out(packet) // Finally return the number of bytes we wrote @@ -235,10 +306,15 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { } func (c *Conn) Close() error { - // Close the session, if it hasn't been closed already - c.session.close() - c.session = nil + c.mutex.Lock() + defer c.mutex.Unlock() + if c.session != nil { + // Close the session, if it hasn't been closed already + c.session.close() + c.session = nil + } // This can't fail yet - TODO? + c.closed = true return nil } From 01ea6d3d80ba3bb7006d13ffab4ebb823be3d7b4 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 26 Apr 2019 21:49:11 -0500 Subject: [PATCH 0062/1109] somehow this doesn't seem to deadlock or crash from buffer reuse (util.PutBytes), but I have no idea why it was doing that before and not now --- src/yggdrasil/conn.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index a019908f..51f352f0 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -60,7 +60,6 @@ func (c *Conn) String() string { func (c *Conn) startSearch() { // The searchCompleted callback is given to the search searchCompleted := func(sinfo *sessionInfo, err error) { - defer c.searching.Store(false) // If the search failed for some reason, e.g. it hit a dead end or timed // out, then do nothing if err != nil { @@ -77,6 +76,7 @@ func (c *Conn) startSearch() { }() return } + defer c.searching.Store(false) // Take the connection mutex c.mutex.Lock() defer c.mutex.Unlock() @@ -180,6 +180,8 @@ func (c *Conn) Read(b []byte) (int, error) { } // Wait for some traffic to come through from the session select { + case <-timer.C: + return 0, ConnError{errors.New("Timeout"), true, false} case p, ok := <-sinfo.recv: // If the session is closed then do nothing if !ok { @@ -197,10 +199,10 @@ func (c *Conn) Read(b []byte) (int, error) { } // Decrypt the packet bs, isOK := crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce) + defer util.PutBytes(bs) // Check if we were unable to decrypt the packet for some reason and // return an error if we couldn't if !isOK { - util.PutBytes(bs) err = errors.New("packet dropped due to decryption failure") return } @@ -249,9 +251,7 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { // Is a search already taking place? if searching, sok := c.searching.Load().(bool); !sok || (sok && !searching) { // No search was already taking place so start a new one - c.core.router.doAdmin(func() { - c.startSearch() - }) + c.core.router.doAdmin(c.startSearch) } // Wait for the search to complete select { From 5d323861f03dc178f18362992d901d5945b392f1 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 26 Apr 2019 22:21:31 -0500 Subject: [PATCH 0063/1109] properly fix the memory errors, it was caused by a function returning and PutBytes-ing a buffer before a worker had a chance to decrypt the buffer, so it would GetBytes the same buffer by dumb luck and then get an illegal overlap --- src/yggdrasil/conn.go | 15 +++------------ src/yggdrasil/router.go | 2 +- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 51f352f0..5c71bb90 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -199,7 +199,7 @@ func (c *Conn) Read(b []byte) (int, error) { } // Decrypt the packet bs, isOK := crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce) - defer util.PutBytes(bs) + defer util.PutBytes(bs) // FIXME commenting this out leads to illegal buffer reuse, this implies there's a memory error somewhere and that this is just flooding things out of the finite pool of old slices that get reused // Check if we were unable to decrypt the packet for some reason and // return an error if we couldn't if !isOK { @@ -223,11 +223,7 @@ func (c *Conn) Read(b []byte) (int, error) { case <-timer.C: return 0, ConnError{errors.New("Timeout"), true, false} } - select { // Wait for worker to return - case <-done: - case <-timer.C: - return 0, ConnError{errors.New("Timeout"), true, false} - } + <-done // Wait for the worker to finish, failing this can cause memory errors (util.[Get||Put]Bytes stuff) // Something went wrong in the session worker so abort if err != nil { return 0, err @@ -270,7 +266,6 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { return 0, errors.New("search failed") } } - // defer util.PutBytes(b) var packet []byte done := make(chan struct{}) workerFunc := func() { @@ -294,11 +289,7 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { case <-timer.C: return 0, ConnError{errors.New("Timeout"), true, false} } - select { // Wait for worker to return - case <-done: - case <-timer.C: - return 0, ConnError{errors.New("Timeout"), true, false} - } + <-done // Wait for the worker to finish, failing this can cause memory errors (util.[Get||Put]Bytes stuff) // Give the packet to the router sinfo.core.router.out(packet) // Finally return the number of bytes we wrote diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index cacf0259..2e32fb6b 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -341,7 +341,7 @@ func (r *router) handleTraffic(packet []byte) { return } select { - case sinfo.recv <- &p: // FIXME ideally this should be FIFO + case sinfo.recv <- &p: // FIXME ideally this should be front drop default: util.PutBytes(p.Payload) } From 6469e39ff188b4adb7e83598f6d02595debe3268 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 26 Apr 2019 22:42:05 -0500 Subject: [PATCH 0064/1109] workaround to random timeouts --- src/util/util.go | 11 +++++------ src/yggdrasil/conn.go | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/util/util.go b/src/util/util.go index 49e0207a..94bd5d6a 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -48,13 +48,12 @@ func PutBytes(bs []byte) { // This is a workaround to go's broken timer implementation func TimerStop(t *time.Timer) bool { - if !t.Stop() { - select { - case <-t.C: - default: - } + stopped := t.Stop() + select { + case <-t.C: + default: } - return true + return stopped } // Run a blocking function with a timeout. diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 5c71bb90..aad1dcdd 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -144,7 +144,7 @@ func (c *Conn) startSearch() { } func getDeadlineTimer(value *atomic.Value) *time.Timer { - timer := time.NewTimer(0) + timer := time.NewTimer(24 * 365 * time.Hour) // FIXME for some reason setting this to 0 doesn't always let it stop and drain the channel correctly util.TimerStop(timer) if deadline, ok := value.Load().(time.Time); ok { timer.Reset(time.Until(deadline)) From 5f66c4c95ceb57625c71e8ae5d4cafd1e4b6af18 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 28 Apr 2019 17:14:09 +0100 Subject: [PATCH 0065/1109] Try using separate workers for each TUN/TAP connection (sometimes produces duplicate packets when communicating with both the node address and a subnet address, sometimes also can't Ctrl-C to quit) --- src/tuntap/conn.go | 75 +++++++ src/tuntap/iface.go | 255 ++++++++++++++++++++++ src/tuntap/tun.go | 520 ++++---------------------------------------- 3 files changed, 376 insertions(+), 474 deletions(-) create mode 100644 src/tuntap/conn.go create mode 100644 src/tuntap/iface.go diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go new file mode 100644 index 00000000..e59e560e --- /dev/null +++ b/src/tuntap/conn.go @@ -0,0 +1,75 @@ +package tuntap + +import ( + "errors" + + "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" +) + +type tunConn struct { + tun *TunAdapter + conn *yggdrasil.Conn + send chan []byte + stop chan interface{} +} + +func (s *tunConn) close() { + close(s.stop) +} + +func (s *tunConn) reader() error { + select { + case _, ok := <-s.stop: + if !ok { + return errors.New("session was already closed") + } + default: + } + var n int + var err error + read := make(chan bool) + b := make([]byte, 65535) + for { + go func() { + if n, err = s.conn.Read(b); err != nil { + s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn read error:", err) + return + } + read <- true + }() + select { + case <-read: + if n > 0 { + s.tun.send <- b[:n] + } + case <-s.stop: + s.tun.log.Debugln("Stopping conn reader for", s) + return nil + } + } +} + +func (s *tunConn) writer() error { + select { + case _, ok := <-s.stop: + if !ok { + return errors.New("session was already closed") + } + default: + } + for { + select { + case <-s.stop: + s.tun.log.Debugln("Stopping conn writer for", s) + return nil + case b, ok := <-s.send: + if !ok { + return errors.New("send closed") + } + if _, err := s.conn.Write(b); err != nil { + s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn write error:", err) + continue + } + } + } +} diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go new file mode 100644 index 00000000..d837633b --- /dev/null +++ b/src/tuntap/iface.go @@ -0,0 +1,255 @@ +package tuntap + +import ( + "bytes" + "errors" + "time" + + "github.com/songgao/packets/ethernet" + "github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/util" +) + +func (tun *TunAdapter) writer() error { + var w int + var err error + for { + b := <-tun.send + n := len(b) + if n == 0 { + continue + } + if tun.iface.IsTAP() { + var dstAddr address.Address + if b[0]&0xf0 == 0x60 { + if len(b) < 40 { + //panic("Tried to send a packet shorter than an IPv6 header...") + util.PutBytes(b) + continue + } + copy(dstAddr[:16], b[24:]) + } else if b[0]&0xf0 == 0x40 { + if len(b) < 20 { + //panic("Tried to send a packet shorter than an IPv4 header...") + util.PutBytes(b) + continue + } + copy(dstAddr[:4], b[16:]) + } else { + return errors.New("Invalid address family") + } + sendndp := func(dstAddr address.Address) { + neigh, known := tun.icmpv6.peermacs[dstAddr] + known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30) + if !known { + request, err := tun.icmpv6.CreateNDPL2(dstAddr) + if err != nil { + panic(err) + } + if _, err := tun.iface.Write(request); err != nil { + panic(err) + } + tun.icmpv6.peermacs[dstAddr] = neighbor{ + lastsolicitation: time.Now(), + } + } + } + var peermac macAddress + var peerknown bool + if b[0]&0xf0 == 0x40 { + dstAddr = tun.addr + } else if b[0]&0xf0 == 0x60 { + if !bytes.Equal(tun.addr[:16], dstAddr[:16]) && !bytes.Equal(tun.subnet[:8], dstAddr[:8]) { + dstAddr = tun.addr + } + } + if neighbor, ok := tun.icmpv6.peermacs[dstAddr]; ok && neighbor.learned { + peermac = neighbor.mac + peerknown = true + } else if neighbor, ok := tun.icmpv6.peermacs[tun.addr]; ok && neighbor.learned { + peermac = neighbor.mac + peerknown = true + sendndp(dstAddr) + } else { + sendndp(tun.addr) + } + if peerknown { + var proto ethernet.Ethertype + switch { + case b[0]&0xf0 == 0x60: + proto = ethernet.IPv6 + case b[0]&0xf0 == 0x40: + proto = ethernet.IPv4 + } + var frame ethernet.Frame + frame.Prepare( + peermac[:6], // Destination MAC address + tun.icmpv6.mymac[:6], // Source MAC address + ethernet.NotTagged, // VLAN tagging + proto, // Ethertype + len(b)) // Payload length + copy(frame[tun_ETHER_HEADER_LENGTH:], b[:n]) + n += tun_ETHER_HEADER_LENGTH + w, err = tun.iface.Write(frame[:n]) + } + } else { + w, err = tun.iface.Write(b[:n]) + } + if err != nil { + tun.log.Errorln("TUN/TAP iface write error:", err) + continue + } + if w != n { + tun.log.Errorln("TUN/TAP iface write mismatch:", w, "bytes written vs", n, "bytes given") + continue + } + } +} + +func (tun *TunAdapter) reader() error { + bs := make([]byte, 65535) + for { + // Wait for a packet to be delivered to us through the TUN/TAP adapter + n, err := tun.iface.Read(bs) + if err != nil { + panic(err) + } + if n == 0 { + continue + } + // If it's a TAP adapter, update the buffer slice so that we no longer + // include the ethernet headers + offset := 0 + if tun.iface.IsTAP() { + // Set our offset to beyond the ethernet headers + offset = tun_ETHER_HEADER_LENGTH + // If we detect an ICMP packet then hand it to the ICMPv6 module + if bs[offset+6] == 58 { + // Found an ICMPv6 packet + b := make([]byte, n) + copy(b, bs) + go tun.icmpv6.ParsePacket(b) + } + // Then offset the buffer so that we can now just treat it as an IP + // packet from now on + bs = bs[offset:] + } + // From the IP header, work out what our source and destination addresses + // and node IDs are. We will need these in order to work out where to send + // the packet + var srcAddr address.Address + var dstAddr address.Address + var dstNodeID *crypto.NodeID + var dstNodeIDMask *crypto.NodeID + var dstSnet address.Subnet + var addrlen int + // Check the IP protocol - if it doesn't match then we drop the packet and + // do nothing with it + if bs[0]&0xf0 == 0x60 { + // Check if we have a fully-sized IPv6 header + if len(bs) < 40 { + continue + } + // Check the packet size + if n != 256*int(bs[4])+int(bs[5])+offset+tun_IPv6_HEADER_LENGTH { + continue + } + // IPv6 address + addrlen = 16 + copy(srcAddr[:addrlen], bs[8:]) + copy(dstAddr[:addrlen], bs[24:]) + copy(dstSnet[:addrlen/2], bs[24:]) + } else if bs[0]&0xf0 == 0x40 { + // Check if we have a fully-sized IPv4 header + if len(bs) < 20 { + continue + } + // Check the packet size + if n != 256*int(bs[2])+int(bs[3])+offset { + continue + } + // IPv4 address + addrlen = 4 + copy(srcAddr[:addrlen], bs[12:]) + copy(dstAddr[:addrlen], bs[16:]) + } else { + // Unknown address length or protocol, so drop the packet and ignore it + continue + } + if !dstAddr.IsValid() && !dstSnet.IsValid() { + // For now don't deal with any non-Yggdrasil ranges + continue + } + // Do we have an active connection for this node address? + tun.mutex.RLock() + session, isIn := tun.addrToConn[dstAddr] + if !isIn || session == nil { + session, isIn = tun.subnetToConn[dstSnet] + if !isIn || session == nil { + // Neither an address nor a subnet mapping matched, therefore populate + // the node ID and mask to commence a search + dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() + } + } + tun.mutex.RUnlock() + // If we don't have a connection then we should open one + if !isIn || session == nil { + // Check we haven't been given empty node ID, really this shouldn't ever + // happen but just to be sure... + if dstNodeID == nil || dstNodeIDMask == nil { + panic("Given empty dstNodeID and dstNodeIDMask - this shouldn't happen") + } + // Dial to the remote node + if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { + // We've been given a connection so prepare the session wrapper + if s, err := tun.wrap(conn); err != nil { + // Something went wrong when storing the connection, typically that + // something already exists for this address or subnet + tun.log.Debugln("TUN/TAP iface wrap:", err) + } else { + // Update our reference to the connection + session, isIn = s, true + } + } else { + // We weren't able to dial for some reason so there's no point in + // continuing this iteration - skip to the next one + continue + } + } + // If we have a connection now, try writing to it + if isIn && session != nil { + select { + case session.send <- bs[:n]: + default: + } + } + + /*if !r.cryptokey.isValidSource(srcAddr, addrlen) { + // The packet had a src address that doesn't belong to us or our + // configured crypto-key routing src subnets + return + } + if !dstAddr.IsValid() && !dstSnet.IsValid() { + // The addresses didn't match valid Yggdrasil node addresses so let's see + // whether it matches a crypto-key routing range instead + if key, err := r.cryptokey.getPublicKeyForAddress(dstAddr, addrlen); err == nil { + // A public key was found, get the node ID for the search + dstPubKey = &key + dstNodeID = crypto.GetNodeID(dstPubKey) + // Do a quick check to ensure that the node ID refers to a vaild Yggdrasil + // address or subnet - this might be superfluous + addr := *address.AddrForNodeID(dstNodeID) + copy(dstAddr[:], addr[:]) + copy(dstSnet[:], addr[:]) + if !dstAddr.IsValid() && !dstSnet.IsValid() { + return + } + } else { + // No public key was found in the CKR table so we've exhausted our options + return + } + }*/ + + } +} diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index c19764c3..cfc8a668 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -6,10 +6,9 @@ package tuntap // TODO: Set MTU of session properly // TODO: Reject packets that exceed session MTU with ICMPv6 for PMTU Discovery // TODO: Connection timeouts (call Conn.Close() when we want to time out) -// TODO: Don't block in ifaceReader on writes that are pending searches +// TODO: Don't block in reader on writes that are pending searches import ( - "bytes" "encoding/hex" "errors" "fmt" @@ -18,14 +17,12 @@ import ( "time" "github.com/gologme/log" - "github.com/songgao/packets/ethernet" "github.com/yggdrasil-network/water" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" - "github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) @@ -47,9 +44,10 @@ type TunAdapter struct { icmpv6 ICMPv6 mtu int iface *water.Interface - mutex sync.RWMutex // Protects the below - addrToConn map[address.Address]*yggdrasil.Conn // Managed by connReader - subnetToConn map[address.Subnet]*yggdrasil.Conn // Managed by connReader + send chan []byte + mutex sync.RWMutex // Protects the below + addrToConn map[address.Address]*tunConn + subnetToConn map[address.Subnet]*tunConn isOpen bool } @@ -112,8 +110,8 @@ func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener tun.log = log tun.listener = listener tun.dialer = dialer - tun.addrToConn = make(map[address.Address]*yggdrasil.Conn) - tun.subnetToConn = make(map[address.Subnet]*yggdrasil.Conn) + tun.addrToConn = make(map[address.Address]*tunConn) + tun.subnetToConn = make(map[address.Subnet]*tunConn) } // Start the setup process for the TUN/TAP adapter. If successful, starts the @@ -148,6 +146,7 @@ func (tun *TunAdapter) Start() error { } tun.mutex.Lock() tun.isOpen = true + tun.send = make(chan []byte, 32) // TODO: is this a sensible value? tun.mutex.Unlock() if iftapmode { go func() { @@ -159,9 +158,7 @@ func (tun *TunAdapter) Start() error { if err != nil { panic(err) } - if _, err := tun.iface.Write(request); err != nil { - panic(err) - } + tun.send <- request time.Sleep(time.Second) } }() @@ -173,7 +170,8 @@ func (tun *TunAdapter) Start() error { } }() go tun.handler() - go tun.ifaceReader() + go tun.reader() + go tun.writer() tun.icmpv6.Init(tun) return nil } @@ -186,473 +184,47 @@ func (tun *TunAdapter) handler() error { tun.log.Errorln("TUN/TAP connection accept error:", err) return err } - go tun.connReader(conn) + if _, err := tun.wrap(conn); err != nil { + // Something went wrong when storing the connection, typically that + // something already exists for this address or subnet + tun.log.Debugln("TUN/TAP handler wrap:", err) + } } } -func (tun *TunAdapter) connReader(conn *yggdrasil.Conn) error { +func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { + // Prepare a session wrapper for the given connection + s := tunConn{ + tun: tun, + conn: conn, + send: make(chan []byte, 32), // TODO: is this a sensible value? + stop: make(chan interface{}), + } + // Get the remote address and subnet of the other side remoteNodeID := conn.RemoteAddr() remoteAddr := address.AddrForNodeID(&remoteNodeID) remoteSubnet := address.SubnetForNodeID(&remoteNodeID) - err := func() error { - tun.mutex.RLock() - defer tun.mutex.RUnlock() - if _, isIn := tun.addrToConn[*remoteAddr]; isIn { - return errors.New("duplicate connection for address " + net.IP(remoteAddr[:]).String()) - } - if _, isIn := tun.subnetToConn[*remoteSubnet]; isIn { - return errors.New("duplicate connection for subnet " + net.IP(remoteSubnet[:]).String()) - } - return nil - }() - if err != nil { - //return err - panic(err) - } - // Store the connection mapped to address and subnet + // Work out if this is already a destination we already know about tun.mutex.Lock() - tun.addrToConn[*remoteAddr] = conn - tun.subnetToConn[*remoteSubnet] = conn - tun.mutex.Unlock() - // Make sure to clean those up later when the connection is closed - defer func() { - tun.mutex.Lock() - delete(tun.addrToConn, *remoteAddr) - delete(tun.subnetToConn, *remoteSubnet) - tun.mutex.Unlock() - }() - b := make([]byte, 65535) - for { - n, err := conn.Read(b) - if err != nil { - tun.log.Errorln(conn.String(), "TUN/TAP conn read error:", err) - continue - } - if n == 0 { - continue - } - var w int - if tun.iface.IsTAP() { - var dstAddr address.Address - if b[0]&0xf0 == 0x60 { - if len(b) < 40 { - //panic("Tried to send a packet shorter than an IPv6 header...") - util.PutBytes(b) - continue - } - copy(dstAddr[:16], b[24:]) - } else if b[0]&0xf0 == 0x40 { - if len(b) < 20 { - //panic("Tried to send a packet shorter than an IPv4 header...") - util.PutBytes(b) - continue - } - copy(dstAddr[:4], b[16:]) - } else { - return errors.New("Invalid address family") - } - sendndp := func(dstAddr address.Address) { - neigh, known := tun.icmpv6.peermacs[dstAddr] - known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30) - if !known { - request, err := tun.icmpv6.CreateNDPL2(dstAddr) - if err != nil { - panic(err) - } - if _, err := tun.iface.Write(request); err != nil { - panic(err) - } - tun.icmpv6.peermacs[dstAddr] = neighbor{ - lastsolicitation: time.Now(), - } - } - } - var peermac macAddress - var peerknown bool - if b[0]&0xf0 == 0x40 { - dstAddr = tun.addr - } else if b[0]&0xf0 == 0x60 { - if !bytes.Equal(tun.addr[:16], dstAddr[:16]) && !bytes.Equal(tun.subnet[:8], dstAddr[:8]) { - dstAddr = tun.addr - } - } - if neighbor, ok := tun.icmpv6.peermacs[dstAddr]; ok && neighbor.learned { - peermac = neighbor.mac - peerknown = true - } else if neighbor, ok := tun.icmpv6.peermacs[tun.addr]; ok && neighbor.learned { - peermac = neighbor.mac - peerknown = true - sendndp(dstAddr) - } else { - sendndp(tun.addr) - } - if peerknown { - var proto ethernet.Ethertype - switch { - case b[0]&0xf0 == 0x60: - proto = ethernet.IPv6 - case b[0]&0xf0 == 0x40: - proto = ethernet.IPv4 - } - var frame ethernet.Frame - frame.Prepare( - peermac[:6], // Destination MAC address - tun.icmpv6.mymac[:6], // Source MAC address - ethernet.NotTagged, // VLAN tagging - proto, // Ethertype - len(b)) // Payload length - copy(frame[tun_ETHER_HEADER_LENGTH:], b[:n]) - n += tun_ETHER_HEADER_LENGTH - w, err = tun.iface.Write(frame[:n]) - } - } else { - w, err = tun.iface.Write(b[:n]) - } - if err != nil { - tun.log.Errorln(conn.String(), "TUN/TAP iface write error:", err) - continue - } - if w != n { - tun.log.Errorln(conn.String(), "TUN/TAP iface write mismatch:", w, "bytes written vs", n, "bytes given") - continue - } + defer tun.mutex.Unlock() + atc, aok := tun.addrToConn[*remoteAddr] + stc, sok := tun.subnetToConn[*remoteSubnet] + // If we know about a connection for this destination already then assume it + // is no longer valid and close it + if aok { + atc.close() + err = errors.New("replaced connection for address") + } else if sok { + stc.close() + err = errors.New("replaced connection for subnet") } + // Save the session wrapper so that we can look it up quickly next time + // we receive a packet through the interface for this address + tun.addrToConn[*remoteAddr] = &s + tun.subnetToConn[*remoteSubnet] = &s + // Start the connection goroutines + go s.reader() + go s.writer() + // Return + return c, err } - -func (tun *TunAdapter) ifaceReader() error { - bs := make([]byte, 65535) - for { - // Wait for a packet to be delivered to us through the TUN/TAP adapter - n, err := tun.iface.Read(bs) - if err != nil { - continue - } - // If it's a TAP adapter, update the buffer slice so that we no longer - // include the ethernet headers - offset := 0 - if tun.iface.IsTAP() { - // Set our offset to beyond the ethernet headers - offset = tun_ETHER_HEADER_LENGTH - // If we detect an ICMP packet then hand it to the ICMPv6 module - if bs[offset+6] == 58 { - // Found an ICMPv6 packet - b := make([]byte, n) - copy(b, bs) - go tun.icmpv6.ParsePacket(b) - } - // Then offset the buffer so that we can now just treat it as an IP - // packet from now on - bs = bs[offset:] - } - // From the IP header, work out what our source and destination addresses - // and node IDs are. We will need these in order to work out where to send - // the packet - var srcAddr address.Address - var dstAddr address.Address - var dstNodeID *crypto.NodeID - var dstNodeIDMask *crypto.NodeID - var dstSnet address.Subnet - var addrlen int - // Check the IP protocol - if it doesn't match then we drop the packet and - // do nothing with it - if bs[0]&0xf0 == 0x60 { - // Check if we have a fully-sized IPv6 header - if len(bs) < 40 { - continue - } - // Check the packet size - if n != 256*int(bs[4])+int(bs[5])+offset+tun_IPv6_HEADER_LENGTH { - continue - } - // IPv6 address - addrlen = 16 - copy(srcAddr[:addrlen], bs[8:]) - copy(dstAddr[:addrlen], bs[24:]) - copy(dstSnet[:addrlen/2], bs[24:]) - } else if bs[0]&0xf0 == 0x40 { - // Check if we have a fully-sized IPv4 header - if len(bs) < 20 { - continue - } - // Check the packet size - if n != 256*int(bs[2])+int(bs[3])+offset { - continue - } - // IPv4 address - addrlen = 4 - copy(srcAddr[:addrlen], bs[12:]) - copy(dstAddr[:addrlen], bs[16:]) - } else { - // Unknown address length or protocol, so drop the packet and ignore it - continue - } - if !dstAddr.IsValid() && !dstSnet.IsValid() { - // For now don't deal with any non-Yggdrasil ranges - continue - } - // Do we have an active connection for this node address? - tun.mutex.RLock() - conn, isIn := tun.addrToConn[dstAddr] - if !isIn || conn == nil { - conn, isIn = tun.subnetToConn[dstSnet] - if !isIn || conn == nil { - // Neither an address nor a subnet mapping matched, therefore populate - // the node ID and mask to commence a search - dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() - } - } - tun.mutex.RUnlock() - // If we don't have a connection then we should open one - if !isIn || conn == nil { - // Check we haven't been given empty node ID, really this shouldn't ever - // happen but just to be sure... - if dstNodeID == nil || dstNodeIDMask == nil { - panic("Given empty dstNodeID and dstNodeIDMask - this shouldn't happen") - } - // Dial to the remote node - if c, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { - // We've been given a connection so start the connection reader goroutine - go tun.connReader(c) - // Then update our reference to the connection - conn, isIn = c, true - } else { - // We weren't able to dial for some reason so there's no point in - // continuing this iteration - skip to the next one - continue - } - } - // If we have a connection now, try writing to it - if isIn && conn != nil { - // If we have an open connection, either because we already had one or - // because we opened one above, try writing the packet to it - w, err := conn.Write(bs[:n]) - if err != nil { - tun.log.Errorln(conn.String(), "TUN/TAP conn write error:", err) - continue - } - if w != n { - tun.log.Errorln(conn.String(), "TUN/TAP conn write mismatch:", w, "bytes written vs", n, "bytes given") - continue - } - } - - /*if !r.cryptokey.isValidSource(srcAddr, addrlen) { - // The packet had a src address that doesn't belong to us or our - // configured crypto-key routing src subnets - return - } - if !dstAddr.IsValid() && !dstSnet.IsValid() { - // The addresses didn't match valid Yggdrasil node addresses so let's see - // whether it matches a crypto-key routing range instead - if key, err := r.cryptokey.getPublicKeyForAddress(dstAddr, addrlen); err == nil { - // A public key was found, get the node ID for the search - dstPubKey = &key - dstNodeID = crypto.GetNodeID(dstPubKey) - // Do a quick check to ensure that the node ID refers to a vaild Yggdrasil - // address or subnet - this might be superfluous - addr := *address.AddrForNodeID(dstNodeID) - copy(dstAddr[:], addr[:]) - copy(dstSnet[:], addr[:]) - if !dstAddr.IsValid() && !dstSnet.IsValid() { - return - } - } else { - // No public key was found in the CKR table so we've exhausted our options - return - } - }*/ - - } -} - -// Writes a packet to the TUN/TAP adapter. If the adapter is running in TAP -// mode then additional ethernet encapsulation is added for the benefit of the -// host operating system. -/* -func (tun *TunAdapter) write() error { - for { - select { - case reject := <-tun.Reject: - switch reject.Reason { - case yggdrasil.PacketTooBig: - if mtu, ok := reject.Detail.(int); ok { - // Create the Packet Too Big response - ptb := &icmp.PacketTooBig{ - MTU: int(mtu), - Data: reject.Packet, - } - - // Create the ICMPv6 response from it - icmpv6Buf, err := CreateICMPv6( - reject.Packet[8:24], reject.Packet[24:40], - ipv6.ICMPTypePacketTooBig, 0, ptb) - - // Send the ICMPv6 response back to the TUN/TAP adapter - if err == nil { - tun.iface.Write(icmpv6Buf) - } - } - fallthrough - default: - continue - } - case data := <-tun.Recv: - if tun.iface == nil { - continue - } - if tun.iface.IsTAP() { - var dstAddr address.Address - if data[0]&0xf0 == 0x60 { - if len(data) < 40 { - //panic("Tried to send a packet shorter than an IPv6 header...") - util.PutBytes(data) - continue - } - copy(dstAddr[:16], data[24:]) - } else if data[0]&0xf0 == 0x40 { - if len(data) < 20 { - //panic("Tried to send a packet shorter than an IPv4 header...") - util.PutBytes(data) - continue - } - copy(dstAddr[:4], data[16:]) - } else { - return errors.New("Invalid address family") - } - sendndp := func(dstAddr address.Address) { - neigh, known := tun.icmpv6.peermacs[dstAddr] - known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30) - if !known { - request, err := tun.icmpv6.CreateNDPL2(dstAddr) - if err != nil { - panic(err) - } - if _, err := tun.iface.Write(request); err != nil { - panic(err) - } - tun.icmpv6.peermacs[dstAddr] = neighbor{ - lastsolicitation: time.Now(), - } - } - } - var peermac macAddress - var peerknown bool - if data[0]&0xf0 == 0x40 { - dstAddr = tun.addr - } else if data[0]&0xf0 == 0x60 { - if !bytes.Equal(tun.addr[:16], dstAddr[:16]) && !bytes.Equal(tun.subnet[:8], dstAddr[:8]) { - dstAddr = tun.addr - } - } - if neighbor, ok := tun.icmpv6.peermacs[dstAddr]; ok && neighbor.learned { - peermac = neighbor.mac - peerknown = true - } else if neighbor, ok := tun.icmpv6.peermacs[tun.addr]; ok && neighbor.learned { - peermac = neighbor.mac - peerknown = true - sendndp(dstAddr) - } else { - sendndp(tun.addr) - } - if peerknown { - var proto ethernet.Ethertype - switch { - case data[0]&0xf0 == 0x60: - proto = ethernet.IPv6 - case data[0]&0xf0 == 0x40: - proto = ethernet.IPv4 - } - var frame ethernet.Frame - frame.Prepare( - peermac[:6], // Destination MAC address - tun.icmpv6.mymac[:6], // Source MAC address - ethernet.NotTagged, // VLAN tagging - proto, // Ethertype - len(data)) // Payload length - copy(frame[tun_ETHER_HEADER_LENGTH:], data[:]) - if _, err := tun.iface.Write(frame); err != nil { - tun.mutex.RLock() - open := tun.isOpen - tun.mutex.RUnlock() - if !open { - return nil - } else { - panic(err) - } - } - } - } else { - if _, err := tun.iface.Write(data); err != nil { - tun.mutex.RLock() - open := tun.isOpen - tun.mutex.RUnlock() - if !open { - return nil - } else { - panic(err) - } - } - } - util.PutBytes(data) - } - } -} - -// Reads any packets that are waiting on the TUN/TAP adapter. If the adapter -// is running in TAP mode then the ethernet headers will automatically be -// processed and stripped if necessary. If an ICMPv6 packet is found, then -// the relevant helper functions in icmpv6.go are called. -func (tun *TunAdapter) read() error { - mtu := tun.mtu - if tun.iface.IsTAP() { - mtu += tun_ETHER_HEADER_LENGTH - } - buf := make([]byte, mtu) - for { - n, err := tun.iface.Read(buf) - if err != nil { - tun.mutex.RLock() - open := tun.isOpen - tun.mutex.RUnlock() - if !open { - return nil - } else { - return err - } - } - o := 0 - if tun.iface.IsTAP() { - o = tun_ETHER_HEADER_LENGTH - } - switch { - case buf[o]&0xf0 == 0x60 && n == 256*int(buf[o+4])+int(buf[o+5])+tun_IPv6_HEADER_LENGTH+o: - case buf[o]&0xf0 == 0x40 && n == 256*int(buf[o+2])+int(buf[o+3])+o: - default: - continue - } - if buf[o+6] == 58 { - if tun.iface.IsTAP() { - // Found an ICMPv6 packet - b := make([]byte, n) - copy(b, buf) - go tun.icmpv6.ParsePacket(b) - } - } - packet := append(util.GetBytes(), buf[o:n]...) - tun.Send <- packet - } -} - -// Closes the TUN/TAP adapter. This is only usually called when the Yggdrasil -// process stops. Typically this operation will happen quickly, but on macOS -// it can block until a read operation is completed. -func (tun *TunAdapter) Close() error { - tun.mutex.Lock() - tun.isOpen = false - tun.mutex.Unlock() - if tun.iface == nil { - return nil - } - return tun.iface.Close() -} -*/ From efdaea1b5ecb00c9de739d54d16718f417deadeb Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 2 May 2019 17:37:49 -0500 Subject: [PATCH 0066/1109] fix some races and GetBytes/PutBytes usage, but this still seems to deadlock somewhere in iperf tests --- src/tuntap/conn.go | 5 +++-- src/tuntap/iface.go | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index e59e560e..ae8fcc0b 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -3,6 +3,7 @@ package tuntap import ( "errors" + "github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) @@ -40,7 +41,7 @@ func (s *tunConn) reader() error { select { case <-read: if n > 0 { - s.tun.send <- b[:n] + s.tun.send <- append(util.GetBytes(), b[:n]...) } case <-s.stop: s.tun.log.Debugln("Stopping conn reader for", s) @@ -68,8 +69,8 @@ func (s *tunConn) writer() error { } if _, err := s.conn.Write(b); err != nil { s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn write error:", err) - continue } + util.PutBytes(b) } } } diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index d837633b..5b8e77bb 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -95,6 +95,7 @@ func (tun *TunAdapter) writer() error { } } else { w, err = tun.iface.Write(b[:n]) + util.PutBytes(b) } if err != nil { tun.log.Errorln("TUN/TAP iface write error:", err) @@ -219,9 +220,11 @@ func (tun *TunAdapter) reader() error { } // If we have a connection now, try writing to it if isIn && session != nil { + packet := append(util.GetBytes(), bs[:n]...) select { - case session.send <- bs[:n]: + case session.send <- packet: default: + util.PutBytes(packet) } } From 5a3c730097ae8b2bd9febc9eefda595a093f5b03 Mon Sep 17 00:00:00 2001 From: fifteenthcommotion Date: Sat, 11 May 2019 16:31:46 -0700 Subject: [PATCH 0067/1109] contribute decently fast yggdrasil address generator in C --- contrib/yggdrasil-brute-simple/.gitignore | 7 ++ contrib/yggdrasil-brute-simple/LICENSE | 1 + contrib/yggdrasil-brute-simple/Makefile | 12 ++ contrib/yggdrasil-brute-simple/README.md | 8 ++ contrib/yggdrasil-brute-simple/util.c | 62 ++++++++++ .../yggdrasil-brute-multi-curve25519.c | 107 +++++++++++++++++ .../yggdrasil-brute-multi-ed25519.c | 109 ++++++++++++++++++ .../yggdrasil-brute-simple/yggdrasil-brute.h | 12 ++ 8 files changed, 318 insertions(+) create mode 100644 contrib/yggdrasil-brute-simple/.gitignore create mode 100644 contrib/yggdrasil-brute-simple/LICENSE create mode 100644 contrib/yggdrasil-brute-simple/Makefile create mode 100644 contrib/yggdrasil-brute-simple/README.md create mode 100644 contrib/yggdrasil-brute-simple/util.c create mode 100644 contrib/yggdrasil-brute-simple/yggdrasil-brute-multi-curve25519.c create mode 100644 contrib/yggdrasil-brute-simple/yggdrasil-brute-multi-ed25519.c create mode 100644 contrib/yggdrasil-brute-simple/yggdrasil-brute.h diff --git a/contrib/yggdrasil-brute-simple/.gitignore b/contrib/yggdrasil-brute-simple/.gitignore new file mode 100644 index 00000000..037dde66 --- /dev/null +++ b/contrib/yggdrasil-brute-simple/.gitignore @@ -0,0 +1,7 @@ +* +!.gitignore +!Makefile +!README.md +!LICENSE +!*.c +!*.h diff --git a/contrib/yggdrasil-brute-simple/LICENSE b/contrib/yggdrasil-brute-simple/LICENSE new file mode 100644 index 00000000..2549ece2 --- /dev/null +++ b/contrib/yggdrasil-brute-simple/LICENSE @@ -0,0 +1 @@ +As public domain as possible. Don't blame me if your computer explodes. diff --git a/contrib/yggdrasil-brute-simple/Makefile b/contrib/yggdrasil-brute-simple/Makefile new file mode 100644 index 00000000..aa2adc86 --- /dev/null +++ b/contrib/yggdrasil-brute-simple/Makefile @@ -0,0 +1,12 @@ +.PHONY: all + +all: util yggdrasil-brute-multi-curve25519 yggdrasil-brute-multi-ed25519 + +util: util.c + gcc -Wall -std=c89 -O3 -c -o util.o util.c + +yggdrasil-brute-multi-ed25519: yggdrasil-brute-multi-ed25519.c util.o + gcc -Wall -std=c89 -O3 -o yggdrasil-brute-multi-ed25519 -lsodium yggdrasil-brute-multi-ed25519.c util.o + +yggdrasil-brute-multi-curve25519: yggdrasil-brute-multi-curve25519.c util.o + gcc -Wall -std=c89 -O3 -o yggdrasil-brute-multi-curve25519 -lsodium yggdrasil-brute-multi-curve25519.c util.o diff --git a/contrib/yggdrasil-brute-simple/README.md b/contrib/yggdrasil-brute-simple/README.md new file mode 100644 index 00000000..f7b68765 --- /dev/null +++ b/contrib/yggdrasil-brute-simple/README.md @@ -0,0 +1,8 @@ +# yggdrasil-brute-simple + +Simple program for finding curve25519 and ed25519 public keys whose sha512 hash has many leading ones. +Because ed25519 private keys consist of a seed that is hashed to find the secret part of the keypair, +this program is near optimal for finding ed25519 keypairs. Curve25519 key generation, on the other hand, +could be further optimized with elliptic curve magic. + +Depends on libsodium. diff --git a/contrib/yggdrasil-brute-simple/util.c b/contrib/yggdrasil-brute-simple/util.c new file mode 100644 index 00000000..fd17e496 --- /dev/null +++ b/contrib/yggdrasil-brute-simple/util.c @@ -0,0 +1,62 @@ +#include "yggdrasil-brute.h" + +int find_where(unsigned char hash[64], unsigned char besthashlist[NUMKEYS][64]) { + /* Where to insert hash into sorted hashlist */ + int j; + int where = -1; + for (j = 0; j < NUMKEYS; ++j) { + if (memcmp(hash, besthashlist[j], 64) > 0) ++where; + else break; + } + return where; +} + +void insert_64(unsigned char itemlist[NUMKEYS][64], unsigned char item[64], int where) { + int j; + for (j = 0; j < where; ++j) { + memcpy(itemlist[j], itemlist[j+1], 64); + } + memcpy(itemlist[where], item, 64); +} + +void insert_32(unsigned char itemlist[NUMKEYS][32], unsigned char item[32], int where) { + int j; + for (j = 0; j < where; ++j) { + memcpy(itemlist[j], itemlist[j+1], 32); + } + memcpy(itemlist[where], item, 32); +} + +void make_addr(unsigned char addr[32], unsigned char hash[64]) { + /* Public key hash to yggdrasil ipv6 address */ + int i; + int offset; + unsigned char mask; + unsigned char c; + int ones = 0; + unsigned char br = 0; /* false */ + for (i = 0; i < 64 && !br; ++i) { + mask = 128; + c = hash[i]; + while (mask) { + if (c & mask) { + ++ones; + } else { + br = 1; /* true */ + break; + } + mask >>= 1; + } + } + + addr[0] = 2; + addr[1] = ones; + + offset = ones + 1; + for (i = 0; i < 14; ++i) { + c = hash[offset/8] << (offset%8); + c |= hash[offset/8 + 1] >> (8 - offset%8); + addr[i + 2] = c; + offset += 8; + } +} diff --git a/contrib/yggdrasil-brute-simple/yggdrasil-brute-multi-curve25519.c b/contrib/yggdrasil-brute-simple/yggdrasil-brute-multi-curve25519.c new file mode 100644 index 00000000..f0b6f559 --- /dev/null +++ b/contrib/yggdrasil-brute-simple/yggdrasil-brute-multi-curve25519.c @@ -0,0 +1,107 @@ +/* +sk: 32 random bytes +sk[0] &= 248; +sk[31] &= 127; +sk[31] |= 64; + +increment sk +pk = curve25519_scalarmult_base(mysecret) +hash = sha512(pk) + +if besthash: + bestsk = sk + besthash = hash +*/ + +#include "yggdrasil-brute.h" + + +void seed(unsigned char sk[32]) { + randombytes_buf(sk, 32); + sk[0] &= 248; + sk[31] &= 127; + sk[31] |= 64; +} + + +int main(int argc, char **argv) { + int i; + int j; + unsigned char addr[16]; + time_t starttime; + time_t requestedtime; + + unsigned char bestsklist[NUMKEYS][32]; + unsigned char bestpklist[NUMKEYS][32]; + unsigned char besthashlist[NUMKEYS][64]; + + unsigned char sk[32]; + unsigned char pk[32]; + unsigned char hash[64]; + + unsigned int runs = 0; + int where; + + + if (argc != 2) { + fprintf(stderr, "usage: ./yggdrasil-brute-multi-curve25519 \n"); + return 1; + } + + if (sodium_init() < 0) { + /* panic! the library couldn't be initialized, it is not safe to use */ + printf("sodium init failed!\n"); + return 1; + } + + starttime = time(NULL); + requestedtime = atoi(argv[1]); + + if (requestedtime < 0) requestedtime = 0; + fprintf(stderr, "Searching for yggdrasil curve25519 keys (this will take slightly longer than %ld seconds)\n", requestedtime); + + sodium_memzero(bestsklist, NUMKEYS * 32); + sodium_memzero(bestpklist, NUMKEYS * 32); + sodium_memzero(besthashlist, NUMKEYS * 64); + seed(sk); + + do { + /* generate pubkey, hash, compare, increment secret. + * this loop should take 4 seconds on modern hardware */ + for (i = 0; i < (1 << 16); ++i) { + ++runs; + if (crypto_scalarmult_curve25519_base(pk, sk) != 0) { + printf("scalarmult to create pub failed!\n"); + return 1; + } + crypto_hash_sha512(hash, pk, 32); + + where = find_where(hash, besthashlist); + if (where >= 0) { + insert_32(bestsklist, sk, where); + insert_32(bestpklist, pk, where); + insert_64(besthashlist, hash, where); + + seed(sk); + } + for (j = 1; j < 31; ++j) if (++sk[j]) break; + } + } while (time(NULL) - starttime < requestedtime || runs < NUMKEYS); + + fprintf(stderr, "--------------addr-------------- -----------------------------secret----------------------------- -----------------------------public-----------------------------\n"); + for (i = 0; i < NUMKEYS; ++i) { + make_addr(addr, besthashlist[i]); + for (j = 0; j < 16; ++j) printf("%02x", addr[j]); + printf(" "); + for (j = 0; j < 32; ++j) printf("%02x", bestsklist[i][j]); + printf(" "); + for (j = 0; j < 32; ++j) printf("%02x", bestpklist[i][j]); + printf("\n"); + } + + sodium_memzero(bestsklist, NUMKEYS * 32); + sodium_memzero(sk, 32); + + return 0; +} + diff --git a/contrib/yggdrasil-brute-simple/yggdrasil-brute-multi-ed25519.c b/contrib/yggdrasil-brute-simple/yggdrasil-brute-multi-ed25519.c new file mode 100644 index 00000000..51e9aefd --- /dev/null +++ b/contrib/yggdrasil-brute-simple/yggdrasil-brute-multi-ed25519.c @@ -0,0 +1,109 @@ +/* +seed: 32 random bytes +sk: sha512(seed) +sk[0] &= 248 +sk[31] &= 127 +sk[31] |= 64 + +pk: scalarmult_ed25519_base(sk) + + +increment seed +generate sk +generate pk +hash = sha512(mypub) + +if besthash: + bestseed = seed + bestseckey = sk + bestpubkey = pk + besthash = hash +*/ + +#include "yggdrasil-brute.h" + + +int main(int argc, char **argv) { + int i; + int j; + time_t starttime; + time_t requestedtime; + + unsigned char bestsklist[NUMKEYS][64]; /* sk contains pk */ + unsigned char besthashlist[NUMKEYS][64]; + + unsigned char seed[32]; + unsigned char sk[64]; + unsigned char pk[32]; + unsigned char hash[64]; + + unsigned int runs = 0; + int where; + + + if (argc != 2) { + fprintf(stderr, "usage: ./yggdrasil-brute-multi-curve25519 \n"); + return 1; + } + + if (sodium_init() < 0) { + /* panic! the library couldn't be initialized, it is not safe to use */ + printf("sodium init failed!\n"); + return 1; + } + + starttime = time(NULL); + requestedtime = atoi(argv[1]); + + if (requestedtime < 0) requestedtime = 0; + fprintf(stderr, "Searching for yggdrasil ed25519 keys (this will take slightly longer than %ld seconds)\n", requestedtime); + + sodium_memzero(bestsklist, NUMKEYS * 64); + sodium_memzero(besthashlist, NUMKEYS * 64); + randombytes_buf(seed, 32); + + do { + /* generate pubkey, hash, compare, increment secret. + * this loop should take 4 seconds on modern hardware */ + for (i = 0; i < (1 << 17); ++i) { + ++runs; + crypto_hash_sha512(sk, seed, 32); + + if (crypto_scalarmult_ed25519_base(pk, sk) != 0) { + printf("scalarmult to create pub failed!\n"); + return 1; + } + memcpy(sk + 32, pk, 32); + + crypto_hash_sha512(hash, pk, 32); + + /* insert into local list of good key */ + where = find_where(hash, besthashlist); + if (where >= 0) { + insert_64(bestsklist, sk, where); + insert_64(besthashlist, hash, where); + randombytes_buf(seed, 32); + } + for (j = 1; j < 31; ++j) if (++seed[j]) break; + } + } while (time(NULL) - starttime < requestedtime || runs < NUMKEYS); + + + fprintf(stderr, "!! Secret key is seed concatenated with public !!\n"); + fprintf(stderr, "---hash--- ------------------------------seed------------------------------ -----------------------------public-----------------------------\n"); + for (i = 0; i < NUMKEYS; ++i) { + for (j = 0; j < 5; ++j) printf("%02x", besthashlist[i][j]); + printf(" "); + for (j = 0; j < 32; ++j) printf("%02x", bestsklist[i][j]); + printf(" "); + for (j = 32; j < 64; ++j) printf("%02x", bestsklist[i][j]); + printf("\n"); + } + + sodium_memzero(bestsklist, NUMKEYS * 64); + sodium_memzero(sk, 64); + sodium_memzero(seed, 32); + + return 0; +} + diff --git a/contrib/yggdrasil-brute-simple/yggdrasil-brute.h b/contrib/yggdrasil-brute-simple/yggdrasil-brute.h new file mode 100644 index 00000000..8e39e0f3 --- /dev/null +++ b/contrib/yggdrasil-brute-simple/yggdrasil-brute.h @@ -0,0 +1,12 @@ +#include +#include /* printf */ +#include /* memcpy */ +#include /* atoi */ +#include /* time */ + + +#define NUMKEYS 10 +void make_addr(unsigned char addr[32], unsigned char hash[64]); +int find_where(unsigned char hash[64], unsigned char besthashlist[NUMKEYS][64]); +void insert_64(unsigned char itemlist[NUMKEYS][64], unsigned char item[64], int where); +void insert_32(unsigned char itemlist[NUMKEYS][32], unsigned char item[32], int where); From db85a111941c3301f3ce04b9ac12eb6625412286 Mon Sep 17 00:00:00 2001 From: fifteenthcommotion Date: Sun, 12 May 2019 02:18:03 -0700 Subject: [PATCH 0068/1109] unlicense and spacing perfectionism --- contrib/yggdrasil-brute-simple/LICENSE | 25 ++++++++++++++++++- .../yggdrasil-brute-multi-curve25519.c | 2 -- .../yggdrasil-brute-multi-ed25519.c | 3 --- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/contrib/yggdrasil-brute-simple/LICENSE b/contrib/yggdrasil-brute-simple/LICENSE index 2549ece2..68a49daa 100644 --- a/contrib/yggdrasil-brute-simple/LICENSE +++ b/contrib/yggdrasil-brute-simple/LICENSE @@ -1 +1,24 @@ -As public domain as possible. Don't blame me if your computer explodes. +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/contrib/yggdrasil-brute-simple/yggdrasil-brute-multi-curve25519.c b/contrib/yggdrasil-brute-simple/yggdrasil-brute-multi-curve25519.c index f0b6f559..a592f38b 100644 --- a/contrib/yggdrasil-brute-simple/yggdrasil-brute-multi-curve25519.c +++ b/contrib/yggdrasil-brute-simple/yggdrasil-brute-multi-curve25519.c @@ -42,7 +42,6 @@ int main(int argc, char **argv) { unsigned int runs = 0; int where; - if (argc != 2) { fprintf(stderr, "usage: ./yggdrasil-brute-multi-curve25519 \n"); return 1; @@ -104,4 +103,3 @@ int main(int argc, char **argv) { return 0; } - diff --git a/contrib/yggdrasil-brute-simple/yggdrasil-brute-multi-ed25519.c b/contrib/yggdrasil-brute-simple/yggdrasil-brute-multi-ed25519.c index 51e9aefd..02218e50 100644 --- a/contrib/yggdrasil-brute-simple/yggdrasil-brute-multi-ed25519.c +++ b/contrib/yggdrasil-brute-simple/yggdrasil-brute-multi-ed25519.c @@ -40,7 +40,6 @@ int main(int argc, char **argv) { unsigned int runs = 0; int where; - if (argc != 2) { fprintf(stderr, "usage: ./yggdrasil-brute-multi-curve25519 \n"); return 1; @@ -88,7 +87,6 @@ int main(int argc, char **argv) { } } while (time(NULL) - starttime < requestedtime || runs < NUMKEYS); - fprintf(stderr, "!! Secret key is seed concatenated with public !!\n"); fprintf(stderr, "---hash--- ------------------------------seed------------------------------ -----------------------------public-----------------------------\n"); for (i = 0; i < NUMKEYS; ++i) { @@ -106,4 +104,3 @@ int main(int argc, char **argv) { return 0; } - From 5bed78c7a71d3cc6e34faed0b688d9472feb75cd Mon Sep 17 00:00:00 2001 From: fifteenthcommotion Date: Sun, 12 May 2019 12:40:45 -0700 Subject: [PATCH 0069/1109] add CC0 for good measure --- contrib/yggdrasil-brute-simple/LICENSE | 126 +++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/contrib/yggdrasil-brute-simple/LICENSE b/contrib/yggdrasil-brute-simple/LICENSE index 68a49daa..2d61b400 100644 --- a/contrib/yggdrasil-brute-simple/LICENSE +++ b/contrib/yggdrasil-brute-simple/LICENSE @@ -1,3 +1,10 @@ +This software is released into the public domain. As such, it can be +used under the Unlicense or CC0 public domain dedications. + + + +The Unlicense + This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or @@ -22,3 +29,122 @@ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to + + + +CC0 1.0 Universal + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator and +subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for the +purpose of contributing to a commons of creative, cultural and scientific +works ("Commons") that the public can reliably and without fear of later +claims of infringement build upon, modify, incorporate in other works, reuse +and redistribute as freely as possible in any form whatsoever and for any +purposes, including without limitation commercial purposes. These owners may +contribute to the Commons to promote the ideal of a free culture and the +further production of creative, cultural and scientific works, or to gain +reputation or greater distribution for their Work in part through the use and +efforts of others. + +For these and/or other purposes and motivations, and without any expectation +of additional consideration or compensation, the person associating CC0 with a +Work (the "Affirmer"), to the extent that he or she is an owner of Copyright +and Related Rights in the Work, voluntarily elects to apply CC0 to the Work +and publicly distribute the Work under its terms, with knowledge of his or her +Copyright and Related Rights in the Work and the meaning and intended legal +effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not limited +to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, communicate, + and translate a Work; + + ii. moral rights retained by the original author(s) and/or performer(s); + + iii. publicity and privacy rights pertaining to a person's image or likeness + depicted in a Work; + + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + + v. rights protecting the extraction, dissemination, use and reuse of data in + a Work; + + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation thereof, + including any amended or successor version of such directive); and + + vii. other similar, equivalent or corresponding rights throughout the world + based on applicable law or treaty, and any national implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, +applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and +unconditionally waives, abandons, and surrenders all of Affirmer's Copyright +and Related Rights and associated claims and causes of action, whether now +known or unknown (including existing as well as future claims and causes of +action), in the Work (i) in all territories worldwide, (ii) for the maximum +duration provided by applicable law or treaty (including future time +extensions), (iii) in any current or future medium and for any number of +copies, and (iv) for any purpose whatsoever, including without limitation +commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes +the Waiver for the benefit of each member of the public at large and to the +detriment of Affirmer's heirs and successors, fully intending that such Waiver +shall not be subject to revocation, rescission, cancellation, termination, or +any other legal or equitable action to disrupt the quiet enjoyment of the Work +by the public as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be +judged legally invalid or ineffective under applicable law, then the Waiver +shall be preserved to the maximum extent permitted taking into account +Affirmer's express Statement of Purpose. In addition, to the extent the Waiver +is so judged Affirmer hereby grants to each affected person a royalty-free, +non transferable, non sublicensable, non exclusive, irrevocable and +unconditional license to exercise Affirmer's Copyright and Related Rights in +the Work (i) in all territories worldwide, (ii) for the maximum duration +provided by applicable law or treaty (including future time extensions), (iii) +in any current or future medium and for any number of copies, and (iv) for any +purpose whatsoever, including without limitation commercial, advertising or +promotional purposes (the "License"). The License shall be deemed effective as +of the date CC0 was applied by Affirmer to the Work. Should any part of the +License for any reason be judged legally invalid or ineffective under +applicable law, such partial invalidity or ineffectiveness shall not +invalidate the remainder of the License, and in such case Affirmer hereby +affirms that he or she will not (i) exercise any of his or her remaining +Copyright and Related Rights in the Work or (ii) assert any associated claims +and causes of action with respect to the Work, in either case contrary to +Affirmer's express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + + b. Affirmer offers the Work as-is and makes no representations or warranties + of any kind concerning the Work, express, implied, statutory or otherwise, + including without limitation warranties of title, merchantability, fitness + for a particular purpose, non infringement, or the absence of latent or + other defects, accuracy, or the present or absence of errors, whether or not + discoverable, all to the greatest extent permissible under applicable law. + + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without limitation + any person's Copyright and Related Rights in the Work. Further, Affirmer + disclaims responsibility for obtaining any necessary consents, permissions + or other rights required for any use of the Work. + + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to this + CC0 or use of the Work. + +For more information, please see + From 522ed147b14f88d5892df3e5fbdfce5df5339189 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 15 May 2019 18:01:26 -0500 Subject: [PATCH 0070/1109] use the subnet derived ID/mask when creating a connection based on a subnet address, fix a potential blocking channel send in tuntap/conn.go, and get debug.go compiling well enough to profile things (the sim is currently still broken) --- src/tuntap/conn.go | 7 ++++++- src/tuntap/iface.go | 6 +++++- src/yggdrasil/debug.go | 28 ++++++++++++++++++++++------ 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index ae8fcc0b..50860f36 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -41,7 +41,12 @@ func (s *tunConn) reader() error { select { case <-read: if n > 0 { - s.tun.send <- append(util.GetBytes(), b[:n]...) + bs := append(util.GetBytes(), b[:n]...) + select { + case s.tun.send <- bs: + default: + util.PutBytes(bs) + } } case <-s.stop: s.tun.log.Debugln("Stopping conn reader for", s) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 5b8e77bb..efccd071 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -190,7 +190,11 @@ func (tun *TunAdapter) reader() error { if !isIn || session == nil { // Neither an address nor a subnet mapping matched, therefore populate // the node ID and mask to commence a search - dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() + if dstAddr.IsValid() { + dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() + } else { + dstNodeID, dstNodeIDMask = dstSnet.GetNodeIDandMask() + } } } tun.mutex.RUnlock() diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index e575b72d..c4eed639 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -59,13 +59,17 @@ func (c *Core) Init() { hbpriv := hex.EncodeToString(bpriv[:]) hspub := hex.EncodeToString(spub[:]) hspriv := hex.EncodeToString(spriv[:]) - c.config = config.NodeConfig{ + cfg := config.NodeConfig{ EncryptionPublicKey: hbpub, EncryptionPrivateKey: hbpriv, SigningPublicKey: hspub, SigningPrivateKey: hspriv, } - c.init( /*bpub, bpriv, spub, spriv*/ ) + c.config = config.NodeState{ + Current: cfg, + Previous: cfg, + } + c.init() c.switchTable.start() c.router.start() } @@ -82,6 +86,7 @@ func (c *Core) DEBUG_getEncryptionPublicKey() crypto.BoxPubKey { return (crypto.BoxPubKey)(c.boxPub) } +/* func (c *Core) DEBUG_getSend() chan<- []byte { return c.router.tun.send } @@ -89,6 +94,7 @@ func (c *Core) DEBUG_getSend() chan<- []byte { func (c *Core) DEBUG_getRecv() <-chan []byte { return c.router.tun.recv } +*/ // Peer @@ -317,6 +323,7 @@ func (c *Core) DEBUG_getAddr() *address.Address { return address.AddrForNodeID(&c.dht.nodeID) } +/* func (c *Core) DEBUG_startTun(ifname string, iftapmode bool) { c.DEBUG_startTunWithMTU(ifname, iftapmode, 1280) } @@ -338,6 +345,7 @@ func (c *Core) DEBUG_startTunWithMTU(ifname string, iftapmode bool, mtu int) { func (c *Core) DEBUG_stopTun() { c.router.tun.close() } +*/ //////////////////////////////////////////////////////////////////////////////// @@ -382,13 +390,17 @@ func (c *Core) DEBUG_init(bpub []byte, hbpriv := hex.EncodeToString(bpriv[:]) hspub := hex.EncodeToString(spub[:]) hspriv := hex.EncodeToString(spriv[:]) - c.config = config.NodeConfig{ + cfg := config.NodeConfig{ EncryptionPublicKey: hbpub, EncryptionPrivateKey: hbpriv, SigningPublicKey: hspub, SigningPrivateKey: hspriv, } - c.init( /*bpub, bpriv, spub, spriv*/ ) + c.config = config.NodeState{ + Current: cfg, + Previous: cfg, + } + c.init() if err := c.router.start(); err != nil { panic(err) @@ -455,7 +467,7 @@ func (c *Core) DEBUG_addSOCKSConn(socksaddr, peeraddr string) { } */ -//* +/* func (c *Core) DEBUG_setupAndStartGlobalTCPInterface(addrport string) { c.config.Listen = []string{addrport} if err := c.link.init(c); err != nil { @@ -503,10 +515,11 @@ func (c *Core) DEBUG_addKCPConn(saddr string) { //////////////////////////////////////////////////////////////////////////////// +/* func (c *Core) DEBUG_setupAndStartAdminInterface(addrport string) { a := admin{} c.config.AdminListen = addrport - a.init(c /*, addrport*/) + a.init() c.admin = a } @@ -516,6 +529,7 @@ func (c *Core) DEBUG_setupAndStartMulticastInterface() { c.multicast = m m.start() } +*/ //////////////////////////////////////////////////////////////////////////////// @@ -579,9 +593,11 @@ func DEBUG_simLinkPeers(p, q *peer) { q.core.switchTable.idleIn <- q.port } +/* func (c *Core) DEBUG_simFixMTU() { c.router.tun.mtu = 65535 } +*/ //////////////////////////////////////////////////////////////////////////////// From 9c01947b1c83a796f94708835b64dfeffc13e70d Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 16 May 2019 18:10:47 -0500 Subject: [PATCH 0071/1109] reduce allocations in switch --- src/yggdrasil/switch.go | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 0e164b9a..1bc40501 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -577,23 +577,28 @@ func (t *switchTable) start() error { return nil } +type closerInfo struct { + port switchPort + dist int +} + // Return a map of ports onto distance, keeping only ports closer to the destination than this node // If the map is empty (or nil), then no peer is closer -func (t *switchTable) getCloser(dest []byte) map[switchPort]int { +func (t *switchTable) getCloser(dest []byte) []closerInfo { table := t.getTable() myDist := table.self.dist(dest) if myDist == 0 { // Skip the iteration step if it's impossible to be closer return nil } - closer := make(map[switchPort]int, len(table.elems)) + t.queues.closer = t.queues.closer[:0] for _, info := range table.elems { dist := info.locator.dist(dest) if dist < myDist { - closer[info.port] = dist + t.queues.closer = append(t.queues.closer, closerInfo{info.port, dist}) } } - return closer + return t.queues.closer } // Returns true if the peer is closer to the destination than ourself @@ -656,9 +661,9 @@ func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) boo var bestDist int var bestTime time.Time ports := t.core.peers.getPorts() - for port, dist := range closer { - to := ports[port] - thisTime, isIdle := idle[port] + for _, cinfo := range closer { + to := ports[cinfo.port] + thisTime, isIdle := idle[cinfo.port] var update bool switch { case to == nil: @@ -667,9 +672,9 @@ func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) boo //nothing case best == nil: update = true - case dist < bestDist: + case cinfo.dist < bestDist: update = true - case dist > bestDist: + case cinfo.dist > bestDist: //nothing case thisTime.Before(bestTime): update = true @@ -678,7 +683,7 @@ func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) boo } if update { best = to - bestDist = dist + bestDist = cinfo.dist bestTime = thisTime } } @@ -711,6 +716,7 @@ type switch_buffers struct { size uint64 // Total size of all buffers, in bytes maxbufs int maxsize uint64 + closer []closerInfo // Scratch space } func (b *switch_buffers) cleanup(t *switchTable) { From 71ccaf753ecd88194fe3c12b09d192b2905e3cfa Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 17 May 2019 22:09:20 +0100 Subject: [PATCH 0072/1109] Add crypto-key routing into TUN/TAP --- src/tuntap/ckr.go | 411 ++++++++++++++++++++++++++++++++++++++++++++ src/tuntap/iface.go | 50 +++--- src/tuntap/tun.go | 6 +- 3 files changed, 436 insertions(+), 31 deletions(-) create mode 100644 src/tuntap/ckr.go diff --git a/src/tuntap/ckr.go b/src/tuntap/ckr.go new file mode 100644 index 00000000..971f0a35 --- /dev/null +++ b/src/tuntap/ckr.go @@ -0,0 +1,411 @@ +package tuntap + +import ( + "bytes" + "encoding/hex" + "errors" + "fmt" + "net" + "sort" + + "github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" +) + +// This module implements crypto-key routing, similar to Wireguard, where we +// allow traffic for non-Yggdrasil ranges to be routed over Yggdrasil. + +type cryptokey struct { + tun *TunAdapter + enabled bool + reconfigure chan chan error + ipv4routes []cryptokey_route + ipv6routes []cryptokey_route + ipv4cache map[address.Address]cryptokey_route + ipv6cache map[address.Address]cryptokey_route + ipv4sources []net.IPNet + ipv6sources []net.IPNet +} + +type cryptokey_route struct { + subnet net.IPNet + destination crypto.BoxPubKey +} + +// Initialise crypto-key routing. This must be done before any other CKR calls. +func (c *cryptokey) init(tun *TunAdapter) { + c.tun = tun + c.reconfigure = make(chan chan error, 1) + go func() { + for { + e := <-c.reconfigure + e <- nil + } + }() + + if err := c.configure(); err != nil { + c.tun.log.Errorln("CKR configuration failed:", err) + } +} + +// Configure the CKR routes - this must only ever be called from the router +// goroutine, e.g. through router.doAdmin +func (c *cryptokey) configure() error { + c.tun.config.Mutex.RLock() + defer c.tun.config.Mutex.RUnlock() + + // Set enabled/disabled state + c.setEnabled(c.tun.config.Current.TunnelRouting.Enable) + + // Clear out existing routes + c.ipv6routes = make([]cryptokey_route, 0) + c.ipv4routes = make([]cryptokey_route, 0) + + // Add IPv6 routes + for ipv6, pubkey := range c.tun.config.Current.TunnelRouting.IPv6Destinations { + if err := c.addRoute(ipv6, pubkey); err != nil { + return err + } + } + + // Add IPv4 routes + for ipv4, pubkey := range c.tun.config.Current.TunnelRouting.IPv4Destinations { + if err := c.addRoute(ipv4, pubkey); err != nil { + return err + } + } + + // Clear out existing sources + c.ipv6sources = make([]net.IPNet, 0) + c.ipv4sources = make([]net.IPNet, 0) + + // Add IPv6 sources + c.ipv6sources = make([]net.IPNet, 0) + for _, source := range c.tun.config.Current.TunnelRouting.IPv6Sources { + if err := c.addSourceSubnet(source); err != nil { + return err + } + } + + // Add IPv4 sources + c.ipv4sources = make([]net.IPNet, 0) + for _, source := range c.tun.config.Current.TunnelRouting.IPv4Sources { + if err := c.addSourceSubnet(source); err != nil { + return err + } + } + + // Wipe the caches + c.ipv4cache = make(map[address.Address]cryptokey_route, 0) + c.ipv6cache = make(map[address.Address]cryptokey_route, 0) + + return nil +} + +// Enable or disable crypto-key routing. +func (c *cryptokey) setEnabled(enabled bool) { + c.enabled = enabled +} + +// Check if crypto-key routing is enabled. +func (c *cryptokey) isEnabled() bool { + return c.enabled +} + +// Check whether the given address (with the address length specified in bytes) +// matches either the current node's address, the node's routed subnet or the +// list of subnets specified in IPv4Sources/IPv6Sources. +func (c *cryptokey) isValidSource(addr address.Address, addrlen int) bool { + ip := net.IP(addr[:addrlen]) + + if addrlen == net.IPv6len { + // Does this match our node's address? + if bytes.Equal(addr[:16], c.tun.addr[:16]) { + return true + } + + // Does this match our node's subnet? + if bytes.Equal(addr[:8], c.tun.subnet[:8]) { + return true + } + } + + // Does it match a configured CKR source? + if c.isEnabled() { + // Build our references to the routing sources + var routingsources *[]net.IPNet + + // Check if the prefix is IPv4 or IPv6 + if addrlen == net.IPv6len { + routingsources = &c.ipv6sources + } else if addrlen == net.IPv4len { + routingsources = &c.ipv4sources + } else { + return false + } + + for _, subnet := range *routingsources { + if subnet.Contains(ip) { + return true + } + } + } + + // Doesn't match any of the above + return false +} + +// Adds a source subnet, which allows traffic with these source addresses to +// be tunnelled using crypto-key routing. +func (c *cryptokey) addSourceSubnet(cidr string) error { + // Is the CIDR we've been given valid? + _, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + return err + } + + // Get the prefix length and size + _, prefixsize := ipnet.Mask.Size() + + // Build our references to the routing sources + var routingsources *[]net.IPNet + + // Check if the prefix is IPv4 or IPv6 + if prefixsize == net.IPv6len*8 { + routingsources = &c.ipv6sources + } else if prefixsize == net.IPv4len*8 { + routingsources = &c.ipv4sources + } else { + return errors.New("Unexpected prefix size") + } + + // Check if we already have this CIDR + for _, subnet := range *routingsources { + if subnet.String() == ipnet.String() { + return errors.New("Source subnet already configured") + } + } + + // Add the source subnet + *routingsources = append(*routingsources, *ipnet) + c.tun.log.Infoln("Added CKR source subnet", cidr) + return nil +} + +// Adds a destination route for the given CIDR to be tunnelled to the node +// with the given BoxPubKey. +func (c *cryptokey) addRoute(cidr string, dest string) error { + // Is the CIDR we've been given valid? + ipaddr, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + return err + } + + // Get the prefix length and size + _, prefixsize := ipnet.Mask.Size() + + // Build our references to the routing table and cache + var routingtable *[]cryptokey_route + var routingcache *map[address.Address]cryptokey_route + + // Check if the prefix is IPv4 or IPv6 + if prefixsize == net.IPv6len*8 { + routingtable = &c.ipv6routes + routingcache = &c.ipv6cache + } else if prefixsize == net.IPv4len*8 { + routingtable = &c.ipv4routes + routingcache = &c.ipv4cache + } else { + return errors.New("Unexpected prefix size") + } + + // Is the route an Yggdrasil destination? + var addr address.Address + var snet address.Subnet + copy(addr[:], ipaddr) + copy(snet[:], ipnet.IP) + if addr.IsValid() || snet.IsValid() { + return errors.New("Can't specify Yggdrasil destination as crypto-key route") + } + // Do we already have a route for this subnet? + for _, route := range *routingtable { + if route.subnet.String() == ipnet.String() { + return errors.New(fmt.Sprintf("Route already exists for %s", cidr)) + } + } + // Decode the public key + if bpk, err := hex.DecodeString(dest); err != nil { + return err + } else if len(bpk) != crypto.BoxPubKeyLen { + return errors.New(fmt.Sprintf("Incorrect key length for %s", dest)) + } else { + // Add the new crypto-key route + var key crypto.BoxPubKey + copy(key[:], bpk) + *routingtable = append(*routingtable, cryptokey_route{ + subnet: *ipnet, + destination: key, + }) + + // Sort so most specific routes are first + sort.Slice(*routingtable, func(i, j int) bool { + im, _ := (*routingtable)[i].subnet.Mask.Size() + jm, _ := (*routingtable)[j].subnet.Mask.Size() + return im > jm + }) + + // Clear the cache as this route might change future routing + // Setting an empty slice keeps the memory whereas nil invokes GC + for k := range *routingcache { + delete(*routingcache, k) + } + + c.tun.log.Infoln("Added CKR destination subnet", cidr) + return nil + } +} + +// Looks up the most specific route for the given address (with the address +// length specified in bytes) from the crypto-key routing table. An error is +// returned if the address is not suitable or no route was found. +func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (crypto.BoxPubKey, error) { + // Check if the address is a valid Yggdrasil address - if so it + // is exempt from all CKR checking + if addr.IsValid() { + return crypto.BoxPubKey{}, errors.New("Cannot look up CKR for Yggdrasil addresses") + } + + // Build our references to the routing table and cache + var routingtable *[]cryptokey_route + var routingcache *map[address.Address]cryptokey_route + + // Check if the prefix is IPv4 or IPv6 + if addrlen == net.IPv6len { + routingtable = &c.ipv6routes + routingcache = &c.ipv6cache + } else if addrlen == net.IPv4len { + routingtable = &c.ipv4routes + routingcache = &c.ipv4cache + } else { + return crypto.BoxPubKey{}, errors.New("Unexpected prefix size") + } + + // Check if there's a cache entry for this addr + if route, ok := (*routingcache)[addr]; ok { + return route.destination, nil + } + + // No cache was found - start by converting the address into a net.IP + ip := make(net.IP, addrlen) + copy(ip[:addrlen], addr[:]) + + // Check if we have a route. At this point c.ipv6routes should be + // pre-sorted so that the most specific routes are first + for _, route := range *routingtable { + // Does this subnet match the given IP? + if route.subnet.Contains(ip) { + // Check if the routing cache is above a certain size, if it is evict + // a random entry so we can make room for this one. We take advantage + // of the fact that the iteration order is random here + for k := range *routingcache { + if len(*routingcache) < 1024 { + break + } + delete(*routingcache, k) + } + + // Cache the entry for future packets to get a faster lookup + (*routingcache)[addr] = route + + // Return the boxPubKey + return route.destination, nil + } + } + + // No route was found if we got to this point + return crypto.BoxPubKey{}, errors.New(fmt.Sprintf("No route to %s", ip.String())) +} + +// Removes a source subnet, which allows traffic with these source addresses to +// be tunnelled using crypto-key routing. +func (c *cryptokey) removeSourceSubnet(cidr string) error { + // Is the CIDR we've been given valid? + _, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + return err + } + + // Get the prefix length and size + _, prefixsize := ipnet.Mask.Size() + + // Build our references to the routing sources + var routingsources *[]net.IPNet + + // Check if the prefix is IPv4 or IPv6 + if prefixsize == net.IPv6len*8 { + routingsources = &c.ipv6sources + } else if prefixsize == net.IPv4len*8 { + routingsources = &c.ipv4sources + } else { + return errors.New("Unexpected prefix size") + } + + // Check if we already have this CIDR + for idx, subnet := range *routingsources { + if subnet.String() == ipnet.String() { + *routingsources = append((*routingsources)[:idx], (*routingsources)[idx+1:]...) + c.tun.log.Infoln("Removed CKR source subnet", cidr) + return nil + } + } + return errors.New("Source subnet not found") +} + +// Removes a destination route for the given CIDR to be tunnelled to the node +// with the given BoxPubKey. +func (c *cryptokey) removeRoute(cidr string, dest string) error { + // Is the CIDR we've been given valid? + _, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + return err + } + + // Get the prefix length and size + _, prefixsize := ipnet.Mask.Size() + + // Build our references to the routing table and cache + var routingtable *[]cryptokey_route + var routingcache *map[address.Address]cryptokey_route + + // Check if the prefix is IPv4 or IPv6 + if prefixsize == net.IPv6len*8 { + routingtable = &c.ipv6routes + routingcache = &c.ipv6cache + } else if prefixsize == net.IPv4len*8 { + routingtable = &c.ipv4routes + routingcache = &c.ipv4cache + } else { + return errors.New("Unexpected prefix size") + } + + // Decode the public key + bpk, err := hex.DecodeString(dest) + if err != nil { + return err + } else if len(bpk) != crypto.BoxPubKeyLen { + return errors.New(fmt.Sprintf("Incorrect key length for %s", dest)) + } + netStr := ipnet.String() + + for idx, route := range *routingtable { + if bytes.Equal(route.destination[:], bpk) && route.subnet.String() == netStr { + *routingtable = append((*routingtable)[:idx], (*routingtable)[idx+1:]...) + for k := range *routingcache { + delete(*routingcache, k) + } + c.tun.log.Infoln("Removed CKR destination subnet %s via %s\n", cidr, dest) + return nil + } + } + return errors.New(fmt.Sprintf("Route does not exists for %s", cidr)) +} diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index efccd071..d70a1305 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -178,10 +178,29 @@ func (tun *TunAdapter) reader() error { // Unknown address length or protocol, so drop the packet and ignore it continue } - if !dstAddr.IsValid() && !dstSnet.IsValid() { - // For now don't deal with any non-Yggdrasil ranges + if !tun.ckr.isValidSource(srcAddr, addrlen) { + // The packet had a source address that doesn't belong to us or our + // configured crypto-key routing source subnets continue } + if !dstAddr.IsValid() && !dstSnet.IsValid() { + if key, err := tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { + // A public key was found, get the node ID for the search + dstNodeID = crypto.GetNodeID(&key) + // Do a quick check to ensure that the node ID refers to a vaild + // Yggdrasil address or subnet - this might be superfluous + addr := *address.AddrForNodeID(dstNodeID) + copy(dstAddr[:], addr[:]) + copy(dstSnet[:], addr[:]) + // Are we certain we looked up a valid node? + if !dstAddr.IsValid() && !dstSnet.IsValid() { + continue + } + } else { + // No public key was found in the CKR table so we've exhausted our options + continue + } + } // Do we have an active connection for this node address? tun.mutex.RLock() session, isIn := tun.addrToConn[dstAddr] @@ -231,32 +250,5 @@ func (tun *TunAdapter) reader() error { util.PutBytes(packet) } } - - /*if !r.cryptokey.isValidSource(srcAddr, addrlen) { - // The packet had a src address that doesn't belong to us or our - // configured crypto-key routing src subnets - return - } - if !dstAddr.IsValid() && !dstSnet.IsValid() { - // The addresses didn't match valid Yggdrasil node addresses so let's see - // whether it matches a crypto-key routing range instead - if key, err := r.cryptokey.getPublicKeyForAddress(dstAddr, addrlen); err == nil { - // A public key was found, get the node ID for the search - dstPubKey = &key - dstNodeID = crypto.GetNodeID(dstPubKey) - // Do a quick check to ensure that the node ID refers to a vaild Yggdrasil - // address or subnet - this might be superfluous - addr := *address.AddrForNodeID(dstNodeID) - copy(dstAddr[:], addr[:]) - copy(dstSnet[:], addr[:]) - if !dstAddr.IsValid() && !dstSnet.IsValid() { - return - } - } else { - // No public key was found in the CKR table so we've exhausted our options - return - } - }*/ - } } diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index cfc8a668..a73dde17 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -41,6 +41,7 @@ type TunAdapter struct { dialer *yggdrasil.Dialer addr address.Address subnet address.Subnet + ckr cryptokey icmpv6 ICMPv6 mtu int iface *water.Interface @@ -117,8 +118,8 @@ func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener // Start the setup process for the TUN/TAP adapter. If successful, starts the // read/write goroutines to handle packets on that interface. func (tun *TunAdapter) Start() error { - tun.config.Mutex.Lock() - defer tun.config.Mutex.Unlock() + tun.config.Mutex.RLock() + defer tun.config.Mutex.RUnlock() if tun.config == nil || tun.listener == nil || tun.dialer == nil { return errors.New("No configuration available to TUN/TAP") } @@ -173,6 +174,7 @@ func (tun *TunAdapter) Start() error { go tun.reader() go tun.writer() tun.icmpv6.Init(tun) + tun.ckr.init(tun) return nil } From ae2cc13d141cd1385bd920b6a657e6cd8323b586 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 17 May 2019 22:29:52 +0100 Subject: [PATCH 0073/1109] Fix configuration reloading support --- cmd/yggdrasil/main.go | 65 ++------------------------------------ src/multicast/multicast.go | 34 ++++++++++++++++++++ src/tuntap/tun.go | 32 +++++++++++++++++++ src/yggdrasil/core.go | 6 ++-- 4 files changed, 71 insertions(+), 66 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 96349835..52ddfe9c 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -280,69 +280,6 @@ func main() { defer func() { n.core.Stop() }() - // Listen for new sessions - /* - go func() { - listener, err := n.core.ListenConn() - if err != nil { - logger.Errorln("Unable to listen for sessions:", err) - return - } - for { - conn, err := listener.Accept() - if err != nil { - logger.Errorln("Accept:", err) - continue - } - logger.Println("Accepted") - for { - b := make([]byte, 100) - if n, err := conn.Read(b); err != nil { - logger.Errorln("Read failed:", err) - time.Sleep(time.Second * 2) - } else { - logger.Println("Read", n, "bytes:", b) - b = []byte{5, 5, 5} - if n, err := conn.Write(b); err != nil { - logger.Errorln("Write failed:", err) - time.Sleep(time.Second * 2) - } else { - logger.Println("Wrote", n, "bytes:", b) - } - } - } - } - }() - // Try creating new sessions - go func() { - if cfg.EncryptionPublicKey != "533574224115f835b7c7db6433986bc5aef855ff9c9568c01abeb0fbed3e8810" { - return - } - time.Sleep(time.Second * 2) - conn, err := n.core.Dial("nodeid", "9890e135604e8aa6039a909e40c629824d852042a70e51957d5b9d700195663d50552e8e869af132b4617d76f8ef00314d94cce23aa8d6b051b3b952a32a4966") - if err != nil { - logger.Errorln("Dial:", err) - return - } - go func() { - for { - time.Sleep(time.Second * 2) - b := []byte{1, 2, 3, 4, 5} - if n, err := conn.Write(b); err != nil { - logger.Errorln("Write failed:", err) - } else { - logger.Println("Wrote", n, "bytes:", b) - b = make([]byte, 100) - if n, err := conn.Read(b); err != nil { - logger.Errorln("Read failed:", err) - } else { - logger.Println("Read", n, "bytes:", b) - } - } - } - }() - }() - */ // Make some nice output that tells us what our IPv6 address and subnet are. // This is just logged to stdout for the user. address := n.core.Address() @@ -367,6 +304,8 @@ func main() { if *useconffile != "" { cfg = readConfig(useconf, useconffile, normaliseconf) n.core.UpdateConfig(cfg) + n.tuntap.UpdateConfig(cfg) + n.multicast.UpdateConfig(cfg) } else { logger.Errorln("Reloading config at runtime is only possible with -useconffile") } diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 4e5bc4b1..8d18889d 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -41,6 +41,10 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log go func() { for { e := <-m.reconfigure + // There's nothing particularly to do here because the multicast module + // already consults the config.NodeState when enumerating multicast + // interfaces on each pass. We just need to return nil so that the + // reconfiguration doesn't block indefinitely e <- nil } }() @@ -89,6 +93,36 @@ func (m *Multicast) Stop() error { return nil } +// UpdateConfig updates the multicast module with the provided config.NodeConfig +// and then signals the various module goroutines to reconfigure themselves if +// needed. +func (m *Multicast) UpdateConfig(config *config.NodeConfig) { + m.log.Debugln("Reloading multicast configuration...") + + m.config.Replace(*config) + + errors := 0 + + components := []chan chan error{ + m.reconfigure, + } + + for _, component := range components { + response := make(chan error) + component <- response + if err := <-response; err != nil { + m.log.Errorln(err) + errors++ + } + } + + if errors > 0 { + m.log.Warnln(errors, "multicast module(s) reported errors during configuration reload") + } else { + m.log.Infoln("Multicast configuration reloaded successfully") + } +} + func (m *Multicast) interfaces() map[string]net.Interface { // Get interface expressions from config current, _ := m.config.Get() diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index a73dde17..310e4211 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -148,6 +148,7 @@ func (tun *TunAdapter) Start() error { tun.mutex.Lock() tun.isOpen = true tun.send = make(chan []byte, 32) // TODO: is this a sensible value? + tun.reconfigure = make(chan chan error) tun.mutex.Unlock() if iftapmode { go func() { @@ -178,6 +179,37 @@ func (tun *TunAdapter) Start() error { return nil } +// UpdateConfig updates the TUN/TAP module with the provided config.NodeConfig +// and then signals the various module goroutines to reconfigure themselves if +// needed. +func (tun *TunAdapter) UpdateConfig(config *config.NodeConfig) { + tun.log.Debugln("Reloading TUN/TAP configuration...") + + tun.config.Replace(*config) + + errors := 0 + + components := []chan chan error{ + tun.reconfigure, + tun.ckr.reconfigure, + } + + for _, component := range components { + response := make(chan error) + component <- response + if err := <-response; err != nil { + tun.log.Errorln(err) + errors++ + } + } + + if errors > 0 { + tun.log.Warnln(errors, "TUN/TAP module(s) reported errors during configuration reload") + } else { + tun.log.Infoln("TUN/TAP configuration reloaded successfully") + } +} + func (tun *TunAdapter) handler() error { for { // Accept the incoming connection diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index ac2b494f..63d33d91 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -114,7 +114,7 @@ func (c *Core) addPeerLoop() { // config.NodeConfig and then signals the various module goroutines to // reconfigure themselves if needed. func (c *Core) UpdateConfig(config *config.NodeConfig) { - c.log.Infoln("Reloading configuration...") + c.log.Debugln("Reloading node configuration...") c.config.Replace(*config) @@ -141,9 +141,9 @@ func (c *Core) UpdateConfig(config *config.NodeConfig) { } if errors > 0 { - c.log.Warnln(errors, "modules reported errors during configuration reload") + c.log.Warnln(errors, "node module(s) reported errors during configuration reload") } else { - c.log.Infoln("Configuration reloaded successfully") + c.log.Infoln("Node configuration reloaded successfully") } } From 2df62e2b9b4febec0e45e3050539b20d2b85f4d2 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 17 May 2019 22:52:14 +0100 Subject: [PATCH 0074/1109] Remove code that translates v0.2 config options (it was commented out anyway) --- cmd/yggdrasil/main.go | 73 ------------------------------------------- 1 file changed, 73 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 52ddfe9c..ccb7061a 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -76,79 +76,6 @@ func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *nodeCo panic(err) } json.Unmarshal(confJson, &cfg) - /* - // For now we will do a little bit to help the user adjust their - // configuration to match the new configuration format, as some of the key - // names have changed recently. - changes := map[string]string{ - "Multicast": "", - "ReadTimeout": "", - "LinkLocal": "MulticastInterfaces", - "BoxPub": "EncryptionPublicKey", - "BoxPriv": "EncryptionPrivateKey", - "SigPub": "SigningPublicKey", - "SigPriv": "SigningPrivateKey", - "AllowedBoxPubs": "AllowedEncryptionPublicKeys", - } - // Loop over the mappings aove and see if we have anything to fix. - for from, to := range changes { - if _, ok := dat[from]; ok { - if to == "" { - if !*normaliseconf { - log.Println("Warning: Config option", from, "is deprecated") - } - } else { - if !*normaliseconf { - log.Println("Warning: Config option", from, "has been renamed - please change to", to) - } - // If the configuration file doesn't already contain a line with the - // new name then set it to the old value. This makes sure that we - // don't overwrite something that was put there intentionally. - if _, ok := dat[to]; !ok { - dat[to] = dat[from] - } - } - } - } - // Check to see if the peers are in a parsable format, if not then default - // them to the TCP scheme - if peers, ok := dat["Peers"].([]interface{}); ok { - for index, peer := range peers { - uri := peer.(string) - if strings.HasPrefix(uri, "tcp://") || strings.HasPrefix(uri, "socks://") { - continue - } - if strings.HasPrefix(uri, "tcp:") { - uri = uri[4:] - } - (dat["Peers"].([]interface{}))[index] = "tcp://" + uri - } - } - // Now do the same with the interface peers - if interfacepeers, ok := dat["InterfacePeers"].(map[string]interface{}); ok { - for intf, peers := range interfacepeers { - for index, peer := range peers.([]interface{}) { - uri := peer.(string) - if strings.HasPrefix(uri, "tcp://") || strings.HasPrefix(uri, "socks://") { - continue - } - if strings.HasPrefix(uri, "tcp:") { - uri = uri[4:] - } - ((dat["InterfacePeers"].(map[string]interface{}))[intf]).([]interface{})[index] = "tcp://" + uri - } - } - } - // Do a quick check for old-format Listen statement so that mapstructure - // doesn't fail and crash - if listen, ok := dat["Listen"].(string); ok { - if strings.HasPrefix(listen, "tcp://") { - dat["Listen"] = []string{listen} - } else { - dat["Listen"] = []string{"tcp://" + listen} - } - } - */ // Overlay our newly mapped configuration onto the autoconf node config that // we generated above. if err = mapstructure.Decode(dat, &cfg); err != nil { From 1b3ec0b93fa93ce5facd80ec30e6ccf09c00283e Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 17 May 2019 22:59:29 +0100 Subject: [PATCH 0075/1109] Fix multicast start check so that it shouldn't give up if interfaces aren't up when Yggdrasil starts (fixes #405) --- src/multicast/multicast.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 8d18889d..cbde7fd9 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -60,7 +60,8 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log // listen for multicast beacons from other hosts and will advertise multicast // beacons out to the network. func (m *Multicast) Start() error { - if len(m.interfaces()) == 0 { + current, _ := m.config.Get() + if len(current.MulticastInterfaces) == 0 { m.log.Infoln("Multicast discovery is disabled") } else { m.log.Infoln("Multicast discovery is enabled") From 080052ce04245e1c50158651d37060dbb7575e3a Mon Sep 17 00:00:00 2001 From: fifteenthcommotion Date: Sat, 18 May 2019 04:25:57 -0700 Subject: [PATCH 0076/1109] remove ygg-brute gitignore --- contrib/yggdrasil-brute-simple/.gitignore | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 contrib/yggdrasil-brute-simple/.gitignore diff --git a/contrib/yggdrasil-brute-simple/.gitignore b/contrib/yggdrasil-brute-simple/.gitignore deleted file mode 100644 index 037dde66..00000000 --- a/contrib/yggdrasil-brute-simple/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -* -!.gitignore -!Makefile -!README.md -!LICENSE -!*.c -!*.h From ce606099064b33c150ff07458d63b53df4ec172e Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 18 May 2019 16:16:32 +0100 Subject: [PATCH 0077/1109] Remove wrappedConn as unnecessary --- src/yggdrasil/dial.go | 58 ------------------------------------------- src/yggdrasil/tcp.go | 7 ------ 2 files changed, 65 deletions(-) delete mode 100644 src/yggdrasil/dial.go diff --git a/src/yggdrasil/dial.go b/src/yggdrasil/dial.go deleted file mode 100644 index 7aec4192..00000000 --- a/src/yggdrasil/dial.go +++ /dev/null @@ -1,58 +0,0 @@ -package yggdrasil - -import ( - "net" - "time" -) - -// wrappedConn implements net.Conn -type wrappedConn struct { - c net.Conn - raddr net.Addr -} - -// wrappedAddr implements net.Addr -type wrappedAddr struct { - network string - addr string -} - -func (a *wrappedAddr) Network() string { - return a.network -} - -func (a *wrappedAddr) String() string { - return a.addr -} - -func (c *wrappedConn) Write(data []byte) (int, error) { - return c.c.Write(data) -} - -func (c *wrappedConn) Read(data []byte) (int, error) { - return c.c.Read(data) -} - -func (c *wrappedConn) SetDeadline(t time.Time) error { - return c.c.SetDeadline(t) -} - -func (c *wrappedConn) SetReadDeadline(t time.Time) error { - return c.c.SetReadDeadline(t) -} - -func (c *wrappedConn) SetWriteDeadline(t time.Time) error { - return c.c.SetWriteDeadline(t) -} - -func (c *wrappedConn) Close() error { - return c.c.Close() -} - -func (c *wrappedConn) LocalAddr() net.Addr { - return c.c.LocalAddr() -} - -func (c *wrappedConn) RemoteAddr() net.Addr { - return c.raddr -} diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 4361ec82..dfb41510 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -255,13 +255,6 @@ func (t *tcp) call(saddr string, options interface{}, sintf string) { if err != nil { return } - conn = &wrappedConn{ - c: conn, - raddr: &wrappedAddr{ - network: "tcp", - addr: saddr, - }, - } t.handler(conn, false, dialerdst.String()) } else { dst, err := net.ResolveTCPAddr("tcp", saddr) From 8a6f6f3b2bc9b11941cfcbff5b43c82ebcc1deeb Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 18 May 2019 17:21:02 +0100 Subject: [PATCH 0078/1109] Implement GetPeers and GetSwitchPeers API functions in Core, in preparation for breaking out the admin socket into a separate module --- src/yggdrasil/api.go | 341 ++++++++++++++++++++++++++++++++++++++++++ src/yggdrasil/core.go | 155 ------------------- 2 files changed, 341 insertions(+), 155 deletions(-) create mode 100644 src/yggdrasil/api.go diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go new file mode 100644 index 00000000..b5278102 --- /dev/null +++ b/src/yggdrasil/api.go @@ -0,0 +1,341 @@ +package yggdrasil + +import ( + "encoding/hex" + "errors" + "net" + "sort" + "sync/atomic" + "time" + + "github.com/gologme/log" + "github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" +) + +// Peer represents a single peer object. This contains information from the +// preferred switch port for this peer, although there may be more than one in +// reality. +type Peer struct { + PublicKey crypto.BoxPubKey + Endpoint string + BytesSent uint64 + BytesRecvd uint64 + Protocol string + Port uint64 + Uptime time.Duration +} + +// SwitchPeer represents a switch connection to a peer. Note that there may be +// multiple switch peers per actual peer, e.g. if there are multiple connections +// to a given node. +type SwitchPeer struct { + PublicKey crypto.BoxPubKey + Coords []byte + BytesSent uint64 + BytesRecvd uint64 + Port uint64 + Protocol string +} + +type DHTEntry struct { + PublicKey crypto.BoxPubKey + Coords []byte + LastSeen time.Duration +} + +type SwitchQueue struct{} +type Session struct{} + +// GetPeers returns one or more Peer objects containing information about active +// peerings with other Yggdrasil nodes, where one of the responses always +// includes information about the current node (with a port number of 0). If +// there is exactly one entry then this node is not connected to any other nodes +// and is therefore isolated. +func (c *Core) GetPeers() []Peer { + ports := c.peers.ports.Load().(map[switchPort]*peer) + var peers []Peer + var ps []switchPort + for port := range ports { + ps = append(ps, port) + } + sort.Slice(ps, func(i, j int) bool { return ps[i] < ps[j] }) + for _, port := range ps { + p := ports[port] + info := Peer{ + Endpoint: p.intf.name, + BytesSent: atomic.LoadUint64(&p.bytesSent), + BytesRecvd: atomic.LoadUint64(&p.bytesRecvd), + Protocol: p.intf.info.linkType, + Port: uint64(port), + Uptime: time.Since(p.firstSeen), + } + copy(info.PublicKey[:], p.box[:]) + peers = append(peers, info) + } + return peers +} + +// GetSwitchPeers returns zero or more SwitchPeer objects containing information +// about switch port connections with other Yggdrasil nodes. Note that, unlike +// GetPeers, GetSwitchPeers does not include information about the current node, +// therefore it is possible for this to return zero elements if the node is +// isolated or not connected to any peers. +func (c *Core) GetSwitchPeers() []SwitchPeer { + var switchpeers []SwitchPeer + table := c.switchTable.table.Load().(lookupTable) + peers := c.peers.ports.Load().(map[switchPort]*peer) + for _, elem := range table.elems { + peer, isIn := peers[elem.port] + if !isIn { + continue + } + coords := elem.locator.getCoords() + info := SwitchPeer{ + Coords: coords, + BytesSent: atomic.LoadUint64(&peer.bytesSent), + BytesRecvd: atomic.LoadUint64(&peer.bytesRecvd), + Port: uint64(elem.port), + Protocol: peer.intf.info.linkType, + } + copy(info.PublicKey[:], peer.box[:]) + switchpeers = append(switchpeers, info) + } + return switchpeers +} + +func (c *Core) GetDHT() []DHTEntry { + /* + var infos []admin_nodeInfo + getDHT := func() { + now := time.Now() + var dhtInfos []*dhtInfo + for _, v := range a.core.dht.table { + dhtInfos = append(dhtInfos, v) + } + sort.SliceStable(dhtInfos, func(i, j int) bool { + return dht_ordered(&a.core.dht.nodeID, dhtInfos[i].getNodeID(), dhtInfos[j].getNodeID()) + }) + for _, v := range dhtInfos { + addr := *address.AddrForNodeID(v.getNodeID()) + info := admin_nodeInfo{ + {"ip", net.IP(addr[:]).String()}, + {"coords", fmt.Sprint(v.coords)}, + {"last_seen", int(now.Sub(v.recv).Seconds())}, + {"box_pub_key", hex.EncodeToString(v.key[:])}, + } + infos = append(infos, info) + } + } + a.core.router.doAdmin(getDHT) + return infos + */ + return []DHTEntry{} +} + +func (c *Core) GetSwitchQueues() []SwitchQueue { + /* + var peerInfos admin_nodeInfo + switchTable := &a.core.switchTable + getSwitchQueues := func() { + queues := make([]map[string]interface{}, 0) + for k, v := range switchTable.queues.bufs { + nexthop := switchTable.bestPortForCoords([]byte(k)) + queue := map[string]interface{}{ + "queue_id": k, + "queue_size": v.size, + "queue_packets": len(v.packets), + "queue_port": nexthop, + } + queues = append(queues, queue) + } + peerInfos = admin_nodeInfo{ + {"queues", queues}, + {"queues_count", len(switchTable.queues.bufs)}, + {"queues_size", switchTable.queues.size}, + {"highest_queues_count", switchTable.queues.maxbufs}, + {"highest_queues_size", switchTable.queues.maxsize}, + {"maximum_queues_size", switchTable.queueTotalMaxSize}, + } + } + a.core.switchTable.doAdmin(getSwitchQueues) + return peerInfos + */ + return []SwitchQueue{} +} + +func (c *Core) GetSessions() []Session { + /* + var infos []admin_nodeInfo + getSessions := func() { + for _, sinfo := range a.core.sessions.sinfos { + // TODO? skipped known but timed out sessions? + info := admin_nodeInfo{ + {"ip", net.IP(sinfo.theirAddr[:]).String()}, + {"coords", fmt.Sprint(sinfo.coords)}, + {"mtu", sinfo.getMTU()}, + {"was_mtu_fixed", sinfo.wasMTUFixed}, + {"bytes_sent", sinfo.bytesSent}, + {"bytes_recvd", sinfo.bytesRecvd}, + {"box_pub_key", hex.EncodeToString(sinfo.theirPermPub[:])}, + } + infos = append(infos, info) + } + } + a.core.router.doAdmin(getSessions) + return infos + */ + return []Session{} +} + +// BuildName gets the current build name. This is usually injected if built +// from git, or returns "unknown" otherwise. +func BuildName() string { + if buildName == "" { + return "unknown" + } + return buildName +} + +// BuildVersion gets the current build version. This is usually injected if +// built from git, or returns "unknown" otherwise. +func BuildVersion() string { + if buildVersion == "" { + return "unknown" + } + return buildVersion +} + +// ListenConn returns a listener for Yggdrasil session connections. +func (c *Core) ConnListen() (*Listener, error) { + c.sessions.listenerMutex.Lock() + defer c.sessions.listenerMutex.Unlock() + if c.sessions.listener != nil { + return nil, errors.New("a listener already exists") + } + c.sessions.listener = &Listener{ + core: c, + conn: make(chan *Conn), + close: make(chan interface{}), + } + return c.sessions.listener, nil +} + +// ConnDialer returns a dialer for Yggdrasil session connections. +func (c *Core) ConnDialer() (*Dialer, error) { + return &Dialer{ + core: c, + }, nil +} + +// ListenTCP starts a new TCP listener. The input URI should match that of the +// "Listen" configuration item, e.g. +// tcp://a.b.c.d:e +func (c *Core) ListenTCP(uri string) (*TcpListener, error) { + return c.link.tcp.listen(uri) +} + +// NewEncryptionKeys generates a new encryption keypair. The encryption keys are +// used to encrypt traffic and to derive the IPv6 address/subnet of the node. +func (c *Core) NewEncryptionKeys() (*crypto.BoxPubKey, *crypto.BoxPrivKey) { + return crypto.NewBoxKeys() +} + +// NewSigningKeys generates a new signing keypair. The signing keys are used to +// derive the structure of the spanning tree. +func (c *Core) NewSigningKeys() (*crypto.SigPubKey, *crypto.SigPrivKey) { + return crypto.NewSigKeys() +} + +// NodeID gets the node ID. +func (c *Core) NodeID() *crypto.NodeID { + return crypto.GetNodeID(&c.boxPub) +} + +// TreeID gets the tree ID. +func (c *Core) TreeID() *crypto.TreeID { + return crypto.GetTreeID(&c.sigPub) +} + +// SigPubKey gets the node's signing public key. +func (c *Core) SigPubKey() string { + return hex.EncodeToString(c.sigPub[:]) +} + +// BoxPubKey gets the node's encryption public key. +func (c *Core) BoxPubKey() string { + return hex.EncodeToString(c.boxPub[:]) +} + +// Address gets the IPv6 address of the Yggdrasil node. This is always a /128 +// address. +func (c *Core) Address() *net.IP { + address := net.IP(address.AddrForNodeID(c.NodeID())[:]) + return &address +} + +// Subnet gets the routed IPv6 subnet of the Yggdrasil node. This is always a +// /64 subnet. +func (c *Core) Subnet() *net.IPNet { + subnet := address.SubnetForNodeID(c.NodeID())[:] + subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0) + return &net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)} +} + +// RouterAddresses returns the raw address and subnet types as used by the +// router +func (c *Core) RouterAddresses() (address.Address, address.Subnet) { + return c.router.addr, c.router.subnet +} + +// NodeInfo gets the currently configured nodeinfo. +func (c *Core) NodeInfo() nodeinfoPayload { + return c.router.nodeinfo.getNodeInfo() +} + +// SetNodeInfo the lcal nodeinfo. Note that nodeinfo can be any value or struct, +// it will be serialised into JSON automatically. +func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) { + c.router.nodeinfo.setNodeInfo(nodeinfo, nodeinfoprivacy) +} + +// SetLogger sets the output logger of the Yggdrasil node after startup. This +// may be useful if you want to redirect the output later. +func (c *Core) SetLogger(log *log.Logger) { + c.log = log +} + +// AddPeer adds a peer. This should be specified in the peer URI format, e.g.: +// tcp://a.b.c.d:e +// socks://a.b.c.d:e/f.g.h.i:j +// This adds the peer to the peer list, so that they will be called again if the +// connection drops. +func (c *Core) AddPeer(addr string, sintf string) error { + if err := c.CallPeer(addr, sintf); err != nil { + return err + } + c.config.Mutex.Lock() + if sintf == "" { + c.config.Current.Peers = append(c.config.Current.Peers, addr) + } else { + c.config.Current.InterfacePeers[sintf] = append(c.config.Current.InterfacePeers[sintf], addr) + } + c.config.Mutex.Unlock() + return nil +} + +// CallPeer calls a peer once. This should be specified in the peer URI format, +// e.g.: +// tcp://a.b.c.d:e +// socks://a.b.c.d:e/f.g.h.i:j +// This does not add the peer to the peer list, so if the connection drops, the +// peer will not be called again automatically. +func (c *Core) CallPeer(addr string, sintf string) error { + return c.link.call(addr, sintf) +} + +// AddAllowedEncryptionPublicKey adds an allowed public key. This allow peerings +// to be restricted only to keys that you have selected. +func (c *Core) AddAllowedEncryptionPublicKey(boxStr string) error { + return c.admin.addAllowedEncryptionPublicKey(boxStr) +} diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 63d33d91..41bb11f7 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -2,14 +2,11 @@ package yggdrasil import ( "encoding/hex" - "errors" "io/ioutil" - "net" "time" "github.com/gologme/log" - "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" ) @@ -147,24 +144,6 @@ func (c *Core) UpdateConfig(config *config.NodeConfig) { } } -// BuildName gets the current build name. This is usually injected if built -// from git, or returns "unknown" otherwise. -func BuildName() string { - if buildName == "" { - return "unknown" - } - return buildName -} - -// BuildVersion gets the current build version. This is usually injected if -// built from git, or returns "unknown" otherwise. -func BuildVersion() string { - if buildVersion == "" { - return "unknown" - } - return buildVersion -} - // Start starts up Yggdrasil using the provided config.NodeConfig, and outputs // debug logging through the provided log.Logger. The started stack will include // TCP and UDP sockets, a multicast discovery socket, an admin socket, router, @@ -226,137 +205,3 @@ func (c *Core) Stop() { c.log.Infoln("Stopping...") c.admin.close() } - -// ListenConn returns a listener for Yggdrasil session connections. -func (c *Core) ConnListen() (*Listener, error) { - c.sessions.listenerMutex.Lock() - defer c.sessions.listenerMutex.Unlock() - if c.sessions.listener != nil { - return nil, errors.New("a listener already exists") - } - c.sessions.listener = &Listener{ - core: c, - conn: make(chan *Conn), - close: make(chan interface{}), - } - return c.sessions.listener, nil -} - -// ConnDialer returns a dialer for Yggdrasil session connections. -func (c *Core) ConnDialer() (*Dialer, error) { - return &Dialer{ - core: c, - }, nil -} - -// ListenTCP starts a new TCP listener. The input URI should match that of the -// "Listen" configuration item, e.g. -// tcp://a.b.c.d:e -func (c *Core) ListenTCP(uri string) (*TcpListener, error) { - return c.link.tcp.listen(uri) -} - -// NewEncryptionKeys generates a new encryption keypair. The encryption keys are -// used to encrypt traffic and to derive the IPv6 address/subnet of the node. -func (c *Core) NewEncryptionKeys() (*crypto.BoxPubKey, *crypto.BoxPrivKey) { - return crypto.NewBoxKeys() -} - -// NewSigningKeys generates a new signing keypair. The signing keys are used to -// derive the structure of the spanning tree. -func (c *Core) NewSigningKeys() (*crypto.SigPubKey, *crypto.SigPrivKey) { - return crypto.NewSigKeys() -} - -// NodeID gets the node ID. -func (c *Core) NodeID() *crypto.NodeID { - return crypto.GetNodeID(&c.boxPub) -} - -// TreeID gets the tree ID. -func (c *Core) TreeID() *crypto.TreeID { - return crypto.GetTreeID(&c.sigPub) -} - -// SigPubKey gets the node's signing public key. -func (c *Core) SigPubKey() string { - return hex.EncodeToString(c.sigPub[:]) -} - -// BoxPubKey gets the node's encryption public key. -func (c *Core) BoxPubKey() string { - return hex.EncodeToString(c.boxPub[:]) -} - -// Address gets the IPv6 address of the Yggdrasil node. This is always a /128 -// address. -func (c *Core) Address() *net.IP { - address := net.IP(address.AddrForNodeID(c.NodeID())[:]) - return &address -} - -// Subnet gets the routed IPv6 subnet of the Yggdrasil node. This is always a -// /64 subnet. -func (c *Core) Subnet() *net.IPNet { - subnet := address.SubnetForNodeID(c.NodeID())[:] - subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0) - return &net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)} -} - -// RouterAddresses returns the raw address and subnet types as used by the -// router -func (c *Core) RouterAddresses() (address.Address, address.Subnet) { - return c.router.addr, c.router.subnet -} - -// NodeInfo gets the currently configured nodeinfo. -func (c *Core) NodeInfo() nodeinfoPayload { - return c.router.nodeinfo.getNodeInfo() -} - -// SetNodeInfo the lcal nodeinfo. Note that nodeinfo can be any value or struct, -// it will be serialised into JSON automatically. -func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) { - c.router.nodeinfo.setNodeInfo(nodeinfo, nodeinfoprivacy) -} - -// SetLogger sets the output logger of the Yggdrasil node after startup. This -// may be useful if you want to redirect the output later. -func (c *Core) SetLogger(log *log.Logger) { - c.log = log -} - -// AddPeer adds a peer. This should be specified in the peer URI format, e.g.: -// tcp://a.b.c.d:e -// socks://a.b.c.d:e/f.g.h.i:j -// This adds the peer to the peer list, so that they will be called again if the -// connection drops. -func (c *Core) AddPeer(addr string, sintf string) error { - if err := c.CallPeer(addr, sintf); err != nil { - return err - } - c.config.Mutex.Lock() - if sintf == "" { - c.config.Current.Peers = append(c.config.Current.Peers, addr) - } else { - c.config.Current.InterfacePeers[sintf] = append(c.config.Current.InterfacePeers[sintf], addr) - } - c.config.Mutex.Unlock() - return nil -} - -// CallPeer calls a peer once. This should be specified in the peer URI format, -// e.g.: -// tcp://a.b.c.d:e -// socks://a.b.c.d:e/f.g.h.i:j -// This does not add the peer to the peer list, so if the connection drops, the -// peer will not be called again automatically. -func (c *Core) CallPeer(addr string, sintf string) error { - return c.link.call(addr, sintf) -} - -// AddAllowedEncryptionPublicKey adds an allowed public key. This allow peerings -// to be restricted only to keys that you have selected. -func (c *Core) AddAllowedEncryptionPublicKey(boxStr string) error { - return c.admin.addAllowedEncryptionPublicKey(boxStr) -} From 7ca5a2533dae672b0d17f5cdc2a26123a3e3423b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 19 May 2019 16:29:04 +0100 Subject: [PATCH 0079/1109] Implement GetDHT, GetSwitchQueues, GetSessions --- src/yggdrasil/api.go | 178 ++++++++++++++++++++++++------------------- 1 file changed, 100 insertions(+), 78 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index b5278102..d41b5f5a 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -38,14 +38,44 @@ type SwitchPeer struct { Protocol string } +// DHTEntry represents a single DHT entry that has been learned or cached from +// DHT searches. type DHTEntry struct { PublicKey crypto.BoxPubKey Coords []byte LastSeen time.Duration } -type SwitchQueue struct{} -type Session struct{} +// SwitchQueues represents information from the switch related to link +// congestion and a list of switch queues created in response to congestion on a +// given link. +type SwitchQueues struct { + Queues []SwitchQueue + Count uint64 + Size uint64 + HighestCount uint64 + HighestSize uint64 + MaximumSize uint64 +} + +// SwitchQueue represents a single switch queue, which is created in response +// to congestion on a given link. +type SwitchQueue struct { + ID string + Size uint64 + Packets uint64 + Port uint64 +} + +// Session represents an open session with another node. +type Session struct { + PublicKey crypto.BoxPubKey + Coords []byte + BytesSent uint64 + BytesRecvd uint64 + MTU uint16 + WasMTUFixed bool +} // GetPeers returns one or more Peer objects containing information about active // peerings with other Yggdrasil nodes, where one of the responses always @@ -104,88 +134,80 @@ func (c *Core) GetSwitchPeers() []SwitchPeer { return switchpeers } +// GetDHT returns zero or more entries as stored in the DHT, cached primarily +// from searches that have already taken place. func (c *Core) GetDHT() []DHTEntry { - /* - var infos []admin_nodeInfo - getDHT := func() { - now := time.Now() - var dhtInfos []*dhtInfo - for _, v := range a.core.dht.table { - dhtInfos = append(dhtInfos, v) - } - sort.SliceStable(dhtInfos, func(i, j int) bool { - return dht_ordered(&a.core.dht.nodeID, dhtInfos[i].getNodeID(), dhtInfos[j].getNodeID()) - }) - for _, v := range dhtInfos { - addr := *address.AddrForNodeID(v.getNodeID()) - info := admin_nodeInfo{ - {"ip", net.IP(addr[:]).String()}, - {"coords", fmt.Sprint(v.coords)}, - {"last_seen", int(now.Sub(v.recv).Seconds())}, - {"box_pub_key", hex.EncodeToString(v.key[:])}, - } - infos = append(infos, info) - } - } - a.core.router.doAdmin(getDHT) - return infos - */ - return []DHTEntry{} + var dhtentries []DHTEntry + getDHT := func() { + now := time.Now() + var dhtentry []*dhtInfo + for _, v := range c.dht.table { + dhtentry = append(dhtentry, v) + } + sort.SliceStable(dhtentry, func(i, j int) bool { + return dht_ordered(&c.dht.nodeID, dhtentry[i].getNodeID(), dhtentry[j].getNodeID()) + }) + for _, v := range dhtentry { + info := DHTEntry{ + Coords: v.coords, + LastSeen: now.Sub(v.recv), + } + copy(info.PublicKey[:], v.key[:]) + dhtentries = append(dhtentries, info) + } + } + c.router.doAdmin(getDHT) + return dhtentries } -func (c *Core) GetSwitchQueues() []SwitchQueue { - /* - var peerInfos admin_nodeInfo - switchTable := &a.core.switchTable - getSwitchQueues := func() { - queues := make([]map[string]interface{}, 0) - for k, v := range switchTable.queues.bufs { - nexthop := switchTable.bestPortForCoords([]byte(k)) - queue := map[string]interface{}{ - "queue_id": k, - "queue_size": v.size, - "queue_packets": len(v.packets), - "queue_port": nexthop, - } - queues = append(queues, queue) - } - peerInfos = admin_nodeInfo{ - {"queues", queues}, - {"queues_count", len(switchTable.queues.bufs)}, - {"queues_size", switchTable.queues.size}, - {"highest_queues_count", switchTable.queues.maxbufs}, - {"highest_queues_size", switchTable.queues.maxsize}, - {"maximum_queues_size", switchTable.queueTotalMaxSize}, - } - } - a.core.switchTable.doAdmin(getSwitchQueues) - return peerInfos - */ - return []SwitchQueue{} +// GetSwitchQueues returns information about the switch queues that are +// currently in effect. These values can change within an instant. +func (c *Core) GetSwitchQueues() SwitchQueues { + var switchqueues SwitchQueues + switchTable := &c.switchTable + getSwitchQueues := func() { + switchqueues = SwitchQueues{ + Count: uint64(len(switchTable.queues.bufs)), + Size: switchTable.queues.size, + HighestCount: uint64(switchTable.queues.maxbufs), + HighestSize: switchTable.queues.maxsize, + MaximumSize: switchTable.queueTotalMaxSize, + } + for k, v := range switchTable.queues.bufs { + nexthop := switchTable.bestPortForCoords([]byte(k)) + queue := SwitchQueue{ + ID: k, + Size: v.size, + Packets: uint64(len(v.packets)), + Port: uint64(nexthop), + } + switchqueues.Queues = append(switchqueues.Queues, queue) + } + + } + c.switchTable.doAdmin(getSwitchQueues) + return switchqueues } +// GetSessions returns a list of open sessions from this node to other nodes. func (c *Core) GetSessions() []Session { - /* - var infos []admin_nodeInfo - getSessions := func() { - for _, sinfo := range a.core.sessions.sinfos { - // TODO? skipped known but timed out sessions? - info := admin_nodeInfo{ - {"ip", net.IP(sinfo.theirAddr[:]).String()}, - {"coords", fmt.Sprint(sinfo.coords)}, - {"mtu", sinfo.getMTU()}, - {"was_mtu_fixed", sinfo.wasMTUFixed}, - {"bytes_sent", sinfo.bytesSent}, - {"bytes_recvd", sinfo.bytesRecvd}, - {"box_pub_key", hex.EncodeToString(sinfo.theirPermPub[:])}, - } - infos = append(infos, info) - } - } - a.core.router.doAdmin(getSessions) - return infos - */ - return []Session{} + var sessions []Session + getSessions := func() { + for _, sinfo := range c.sessions.sinfos { + // TODO? skipped known but timed out sessions? + session := Session{ + Coords: sinfo.coords, + MTU: sinfo.getMTU(), + BytesSent: sinfo.bytesSent, + BytesRecvd: sinfo.bytesRecvd, + WasMTUFixed: sinfo.wasMTUFixed, + } + copy(session.PublicKey[:], sinfo.theirPermPub[:]) + sessions = append(sessions, session) + } + } + c.router.doAdmin(getSessions) + return sessions } // BuildName gets the current build name. This is usually injected if built From 8ef1978cb1c0e1445ce51921084abdd518554178 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 19 May 2019 17:27:48 +0100 Subject: [PATCH 0080/1109] Start factoring out the admin socket into a separate module (not all functions implemented yet) --- cmd/yggdrasil/main.go | 7 + src/{yggdrasil => admin}/admin.go | 382 +++++++++--------------------- src/yggdrasil/api.go | 11 +- src/yggdrasil/core.go | 9 - 4 files changed, 123 insertions(+), 286 deletions(-) rename src/{yggdrasil => admin}/admin.go (70%) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index ccb7061a..756947d8 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -18,6 +18,7 @@ import ( "github.com/kardianos/minwinsvc" "github.com/mitchellh/mapstructure" + "github.com/yggdrasil-network/yggdrasil-go/src/admin" "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/multicast" "github.com/yggdrasil-network/yggdrasil-go/src/tuntap" @@ -31,6 +32,7 @@ type node struct { core Core tuntap tuntap.TunAdapter multicast multicast.Multicast + admin admin.AdminSocket } func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *nodeConfig { @@ -184,6 +186,11 @@ func main() { logger.Errorln("An error occurred during startup") panic(err) } + // Start the admin socket + n.admin.Init(&n.core, state, logger, nil) + if err := n.admin.Start(); err != nil { + logger.Errorln("An error occurred starting admin socket:", err) + } // Start the multicast interface n.multicast.Init(&n.core, state, logger, nil) if err := n.multicast.Start(); err != nil { diff --git a/src/yggdrasil/admin.go b/src/admin/admin.go similarity index 70% rename from src/yggdrasil/admin.go rename to src/admin/admin.go index a24a5852..a7e9ac72 100644 --- a/src/yggdrasil/admin.go +++ b/src/admin/admin.go @@ -1,27 +1,27 @@ -package yggdrasil +package admin import ( - "encoding/hex" "encoding/json" - "errors" "fmt" "net" "net/url" "os" - "sort" - "strconv" "strings" - "sync/atomic" "time" + "github.com/gologme/log" + "github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) // TODO: Add authentication -type admin struct { - core *Core +type AdminSocket struct { + core *yggdrasil.Core + log *log.Logger reconfigure chan chan error listenaddr string listener net.Listener @@ -46,27 +46,28 @@ type admin_pair struct { type admin_nodeInfo []admin_pair // addHandler is called for each admin function to add the handler and help documentation to the API. -func (a *admin) addHandler(name string, args []string, handler func(admin_info) (admin_info, error)) { +func (a *AdminSocket) addHandler(name string, args []string, handler func(admin_info) (admin_info, error)) { a.handlers = append(a.handlers, admin_handlerInfo{name, args, handler}) } // init runs the initial admin setup. -func (a *admin) init(c *Core) { +func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) { a.core = c + a.log = log a.reconfigure = make(chan chan error, 1) go func() { for { e := <-a.reconfigure - current, previous := a.core.config.Get() + current, previous := state.Get() if current.AdminListen != previous.AdminListen { a.listenaddr = current.AdminListen - a.close() - a.start() + a.Stop() + a.Start() } e <- nil } }() - current, _ := a.core.config.Get() + current, _ := state.Get() a.listenaddr = current.AdminListen a.addHandler("list", []string{}, func(in admin_info) (admin_info, error) { handlers := make(map[string]interface{}) @@ -75,64 +76,93 @@ func (a *admin) init(c *Core) { } return admin_info{"list": handlers}, nil }) - a.addHandler("dot", []string{}, func(in admin_info) (admin_info, error) { + /*a.addHandler("dot", []string{}, func(in admin_info) (admin_info, error) { return admin_info{"dot": string(a.getResponse_dot())}, nil - }) + })*/ a.addHandler("getSelf", []string{}, func(in admin_info) (admin_info, error) { - self := a.getData_getSelf().asMap() - ip := fmt.Sprint(self["ip"]) - delete(self, "ip") - return admin_info{"self": admin_info{ip: self}}, nil + ip := c.Address().String() + return admin_info{ + "self": admin_info{ + ip: admin_info{ + "box_pub_key": c.BoxPubKey(), + "build_name": yggdrasil.BuildName(), + "build_version": yggdrasil.BuildVersion(), + "coords": fmt.Sprintf("%v", c.Coords()), + "subnet": c.Subnet().String(), + }, + }, + }, nil }) a.addHandler("getPeers", []string{}, func(in admin_info) (admin_info, error) { - sort := "ip" peers := make(admin_info) - for _, peerdata := range a.getData_getPeers() { - p := peerdata.asMap() - so := fmt.Sprint(p[sort]) - peers[so] = p - delete(peers[so].(map[string]interface{}), sort) + for _, p := range a.core.GetPeers() { + addr := *address.AddrForNodeID(crypto.GetNodeID(&p.PublicKey)) + so := net.IP(addr[:]).String() + peers[so] = admin_info{ + "ip": so, + "port": p.Port, + "uptime": p.Uptime.Seconds(), + "bytes_sent": p.BytesSent, + "bytes_recvd": p.BytesRecvd, + "proto": p.Protocol, + "endpoint": p.Endpoint, + "box_pub_key": p.PublicKey, + } } return admin_info{"peers": peers}, nil }) a.addHandler("getSwitchPeers", []string{}, func(in admin_info) (admin_info, error) { - sort := "port" switchpeers := make(admin_info) - for _, s := range a.getData_getSwitchPeers() { - p := s.asMap() - so := fmt.Sprint(p[sort]) - switchpeers[so] = p - delete(switchpeers[so].(map[string]interface{}), sort) + for _, s := range a.core.GetSwitchPeers() { + addr := *address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey)) + so := fmt.Sprint(s.Port) + switchpeers[so] = admin_info{ + "ip": net.IP(addr[:]).String(), + "coords": fmt.Sprintf("%v", s.Coords), + "port": s.Port, + "bytes_sent": s.BytesSent, + "bytes_recvd": s.BytesRecvd, + "proto": s.Protocol, + "endpoint": s.Endpoint, + "box_pub_key": s.PublicKey, + } } return admin_info{"switchpeers": switchpeers}, nil }) - a.addHandler("getSwitchQueues", []string{}, func(in admin_info) (admin_info, error) { - queues := a.getData_getSwitchQueues() + /*a.addHandler("getSwitchQueues", []string{}, func(in admin_info) (admin_info, error) { + queues := a.core.GetSwitchQueues() return admin_info{"switchqueues": queues.asMap()}, nil - }) + })*/ a.addHandler("getDHT", []string{}, func(in admin_info) (admin_info, error) { - sort := "ip" dht := make(admin_info) - for _, d := range a.getData_getDHT() { - p := d.asMap() - so := fmt.Sprint(p[sort]) - dht[so] = p - delete(dht[so].(map[string]interface{}), sort) + for _, d := range a.core.GetDHT() { + addr := *address.AddrForNodeID(crypto.GetNodeID(&d.PublicKey)) + so := net.IP(addr[:]).String() + dht[so] = admin_info{ + "coords": fmt.Sprintf("%v", d.Coords), + "last_seen": d.LastSeen.Seconds(), + "box_pub_key": d.PublicKey, + } } return admin_info{"dht": dht}, nil }) a.addHandler("getSessions", []string{}, func(in admin_info) (admin_info, error) { - sort := "ip" sessions := make(admin_info) - for _, s := range a.getData_getSessions() { - p := s.asMap() - so := fmt.Sprint(p[sort]) - sessions[so] = p - delete(sessions[so].(map[string]interface{}), sort) + for _, s := range a.core.GetSessions() { + addr := *address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey)) + so := net.IP(addr[:]).String() + sessions[so] = admin_info{ + "coords": fmt.Sprintf("%v", s.Coords), + "bytes_sent": s.BytesSent, + "bytes_recvd": s.BytesRecvd, + "mtu": s.MTU, + "was_mtu_fixed": s.WasMTUFixed, + "box_pub_key": s.PublicKey, + } } return admin_info{"sessions": sessions}, nil }) - a.addHandler("addPeer", []string{"uri", "[interface]"}, func(in admin_info) (admin_info, error) { + /*a.addHandler("addPeer", []string{"uri", "[interface]"}, func(in admin_info) (admin_info, error) { // Set sane defaults intf := "" // Has interface been specified? @@ -168,7 +198,7 @@ func (a *admin) init(c *Core) { }, errors.New("Failed to remove peer") } }) - /* a.addHandler("getTunTap", []string{}, func(in admin_info) (r admin_info, e error) { + a.addHandler("getTunTap", []string{}, func(in admin_info) (r admin_info, e error) { defer func() { if err := recover(); err != nil { r = admin_info{"none": admin_info{}} @@ -215,7 +245,7 @@ func (a *admin) init(c *Core) { intfs = append(intfs, v.Name) } return admin_info{"multicast_interfaces": intfs}, nil - })*/ + }) a.addHandler("getAllowedEncryptionPublicKeys", []string{}, func(in admin_info) (admin_info, error) { return admin_info{"allowed_box_pubs": a.getAllowedEncryptionPublicKeys()}, nil }) @@ -249,7 +279,7 @@ func (a *admin) init(c *Core) { }, errors.New("Failed to remove allowed key") } }) - /*a.addHandler("getTunnelRouting", []string{}, func(in admin_info) (admin_info, error) { + a.addHandler("getTunnelRouting", []string{}, func(in admin_info) (admin_info, error) { enabled := false a.core.router.doAdmin(func() { enabled = a.core.router.cryptokey.isEnabled() @@ -335,7 +365,7 @@ func (a *admin) init(c *Core) { } else { return admin_info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route") } - })*/ + }) a.addHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in admin_info) (admin_info, error) { if in["target"] == nil { in["target"] = "none" @@ -390,11 +420,11 @@ func (a *admin) init(c *Core) { } else { return admin_info{}, err } - }) + })*/ } // start runs the admin API socket to listen for / respond to admin API calls. -func (a *admin) start() error { +func (a *AdminSocket) Start() error { if a.listenaddr != "none" && a.listenaddr != "" { go a.listen() } @@ -402,7 +432,7 @@ func (a *admin) start() error { } // cleans up when stopping -func (a *admin) close() error { +func (a *AdminSocket) Stop() error { if a.listener != nil { return a.listener.Close() } else { @@ -411,21 +441,21 @@ func (a *admin) close() error { } // listen is run by start and manages API connections. -func (a *admin) listen() { +func (a *AdminSocket) listen() { u, err := url.Parse(a.listenaddr) if err == nil { switch strings.ToLower(u.Scheme) { case "unix": if _, err := os.Stat(a.listenaddr[7:]); err == nil { - a.core.log.Debugln("Admin socket", a.listenaddr[7:], "already exists, trying to clean up") + a.log.Debugln("Admin socket", a.listenaddr[7:], "already exists, trying to clean up") if _, err := net.DialTimeout("unix", a.listenaddr[7:], time.Second*2); err == nil || err.(net.Error).Timeout() { - a.core.log.Errorln("Admin socket", a.listenaddr[7:], "already exists and is in use by another process") + a.log.Errorln("Admin socket", a.listenaddr[7:], "already exists and is in use by another process") os.Exit(1) } else { if err := os.Remove(a.listenaddr[7:]); err == nil { - a.core.log.Debugln(a.listenaddr[7:], "was cleaned up") + a.log.Debugln(a.listenaddr[7:], "was cleaned up") } else { - a.core.log.Errorln(a.listenaddr[7:], "already exists and was not cleaned up:", err) + a.log.Errorln(a.listenaddr[7:], "already exists and was not cleaned up:", err) os.Exit(1) } } @@ -436,7 +466,7 @@ func (a *admin) listen() { case "@": // maybe abstract namespace default: if err := os.Chmod(a.listenaddr[7:], 0660); err != nil { - a.core.log.Warnln("WARNING:", a.listenaddr[:7], "may have unsafe permissions!") + a.log.Warnln("WARNING:", a.listenaddr[:7], "may have unsafe permissions!") } } } @@ -450,10 +480,10 @@ func (a *admin) listen() { a.listener, err = net.Listen("tcp", a.listenaddr) } if err != nil { - a.core.log.Errorf("Admin socket failed to listen: %v", err) + a.log.Errorf("Admin socket failed to listen: %v", err) os.Exit(1) } - a.core.log.Infof("%s admin socket listening on %s", + a.log.Infof("%s admin socket listening on %s", strings.ToUpper(a.listener.Addr().Network()), a.listener.Addr().String()) defer a.listener.Close() @@ -466,7 +496,7 @@ func (a *admin) listen() { } // handleRequest calls the request handler for each request sent to the admin API. -func (a *admin) handleRequest(conn net.Conn) { +func (a *AdminSocket) handleRequest(conn net.Conn) { decoder := json.NewDecoder(conn) encoder := json.NewEncoder(conn) encoder.SetIndent("", " ") @@ -480,9 +510,9 @@ func (a *admin) handleRequest(conn net.Conn) { "status": "error", "error": "Unrecoverable error, possibly as a result of invalid input types or malformed syntax", } - a.core.log.Errorln("Admin socket error:", r) + a.log.Errorln("Admin socket error:", r) if err := encoder.Encode(&send); err != nil { - a.core.log.Errorln("Admin socket JSON encode error:", err) + a.log.Errorln("Admin socket JSON encode error:", err) } conn.Close() } @@ -577,7 +607,7 @@ func (n *admin_nodeInfo) toString() string { } // printInfos returns a newline separated list of strings from admin_nodeInfos, e.g. a printable string of info about all peers. -func (a *admin) printInfos(infos []admin_nodeInfo) string { +func (a *AdminSocket) printInfos(infos []admin_nodeInfo) string { var out []string for _, info := range infos { out = append(out, info.toString()) @@ -586,8 +616,9 @@ func (a *admin) printInfos(infos []admin_nodeInfo) string { return strings.Join(out, "\n") } +/* // addPeer triggers a connection attempt to a node. -func (a *admin) addPeer(addr string, sintf string) error { +func (a *AdminSocket) addPeer(addr string, sintf string) error { err := a.core.link.call(addr, sintf) if err != nil { return err @@ -596,220 +627,18 @@ func (a *admin) addPeer(addr string, sintf string) error { } // removePeer disconnects an existing node (given by the node's port number). -func (a *admin) removePeer(p string) error { +func (a *AdminSocket) removePeer(p string) error { iport, err := strconv.Atoi(p) if err != nil { return err } - a.core.peers.removePeer(switchPort(iport)) - return nil -} - -// startTunWithMTU creates the tun/tap device, sets its address, and sets the MTU to the provided value. -/* -func (a *admin) startTunWithMTU(ifname string, iftapmode bool, ifmtu int) error { - // Close the TUN first if open - _ = a.core.router.tun.close() - // Then reconfigure and start it - addr := a.core.router.addr - straddr := fmt.Sprintf("%s/%v", net.IP(addr[:]).String(), 8*len(address.GetPrefix())-1) - if ifname != "none" { - err := a.core.router.tun.setup(ifname, iftapmode, straddr, ifmtu) - if err != nil { - return err - } - // If we have open sessions then we need to notify them - // that our MTU has now changed - for _, sinfo := range a.core.sessions.sinfos { - if ifname == "none" { - sinfo.myMTU = 0 - } else { - sinfo.myMTU = uint16(ifmtu) - } - a.core.sessions.sendPingPong(sinfo, false) - } - // Aaaaand... go! - go a.core.router.tun.read() - } - go a.core.router.tun.write() + a.core.RemovePeer(iport) return nil } */ - -// getData_getSelf returns the self node's info for admin responses. -func (a *admin) getData_getSelf() *admin_nodeInfo { - table := a.core.switchTable.table.Load().(lookupTable) - coords := table.self.getCoords() - nodeid := *crypto.GetNodeID(&a.core.boxPub) - self := admin_nodeInfo{ - {"node_id", hex.EncodeToString(nodeid[:])}, - {"box_pub_key", hex.EncodeToString(a.core.boxPub[:])}, - {"ip", a.core.Address().String()}, - {"subnet", a.core.Subnet().String()}, - {"coords", fmt.Sprint(coords)}, - } - if name := BuildName(); name != "unknown" { - self = append(self, admin_pair{"build_name", name}) - } - if version := BuildVersion(); version != "unknown" { - self = append(self, admin_pair{"build_version", version}) - } - - return &self -} - -// getData_getPeers returns info from Core.peers for an admin response. -func (a *admin) getData_getPeers() []admin_nodeInfo { - ports := a.core.peers.ports.Load().(map[switchPort]*peer) - var peerInfos []admin_nodeInfo - var ps []switchPort - for port := range ports { - ps = append(ps, port) - } - sort.Slice(ps, func(i, j int) bool { return ps[i] < ps[j] }) - for _, port := range ps { - p := ports[port] - addr := *address.AddrForNodeID(crypto.GetNodeID(&p.box)) - info := admin_nodeInfo{ - {"ip", net.IP(addr[:]).String()}, - {"port", port}, - {"uptime", int(time.Since(p.firstSeen).Seconds())}, - {"bytes_sent", atomic.LoadUint64(&p.bytesSent)}, - {"bytes_recvd", atomic.LoadUint64(&p.bytesRecvd)}, - {"proto", p.intf.info.linkType}, - {"endpoint", p.intf.name}, - {"box_pub_key", hex.EncodeToString(p.box[:])}, - } - peerInfos = append(peerInfos, info) - } - return peerInfos -} - -// getData_getSwitchPeers returns info from Core.switchTable for an admin response. -func (a *admin) getData_getSwitchPeers() []admin_nodeInfo { - var peerInfos []admin_nodeInfo - table := a.core.switchTable.table.Load().(lookupTable) - peers := a.core.peers.ports.Load().(map[switchPort]*peer) - for _, elem := range table.elems { - peer, isIn := peers[elem.port] - if !isIn { - continue - } - addr := *address.AddrForNodeID(crypto.GetNodeID(&peer.box)) - coords := elem.locator.getCoords() - info := admin_nodeInfo{ - {"ip", net.IP(addr[:]).String()}, - {"coords", fmt.Sprint(coords)}, - {"port", elem.port}, - {"bytes_sent", atomic.LoadUint64(&peer.bytesSent)}, - {"bytes_recvd", atomic.LoadUint64(&peer.bytesRecvd)}, - {"proto", peer.intf.info.linkType}, - {"endpoint", peer.intf.info.remote}, - {"box_pub_key", hex.EncodeToString(peer.box[:])}, - } - peerInfos = append(peerInfos, info) - } - return peerInfos -} - -// getData_getSwitchQueues returns info from Core.switchTable for an queue data. -func (a *admin) getData_getSwitchQueues() admin_nodeInfo { - var peerInfos admin_nodeInfo - switchTable := &a.core.switchTable - getSwitchQueues := func() { - queues := make([]map[string]interface{}, 0) - for k, v := range switchTable.queues.bufs { - nexthop := switchTable.bestPortForCoords([]byte(k)) - queue := map[string]interface{}{ - "queue_id": k, - "queue_size": v.size, - "queue_packets": len(v.packets), - "queue_port": nexthop, - } - queues = append(queues, queue) - } - peerInfos = admin_nodeInfo{ - {"queues", queues}, - {"queues_count", len(switchTable.queues.bufs)}, - {"queues_size", switchTable.queues.size}, - {"highest_queues_count", switchTable.queues.maxbufs}, - {"highest_queues_size", switchTable.queues.maxsize}, - {"maximum_queues_size", switchTable.queueTotalMaxSize}, - } - } - a.core.switchTable.doAdmin(getSwitchQueues) - return peerInfos -} - -// getData_getDHT returns info from Core.dht for an admin response. -func (a *admin) getData_getDHT() []admin_nodeInfo { - var infos []admin_nodeInfo - getDHT := func() { - now := time.Now() - var dhtInfos []*dhtInfo - for _, v := range a.core.dht.table { - dhtInfos = append(dhtInfos, v) - } - sort.SliceStable(dhtInfos, func(i, j int) bool { - return dht_ordered(&a.core.dht.nodeID, dhtInfos[i].getNodeID(), dhtInfos[j].getNodeID()) - }) - for _, v := range dhtInfos { - addr := *address.AddrForNodeID(v.getNodeID()) - info := admin_nodeInfo{ - {"ip", net.IP(addr[:]).String()}, - {"coords", fmt.Sprint(v.coords)}, - {"last_seen", int(now.Sub(v.recv).Seconds())}, - {"box_pub_key", hex.EncodeToString(v.key[:])}, - } - infos = append(infos, info) - } - } - a.core.router.doAdmin(getDHT) - return infos -} - -// getData_getSessions returns info from Core.sessions for an admin response. -func (a *admin) getData_getSessions() []admin_nodeInfo { - var infos []admin_nodeInfo - getSessions := func() { - for _, sinfo := range a.core.sessions.sinfos { - // TODO? skipped known but timed out sessions? - info := admin_nodeInfo{ - {"ip", net.IP(sinfo.theirAddr[:]).String()}, - {"coords", fmt.Sprint(sinfo.coords)}, - {"mtu", sinfo.getMTU()}, - {"was_mtu_fixed", sinfo.wasMTUFixed}, - {"bytes_sent", sinfo.bytesSent}, - {"bytes_recvd", sinfo.bytesRecvd}, - {"box_pub_key", hex.EncodeToString(sinfo.theirPermPub[:])}, - } - infos = append(infos, info) - } - } - a.core.router.doAdmin(getSessions) - return infos -} - -// getAllowedEncryptionPublicKeys returns the public keys permitted for incoming peer connections. -func (a *admin) getAllowedEncryptionPublicKeys() []string { - return a.core.peers.getAllowedEncryptionPublicKeys() -} - -// addAllowedEncryptionPublicKey whitelists a key for incoming peer connections. -func (a *admin) addAllowedEncryptionPublicKey(bstr string) (err error) { - a.core.peers.addAllowedEncryptionPublicKey(bstr) - return nil -} - -// removeAllowedEncryptionPublicKey removes a key from the whitelist for incoming peer connections. -// If none are set, an empty list permits all incoming connections. -func (a *admin) removeAllowedEncryptionPublicKey(bstr string) (err error) { - a.core.peers.removeAllowedEncryptionPublicKey(bstr) - return nil -} - +/* // Send a DHT ping to the node with the provided key and coords, optionally looking up the specified target NodeID. -func (a *admin) admin_dhtPing(keyString, coordString, targetString string) (dhtRes, error) { +func (a *AdminSocket) admin_dhtPing(keyString, coordString, targetString string) (dhtRes, error) { var key crypto.BoxPubKey if keyBytes, err := hex.DecodeString(keyString); err != nil { return dhtRes{}, err @@ -866,7 +695,7 @@ func (a *admin) admin_dhtPing(keyString, coordString, targetString string) (dhtR return dhtRes{}, errors.New(fmt.Sprintf("DHT ping timeout: %s", keyString)) } -func (a *admin) admin_getNodeInfo(keyString, coordString string, nocache bool) (nodeinfoPayload, error) { +func (a *AdminSocket) admin_getNodeInfo(keyString, coordString string, nocache bool) (nodeinfoPayload, error) { var key crypto.BoxPubKey if keyBytes, err := hex.DecodeString(keyString); err != nil { return nodeinfoPayload{}, err @@ -915,7 +744,7 @@ func (a *admin) admin_getNodeInfo(keyString, coordString string, nocache bool) ( // getResponse_dot returns a response for a graphviz dot formatted representation of the known parts of the network. // This is color-coded and labeled, and includes the self node, switch peers, nodes known to the DHT, and nodes with open sessions. // The graph is structured as a tree with directed links leading away from the root. -func (a *admin) getResponse_dot() []byte { +func (a *AdminSocket) getResponse_dot() []byte { self := a.getData_getSelf() peers := a.getData_getSwitchPeers() dht := a.getData_getDHT() @@ -1037,3 +866,4 @@ func (a *admin) getResponse_dot() []byte { put("}\n") return out } +*/ diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index d41b5f5a..e7050c7b 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -36,6 +36,7 @@ type SwitchPeer struct { BytesRecvd uint64 Port uint64 Protocol string + Endpoint string } // DHTEntry represents a single DHT entry that has been learned or cached from @@ -127,6 +128,7 @@ func (c *Core) GetSwitchPeers() []SwitchPeer { BytesRecvd: atomic.LoadUint64(&peer.bytesRecvd), Port: uint64(elem.port), Protocol: peer.intf.info.linkType, + Endpoint: peer.intf.info.remote, } copy(info.PublicKey[:], peer.box[:]) switchpeers = append(switchpeers, info) @@ -289,6 +291,12 @@ func (c *Core) BoxPubKey() string { return hex.EncodeToString(c.boxPub[:]) } +// Coords returns the current coordinates of the node. +func (c *Core) Coords() []byte { + table := c.switchTable.table.Load().(lookupTable) + return table.self.getCoords() +} + // Address gets the IPv6 address of the Yggdrasil node. This is always a /128 // address. func (c *Core) Address() *net.IP { @@ -359,5 +367,6 @@ func (c *Core) CallPeer(addr string, sintf string) error { // AddAllowedEncryptionPublicKey adds an allowed public key. This allow peerings // to be restricted only to keys that you have selected. func (c *Core) AddAllowedEncryptionPublicKey(boxStr string) error { - return c.admin.addAllowedEncryptionPublicKey(boxStr) + //return c.admin.addAllowedEncryptionPublicKey(boxStr) + return nil } diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 41bb11f7..3a7f9f1b 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -30,7 +30,6 @@ type Core struct { sessions sessions router router dht dht - admin admin searches searches link link log *log.Logger @@ -69,7 +68,6 @@ func (c *Core) init() error { copy(c.sigPub[:], sigPubHex) copy(c.sigPriv[:], sigPrivHex) - c.admin.init(c) c.searches.init(c) c.dht.init(c) c.sessions.init(c) @@ -118,7 +116,6 @@ func (c *Core) UpdateConfig(config *config.NodeConfig) { errors := 0 components := []chan chan error{ - c.admin.reconfigure, c.searches.reconfigure, c.dht.reconfigure, c.sessions.reconfigure, @@ -189,11 +186,6 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, return nil, err } - if err := c.admin.start(); err != nil { - c.log.Errorln("Failed to start admin socket") - return nil, err - } - go c.addPeerLoop() c.log.Infoln("Startup complete") @@ -203,5 +195,4 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, // Stop shuts down the Yggdrasil node. func (c *Core) Stop() { c.log.Infoln("Stopping...") - c.admin.close() } From d575b83ec190837e555da17cb5ba286675d5d2b4 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 19 May 2019 22:02:04 +0100 Subject: [PATCH 0081/1109] Refactor admin socket somewhat, allow modules to set up their own handlers --- cmd/yggdrasil/main.go | 2 + src/admin/admin.go | 628 +++++++++++++++-------------------------- src/multicast/admin.go | 13 + src/tuntap/admin.go | 136 +++++++++ 4 files changed, 379 insertions(+), 400 deletions(-) create mode 100644 src/multicast/admin.go create mode 100644 src/tuntap/admin.go diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 756947d8..5dba3a51 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -196,6 +196,7 @@ func main() { if err := n.multicast.Start(); err != nil { logger.Errorln("An error occurred starting multicast:", err) } + n.multicast.SetupAdminHandlers(&n.admin) // Start the TUN/TAP interface if listener, err := n.core.ConnListen(); err == nil { if dialer, err := n.core.ConnDialer(); err == nil { @@ -203,6 +204,7 @@ func main() { if err := n.tuntap.Start(); err != nil { logger.Errorln("An error occurred starting TUN/TAP:", err) } + n.tuntap.SetupAdminHandlers(&n.admin) } else { logger.Errorln("Unable to get Dialer:", err) } diff --git a/src/admin/admin.go b/src/admin/admin.go index a7e9ac72..ff5476e4 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -2,6 +2,7 @@ package admin import ( "encoding/json" + "errors" "fmt" "net" "net/url" @@ -25,29 +26,27 @@ type AdminSocket struct { reconfigure chan chan error listenaddr string listener net.Listener - handlers []admin_handlerInfo + handlers map[string]handler } -type admin_info map[string]interface{} +// Info refers to information that is returned to the admin socket handler. +type Info map[string]interface{} -type admin_handlerInfo struct { - name string // Checked against the first word of the api call - args []string // List of human-readable argument names - handler func(admin_info) (admin_info, error) // First is input map, second is output +type handler struct { + args []string // List of human-readable argument names + handler func(Info) (Info, error) // First is input map, second is output } -// admin_pair maps things like "IP", "port", "bucket", or "coords" onto values. -type admin_pair struct { - key string - val interface{} -} - -// admin_nodeInfo represents the information we know about a node for an admin response. -type admin_nodeInfo []admin_pair - -// addHandler is called for each admin function to add the handler and help documentation to the API. -func (a *AdminSocket) addHandler(name string, args []string, handler func(admin_info) (admin_info, error)) { - a.handlers = append(a.handlers, admin_handlerInfo{name, args, handler}) +// AddHandler is called for each admin function to add the handler and help documentation to the API. +func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc func(Info) (Info, error)) error { + if _, ok := a.handlers[strings.ToLower(name)]; ok { + return errors.New("handler already exists") + } + a.handlers[strings.ToLower(name)] = handler{ + args: args, + handler: handlerfunc, + } + return nil } // init runs the initial admin setup. @@ -55,6 +54,7 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. a.core = c a.log = log a.reconfigure = make(chan chan error, 1) + a.handlers = make(map[string]handler) go func() { for { e := <-a.reconfigure @@ -69,21 +69,23 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. }() current, _ := state.Get() a.listenaddr = current.AdminListen - a.addHandler("list", []string{}, func(in admin_info) (admin_info, error) { + a.AddHandler("list", []string{}, func(in Info) (Info, error) { handlers := make(map[string]interface{}) - for _, handler := range a.handlers { - handlers[handler.name] = admin_info{"fields": handler.args} + for handlername, handler := range a.handlers { + handlers[handlername] = Info{"fields": handler.args} } - return admin_info{"list": handlers}, nil + return Info{"list": handlers}, nil }) - /*a.addHandler("dot", []string{}, func(in admin_info) (admin_info, error) { - return admin_info{"dot": string(a.getResponse_dot())}, nil - })*/ - a.addHandler("getSelf", []string{}, func(in admin_info) (admin_info, error) { + /* + a.AddHandler("dot", []string{}, func(in Info) (Info, error) { + return Info{"dot": string(a.getResponse_dot())}, nil + }) + */ + a.AddHandler("getSelf", []string{}, func(in Info) (Info, error) { ip := c.Address().String() - return admin_info{ - "self": admin_info{ - ip: admin_info{ + return Info{ + "self": Info{ + ip: Info{ "box_pub_key": c.BoxPubKey(), "build_name": yggdrasil.BuildName(), "build_version": yggdrasil.BuildVersion(), @@ -93,12 +95,12 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. }, }, nil }) - a.addHandler("getPeers", []string{}, func(in admin_info) (admin_info, error) { - peers := make(admin_info) + a.AddHandler("getPeers", []string{}, func(in Info) (Info, error) { + peers := make(Info) for _, p := range a.core.GetPeers() { addr := *address.AddrForNodeID(crypto.GetNodeID(&p.PublicKey)) so := net.IP(addr[:]).String() - peers[so] = admin_info{ + peers[so] = Info{ "ip": so, "port": p.Port, "uptime": p.Uptime.Seconds(), @@ -109,14 +111,14 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. "box_pub_key": p.PublicKey, } } - return admin_info{"peers": peers}, nil + return Info{"peers": peers}, nil }) - a.addHandler("getSwitchPeers", []string{}, func(in admin_info) (admin_info, error) { - switchpeers := make(admin_info) + a.AddHandler("getSwitchPeers", []string{}, func(in Info) (Info, error) { + switchpeers := make(Info) for _, s := range a.core.GetSwitchPeers() { addr := *address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey)) so := fmt.Sprint(s.Port) - switchpeers[so] = admin_info{ + switchpeers[so] = Info{ "ip": net.IP(addr[:]).String(), "coords": fmt.Sprintf("%v", s.Coords), "port": s.Port, @@ -127,31 +129,33 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. "box_pub_key": s.PublicKey, } } - return admin_info{"switchpeers": switchpeers}, nil + return Info{"switchpeers": switchpeers}, nil }) - /*a.addHandler("getSwitchQueues", []string{}, func(in admin_info) (admin_info, error) { - queues := a.core.GetSwitchQueues() - return admin_info{"switchqueues": queues.asMap()}, nil - })*/ - a.addHandler("getDHT", []string{}, func(in admin_info) (admin_info, error) { - dht := make(admin_info) + /* + a.AddHandler("getSwitchQueues", []string{}, func(in Info) (Info, error) { + queues := a.core.GetSwitchQueues() + return Info{"switchqueues": queues.asMap()}, nil + }) + */ + a.AddHandler("getDHT", []string{}, func(in Info) (Info, error) { + dht := make(Info) for _, d := range a.core.GetDHT() { addr := *address.AddrForNodeID(crypto.GetNodeID(&d.PublicKey)) so := net.IP(addr[:]).String() - dht[so] = admin_info{ + dht[so] = Info{ "coords": fmt.Sprintf("%v", d.Coords), "last_seen": d.LastSeen.Seconds(), "box_pub_key": d.PublicKey, } } - return admin_info{"dht": dht}, nil + return Info{"dht": dht}, nil }) - a.addHandler("getSessions", []string{}, func(in admin_info) (admin_info, error) { - sessions := make(admin_info) + a.AddHandler("getSessions", []string{}, func(in Info) (Info, error) { + sessions := make(Info) for _, s := range a.core.GetSessions() { addr := *address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey)) so := net.IP(addr[:]).String() - sessions[so] = admin_info{ + sessions[so] = Info{ "coords": fmt.Sprintf("%v", s.Coords), "bytes_sent": s.BytesSent, "bytes_recvd": s.BytesRecvd, @@ -160,267 +164,134 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. "box_pub_key": s.PublicKey, } } - return admin_info{"sessions": sessions}, nil + return Info{"sessions": sessions}, nil }) - /*a.addHandler("addPeer", []string{"uri", "[interface]"}, func(in admin_info) (admin_info, error) { - // Set sane defaults - intf := "" - // Has interface been specified? - if itf, ok := in["interface"]; ok { - intf = itf.(string) - } - if a.addPeer(in["uri"].(string), intf) == nil { - return admin_info{ - "added": []string{ - in["uri"].(string), - }, - }, nil - } else { - return admin_info{ - "not_added": []string{ - in["uri"].(string), - }, - }, errors.New("Failed to add peer") - } - }) - a.addHandler("removePeer", []string{"port"}, func(in admin_info) (admin_info, error) { - if a.removePeer(fmt.Sprint(in["port"])) == nil { - return admin_info{ - "removed": []string{ - fmt.Sprint(in["port"]), - }, - }, nil - } else { - return admin_info{ - "not_removed": []string{ - fmt.Sprint(in["port"]), - }, - }, errors.New("Failed to remove peer") - } - }) - a.addHandler("getTunTap", []string{}, func(in admin_info) (r admin_info, e error) { - defer func() { - if err := recover(); err != nil { - r = admin_info{"none": admin_info{}} - e = nil - } - }() - - return admin_info{ - a.core.router.tun.iface.Name(): admin_info{ - "tap_mode": a.core.router.tun.iface.IsTAP(), - "mtu": a.core.router.tun.mtu, - }, - }, nil - }) - a.addHandler("setTunTap", []string{"name", "[tap_mode]", "[mtu]"}, func(in admin_info) (admin_info, error) { + /* + a.AddHandler("addPeer", []string{"uri", "[interface]"}, func(in Info) (Info, error) { // Set sane defaults - iftapmode := defaults.GetDefaults().DefaultIfTAPMode - ifmtu := defaults.GetDefaults().DefaultIfMTU - // Has TAP mode been specified? - if tap, ok := in["tap_mode"]; ok { - iftapmode = tap.(bool) + intf := "" + // Has interface been specified? + if itf, ok := in["interface"]; ok { + intf = itf.(string) } - // Check we have enough params for MTU - if mtu, ok := in["mtu"]; ok { - if mtu.(float64) >= 1280 && ifmtu <= defaults.GetDefaults().MaximumIfMTU { - ifmtu = int(in["mtu"].(float64)) - } - } - // Start the TUN adapter - if err := a.startTunWithMTU(in["name"].(string), iftapmode, ifmtu); err != nil { - return admin_info{}, errors.New("Failed to configure adapter") - } else { - return admin_info{ - a.core.router.tun.iface.Name(): admin_info{ - "tap_mode": a.core.router.tun.iface.IsTAP(), - "mtu": ifmtu, + if a.addPeer(in["uri"].(string), intf) == nil { + return Info{ + "added": []string{ + in["uri"].(string), }, }, nil - } - })*/ - /*a.addHandler("getMulticastInterfaces", []string{}, func(in admin_info) (admin_info, error) { - var intfs []string - for _, v := range a.core.multicast.interfaces() { - intfs = append(intfs, v.Name) - } - return admin_info{"multicast_interfaces": intfs}, nil - }) - a.addHandler("getAllowedEncryptionPublicKeys", []string{}, func(in admin_info) (admin_info, error) { - return admin_info{"allowed_box_pubs": a.getAllowedEncryptionPublicKeys()}, nil - }) - a.addHandler("addAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in admin_info) (admin_info, error) { - if a.addAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { - return admin_info{ - "added": []string{ - in["box_pub_key"].(string), - }, - }, nil - } else { - return admin_info{ - "not_added": []string{ - in["box_pub_key"].(string), - }, - }, errors.New("Failed to add allowed key") - } - }) - a.addHandler("removeAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in admin_info) (admin_info, error) { - if a.removeAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { - return admin_info{ - "removed": []string{ - in["box_pub_key"].(string), - }, - }, nil - } else { - return admin_info{ - "not_removed": []string{ - in["box_pub_key"].(string), - }, - }, errors.New("Failed to remove allowed key") - } - }) - a.addHandler("getTunnelRouting", []string{}, func(in admin_info) (admin_info, error) { - enabled := false - a.core.router.doAdmin(func() { - enabled = a.core.router.cryptokey.isEnabled() - }) - return admin_info{"enabled": enabled}, nil - }) - a.addHandler("setTunnelRouting", []string{"enabled"}, func(in admin_info) (admin_info, error) { - enabled := false - if e, ok := in["enabled"].(bool); ok { - enabled = e - } - a.core.router.doAdmin(func() { - a.core.router.cryptokey.setEnabled(enabled) - }) - return admin_info{"enabled": enabled}, nil - }) - a.addHandler("addSourceSubnet", []string{"subnet"}, func(in admin_info) (admin_info, error) { - var err error - a.core.router.doAdmin(func() { - err = a.core.router.cryptokey.addSourceSubnet(in["subnet"].(string)) - }) - if err == nil { - return admin_info{"added": []string{in["subnet"].(string)}}, nil - } else { - return admin_info{"not_added": []string{in["subnet"].(string)}}, errors.New("Failed to add source subnet") - } - }) - a.addHandler("addRoute", []string{"subnet", "box_pub_key"}, func(in admin_info) (admin_info, error) { - var err error - a.core.router.doAdmin(func() { - err = a.core.router.cryptokey.addRoute(in["subnet"].(string), in["box_pub_key"].(string)) - }) - if err == nil { - return admin_info{"added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil - } else { - return admin_info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to add route") - } - }) - a.addHandler("getSourceSubnets", []string{}, func(in admin_info) (admin_info, error) { - var subnets []string - a.core.router.doAdmin(func() { - getSourceSubnets := func(snets []net.IPNet) { - for _, subnet := range snets { - subnets = append(subnets, subnet.String()) - } - } - getSourceSubnets(a.core.router.cryptokey.ipv4sources) - getSourceSubnets(a.core.router.cryptokey.ipv6sources) - }) - return admin_info{"source_subnets": subnets}, nil - }) - a.addHandler("getRoutes", []string{}, func(in admin_info) (admin_info, error) { - routes := make(admin_info) - a.core.router.doAdmin(func() { - getRoutes := func(ckrs []cryptokey_route) { - for _, ckr := range ckrs { - routes[ckr.subnet.String()] = hex.EncodeToString(ckr.destination[:]) - } - } - getRoutes(a.core.router.cryptokey.ipv4routes) - getRoutes(a.core.router.cryptokey.ipv6routes) - }) - return admin_info{"routes": routes}, nil - }) - a.addHandler("removeSourceSubnet", []string{"subnet"}, func(in admin_info) (admin_info, error) { - var err error - a.core.router.doAdmin(func() { - err = a.core.router.cryptokey.removeSourceSubnet(in["subnet"].(string)) - }) - if err == nil { - return admin_info{"removed": []string{in["subnet"].(string)}}, nil - } else { - return admin_info{"not_removed": []string{in["subnet"].(string)}}, errors.New("Failed to remove source subnet") - } - }) - a.addHandler("removeRoute", []string{"subnet", "box_pub_key"}, func(in admin_info) (admin_info, error) { - var err error - a.core.router.doAdmin(func() { - err = a.core.router.cryptokey.removeRoute(in["subnet"].(string), in["box_pub_key"].(string)) - }) - if err == nil { - return admin_info{"removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil - } else { - return admin_info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route") - } - }) - a.addHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in admin_info) (admin_info, error) { - if in["target"] == nil { - in["target"] = "none" - } - result, err := a.admin_dhtPing(in["box_pub_key"].(string), in["coords"].(string), in["target"].(string)) - if err == nil { - infos := make(map[string]map[string]string, len(result.Infos)) - for _, dinfo := range result.Infos { - info := map[string]string{ - "box_pub_key": hex.EncodeToString(dinfo.key[:]), - "coords": fmt.Sprintf("%v", dinfo.coords), - } - addr := net.IP(address.AddrForNodeID(crypto.GetNodeID(&dinfo.key))[:]).String() - infos[addr] = info - } - return admin_info{"nodes": infos}, nil - } else { - return admin_info{}, err - } - }) - a.addHandler("getNodeInfo", []string{"[box_pub_key]", "[coords]", "[nocache]"}, func(in admin_info) (admin_info, error) { - var nocache bool - if in["nocache"] != nil { - nocache = in["nocache"].(string) == "true" - } - var box_pub_key, coords string - if in["box_pub_key"] == nil && in["coords"] == nil { - var nodeinfo []byte - a.core.router.doAdmin(func() { - nodeinfo = []byte(a.core.router.nodeinfo.getNodeInfo()) - }) - var jsoninfo interface{} - if err := json.Unmarshal(nodeinfo, &jsoninfo); err != nil { - return admin_info{}, err } else { - return admin_info{"nodeinfo": jsoninfo}, nil + return Info{ + "not_added": []string{ + in["uri"].(string), + }, + }, errors.New("Failed to add peer") } - } else if in["box_pub_key"] == nil || in["coords"] == nil { - return admin_info{}, errors.New("Expecting both box_pub_key and coords") - } else { - box_pub_key = in["box_pub_key"].(string) - coords = in["coords"].(string) - } - result, err := a.admin_getNodeInfo(box_pub_key, coords, nocache) - if err == nil { - var m map[string]interface{} - if err = json.Unmarshal(result, &m); err == nil { - return admin_info{"nodeinfo": m}, nil + }) + a.AddHandler("removePeer", []string{"port"}, func(in Info) (Info, error) { + if a.removePeer(fmt.Sprint(in["port"])) == nil { + return Info{ + "removed": []string{ + fmt.Sprint(in["port"]), + }, + }, nil } else { - return admin_info{}, err + return Info{ + "not_removed": []string{ + fmt.Sprint(in["port"]), + }, + }, errors.New("Failed to remove peer") } - } else { - return admin_info{}, err - } - })*/ + }) + a.AddHandler("getAllowedEncryptionPublicKeys", []string{}, func(in Info) (Info, error) { + return Info{"allowed_box_pubs": a.getAllowedEncryptionPublicKeys()}, nil + }) + a.AddHandler("addAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) { + if a.addAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { + return Info{ + "added": []string{ + in["box_pub_key"].(string), + }, + }, nil + } else { + return Info{ + "not_added": []string{ + in["box_pub_key"].(string), + }, + }, errors.New("Failed to add allowed key") + } + }) + a.AddHandler("removeAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) { + if a.removeAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { + return Info{ + "removed": []string{ + in["box_pub_key"].(string), + }, + }, nil + } else { + return Info{ + "not_removed": []string{ + in["box_pub_key"].(string), + }, + }, errors.New("Failed to remove allowed key") + } + }) + a.AddHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in Info) (Info, error) { + if in["target"] == nil { + in["target"] = "none" + } + result, err := a.admin_dhtPing(in["box_pub_key"].(string), in["coords"].(string), in["target"].(string)) + if err == nil { + infos := make(map[string]map[string]string, len(result.Infos)) + for _, dinfo := range result.Infos { + info := map[string]string{ + "box_pub_key": hex.EncodeToString(dinfo.key[:]), + "coords": fmt.Sprintf("%v", dinfo.coords), + } + addr := net.IP(address.AddrForNodeID(crypto.GetNodeID(&dinfo.key))[:]).String() + infos[addr] = info + } + return Info{"nodes": infos}, nil + } else { + return Info{}, err + } + }) + a.AddHandler("getNodeInfo", []string{"[box_pub_key]", "[coords]", "[nocache]"}, func(in Info) (Info, error) { + var nocache bool + if in["nocache"] != nil { + nocache = in["nocache"].(string) == "true" + } + var box_pub_key, coords string + if in["box_pub_key"] == nil && in["coords"] == nil { + var nodeinfo []byte + a.core.router.doAdmin(func() { + nodeinfo = []byte(a.core.router.nodeinfo.getNodeInfo()) + }) + var jsoninfo interface{} + if err := json.Unmarshal(nodeinfo, &jsoninfo); err != nil { + return Info{}, err + } else { + return Info{"nodeinfo": jsoninfo}, nil + } + } else if in["box_pub_key"] == nil || in["coords"] == nil { + return Info{}, errors.New("Expecting both box_pub_key and coords") + } else { + box_pub_key = in["box_pub_key"].(string) + coords = in["coords"].(string) + } + result, err := a.admin_getNodeInfo(box_pub_key, coords, nocache) + if err == nil { + var m map[string]interface{} + if err = json.Unmarshal(result, &m); err == nil { + return Info{"nodeinfo": m}, nil + } else { + return Info{}, err + } + } else { + return Info{}, err + } + }) + */ } // start runs the admin API socket to listen for / respond to admin API calls. @@ -500,13 +371,13 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { decoder := json.NewDecoder(conn) encoder := json.NewEncoder(conn) encoder.SetIndent("", " ") - recv := make(admin_info) - send := make(admin_info) + recv := make(Info) + send := make(Info) defer func() { r := recover() if r != nil { - send = admin_info{ + send = Info{ "status": "error", "error": "Unrecoverable error, possibly as a result of invalid input types or malformed syntax", } @@ -520,8 +391,8 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { for { // Start with a clean slate on each request - recv = admin_info{} - send = admin_info{} + recv = Info{} + send = Info{} // Decode the input if err := decoder.Decode(&recv); err != nil { @@ -534,44 +405,46 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { send["request"] = recv send["status"] = "error" - handlers: - for _, handler := range a.handlers { - // We've found the handler that matches the request - if strings.ToLower(recv["request"].(string)) == strings.ToLower(handler.name) { - // Check that we have all the required arguments - for _, arg := range handler.args { - // An argument in [square brackets] is optional and not required, - // so we can safely ignore those - if strings.HasPrefix(arg, "[") && strings.HasSuffix(arg, "]") { - continue - } - // Check if the field is missing - if _, ok := recv[arg]; !ok { - send = admin_info{ - "status": "error", - "error": "Expected field missing: " + arg, - "expecting": arg, - } - break handlers - } - } + if _, ok := recv["request"]; !ok { + send["error"] = "No request sent" + break + } - // By this point we should have all the fields we need, so call - // the handler - response, err := handler.handler(recv) - if err != nil { - send["error"] = err.Error() - if response != nil { - send["response"] = response - } - } else { - send["status"] = "success" - if response != nil { - send["response"] = response - } - } + n := strings.ToLower(recv["request"].(string)) + if h, ok := a.handlers[strings.ToLower(n)]; ok { + fmt.Println("HANDLER FOUND", n, h) - break + // Check that we have all the required arguments + for _, arg := range h.args { + // An argument in [square brackets] is optional and not required, + // so we can safely ignore those + if strings.HasPrefix(arg, "[") && strings.HasSuffix(arg, "]") { + continue + } + // Check if the field is missing + if _, ok := recv[arg]; !ok { + send = Info{ + "status": "error", + "error": "Expected field missing: " + arg, + "expecting": arg, + } + break + } + } + + // By this point we should have all the fields we need, so call + // the handler + response, err := h.handler(recv) + if err != nil { + send["error"] = err.Error() + if response != nil { + send["response"] = response + } + } else { + send["status"] = "success" + if response != nil { + send["response"] = response + } } } @@ -587,55 +460,6 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { } } -// asMap converts an admin_nodeInfo into a map of key/value pairs. -func (n *admin_nodeInfo) asMap() map[string]interface{} { - m := make(map[string]interface{}, len(*n)) - for _, p := range *n { - m[p.key] = p.val - } - return m -} - -// toString creates a printable string representation of an admin_nodeInfo. -func (n *admin_nodeInfo) toString() string { - // TODO return something nicer looking than this - var out []string - for _, p := range *n { - out = append(out, fmt.Sprintf("%v: %v", p.key, p.val)) - } - return strings.Join(out, ", ") -} - -// printInfos returns a newline separated list of strings from admin_nodeInfos, e.g. a printable string of info about all peers. -func (a *AdminSocket) printInfos(infos []admin_nodeInfo) string { - var out []string - for _, info := range infos { - out = append(out, info.toString()) - } - out = append(out, "") // To add a trailing "\n" in the join - return strings.Join(out, "\n") -} - -/* -// addPeer triggers a connection attempt to a node. -func (a *AdminSocket) addPeer(addr string, sintf string) error { - err := a.core.link.call(addr, sintf) - if err != nil { - return err - } - return nil -} - -// removePeer disconnects an existing node (given by the node's port number). -func (a *AdminSocket) removePeer(p string) error { - iport, err := strconv.Atoi(p) - if err != nil { - return err - } - a.core.RemovePeer(iport) - return nil -} -*/ /* // Send a DHT ping to the node with the provided key and coords, optionally looking up the specified target NodeID. func (a *AdminSocket) admin_dhtPing(keyString, coordString, targetString string) (dhtRes, error) { @@ -740,21 +564,25 @@ func (a *AdminSocket) admin_getNodeInfo(keyString, coordString string, nocache b } return nodeinfoPayload{}, errors.New(fmt.Sprintf("getNodeInfo timeout: %s", keyString)) } +*/ -// getResponse_dot returns a response for a graphviz dot formatted representation of the known parts of the network. -// This is color-coded and labeled, and includes the self node, switch peers, nodes known to the DHT, and nodes with open sessions. -// The graph is structured as a tree with directed links leading away from the root. +// getResponse_dot returns a response for a graphviz dot formatted +// representation of the known parts of the network. This is color-coded and +// labeled, and includes the self node, switch peers, nodes known to the DHT, +// and nodes with open sessions. The graph is structured as a tree with directed +// links leading away from the root. +/* func (a *AdminSocket) getResponse_dot() []byte { - self := a.getData_getSelf() - peers := a.getData_getSwitchPeers() - dht := a.getData_getDHT() - sessions := a.getData_getSessions() + //self := a.getData_getSelf() + peers := a.core.GetSwitchPeers() + dht := a.core.GetDHT() + sessions := a.core.GetSessions() // Start building a tree from all known nodes type nodeInfo struct { name string key string parent string - port switchPort + port uint64 options string } infos := make(map[string]nodeInfo) @@ -782,7 +610,7 @@ func (a *AdminSocket) getResponse_dot() []byte { portStr := coordsSplit[len(coordsSplit)-1] portUint, err := strconv.ParseUint(portStr, 10, 64) if err == nil { - info.port = switchPort(portUint) + info.port = portUint } } infos[info.key] = info @@ -811,7 +639,7 @@ func (a *AdminSocket) getResponse_dot() []byte { portStr := coordsSplit[len(coordsSplit)-1] portUint, err := strconv.ParseUint(portStr, 10, 64) if err == nil { - newInfo.port = switchPort(portUint) + newInfo.port = portUint } } diff --git a/src/multicast/admin.go b/src/multicast/admin.go new file mode 100644 index 00000000..672b7ca4 --- /dev/null +++ b/src/multicast/admin.go @@ -0,0 +1,13 @@ +package multicast + +import "github.com/yggdrasil-network/yggdrasil-go/src/admin" + +func (m *Multicast) SetupAdminHandlers(a *admin.AdminSocket) { + a.AddHandler("getMulticastInterfaces", []string{}, func(in admin.Info) (admin.Info, error) { + var intfs []string + for _, v := range m.interfaces() { + intfs = append(intfs, v.Name) + } + return admin.Info{"multicast_interfaces": intfs}, nil + }) +} diff --git a/src/tuntap/admin.go b/src/tuntap/admin.go new file mode 100644 index 00000000..1d0b5876 --- /dev/null +++ b/src/tuntap/admin.go @@ -0,0 +1,136 @@ +package tuntap + +import "github.com/yggdrasil-network/yggdrasil-go/src/admin" + +func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { + a.AddHandler("getTunTap", []string{}, func(in admin.Info) (r admin.Info, e error) { + defer func() { + if err := recover(); err != nil { + r = admin.Info{"none": admin.Info{}} + e = nil + } + }() + + return admin.Info{ + t.iface.Name(): admin.Info{ + "tap_mode": t.iface.IsTAP(), + "mtu": t.mtu, + }, + }, nil + }) + /* + a.AddHandler("setTunTap", []string{"name", "[tap_mode]", "[mtu]"}, func(in Info) (Info, error) { + // Set sane defaults + iftapmode := defaults.GetDefaults().DefaultIfTAPMode + ifmtu := defaults.GetDefaults().DefaultIfMTU + // Has TAP mode been specified? + if tap, ok := in["tap_mode"]; ok { + iftapmode = tap.(bool) + } + // Check we have enough params for MTU + if mtu, ok := in["mtu"]; ok { + if mtu.(float64) >= 1280 && ifmtu <= defaults.GetDefaults().MaximumIfMTU { + ifmtu = int(in["mtu"].(float64)) + } + } + // Start the TUN adapter + if err := a.startTunWithMTU(in["name"].(string), iftapmode, ifmtu); err != nil { + return Info{}, errors.New("Failed to configure adapter") + } else { + return Info{ + a.core.router.tun.iface.Name(): Info{ + "tap_mode": a.core.router.tun.iface.IsTAP(), + "mtu": ifmtu, + }, + }, nil + } + }) + a.AddHandler("getTunnelRouting", []string{}, func(in Info) (Info, error) { + enabled := false + a.core.router.doAdmin(func() { + enabled = a.core.router.cryptokey.isEnabled() + }) + return Info{"enabled": enabled}, nil + }) + a.AddHandler("setTunnelRouting", []string{"enabled"}, func(in Info) (Info, error) { + enabled := false + if e, ok := in["enabled"].(bool); ok { + enabled = e + } + a.core.router.doAdmin(func() { + a.core.router.cryptokey.setEnabled(enabled) + }) + return Info{"enabled": enabled}, nil + }) + a.AddHandler("addSourceSubnet", []string{"subnet"}, func(in Info) (Info, error) { + var err error + a.core.router.doAdmin(func() { + err = a.core.router.cryptokey.addSourceSubnet(in["subnet"].(string)) + }) + if err == nil { + return Info{"added": []string{in["subnet"].(string)}}, nil + } else { + return Info{"not_added": []string{in["subnet"].(string)}}, errors.New("Failed to add source subnet") + } + }) + a.AddHandler("addRoute", []string{"subnet", "box_pub_key"}, func(in Info) (Info, error) { + var err error + a.core.router.doAdmin(func() { + err = a.core.router.cryptokey.addRoute(in["subnet"].(string), in["box_pub_key"].(string)) + }) + if err == nil { + return Info{"added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil + } else { + return Info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to add route") + } + }) + a.AddHandler("getSourceSubnets", []string{}, func(in Info) (Info, error) { + var subnets []string + a.core.router.doAdmin(func() { + getSourceSubnets := func(snets []net.IPNet) { + for _, subnet := range snets { + subnets = append(subnets, subnet.String()) + } + } + getSourceSubnets(a.core.router.cryptokey.ipv4sources) + getSourceSubnets(a.core.router.cryptokey.ipv6sources) + }) + return Info{"source_subnets": subnets}, nil + }) + a.AddHandler("getRoutes", []string{}, func(in Info) (Info, error) { + routes := make(Info) + a.core.router.doAdmin(func() { + getRoutes := func(ckrs []cryptokey_route) { + for _, ckr := range ckrs { + routes[ckr.subnet.String()] = hex.EncodeToString(ckr.destination[:]) + } + } + getRoutes(a.core.router.cryptokey.ipv4routes) + getRoutes(a.core.router.cryptokey.ipv6routes) + }) + return Info{"routes": routes}, nil + }) + a.AddHandler("removeSourceSubnet", []string{"subnet"}, func(in Info) (Info, error) { + var err error + a.core.router.doAdmin(func() { + err = a.core.router.cryptokey.removeSourceSubnet(in["subnet"].(string)) + }) + if err == nil { + return Info{"removed": []string{in["subnet"].(string)}}, nil + } else { + return Info{"not_removed": []string{in["subnet"].(string)}}, errors.New("Failed to remove source subnet") + } + }) + a.AddHandler("removeRoute", []string{"subnet", "box_pub_key"}, func(in Info) (Info, error) { + var err error + a.core.router.doAdmin(func() { + err = a.core.router.cryptokey.removeRoute(in["subnet"].(string), in["box_pub_key"].(string)) + }) + if err == nil { + return Info{"removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil + } else { + return Info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route") + } + }) + */ +} From e9e2d7bc6fffb7f29e86222d299106ec12d19f96 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 19 May 2019 22:03:20 +0100 Subject: [PATCH 0082/1109] Remove debug println --- src/admin/admin.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/admin/admin.go b/src/admin/admin.go index ff5476e4..7cf5f330 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -396,7 +396,7 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { // Decode the input if err := decoder.Decode(&recv); err != nil { - // fmt.Println("Admin socket JSON decode error:", err) + a.log.Debugln("Admin socket JSON decode error:", err) return } @@ -412,8 +412,6 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { n := strings.ToLower(recv["request"].(string)) if h, ok := a.handlers[strings.ToLower(n)]; ok { - fmt.Println("HANDLER FOUND", n, h) - // Check that we have all the required arguments for _, arg := range h.args { // An argument in [square brackets] is optional and not required, From 5b8d8a9341480e36f31c81104ecaa9f7564c9b53 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 20 May 2019 19:51:44 +0100 Subject: [PATCH 0083/1109] Reimplement getNodeInfo, dhtPing, get/add/removeAllowedEncryptionPublicKey, add/removePeer --- src/admin/admin.go | 351 +++++++++++++++---------------------------- src/yggdrasil/api.go | 178 +++++++++++++++++++++- 2 files changed, 293 insertions(+), 236 deletions(-) diff --git a/src/admin/admin.go b/src/admin/admin.go index 7cf5f330..27dabdcc 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -1,12 +1,14 @@ package admin import ( + "encoding/hex" "encoding/json" "errors" "fmt" "net" "net/url" "os" + "strconv" "strings" "time" @@ -166,132 +168,131 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. } return Info{"sessions": sessions}, nil }) - /* - a.AddHandler("addPeer", []string{"uri", "[interface]"}, func(in Info) (Info, error) { - // Set sane defaults - intf := "" - // Has interface been specified? - if itf, ok := in["interface"]; ok { - intf = itf.(string) - } - if a.addPeer(in["uri"].(string), intf) == nil { - return Info{ - "added": []string{ - in["uri"].(string), - }, - }, nil - } else { - return Info{ - "not_added": []string{ - in["uri"].(string), - }, - }, errors.New("Failed to add peer") - } - }) - a.AddHandler("removePeer", []string{"port"}, func(in Info) (Info, error) { - if a.removePeer(fmt.Sprint(in["port"])) == nil { - return Info{ - "removed": []string{ - fmt.Sprint(in["port"]), - }, - }, nil - } else { - return Info{ - "not_removed": []string{ - fmt.Sprint(in["port"]), - }, - }, errors.New("Failed to remove peer") - } - }) - a.AddHandler("getAllowedEncryptionPublicKeys", []string{}, func(in Info) (Info, error) { - return Info{"allowed_box_pubs": a.getAllowedEncryptionPublicKeys()}, nil - }) - a.AddHandler("addAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) { - if a.addAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { - return Info{ - "added": []string{ - in["box_pub_key"].(string), - }, - }, nil - } else { - return Info{ - "not_added": []string{ - in["box_pub_key"].(string), - }, - }, errors.New("Failed to add allowed key") - } - }) - a.AddHandler("removeAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) { - if a.removeAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { - return Info{ - "removed": []string{ - in["box_pub_key"].(string), - }, - }, nil - } else { - return Info{ - "not_removed": []string{ - in["box_pub_key"].(string), - }, - }, errors.New("Failed to remove allowed key") - } - }) - a.AddHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in Info) (Info, error) { - if in["target"] == nil { - in["target"] = "none" - } - result, err := a.admin_dhtPing(in["box_pub_key"].(string), in["coords"].(string), in["target"].(string)) - if err == nil { - infos := make(map[string]map[string]string, len(result.Infos)) - for _, dinfo := range result.Infos { - info := map[string]string{ - "box_pub_key": hex.EncodeToString(dinfo.key[:]), - "coords": fmt.Sprintf("%v", dinfo.coords), - } - addr := net.IP(address.AddrForNodeID(crypto.GetNodeID(&dinfo.key))[:]).String() - infos[addr] = info + a.AddHandler("addPeer", []string{"uri", "[interface]"}, func(in Info) (Info, error) { + // Set sane defaults + intf := "" + // Has interface been specified? + if itf, ok := in["interface"]; ok { + intf = itf.(string) + } + if a.core.AddPeer(in["uri"].(string), intf) == nil { + return Info{ + "added": []string{ + in["uri"].(string), + }, + }, nil + } else { + return Info{ + "not_added": []string{ + in["uri"].(string), + }, + }, errors.New("Failed to add peer") + } + }) + a.AddHandler("removePeer", []string{"port"}, func(in Info) (Info, error) { + port, err := strconv.ParseInt(fmt.Sprint(in["port"]), 10, 64) + if err != nil { + return Info{}, err + } + if a.core.DisconnectPeer(uint64(port)) == nil { + return Info{ + "removed": []string{ + fmt.Sprint(port), + }, + }, nil + } else { + return Info{ + "not_removed": []string{ + fmt.Sprint(port), + }, + }, errors.New("Failed to remove peer") + } + }) + a.AddHandler("getAllowedEncryptionPublicKeys", []string{}, func(in Info) (Info, error) { + return Info{"allowed_box_pubs": a.core.GetAllowedEncryptionPublicKeys()}, nil + }) + a.AddHandler("addAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) { + if a.core.AddAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { + return Info{ + "added": []string{ + in["box_pub_key"].(string), + }, + }, nil + } else { + return Info{ + "not_added": []string{ + in["box_pub_key"].(string), + }, + }, errors.New("Failed to add allowed key") + } + }) + a.AddHandler("removeAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) { + if a.core.RemoveAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { + return Info{ + "removed": []string{ + in["box_pub_key"].(string), + }, + }, nil + } else { + return Info{ + "not_removed": []string{ + in["box_pub_key"].(string), + }, + }, errors.New("Failed to remove allowed key") + } + }) + a.AddHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in Info) (Info, error) { + if in["target"] == nil { + in["target"] = "none" + } + result, err := a.core.DHTPing(in["box_pub_key"].(string), in["coords"].(string), in["target"].(string)) + if err == nil { + infos := make(map[string]map[string]string, len(result.Infos)) + for _, dinfo := range result.Infos { + info := map[string]string{ + "box_pub_key": hex.EncodeToString(dinfo.PublicKey[:]), + "coords": fmt.Sprintf("%v", dinfo.Coords), } - return Info{"nodes": infos}, nil + addr := net.IP(address.AddrForNodeID(crypto.GetNodeID(&dinfo.PublicKey))[:]).String() + infos[addr] = info + } + return Info{"nodes": infos}, nil + } else { + return Info{}, err + } + }) + a.AddHandler("getNodeInfo", []string{"[box_pub_key]", "[coords]", "[nocache]"}, func(in Info) (Info, error) { + var nocache bool + if in["nocache"] != nil { + nocache = in["nocache"].(string) == "true" + } + var box_pub_key, coords string + if in["box_pub_key"] == nil && in["coords"] == nil { + nodeinfo := a.core.MyNodeInfo() + var jsoninfo interface{} + if err := json.Unmarshal(nodeinfo, &jsoninfo); err != nil { + return Info{}, err + } else { + return Info{"nodeinfo": jsoninfo}, nil + } + } else if in["box_pub_key"] == nil || in["coords"] == nil { + return Info{}, errors.New("Expecting both box_pub_key and coords") + } else { + box_pub_key = in["box_pub_key"].(string) + coords = in["coords"].(string) + } + result, err := a.core.GetNodeInfo(box_pub_key, coords, nocache) + if err == nil { + var m map[string]interface{} + if err = json.Unmarshal(result, &m); err == nil { + return Info{"nodeinfo": m}, nil } else { return Info{}, err } - }) - a.AddHandler("getNodeInfo", []string{"[box_pub_key]", "[coords]", "[nocache]"}, func(in Info) (Info, error) { - var nocache bool - if in["nocache"] != nil { - nocache = in["nocache"].(string) == "true" - } - var box_pub_key, coords string - if in["box_pub_key"] == nil && in["coords"] == nil { - var nodeinfo []byte - a.core.router.doAdmin(func() { - nodeinfo = []byte(a.core.router.nodeinfo.getNodeInfo()) - }) - var jsoninfo interface{} - if err := json.Unmarshal(nodeinfo, &jsoninfo); err != nil { - return Info{}, err - } else { - return Info{"nodeinfo": jsoninfo}, nil - } - } else if in["box_pub_key"] == nil || in["coords"] == nil { - return Info{}, errors.New("Expecting both box_pub_key and coords") - } else { - box_pub_key = in["box_pub_key"].(string) - coords = in["coords"].(string) - } - result, err := a.admin_getNodeInfo(box_pub_key, coords, nocache) - if err == nil { - var m map[string]interface{} - if err = json.Unmarshal(result, &m); err == nil { - return Info{"nodeinfo": m}, nil - } else { - return Info{}, err - } - } else { - return Info{}, err - } - }) - */ + } else { + return Info{}, err + } + }) } // start runs the admin API socket to listen for / respond to admin API calls. @@ -458,112 +459,6 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { } } -/* -// Send a DHT ping to the node with the provided key and coords, optionally looking up the specified target NodeID. -func (a *AdminSocket) admin_dhtPing(keyString, coordString, targetString string) (dhtRes, error) { - var key crypto.BoxPubKey - if keyBytes, err := hex.DecodeString(keyString); err != nil { - return dhtRes{}, err - } else { - copy(key[:], keyBytes) - } - var coords []byte - for _, cstr := range strings.Split(strings.Trim(coordString, "[]"), " ") { - if cstr == "" { - // Special case, happens if trimmed is the empty string, e.g. this is the root - continue - } - if u64, err := strconv.ParseUint(cstr, 10, 8); err != nil { - return dhtRes{}, err - } else { - coords = append(coords, uint8(u64)) - } - } - resCh := make(chan *dhtRes, 1) - info := dhtInfo{ - key: key, - coords: coords, - } - target := *info.getNodeID() - if targetString == "none" { - // Leave the default target in place - } else if targetBytes, err := hex.DecodeString(targetString); err != nil { - return dhtRes{}, err - } else if len(targetBytes) != len(target) { - return dhtRes{}, errors.New("Incorrect target NodeID length") - } else { - var target crypto.NodeID - copy(target[:], targetBytes) - } - rq := dhtReqKey{info.key, target} - sendPing := func() { - a.core.dht.addCallback(&rq, func(res *dhtRes) { - defer func() { recover() }() - select { - case resCh <- res: - default: - } - }) - a.core.dht.ping(&info, &target) - } - a.core.router.doAdmin(sendPing) - go func() { - time.Sleep(6 * time.Second) - close(resCh) - }() - for res := range resCh { - return *res, nil - } - return dhtRes{}, errors.New(fmt.Sprintf("DHT ping timeout: %s", keyString)) -} - -func (a *AdminSocket) admin_getNodeInfo(keyString, coordString string, nocache bool) (nodeinfoPayload, error) { - var key crypto.BoxPubKey - if keyBytes, err := hex.DecodeString(keyString); err != nil { - return nodeinfoPayload{}, err - } else { - copy(key[:], keyBytes) - } - if !nocache { - if response, err := a.core.router.nodeinfo.getCachedNodeInfo(key); err == nil { - return response, nil - } - } - var coords []byte - for _, cstr := range strings.Split(strings.Trim(coordString, "[]"), " ") { - if cstr == "" { - // Special case, happens if trimmed is the empty string, e.g. this is the root - continue - } - if u64, err := strconv.ParseUint(cstr, 10, 8); err != nil { - return nodeinfoPayload{}, err - } else { - coords = append(coords, uint8(u64)) - } - } - response := make(chan *nodeinfoPayload, 1) - sendNodeInfoRequest := func() { - a.core.router.nodeinfo.addCallback(key, func(nodeinfo *nodeinfoPayload) { - defer func() { recover() }() - select { - case response <- nodeinfo: - default: - } - }) - a.core.router.nodeinfo.sendNodeInfo(key, coords, false) - } - a.core.router.doAdmin(sendNodeInfoRequest) - go func() { - time.Sleep(6 * time.Second) - close(response) - }() - for res := range response { - return *res, nil - } - return nodeinfoPayload{}, errors.New(fmt.Sprintf("getNodeInfo timeout: %s", keyString)) -} -*/ - // getResponse_dot returns a response for a graphviz dot formatted // representation of the known parts of the network. This is color-coded and // labeled, and includes the self node, switch peers, nodes known to the DHT, diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index e7050c7b..40e5a2b2 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -3,8 +3,11 @@ package yggdrasil import ( "encoding/hex" "errors" + "fmt" "net" "sort" + "strconv" + "strings" "sync/atomic" "time" @@ -47,6 +50,17 @@ type DHTEntry struct { LastSeen time.Duration } +// DHTRes represents a DHT response, as returned by DHTPing. +type DHTRes struct { + PublicKey crypto.BoxPubKey // key of the sender + Coords []byte // coords of the sender + Dest crypto.NodeID // the destination node ID + Infos []DHTEntry // response +} + +// NodeInfoPayload represents a RequestNodeInfo response, in bytes. +type NodeInfoPayload nodeinfoPayload + // SwitchQueues represents information from the switch related to link // congestion and a list of switch queues created in response to congestion on a // given link. @@ -123,7 +137,7 @@ func (c *Core) GetSwitchPeers() []SwitchPeer { } coords := elem.locator.getCoords() info := SwitchPeer{ - Coords: coords, + Coords: append([]byte{}, coords...), BytesSent: atomic.LoadUint64(&peer.bytesSent), BytesRecvd: atomic.LoadUint64(&peer.bytesRecvd), Port: uint64(elem.port), @@ -151,7 +165,7 @@ func (c *Core) GetDHT() []DHTEntry { }) for _, v := range dhtentry { info := DHTEntry{ - Coords: v.coords, + Coords: append([]byte{}, v.coords...), LastSeen: now.Sub(v.recv), } copy(info.PublicKey[:], v.key[:]) @@ -198,7 +212,7 @@ func (c *Core) GetSessions() []Session { for _, sinfo := range c.sessions.sinfos { // TODO? skipped known but timed out sessions? session := Session{ - Coords: sinfo.coords, + Coords: append([]byte{}, sinfo.coords...), MTU: sinfo.getMTU(), BytesSent: sinfo.bytesSent, BytesRecvd: sinfo.bytesRecvd, @@ -319,7 +333,7 @@ func (c *Core) RouterAddresses() (address.Address, address.Subnet) { } // NodeInfo gets the currently configured nodeinfo. -func (c *Core) NodeInfo() nodeinfoPayload { +func (c *Core) MyNodeInfo() nodeinfoPayload { return c.router.nodeinfo.getNodeInfo() } @@ -329,6 +343,56 @@ func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) { c.router.nodeinfo.setNodeInfo(nodeinfo, nodeinfoprivacy) } +// GetNodeInfo requests nodeinfo from a remote node, as specified by the public +// key and coordinates specified. The third parameter specifies whether a cached +// result is acceptable - this results in less traffic being generated than is +// necessary when, e.g. crawling the network. +func (c *Core) GetNodeInfo(keyString, coordString string, nocache bool) (NodeInfoPayload, error) { + var key crypto.BoxPubKey + if keyBytes, err := hex.DecodeString(keyString); err != nil { + return NodeInfoPayload{}, err + } else { + copy(key[:], keyBytes) + } + if !nocache { + if response, err := c.router.nodeinfo.getCachedNodeInfo(key); err == nil { + return NodeInfoPayload(response), nil + } + } + var coords []byte + for _, cstr := range strings.Split(strings.Trim(coordString, "[]"), " ") { + if cstr == "" { + // Special case, happens if trimmed is the empty string, e.g. this is the root + continue + } + if u64, err := strconv.ParseUint(cstr, 10, 8); err != nil { + return NodeInfoPayload{}, err + } else { + coords = append(coords, uint8(u64)) + } + } + response := make(chan *nodeinfoPayload, 1) + sendNodeInfoRequest := func() { + c.router.nodeinfo.addCallback(key, func(nodeinfo *nodeinfoPayload) { + defer func() { recover() }() + select { + case response <- nodeinfo: + default: + } + }) + c.router.nodeinfo.sendNodeInfo(key, coords, false) + } + c.router.doAdmin(sendNodeInfoRequest) + go func() { + time.Sleep(6 * time.Second) + close(response) + }() + for res := range response { + return NodeInfoPayload(*res), nil + } + return NodeInfoPayload{}, errors.New(fmt.Sprintf("getNodeInfo timeout: %s", keyString)) +} + // SetLogger sets the output logger of the Yggdrasil node after startup. This // may be useful if you want to redirect the output later. func (c *Core) SetLogger(log *log.Logger) { @@ -354,6 +418,14 @@ func (c *Core) AddPeer(addr string, sintf string) error { return nil } +// RemovePeer is not implemented yet. +func (c *Core) RemovePeer(addr string, sintf string) error { + // TODO: Implement a reverse of AddPeer, where we look up the port number + // based on the addr and sintf, disconnect it and then remove it from the + // peers list so we don't reconnect to it later + return errors.New("not implemented") +} + // CallPeer calls a peer once. This should be specified in the peer URI format, // e.g.: // tcp://a.b.c.d:e @@ -364,9 +436,99 @@ func (c *Core) CallPeer(addr string, sintf string) error { return c.link.call(addr, sintf) } -// AddAllowedEncryptionPublicKey adds an allowed public key. This allow peerings -// to be restricted only to keys that you have selected. -func (c *Core) AddAllowedEncryptionPublicKey(boxStr string) error { - //return c.admin.addAllowedEncryptionPublicKey(boxStr) +// DisconnectPeer disconnects a peer once. This should be specified as a port +// number. +func (c *Core) DisconnectPeer(port uint64) error { + c.peers.removePeer(switchPort(port)) return nil } + +// GetAllowedEncryptionPublicKeys returns the public keys permitted for incoming +// peer connections. +func (c *Core) GetAllowedEncryptionPublicKeys() []string { + return c.peers.getAllowedEncryptionPublicKeys() +} + +// AddAllowedEncryptionPublicKey whitelists a key for incoming peer connections. +func (c *Core) AddAllowedEncryptionPublicKey(bstr string) (err error) { + c.peers.addAllowedEncryptionPublicKey(bstr) + return nil +} + +// RemoveAllowedEncryptionPublicKey removes a key from the whitelist for +// incoming peer connections. If none are set, an empty list permits all +// incoming connections. +func (c *Core) RemoveAllowedEncryptionPublicKey(bstr string) (err error) { + c.peers.removeAllowedEncryptionPublicKey(bstr) + return nil +} + +// Send a DHT ping to the node with the provided key and coords, optionally looking up the specified target NodeID. +func (c *Core) DHTPing(keyString, coordString, targetString string) (DHTRes, error) { + var key crypto.BoxPubKey + if keyBytes, err := hex.DecodeString(keyString); err != nil { + return DHTRes{}, err + } else { + copy(key[:], keyBytes) + } + var coords []byte + for _, cstr := range strings.Split(strings.Trim(coordString, "[]"), " ") { + if cstr == "" { + // Special case, happens if trimmed is the empty string, e.g. this is the root + continue + } + if u64, err := strconv.ParseUint(cstr, 10, 8); err != nil { + return DHTRes{}, err + } else { + coords = append(coords, uint8(u64)) + } + } + resCh := make(chan *dhtRes, 1) + info := dhtInfo{ + key: key, + coords: coords, + } + target := *info.getNodeID() + if targetString == "none" { + // Leave the default target in place + } else if targetBytes, err := hex.DecodeString(targetString); err != nil { + return DHTRes{}, err + } else if len(targetBytes) != len(target) { + return DHTRes{}, errors.New("Incorrect target NodeID length") + } else { + var target crypto.NodeID + copy(target[:], targetBytes) + } + rq := dhtReqKey{info.key, target} + sendPing := func() { + c.dht.addCallback(&rq, func(res *dhtRes) { + defer func() { recover() }() + select { + case resCh <- res: + default: + } + }) + c.dht.ping(&info, &target) + } + c.router.doAdmin(sendPing) + go func() { + time.Sleep(6 * time.Second) + close(resCh) + }() + // TODO: do something better than the below... + for res := range resCh { + r := DHTRes{ + Coords: append([]byte{}, res.Coords...), + } + copy(r.PublicKey[:], res.Key[:]) + for _, i := range res.Infos { + e := DHTEntry{ + Coords: append([]byte{}, i.coords...), + } + copy(e.PublicKey[:], i.key[:]) + r.Infos = append(r.Infos, e) + } + return r, nil + } + return DHTRes{}, errors.New(fmt.Sprintf("DHT ping timeout: %s", keyString)) +} From 70774fc3de52b2f72ba6b771a92ed9ff419ba536 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 20 May 2019 21:45:33 +0100 Subject: [PATCH 0084/1109] Reimplement get/setTunnelRouting, add/removeSourceSubnet, add/removeRoute, getRoutes, getSourceSubnets, make CKR threadsafe --- src/tuntap/admin.go | 209 ++++++++++++++++++++------------------------ src/tuntap/ckr.go | 76 +++++++++++++--- 2 files changed, 158 insertions(+), 127 deletions(-) diff --git a/src/tuntap/admin.go b/src/tuntap/admin.go index 1d0b5876..21c7048d 100644 --- a/src/tuntap/admin.go +++ b/src/tuntap/admin.go @@ -1,6 +1,13 @@ package tuntap -import "github.com/yggdrasil-network/yggdrasil-go/src/admin" +import ( + "encoding/hex" + "errors" + "fmt" + "net" + + "github.com/yggdrasil-network/yggdrasil-go/src/admin" +) func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { a.AddHandler("getTunTap", []string{}, func(in admin.Info) (r admin.Info, e error) { @@ -19,118 +26,94 @@ func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { }, nil }) /* - a.AddHandler("setTunTap", []string{"name", "[tap_mode]", "[mtu]"}, func(in Info) (Info, error) { - // Set sane defaults - iftapmode := defaults.GetDefaults().DefaultIfTAPMode - ifmtu := defaults.GetDefaults().DefaultIfMTU - // Has TAP mode been specified? - if tap, ok := in["tap_mode"]; ok { - iftapmode = tap.(bool) - } - // Check we have enough params for MTU - if mtu, ok := in["mtu"]; ok { - if mtu.(float64) >= 1280 && ifmtu <= defaults.GetDefaults().MaximumIfMTU { - ifmtu = int(in["mtu"].(float64)) - } - } - // Start the TUN adapter - if err := a.startTunWithMTU(in["name"].(string), iftapmode, ifmtu); err != nil { - return Info{}, errors.New("Failed to configure adapter") - } else { - return Info{ - a.core.router.tun.iface.Name(): Info{ - "tap_mode": a.core.router.tun.iface.IsTAP(), - "mtu": ifmtu, - }, - }, nil - } - }) - a.AddHandler("getTunnelRouting", []string{}, func(in Info) (Info, error) { - enabled := false - a.core.router.doAdmin(func() { - enabled = a.core.router.cryptokey.isEnabled() - }) - return Info{"enabled": enabled}, nil - }) - a.AddHandler("setTunnelRouting", []string{"enabled"}, func(in Info) (Info, error) { - enabled := false - if e, ok := in["enabled"].(bool); ok { - enabled = e + // TODO: rewrite this as I'm fairly sure it doesn't work right on many + // platforms anyway, but it may require changes to Water + a.AddHandler("setTunTap", []string{"name", "[tap_mode]", "[mtu]"}, func(in Info) (Info, error) { + // Set sane defaults + iftapmode := defaults.GetDefaults().DefaultIfTAPMode + ifmtu := defaults.GetDefaults().DefaultIfMTU + // Has TAP mode been specified? + if tap, ok := in["tap_mode"]; ok { + iftapmode = tap.(bool) + } + // Check we have enough params for MTU + if mtu, ok := in["mtu"]; ok { + if mtu.(float64) >= 1280 && ifmtu <= defaults.GetDefaults().MaximumIfMTU { + ifmtu = int(in["mtu"].(float64)) } - a.core.router.doAdmin(func() { - a.core.router.cryptokey.setEnabled(enabled) - }) - return Info{"enabled": enabled}, nil - }) - a.AddHandler("addSourceSubnet", []string{"subnet"}, func(in Info) (Info, error) { - var err error - a.core.router.doAdmin(func() { - err = a.core.router.cryptokey.addSourceSubnet(in["subnet"].(string)) - }) - if err == nil { - return Info{"added": []string{in["subnet"].(string)}}, nil - } else { - return Info{"not_added": []string{in["subnet"].(string)}}, errors.New("Failed to add source subnet") - } - }) - a.AddHandler("addRoute", []string{"subnet", "box_pub_key"}, func(in Info) (Info, error) { - var err error - a.core.router.doAdmin(func() { - err = a.core.router.cryptokey.addRoute(in["subnet"].(string), in["box_pub_key"].(string)) - }) - if err == nil { - return Info{"added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil - } else { - return Info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to add route") - } - }) - a.AddHandler("getSourceSubnets", []string{}, func(in Info) (Info, error) { - var subnets []string - a.core.router.doAdmin(func() { - getSourceSubnets := func(snets []net.IPNet) { - for _, subnet := range snets { - subnets = append(subnets, subnet.String()) - } - } - getSourceSubnets(a.core.router.cryptokey.ipv4sources) - getSourceSubnets(a.core.router.cryptokey.ipv6sources) - }) - return Info{"source_subnets": subnets}, nil - }) - a.AddHandler("getRoutes", []string{}, func(in Info) (Info, error) { - routes := make(Info) - a.core.router.doAdmin(func() { - getRoutes := func(ckrs []cryptokey_route) { - for _, ckr := range ckrs { - routes[ckr.subnet.String()] = hex.EncodeToString(ckr.destination[:]) - } - } - getRoutes(a.core.router.cryptokey.ipv4routes) - getRoutes(a.core.router.cryptokey.ipv6routes) - }) - return Info{"routes": routes}, nil - }) - a.AddHandler("removeSourceSubnet", []string{"subnet"}, func(in Info) (Info, error) { - var err error - a.core.router.doAdmin(func() { - err = a.core.router.cryptokey.removeSourceSubnet(in["subnet"].(string)) - }) - if err == nil { - return Info{"removed": []string{in["subnet"].(string)}}, nil - } else { - return Info{"not_removed": []string{in["subnet"].(string)}}, errors.New("Failed to remove source subnet") - } - }) - a.AddHandler("removeRoute", []string{"subnet", "box_pub_key"}, func(in Info) (Info, error) { - var err error - a.core.router.doAdmin(func() { - err = a.core.router.cryptokey.removeRoute(in["subnet"].(string), in["box_pub_key"].(string)) - }) - if err == nil { - return Info{"removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil - } else { - return Info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route") - } - }) + } + // Start the TUN adapter + if err := a.startTunWithMTU(in["name"].(string), iftapmode, ifmtu); err != nil { + return Info{}, errors.New("Failed to configure adapter") + } else { + return Info{ + a.core.router.tun.iface.Name(): Info{ + "tap_mode": a.core.router.tun.iface.IsTAP(), + "mtu": ifmtu, + }, + }, nil + } + }) */ + a.AddHandler("getTunnelRouting", []string{}, func(in admin.Info) (admin.Info, error) { + return admin.Info{"enabled": t.ckr.isEnabled()}, nil + }) + a.AddHandler("setTunnelRouting", []string{"enabled"}, func(in admin.Info) (admin.Info, error) { + enabled := false + if e, ok := in["enabled"].(bool); ok { + enabled = e + } + t.ckr.setEnabled(enabled) + return admin.Info{"enabled": enabled}, nil + }) + a.AddHandler("addSourceSubnet", []string{"subnet"}, func(in admin.Info) (admin.Info, error) { + if err := t.ckr.addSourceSubnet(in["subnet"].(string)); err == nil { + return admin.Info{"added": []string{in["subnet"].(string)}}, nil + } else { + return admin.Info{"not_added": []string{in["subnet"].(string)}}, errors.New("Failed to add source subnet") + } + }) + a.AddHandler("addRoute", []string{"subnet", "box_pub_key"}, func(in admin.Info) (admin.Info, error) { + if err := t.ckr.addRoute(in["subnet"].(string), in["box_pub_key"].(string)); err == nil { + return admin.Info{"added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil + } else { + return admin.Info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to add route") + } + }) + a.AddHandler("getSourceSubnets", []string{}, func(in admin.Info) (admin.Info, error) { + var subnets []string + getSourceSubnets := func(snets []net.IPNet) { + for _, subnet := range snets { + subnets = append(subnets, subnet.String()) + } + } + getSourceSubnets(t.ckr.ipv4sources) + getSourceSubnets(t.ckr.ipv6sources) + return admin.Info{"source_subnets": subnets}, nil + }) + a.AddHandler("getRoutes", []string{}, func(in admin.Info) (admin.Info, error) { + routes := make(admin.Info) + getRoutes := func(ckrs []cryptokey_route) { + for _, ckr := range ckrs { + routes[ckr.subnet.String()] = hex.EncodeToString(ckr.destination[:]) + } + } + getRoutes(t.ckr.ipv4routes) + getRoutes(t.ckr.ipv6routes) + return admin.Info{"routes": routes}, nil + }) + a.AddHandler("removeSourceSubnet", []string{"subnet"}, func(in admin.Info) (admin.Info, error) { + if err := t.ckr.removeSourceSubnet(in["subnet"].(string)); err == nil { + return admin.Info{"removed": []string{in["subnet"].(string)}}, nil + } else { + return admin.Info{"not_removed": []string{in["subnet"].(string)}}, errors.New("Failed to remove source subnet") + } + }) + a.AddHandler("removeRoute", []string{"subnet", "box_pub_key"}, func(in admin.Info) (admin.Info, error) { + if err := t.ckr.removeRoute(in["subnet"].(string), in["box_pub_key"].(string)); err == nil { + return admin.Info{"removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil + } else { + return admin.Info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route") + } + }) } diff --git a/src/tuntap/ckr.go b/src/tuntap/ckr.go index 971f0a35..c996c397 100644 --- a/src/tuntap/ckr.go +++ b/src/tuntap/ckr.go @@ -7,6 +7,8 @@ import ( "fmt" "net" "sort" + "sync" + "sync/atomic" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" @@ -16,15 +18,18 @@ import ( // allow traffic for non-Yggdrasil ranges to be routed over Yggdrasil. type cryptokey struct { - tun *TunAdapter - enabled bool - reconfigure chan chan error - ipv4routes []cryptokey_route - ipv6routes []cryptokey_route - ipv4cache map[address.Address]cryptokey_route - ipv6cache map[address.Address]cryptokey_route - ipv4sources []net.IPNet - ipv6sources []net.IPNet + tun *TunAdapter + enabled atomic.Value // bool + reconfigure chan chan error + ipv4routes []cryptokey_route + ipv6routes []cryptokey_route + ipv4cache map[address.Address]cryptokey_route + ipv6cache map[address.Address]cryptokey_route + ipv4sources []net.IPNet + ipv6sources []net.IPNet + mutexroutes sync.RWMutex + mutexcaches sync.RWMutex + mutexsources sync.RWMutex } type cryptokey_route struct { @@ -58,8 +63,10 @@ func (c *cryptokey) configure() error { c.setEnabled(c.tun.config.Current.TunnelRouting.Enable) // Clear out existing routes + c.mutexroutes.Lock() c.ipv6routes = make([]cryptokey_route, 0) c.ipv4routes = make([]cryptokey_route, 0) + c.mutexroutes.Unlock() // Add IPv6 routes for ipv6, pubkey := range c.tun.config.Current.TunnelRouting.IPv6Destinations { @@ -76,8 +83,10 @@ func (c *cryptokey) configure() error { } // Clear out existing sources + c.mutexsources.Lock() c.ipv6sources = make([]net.IPNet, 0) c.ipv4sources = make([]net.IPNet, 0) + c.mutexsources.Unlock() // Add IPv6 sources c.ipv6sources = make([]net.IPNet, 0) @@ -96,26 +105,31 @@ func (c *cryptokey) configure() error { } // Wipe the caches + c.mutexcaches.Lock() c.ipv4cache = make(map[address.Address]cryptokey_route, 0) c.ipv6cache = make(map[address.Address]cryptokey_route, 0) + c.mutexcaches.Unlock() return nil } // Enable or disable crypto-key routing. func (c *cryptokey) setEnabled(enabled bool) { - c.enabled = enabled + c.enabled.Store(true) } // Check if crypto-key routing is enabled. func (c *cryptokey) isEnabled() bool { - return c.enabled + return c.enabled.Load().(bool) } // Check whether the given address (with the address length specified in bytes) // matches either the current node's address, the node's routed subnet or the // list of subnets specified in IPv4Sources/IPv6Sources. func (c *cryptokey) isValidSource(addr address.Address, addrlen int) bool { + c.mutexsources.RLock() + defer c.mutexsources.RUnlock() + ip := net.IP(addr[:addrlen]) if addrlen == net.IPv6len { @@ -158,6 +172,9 @@ func (c *cryptokey) isValidSource(addr address.Address, addrlen int) bool { // Adds a source subnet, which allows traffic with these source addresses to // be tunnelled using crypto-key routing. func (c *cryptokey) addSourceSubnet(cidr string) error { + c.mutexsources.Lock() + defer c.mutexsources.Unlock() + // Is the CIDR we've been given valid? _, ipnet, err := net.ParseCIDR(cidr) if err != nil { @@ -195,6 +212,11 @@ func (c *cryptokey) addSourceSubnet(cidr string) error { // Adds a destination route for the given CIDR to be tunnelled to the node // with the given BoxPubKey. func (c *cryptokey) addRoute(cidr string, dest string) error { + c.mutexroutes.Lock() + c.mutexcaches.Lock() + defer c.mutexroutes.Unlock() + defer c.mutexcaches.Unlock() + // Is the CIDR we've been given valid? ipaddr, ipnet, err := net.ParseCIDR(cidr) if err != nil { @@ -269,6 +291,8 @@ func (c *cryptokey) addRoute(cidr string, dest string) error { // length specified in bytes) from the crypto-key routing table. An error is // returned if the address is not suitable or no route was found. func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (crypto.BoxPubKey, error) { + c.mutexcaches.RLock() + // Check if the address is a valid Yggdrasil address - if so it // is exempt from all CKR checking if addr.IsValid() { @@ -281,10 +305,8 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c // Check if the prefix is IPv4 or IPv6 if addrlen == net.IPv6len { - routingtable = &c.ipv6routes routingcache = &c.ipv6cache } else if addrlen == net.IPv4len { - routingtable = &c.ipv4routes routingcache = &c.ipv4cache } else { return crypto.BoxPubKey{}, errors.New("Unexpected prefix size") @@ -292,9 +314,24 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c // Check if there's a cache entry for this addr if route, ok := (*routingcache)[addr]; ok { + c.mutexcaches.RUnlock() return route.destination, nil } + c.mutexcaches.RUnlock() + + c.mutexroutes.RLock() + defer c.mutexroutes.RUnlock() + + // Check if the prefix is IPv4 or IPv6 + if addrlen == net.IPv6len { + routingtable = &c.ipv6routes + } else if addrlen == net.IPv4len { + routingtable = &c.ipv4routes + } else { + return crypto.BoxPubKey{}, errors.New("Unexpected prefix size") + } + // No cache was found - start by converting the address into a net.IP ip := make(net.IP, addrlen) copy(ip[:addrlen], addr[:]) @@ -304,6 +341,9 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c for _, route := range *routingtable { // Does this subnet match the given IP? if route.subnet.Contains(ip) { + c.mutexcaches.Lock() + defer c.mutexcaches.Unlock() + // Check if the routing cache is above a certain size, if it is evict // a random entry so we can make room for this one. We take advantage // of the fact that the iteration order is random here @@ -329,6 +369,9 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c // Removes a source subnet, which allows traffic with these source addresses to // be tunnelled using crypto-key routing. func (c *cryptokey) removeSourceSubnet(cidr string) error { + c.mutexsources.Lock() + defer c.mutexsources.Unlock() + // Is the CIDR we've been given valid? _, ipnet, err := net.ParseCIDR(cidr) if err != nil { @@ -364,6 +407,11 @@ func (c *cryptokey) removeSourceSubnet(cidr string) error { // Removes a destination route for the given CIDR to be tunnelled to the node // with the given BoxPubKey. func (c *cryptokey) removeRoute(cidr string, dest string) error { + c.mutexroutes.Lock() + c.mutexcaches.Lock() + defer c.mutexroutes.Unlock() + defer c.mutexcaches.Unlock() + // Is the CIDR we've been given valid? _, ipnet, err := net.ParseCIDR(cidr) if err != nil { @@ -403,7 +451,7 @@ func (c *cryptokey) removeRoute(cidr string, dest string) error { for k := range *routingcache { delete(*routingcache, k) } - c.tun.log.Infoln("Removed CKR destination subnet %s via %s\n", cidr, dest) + c.tun.log.Infof("Removed CKR destination subnet %s via %s\n", cidr, dest) return nil } } From 5ea864869a4064ee33b2e973b9663fd5b01b887f Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 23 May 2019 20:27:52 -0500 Subject: [PATCH 0085/1109] don't spam searches for unused connections. todo: timeout old connections somehow --- src/tuntap/conn.go | 13 +++++++++++ src/tuntap/tun.go | 16 ++++++------- src/yggdrasil/conn.go | 54 ++++++++++++++++++++----------------------- 3 files changed, 46 insertions(+), 37 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index 50860f36..ce4645f3 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -3,6 +3,7 @@ package tuntap import ( "errors" + "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) @@ -10,11 +11,21 @@ import ( type tunConn struct { tun *TunAdapter conn *yggdrasil.Conn + addr address.Address + snet address.Subnet send chan []byte stop chan interface{} } func (s *tunConn) close() { + s.tun.mutex.Lock() + s._close_nomutex() + s.tun.mutex.Unlock() +} + +func (s *tunConn) _close_nomutex() { + delete(s.tun.addrToConn, s.addr) + delete(s.tun.subnetToConn, s.snet) close(s.stop) } @@ -32,6 +43,7 @@ func (s *tunConn) reader() error { b := make([]byte, 65535) for { go func() { + // TODO read timeout and close if n, err = s.conn.Read(b); err != nil { s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn read error:", err) return @@ -72,6 +84,7 @@ func (s *tunConn) writer() error { if !ok { return errors.New("send closed") } + // TODO write timeout and close if _, err := s.conn.Write(b); err != nil { s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn write error:", err) } diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 310e4211..b9ff04ec 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -236,26 +236,26 @@ func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { } // Get the remote address and subnet of the other side remoteNodeID := conn.RemoteAddr() - remoteAddr := address.AddrForNodeID(&remoteNodeID) - remoteSubnet := address.SubnetForNodeID(&remoteNodeID) + s.addr = *address.AddrForNodeID(&remoteNodeID) + s.snet = *address.SubnetForNodeID(&remoteNodeID) // Work out if this is already a destination we already know about tun.mutex.Lock() defer tun.mutex.Unlock() - atc, aok := tun.addrToConn[*remoteAddr] - stc, sok := tun.subnetToConn[*remoteSubnet] + atc, aok := tun.addrToConn[s.addr] + stc, sok := tun.subnetToConn[s.snet] // If we know about a connection for this destination already then assume it // is no longer valid and close it if aok { - atc.close() + atc._close_nomutex() err = errors.New("replaced connection for address") } else if sok { - stc.close() + stc._close_nomutex() err = errors.New("replaced connection for subnet") } // Save the session wrapper so that we can look it up quickly next time // we receive a packet through the interface for this address - tun.addrToConn[*remoteAddr] = &s - tun.subnetToConn[*remoteSubnet] = &s + tun.addrToConn[s.addr] = &s + tun.subnetToConn[s.snet] = &s // Start the connection goroutines go s.reader() go s.writer() diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index aad1dcdd..1290ad0d 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -37,6 +37,7 @@ type Conn struct { writeDeadline atomic.Value // time.Time // TODO timer searching atomic.Value // bool searchwait chan struct{} // Never reset this, it's only used for the initial search + writebuf [][]byte // Packets to be sent if/when the search finishes } // TODO func NewConn() that initializes additional fields as needed @@ -60,23 +61,13 @@ func (c *Conn) String() string { func (c *Conn) startSearch() { // The searchCompleted callback is given to the search searchCompleted := func(sinfo *sessionInfo, err error) { + defer c.searching.Store(false) // If the search failed for some reason, e.g. it hit a dead end or timed // out, then do nothing if err != nil { c.core.log.Debugln(c.String(), "DHT search failed:", err) - go func() { - time.Sleep(time.Second) - c.mutex.RLock() - closed := c.closed - c.mutex.RUnlock() - if !closed { - // Restart the search, or else Write can stay blocked forever - c.core.router.admin <- c.startSearch - } - }() return } - defer c.searching.Store(false) // Take the connection mutex c.mutex.Lock() defer c.mutex.Unlock() @@ -102,6 +93,16 @@ func (c *Conn) startSearch() { // Things were closed before the search returned // Go ahead and close it again to make sure the session is cleaned up go c.Close() + } else { + // Send any messages we may have buffered + var msgs [][]byte + msgs, c.writebuf = c.writebuf, nil + go func() { + for _, msg := range msgs { + c.Write(msg) + util.PutBytes(msg) + } + }() } } // doSearch will be called below in response to one or more conditions @@ -238,8 +239,6 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { c.mutex.RLock() sinfo := c.session c.mutex.RUnlock() - timer := getDeadlineTimer(&c.writeDeadline) - defer util.TimerStop(timer) // If the session doesn't exist, or isn't initialised (which probably means // that the search didn't complete successfully) then we may need to wait for // the search to complete or start the search again @@ -249,22 +248,15 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { // No search was already taking place so start a new one c.core.router.doAdmin(c.startSearch) } - // Wait for the search to complete - select { - case <-c.searchwait: - case <-timer.C: - return 0, ConnError{errors.New("Timeout"), true, false} - } - // Retrieve our session info again - c.mutex.RLock() - sinfo = c.session - c.mutex.RUnlock() - // If sinfo is still nil at this point then the search failed and the - // searchwait channel has been recreated, so might as well give up and - // return an error code - if sinfo == nil { - return 0, errors.New("search failed") + // Buffer the packet to be sent if/when the search is finished + c.mutex.Lock() + defer c.mutex.Unlock() + c.writebuf = append(c.writebuf, append(util.GetBytes(), b...)) + for len(c.writebuf) > 32 { + util.PutBytes(c.writebuf[0]) + c.writebuf = c.writebuf[1:] } + return len(b), nil } var packet []byte done := make(chan struct{}) @@ -283,13 +275,17 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { packet = p.encode() sinfo.bytesSent += uint64(len(b)) } + // Set up a timer so this doesn't block forever + timer := getDeadlineTimer(&c.writeDeadline) + defer util.TimerStop(timer) // Hand over to the session worker select { // Send to worker case sinfo.worker <- workerFunc: case <-timer.C: return 0, ConnError{errors.New("Timeout"), true, false} } - <-done // Wait for the worker to finish, failing this can cause memory errors (util.[Get||Put]Bytes stuff) + // Wait for the worker to finish, otherwise there are memory errors ([Get||Put]Bytes stuff) + <-done // Give the packet to the router sinfo.core.router.out(packet) // Finally return the number of bytes we wrote From b2513fce563aa0817642cadc5bfbf44eb8add001 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Tue, 28 May 2019 18:35:52 -0500 Subject: [PATCH 0086/1109] have the tunConn close things after a 2 minute timeout --- src/tuntap/conn.go | 56 ++++++++++++++++++++++++++++++++++++++-------- src/tuntap/tun.go | 10 +++++---- 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index ce4645f3..66c10119 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -2,6 +2,7 @@ package tuntap import ( "errors" + "time" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/util" @@ -9,24 +10,33 @@ import ( ) type tunConn struct { - tun *TunAdapter - conn *yggdrasil.Conn - addr address.Address - snet address.Subnet - send chan []byte - stop chan interface{} + tun *TunAdapter + conn *yggdrasil.Conn + addr address.Address + snet address.Subnet + send chan []byte + stop chan struct{} + alive chan struct{} } func (s *tunConn) close() { s.tun.mutex.Lock() + defer s.tun.mutex.Unlock() s._close_nomutex() - s.tun.mutex.Unlock() } func (s *tunConn) _close_nomutex() { + s.conn.Close() delete(s.tun.addrToConn, s.addr) delete(s.tun.subnetToConn, s.snet) - close(s.stop) + func() { + defer func() { recover() }() + close(s.stop) // Closes reader/writer goroutines + }() + func() { + defer func() { recover() }() + close(s.alive) // Closes timeout goroutine + }() } func (s *tunConn) reader() error { @@ -43,7 +53,7 @@ func (s *tunConn) reader() error { b := make([]byte, 65535) for { go func() { - // TODO read timeout and close + // TODO don't start a new goroutine for every packet read, this is probably a big part of the slowdowns we saw when refactoring if n, err = s.conn.Read(b); err != nil { s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn read error:", err) return @@ -60,6 +70,7 @@ func (s *tunConn) reader() error { util.PutBytes(bs) } } + s.stillAlive() // TODO? Only stay alive if we read >0 bytes? case <-s.stop: s.tun.log.Debugln("Stopping conn reader for", s) return nil @@ -89,6 +100,33 @@ func (s *tunConn) writer() error { s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn write error:", err) } util.PutBytes(b) + s.stillAlive() + } + } +} + +func (s *tunConn) stillAlive() { + select { + case s.alive <- struct{}{}: + default: + } +} + +func (s *tunConn) checkForTimeouts() error { + const timeout = 2 * time.Minute + timer := time.NewTimer(timeout) + defer util.TimerStop(timer) + defer s.close() + for { + select { + case _, ok := <-s.alive: + if !ok { + return errors.New("connection closed") + } + util.TimerStop(timer) + timer.Reset(timeout) + case <-timer.C: + return errors.New("timed out") } } } diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index b9ff04ec..683b83ac 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -229,10 +229,11 @@ func (tun *TunAdapter) handler() error { func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { // Prepare a session wrapper for the given connection s := tunConn{ - tun: tun, - conn: conn, - send: make(chan []byte, 32), // TODO: is this a sensible value? - stop: make(chan interface{}), + tun: tun, + conn: conn, + send: make(chan []byte, 32), // TODO: is this a sensible value? + stop: make(chan struct{}), + alive: make(chan struct{}, 1), } // Get the remote address and subnet of the other side remoteNodeID := conn.RemoteAddr() @@ -259,6 +260,7 @@ func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { // Start the connection goroutines go s.reader() go s.writer() + go s.checkForTimeouts() // Return return c, err } From 78eb40cbad346202190c817f810b3fd2fc76a4ff Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 29 May 2019 12:59:36 +0100 Subject: [PATCH 0087/1109] Record session uptime (purely for the admin socket) --- cmd/yggdrasilctl/main.go | 2 +- src/admin/admin.go | 1 + src/yggdrasil/api.go | 2 ++ src/yggdrasil/session.go | 2 ++ 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index b70bbe9e..51f4fa51 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -200,7 +200,7 @@ func main() { if !keysOrdered { for k := range slv.(map[string]interface{}) { if !*verbose { - if k == "box_pub_key" || k == "box_sig_key" || k == "nodeinfo" { + if k == "box_pub_key" || k == "box_sig_key" || k == "nodeinfo" || k == "was_mtu_fixed" { continue } } diff --git a/src/admin/admin.go b/src/admin/admin.go index 27dabdcc..c3f140ae 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -162,6 +162,7 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. "bytes_sent": s.BytesSent, "bytes_recvd": s.BytesRecvd, "mtu": s.MTU, + "uptime": s.Uptime.Seconds(), "was_mtu_fixed": s.WasMTUFixed, "box_pub_key": s.PublicKey, } diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 40e5a2b2..5e58ffaf 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -89,6 +89,7 @@ type Session struct { BytesSent uint64 BytesRecvd uint64 MTU uint16 + Uptime time.Duration WasMTUFixed bool } @@ -216,6 +217,7 @@ func (c *Core) GetSessions() []Session { MTU: sinfo.getMTU(), BytesSent: sinfo.bytesSent, BytesRecvd: sinfo.bytesRecvd, + Uptime: time.Now().Sub(sinfo.timeOpened), WasMTUFixed: sinfo.wasMTUFixed, } copy(session.PublicKey[:], sinfo.theirPermPub[:]) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 724152dd..3a4eb2e9 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -34,6 +34,7 @@ type sessionInfo struct { theirMTU uint16 // myMTU uint16 // wasMTUFixed bool // Was the MTU fixed by a receive error? + timeOpened time.Time // Time the sessino was opened time time.Time // Time we last received a packet mtuTime time.Time // time myMTU was last changed pingTime time.Time // time the first ping was sent since the last received packet @@ -284,6 +285,7 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.theirMTU = 1280 sinfo.myMTU = 1280 now := time.Now() + sinfo.timeOpened = now sinfo.time = now sinfo.mtuTime = now sinfo.pingTime = now From 3b6c726a3cb6345f8afd647944cde58c57b6f573 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 29 May 2019 19:11:12 +0100 Subject: [PATCH 0088/1109] Fix bug where MTU was ignored by sessions, resulting in default 1280 --- src/yggdrasil/session.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 3a4eb2e9..68b90950 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -283,7 +283,9 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.mySesPriv = *priv sinfo.myNonce = *crypto.NewBoxNonce() sinfo.theirMTU = 1280 - sinfo.myMTU = 1280 + ss.core.config.Mutex.RLock() + sinfo.myMTU = uint16(ss.core.config.Current.IfMTU) + ss.core.config.Mutex.RUnlock() now := time.Now() sinfo.timeOpened = now sinfo.time = now From 0096d1ae3ec4fdd058dacec9b7c3e0ade2d88b2a Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 29 May 2019 20:16:17 +0100 Subject: [PATCH 0089/1109] Re-add ICMPv6 packet too big handling --- src/tuntap/conn.go | 18 +++++++++++++++++- src/yggdrasil/conn.go | 34 +++++++++++++++++++++++++++------- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index 66c10119..f303e0c3 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -7,6 +7,8 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" + "golang.org/x/net/icmp" + "golang.org/x/net/ipv6" ) type tunConn struct { @@ -97,7 +99,21 @@ func (s *tunConn) writer() error { } // TODO write timeout and close if _, err := s.conn.Write(b); err != nil { - s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn write error:", err) + e, eok := err.(yggdrasil.ConnError) + if !eok { + s.tun.log.Errorln(s.conn.String(), "TUN/TAP generic write error:", err) + } else if ispackettoobig, maxsize := e.PacketTooBig(); ispackettoobig { + // TODO: This currently isn't aware of IPv4 for CKR + ptb := &icmp.PacketTooBig{ + MTU: int(maxsize), + Data: b[:900], + } + if packet, err := CreateICMPv6(b[8:24], b[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb); err == nil { + s.tun.send <- packet + } + } else { + s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn write error:", err) + } } util.PutBytes(b) s.stillAlive() diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 1290ad0d..9175aa52 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -11,21 +11,33 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/util" ) -// Error implements the net.Error interface +// ConnError implements the net.Error interface type ConnError struct { error timeout bool temporary bool + maxsize int } +// Timeout returns true if the error relates to a timeout condition on the +// connection. func (e *ConnError) Timeout() bool { return e.timeout } +// Temporary return true if the error is temporary or false if it is a permanent +// error condition. func (e *ConnError) Temporary() bool { return e.temporary } +// PacketTooBig returns in response to sending a packet that is too large, and +// if so, the maximum supported packet size that should be used for the +// connection. +func (e *ConnError) PacketTooBig() (bool, int) { + return e.maxsize > 0, e.maxsize +} + type Conn struct { core *Core nodeID *crypto.NodeID @@ -166,7 +178,7 @@ func (c *Conn) Read(b []byte) (int, error) { select { case <-c.searchwait: case <-timer.C: - return 0, ConnError{errors.New("Timeout"), true, false} + return 0, ConnError{errors.New("timeout"), true, false, 0} } // Retrieve our session info again c.mutex.RLock() @@ -182,7 +194,7 @@ func (c *Conn) Read(b []byte) (int, error) { // Wait for some traffic to come through from the session select { case <-timer.C: - return 0, ConnError{errors.New("Timeout"), true, false} + return 0, ConnError{errors.New("timeout"), true, false, 0} case p, ok := <-sinfo.recv: // If the session is closed then do nothing if !ok { @@ -222,7 +234,7 @@ func (c *Conn) Read(b []byte) (int, error) { select { // Send to worker case sinfo.worker <- workerFunc: case <-timer.C: - return 0, ConnError{errors.New("Timeout"), true, false} + return 0, ConnError{errors.New("timeout"), true, false, 0} } <-done // Wait for the worker to finish, failing this can cause memory errors (util.[Get||Put]Bytes stuff) // Something went wrong in the session worker so abort @@ -260,8 +272,14 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { } var packet []byte done := make(chan struct{}) + written := len(b) workerFunc := func() { defer close(done) + // Does the packet exceed the permitted size for the session? + if uint16(len(b)) > sinfo.getMTU() { + written, err = 0, ConnError{errors.New("packet too big"), true, false, int(sinfo.getMTU())} + return + } // Encrypt the packet payload, nonce := crypto.BoxSeal(&sinfo.sharedSesKey, b, &sinfo.myNonce) defer util.PutBytes(payload) @@ -282,14 +300,16 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { select { // Send to worker case sinfo.worker <- workerFunc: case <-timer.C: - return 0, ConnError{errors.New("Timeout"), true, false} + return 0, ConnError{errors.New("timeout"), true, false, 0} } // Wait for the worker to finish, otherwise there are memory errors ([Get||Put]Bytes stuff) <-done // Give the packet to the router - sinfo.core.router.out(packet) + if written > 0 { + sinfo.core.router.out(packet) + } // Finally return the number of bytes we wrote - return len(b), nil + return written, err } func (c *Conn) Close() error { From 9e086e70f09a2ed3a42c89269faf4fcf31895237 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 30 May 2019 12:44:47 +0100 Subject: [PATCH 0090/1109] Don't indefinitely block TUN/TAP reader goroutine when a conn error happens --- src/tuntap/conn.go | 9 +++++++-- src/yggdrasil/conn.go | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index f303e0c3..d6bb7a7e 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -58,13 +58,18 @@ func (s *tunConn) reader() error { // TODO don't start a new goroutine for every packet read, this is probably a big part of the slowdowns we saw when refactoring if n, err = s.conn.Read(b); err != nil { s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn read error:", err) + if e, eok := err.(yggdrasil.ConnError); eok && !e.Temporary() { + close(s.stop) + } else { + read <- false + } return } read <- true }() select { - case <-read: - if n > 0 { + case r := <-read: + if r && n > 0 { bs := append(util.GetBytes(), b[:n]...) select { case s.tun.send <- bs: diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 9175aa52..d2458db5 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -207,7 +207,7 @@ func (c *Conn) Read(b []byte) (int, error) { defer close(done) // If the nonce is bad then drop the packet and return an error if !sinfo.nonceIsOK(&p.Nonce) { - err = errors.New("packet dropped due to invalid nonce") + err = ConnError{errors.New("packet dropped due to invalid nonce"), false, true, 0} return } // Decrypt the packet From f0422dbd8b99f0a9f6fbfce2b64475095716b6da Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 30 May 2019 17:33:59 +0100 Subject: [PATCH 0091/1109] Fix panic when determining if CKR is enabled --- src/tuntap/ckr.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tuntap/ckr.go b/src/tuntap/ckr.go index c996c397..c9233e64 100644 --- a/src/tuntap/ckr.go +++ b/src/tuntap/ckr.go @@ -115,12 +115,13 @@ func (c *cryptokey) configure() error { // Enable or disable crypto-key routing. func (c *cryptokey) setEnabled(enabled bool) { - c.enabled.Store(true) + c.enabled.Store(enabled) } // Check if crypto-key routing is enabled. func (c *cryptokey) isEnabled() bool { - return c.enabled.Load().(bool) + enabled, ok := c.enabled.Load().(bool) + return ok && enabled } // Check whether the given address (with the address length specified in bytes) From 1addf08ccd48c96e464f13bea169f01abf0158dd Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 31 May 2019 17:51:01 -0500 Subject: [PATCH 0092/1109] don't have Conn.Read return an error for temorary crypto failures from e.g. out of order packets, just drop the packet and keep blocking until there's usable traffic --- src/yggdrasil/conn.go | 105 ++++++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 50 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index d2458db5..3df7c793 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -191,59 +191,64 @@ func (c *Conn) Read(b []byte) (int, error) { return 0, errors.New("search failed") } } - // Wait for some traffic to come through from the session - select { - case <-timer.C: - return 0, ConnError{errors.New("timeout"), true, false, 0} - case p, ok := <-sinfo.recv: - // If the session is closed then do nothing - if !ok { - return 0, errors.New("session is closed") - } - defer util.PutBytes(p.Payload) - var err error - done := make(chan struct{}) - workerFunc := func() { - defer close(done) - // If the nonce is bad then drop the packet and return an error - if !sinfo.nonceIsOK(&p.Nonce) { - err = ConnError{errors.New("packet dropped due to invalid nonce"), false, true, 0} - return - } - // Decrypt the packet - bs, isOK := crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce) - defer util.PutBytes(bs) // FIXME commenting this out leads to illegal buffer reuse, this implies there's a memory error somewhere and that this is just flooding things out of the finite pool of old slices that get reused - // Check if we were unable to decrypt the packet for some reason and - // return an error if we couldn't - if !isOK { - err = errors.New("packet dropped due to decryption failure") - return - } - // Return the newly decrypted buffer back to the slice we were given - copy(b, bs) - // Trim the slice down to size based on the data we received - if len(bs) < len(b) { - b = b[:len(bs)] - } - // Update the session - sinfo.updateNonce(&p.Nonce) - sinfo.time = time.Now() - sinfo.bytesRecvd += uint64(len(b)) - } - // Hand over to the session worker - select { // Send to worker - case sinfo.worker <- workerFunc: + for { + // Wait for some traffic to come through from the session + select { case <-timer.C: return 0, ConnError{errors.New("timeout"), true, false, 0} + case p, ok := <-sinfo.recv: + // If the session is closed then do nothing + if !ok { + return 0, errors.New("session is closed") + } + defer util.PutBytes(p.Payload) + var err error + done := make(chan struct{}) + workerFunc := func() { + defer close(done) + // If the nonce is bad then drop the packet and return an error + if !sinfo.nonceIsOK(&p.Nonce) { + err = ConnError{errors.New("packet dropped due to invalid nonce"), false, true, 0} + return + } + // Decrypt the packet + bs, isOK := crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce) + defer util.PutBytes(bs) // FIXME commenting this out leads to illegal buffer reuse, this implies there's a memory error somewhere and that this is just flooding things out of the finite pool of old slices that get reused + // Check if we were unable to decrypt the packet for some reason and + // return an error if we couldn't + if !isOK { + err = ConnError{errors.New("packet dropped due to decryption failure"), false, true, 0} + return + } + // Return the newly decrypted buffer back to the slice we were given + copy(b, bs) + // Trim the slice down to size based on the data we received + if len(bs) < len(b) { + b = b[:len(bs)] + } + // Update the session + sinfo.updateNonce(&p.Nonce) + sinfo.time = time.Now() + sinfo.bytesRecvd += uint64(len(b)) + } + // Hand over to the session worker + select { // Send to worker + case sinfo.worker <- workerFunc: + case <-timer.C: + return 0, ConnError{errors.New("timeout"), true, false, 0} + } + <-done // Wait for the worker to finish, failing this can cause memory errors (util.[Get||Put]Bytes stuff) + // Something went wrong in the session worker so abort + if err != nil { + if ce, ok := err.(*ConnError); ok && ce.Temporary() { + continue + } + return 0, err + } + // If we've reached this point then everything went to plan, return the + // number of bytes we populated back into the given slice + return len(b), nil } - <-done // Wait for the worker to finish, failing this can cause memory errors (util.[Get||Put]Bytes stuff) - // Something went wrong in the session worker so abort - if err != nil { - return 0, err - } - // If we've reached this point then everything went to plan, return the - // number of bytes we populated back into the given slice - return len(b), nil } } From 4b56849b082e944edce0ca49ca217a656717749b Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 10 Jun 2019 22:09:12 -0500 Subject: [PATCH 0093/1109] fix issue with sessions dying and never being fixed --- src/yggdrasil/conn.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 3df7c793..9ce5563d 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -274,6 +274,10 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { c.writebuf = c.writebuf[1:] } return len(b), nil + } else { + // This triggers some session keepalive traffic + // FIXME this desparately needs to be refactored, since the ping case needlessly goes through the router goroutine just to have it pass a function to the session worker when it determines that a session already exists. + c.core.router.doAdmin(c.startSearch) } var packet []byte done := make(chan struct{}) From 17175b49f2b28772ffafc606715b5c72c96eafb4 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 11 Jun 2019 10:18:59 +0100 Subject: [PATCH 0094/1109] Add multicast interfaces to platform-specific defaults (this makes it easier to avoid bringing AWDL up by default on macOS as an example, or over L2 VPNs when not expected) --- src/config/config.go | 2 +- src/defaults/defaults.go | 3 +++ src/defaults/defaults_darwin.go | 6 ++++++ src/defaults/defaults_freebsd.go | 5 +++++ src/defaults/defaults_linux.go | 8 ++++++++ src/defaults/defaults_netbsd.go | 5 +++++ src/defaults/defaults_openbsd.go | 5 +++++ src/defaults/defaults_other.go | 5 +++++ src/defaults/defaults_windows.go | 5 +++++ 9 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/config/config.go b/src/config/config.go index 8137cac9..9f8f3f54 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -102,7 +102,7 @@ func GenerateConfig() *NodeConfig { cfg.Peers = []string{} cfg.InterfacePeers = map[string][]string{} cfg.AllowedEncryptionPublicKeys = []string{} - cfg.MulticastInterfaces = []string{".*"} + cfg.MulticastInterfaces = defaults.GetDefaults().DefaultMulticastInterfaces cfg.IfName = defaults.GetDefaults().DefaultIfName cfg.IfMTU = defaults.GetDefaults().DefaultIfMTU cfg.IfTAPMode = defaults.GetDefaults().DefaultIfTAPMode diff --git a/src/defaults/defaults.go b/src/defaults/defaults.go index 3834990e..a5784198 100644 --- a/src/defaults/defaults.go +++ b/src/defaults/defaults.go @@ -10,6 +10,9 @@ type platformDefaultParameters struct { // Configuration (used for yggdrasilctl) DefaultConfigFile string + // Multicast interfaces + DefaultMulticastInterfaces []string + // TUN/TAP MaximumIfMTU int DefaultIfMTU int diff --git a/src/defaults/defaults_darwin.go b/src/defaults/defaults_darwin.go index 9bac3aad..26fd1f2d 100644 --- a/src/defaults/defaults_darwin.go +++ b/src/defaults/defaults_darwin.go @@ -12,6 +12,12 @@ func GetDefaults() platformDefaultParameters { // Configuration (used for yggdrasilctl) DefaultConfigFile: "/etc/yggdrasil.conf", + // Multicast interfaces + DefaultMulticastInterfaces: []string{ + "en*", + "bridge*", + }, + // TUN/TAP MaximumIfMTU: 65535, DefaultIfMTU: 65535, diff --git a/src/defaults/defaults_freebsd.go b/src/defaults/defaults_freebsd.go index df1a3c65..0e523483 100644 --- a/src/defaults/defaults_freebsd.go +++ b/src/defaults/defaults_freebsd.go @@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters { // Configuration (used for yggdrasilctl) DefaultConfigFile: "/etc/yggdrasil.conf", + // Multicast interfaces + DefaultMulticastInterfaces: []string{ + ".*", + }, + // TUN/TAP MaximumIfMTU: 32767, DefaultIfMTU: 32767, diff --git a/src/defaults/defaults_linux.go b/src/defaults/defaults_linux.go index 2f3459ca..67404e2d 100644 --- a/src/defaults/defaults_linux.go +++ b/src/defaults/defaults_linux.go @@ -12,6 +12,14 @@ func GetDefaults() platformDefaultParameters { // Configuration (used for yggdrasilctl) DefaultConfigFile: "/etc/yggdrasil.conf", + // Multicast interfaces + DefaultMulticastInterfaces: []string{ + "en*", + "eth*", + "wlan*", + "br*", + }, + // TUN/TAP MaximumIfMTU: 65535, DefaultIfMTU: 65535, diff --git a/src/defaults/defaults_netbsd.go b/src/defaults/defaults_netbsd.go index 40476dcb..52a487b7 100644 --- a/src/defaults/defaults_netbsd.go +++ b/src/defaults/defaults_netbsd.go @@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters { // Configuration (used for yggdrasilctl) DefaultConfigFile: "/etc/yggdrasil.conf", + // Multicast interfaces + DefaultMulticastInterfaces: []string{ + ".*", + }, + // TUN/TAP MaximumIfMTU: 9000, DefaultIfMTU: 9000, diff --git a/src/defaults/defaults_openbsd.go b/src/defaults/defaults_openbsd.go index cd6d202a..d44e5714 100644 --- a/src/defaults/defaults_openbsd.go +++ b/src/defaults/defaults_openbsd.go @@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters { // Configuration (used for yggdrasilctl) DefaultConfigFile: "/etc/yggdrasil.conf", + // Multicast interfaces + DefaultMulticastInterfaces: []string{ + ".*", + }, + // TUN/TAP MaximumIfMTU: 16384, DefaultIfMTU: 16384, diff --git a/src/defaults/defaults_other.go b/src/defaults/defaults_other.go index a01ab7af..0ba825c5 100644 --- a/src/defaults/defaults_other.go +++ b/src/defaults/defaults_other.go @@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters { // Configuration (used for yggdrasilctl) DefaultConfigFile: "/etc/yggdrasil.conf", + // Multicast interfaces + DefaultMulticastInterfaces: []string{ + ".*", + }, + // TUN/TAP MaximumIfMTU: 65535, DefaultIfMTU: 65535, diff --git a/src/defaults/defaults_windows.go b/src/defaults/defaults_windows.go index 3b04783a..6d53225a 100644 --- a/src/defaults/defaults_windows.go +++ b/src/defaults/defaults_windows.go @@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters { // Configuration (used for yggdrasilctl) DefaultConfigFile: "C:\\Program Files\\Yggdrasil\\yggdrasil.conf", + // Multicast interfaces + DefaultMulticastInterfaces: []string{ + ".*", + }, + // TUN/TAP MaximumIfMTU: 65535, DefaultIfMTU: 65535, From 720a078a35a8ce155574ef1a89176da13f1e5064 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 11 Jun 2019 10:52:21 +0100 Subject: [PATCH 0095/1109] Add SetSessionGatekeeper This allows you to define a function which determines whether a session connection (either incoming or outgoing) is allowed based on the public key. --- src/yggdrasil/api.go | 12 +++++ src/yggdrasil/session.go | 99 ++++++++++------------------------------ 2 files changed, 35 insertions(+), 76 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 5e58ffaf..9d640997 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -395,6 +395,18 @@ func (c *Core) GetNodeInfo(keyString, coordString string, nocache bool) (NodeInf return NodeInfoPayload{}, errors.New(fmt.Sprintf("getNodeInfo timeout: %s", keyString)) } +// SetSessionGatekeeper allows you to configure a handler function for deciding +// whether a session should be allowed or not. The default session firewall is +// implemented in this way. The function receives the public key of the remote +// side, and a boolean which is true if we initiated the session or false if we +// received an incoming session request. +func (c *Core) SetSessionGatekeeper(f func(pubkey *crypto.BoxPubKey, initiator bool) bool) { + c.sessions.isAllowedMutex.Lock() + defer c.sessions.isAllowedMutex.Unlock() + + c.sessions.isAllowedHandler = f +} + // SetLogger sets the output logger of the Yggdrasil node after startup. This // may be useful if you want to redirect the output later. func (c *Core) SetLogger(log *log.Logger) { diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 68b90950..46628c3e 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -6,7 +6,6 @@ package yggdrasil import ( "bytes" - "encoding/hex" "sync" "time" @@ -111,18 +110,20 @@ func (s *sessionInfo) update(p *sessionPing) bool { // Sessions are indexed by handle. // Additionally, stores maps of address/subnet onto keys, and keys onto handles. type sessions struct { - core *Core - listener *Listener - listenerMutex sync.Mutex - reconfigure chan chan error - lastCleanup time.Time - permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey // Maps known permanent keys to their shared key, used by DHT a lot - sinfos map[crypto.Handle]*sessionInfo // Maps (secret) handle onto session info - conns map[crypto.Handle]*Conn // Maps (secret) handle onto connections - byMySes map[crypto.BoxPubKey]*crypto.Handle // Maps mySesPub onto handle - byTheirPerm map[crypto.BoxPubKey]*crypto.Handle // Maps theirPermPub onto handle - addrToPerm map[address.Address]*crypto.BoxPubKey - subnetToPerm map[address.Subnet]*crypto.BoxPubKey + core *Core + listener *Listener + listenerMutex sync.Mutex + reconfigure chan chan error + lastCleanup time.Time + isAllowedHandler func(pubkey *crypto.BoxPubKey, initiator bool) bool // Returns true or false if session setup is allowed + isAllowedMutex sync.RWMutex // Protects the above + permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey // Maps known permanent keys to their shared key, used by DHT a lot + sinfos map[crypto.Handle]*sessionInfo // Maps (secret) handle onto session info + conns map[crypto.Handle]*Conn // Maps (secret) handle onto connections + byMySes map[crypto.BoxPubKey]*crypto.Handle // Maps mySesPub onto handle + byTheirPerm map[crypto.BoxPubKey]*crypto.Handle // Maps theirPermPub onto handle + addrToPerm map[address.Address]*crypto.BoxPubKey + subnetToPerm map[address.Subnet]*crypto.BoxPubKey } // Initializes the session struct. @@ -155,70 +156,17 @@ func (ss *sessions) init(core *Core) { ss.lastCleanup = time.Now() } -// Determines whether the session firewall is enabled. -func (ss *sessions) isSessionFirewallEnabled() bool { - ss.core.config.Mutex.RLock() - defer ss.core.config.Mutex.RUnlock() - - return ss.core.config.Current.SessionFirewall.Enable -} - // Determines whether the session with a given publickey is allowed based on // session firewall rules. func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) bool { - ss.core.config.Mutex.RLock() - defer ss.core.config.Mutex.RUnlock() + ss.isAllowedMutex.RLock() + defer ss.isAllowedMutex.RUnlock() - // Allow by default if the session firewall is disabled - if !ss.isSessionFirewallEnabled() { + if ss.isAllowedHandler == nil { return true } - // Prepare for checking whitelist/blacklist - var box crypto.BoxPubKey - // Reject blacklisted nodes - for _, b := range ss.core.config.Current.SessionFirewall.BlacklistEncryptionPublicKeys { - key, err := hex.DecodeString(b) - if err == nil { - copy(box[:crypto.BoxPubKeyLen], key) - if box == *pubkey { - return false - } - } - } - // Allow whitelisted nodes - for _, b := range ss.core.config.Current.SessionFirewall.WhitelistEncryptionPublicKeys { - key, err := hex.DecodeString(b) - if err == nil { - copy(box[:crypto.BoxPubKeyLen], key) - if box == *pubkey { - return true - } - } - } - // Allow outbound sessions if appropriate - if ss.core.config.Current.SessionFirewall.AlwaysAllowOutbound { - if initiator { - return true - } - } - // Look and see if the pubkey is that of a direct peer - var isDirectPeer bool - for _, peer := range ss.core.peers.ports.Load().(map[switchPort]*peer) { - if peer.box == *pubkey { - isDirectPeer = true - break - } - } - // Allow direct peers if appropriate - if ss.core.config.Current.SessionFirewall.AllowFromDirect && isDirectPeer { - return true - } - // Allow remote nodes if appropriate - if ss.core.config.Current.SessionFirewall.AllowFromRemote && !isDirectPeer { - return true - } - // Finally, default-deny if not matching any of the above rules - return false + + return ss.isAllowedHandler(pubkey, initiator) } // Gets the session corresponding to a given handle. @@ -444,12 +392,11 @@ func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) { func (ss *sessions) handlePing(ping *sessionPing) { // Get the corresponding session (or create a new session) sinfo, isIn := ss.getByTheirPerm(&ping.SendPermPub) - // Check the session firewall - if !isIn && ss.isSessionFirewallEnabled() { - if !ss.isSessionAllowed(&ping.SendPermPub, false) { - return - } + // Check if the session is allowed + if !isIn && !ss.isSessionAllowed(&ping.SendPermPub, false) { + return } + // Create the session if it doesn't already exist if !isIn { ss.createSession(&ping.SendPermPub) sinfo, isIn = ss.getByTheirPerm(&ping.SendPermPub) From 907986f2009f649a27df4f73676975a59319a2d2 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 11 Jun 2019 12:50:01 +0100 Subject: [PATCH 0096/1109] Implement session firewall as gatekeeper func in cmd/yggdrasil --- cmd/yggdrasil/main.go | 85 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 75 insertions(+), 10 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 5dba3a51..fb6cccbd 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "encoding/hex" "encoding/json" "flag" "fmt" @@ -20,22 +21,21 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/admin" "github.com/yggdrasil-network/yggdrasil-go/src/config" + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/multicast" "github.com/yggdrasil-network/yggdrasil-go/src/tuntap" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) -type nodeConfig = config.NodeConfig -type Core = yggdrasil.Core - type node struct { - core Core + core yggdrasil.Core + state *config.NodeState tuntap tuntap.TunAdapter multicast multicast.Multicast admin admin.AdminSocket } -func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *nodeConfig { +func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *config.NodeConfig { // Use a configuration file. If -useconf, the configuration will be read // from stdin. If -useconffile, the configuration will be read from the // filesystem. @@ -116,7 +116,7 @@ func main() { logging := flag.String("logging", "info,warn,error", "comma-separated list of logging levels to enable") flag.Parse() - var cfg *nodeConfig + var cfg *config.NodeConfig var err error switch { case *version: @@ -181,18 +181,20 @@ func main() { n := node{} // Now start Yggdrasil - this starts the DHT, router, switch and other core // components needed for Yggdrasil to operate - state, err := n.core.Start(cfg, logger) + n.state, err = n.core.Start(cfg, logger) if err != nil { logger.Errorln("An error occurred during startup") panic(err) } + // Register the session firewall gatekeeper function + n.core.SetSessionGatekeeper(n.sessionFirewall) // Start the admin socket - n.admin.Init(&n.core, state, logger, nil) + n.admin.Init(&n.core, n.state, logger, nil) if err := n.admin.Start(); err != nil { logger.Errorln("An error occurred starting admin socket:", err) } // Start the multicast interface - n.multicast.Init(&n.core, state, logger, nil) + n.multicast.Init(&n.core, n.state, logger, nil) if err := n.multicast.Start(); err != nil { logger.Errorln("An error occurred starting multicast:", err) } @@ -200,7 +202,7 @@ func main() { // Start the TUN/TAP interface if listener, err := n.core.ConnListen(); err == nil { if dialer, err := n.core.ConnDialer(); err == nil { - n.tuntap.Init(state, logger, listener, dialer) + n.tuntap.Init(n.state, logger, listener, dialer) if err := n.tuntap.Start(); err != nil { logger.Errorln("An error occurred starting TUN/TAP:", err) } @@ -251,3 +253,66 @@ func main() { } exit: } + +func (n *node) sessionFirewall(pubkey *crypto.BoxPubKey, initiator bool) bool { + n.state.Mutex.RLock() + defer n.state.Mutex.RUnlock() + + // Allow by default if the session firewall is disabled + if !n.state.Current.SessionFirewall.Enable { + return true + } + + // Prepare for checking whitelist/blacklist + var box crypto.BoxPubKey + // Reject blacklisted nodes + for _, b := range n.state.Current.SessionFirewall.BlacklistEncryptionPublicKeys { + key, err := hex.DecodeString(b) + if err == nil { + copy(box[:crypto.BoxPubKeyLen], key) + if box == *pubkey { + return false + } + } + } + + // Allow whitelisted nodes + for _, b := range n.state.Current.SessionFirewall.WhitelistEncryptionPublicKeys { + key, err := hex.DecodeString(b) + if err == nil { + copy(box[:crypto.BoxPubKeyLen], key) + if box == *pubkey { + return true + } + } + } + + // Allow outbound sessions if appropriate + if n.state.Current.SessionFirewall.AlwaysAllowOutbound { + if initiator { + return true + } + } + + // Look and see if the pubkey is that of a direct peer + var isDirectPeer bool + for _, peer := range n.core.GetPeers() { + if peer.PublicKey == *pubkey { + isDirectPeer = true + break + } + } + + // Allow direct peers if appropriate + if n.state.Current.SessionFirewall.AllowFromDirect && isDirectPeer { + return true + } + + // Allow remote nodes if appropriate + if n.state.Current.SessionFirewall.AllowFromRemote && !isDirectPeer { + return true + } + + // Finally, default-deny if not matching any of the above rules + return false +} From e229ad6e2b7253a8db3da9876794aa220b5314bb Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 11 Jun 2019 12:52:13 +0100 Subject: [PATCH 0097/1109] Update comments --- src/yggdrasil/api.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 9d640997..25f9869c 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -398,8 +398,9 @@ func (c *Core) GetNodeInfo(keyString, coordString string, nocache bool) (NodeInf // SetSessionGatekeeper allows you to configure a handler function for deciding // whether a session should be allowed or not. The default session firewall is // implemented in this way. The function receives the public key of the remote -// side, and a boolean which is true if we initiated the session or false if we -// received an incoming session request. +// side and a boolean which is true if we initiated the session or false if we +// received an incoming session request. The function should return true to +// allow the session or false to reject it. func (c *Core) SetSessionGatekeeper(f func(pubkey *crypto.BoxPubKey, initiator bool) bool) { c.sessions.isAllowedMutex.Lock() defer c.sessions.isAllowedMutex.Unlock() From ec5bb849752c1f9302e945dbff822c57cac137ba Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 11 Jun 2019 15:30:55 +0100 Subject: [PATCH 0098/1109] Try to build the new RPM using CircleCI --- .circleci/config.yml | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9cf7d95b..03244ce3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,13 +16,15 @@ jobs: mkdir /tmp/upload echo 'export CINAME=$(sh contrib/semver/name.sh)' >> $BASH_ENV echo 'export CIVERSION=$(sh contrib/semver/version.sh --bare)' >> $BASH_ENV + echo 'export CIVERSIONRPM=$(sh contrib/semver/version.sh --bare | tr "-" ".")' >> $BASH_ENV git config --global user.email "$(git log --format='%ae' HEAD -1)"; git config --global user.name "$(git log --format='%an' HEAD -1)"; - run: - name: Install alien + name: Install RPM utilities command: | - sudo apt-get install -y alien + sudo apt-get install -y rpm file + mkdir -p ~/rpmbuild/BUILD ~/rpmbuild/RPMS ~/rpmbuild/SOURCES ~/rpmbuild/SPECS ~/rpmbuild/SRPMS # - run: # name: Test debug builds @@ -31,7 +33,7 @@ jobs: # test -f yggdrasil && test -f yggdrasilctl - run: - name: Build for Linux (including Debian packages and RPMs) + name: Build for Linux (including Debian packages) command: | rm -f {yggdrasil,yggdrasilctl} PKGARCH=amd64 sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-amd64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-amd64; @@ -40,9 +42,23 @@ jobs: PKGARCH=mips sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-mips && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-mips; PKGARCH=armhf sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-armhf && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-armhf; PKGARCH=arm64 sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-arm64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-arm64; - sudo alien --to-rpm yggdrasil*.deb --scripts --keep-version && mv *.rpm /tmp/upload/; mv *.deb /tmp/upload/ + - run: + name: Build for Linux (RPM packages) + command: | + git clone https://github.com/yggdrasil-network/yggdrasil-package-rpm ~/rpmbuild/SPECS + cd ../ && tar -czvf ~/rpmbuild/SOURCES/v$CIVERSIONRPM --transform "s/project/yggdrasil-go-$CIRCLE_BRANCH-$CIVERSIONRPM/" project + sed -i "s/yggdrasil-go/yggdrasil-go-$CIRCLE_BRANCH/" ~/rpmbuild/SPECS/yggdrasil.spec + sed -i "s/^PKGNAME=yggdrasil/PKGNAME=yggdrasil-$CIRCLE_BRANCH/" ~/rpmbuild/SPECS/yggdrasil.spec + sed -i "s/^Name\:.*/Name\: $CINAME/" ~/rpmbuild/SPECS/yggdrasil.spec + sed -i "s/^Version\:.*/Version\: $CIVERSIONRPM/" ~/rpmbuild/SPECS/yggdrasil.spec + cat ~/rpmbuild/SPECS/yggdrasil.spec + GOARCH=amd64 rpmbuild -v --nodeps --target=x86_64 -ba ~/rpmbuild/SPECS/yggdrasil.spec + #GOARCH=386 rpmbuild -v --nodeps --target=i386 -bb ~/rpmbuild/SPECS/yggdrasil.spec + find ~/rpmbuild/RPMS/ -name '*.rpm' -exec mv {} /tmp/upload \; + find ~/rpmbuild/SRPMS/ -name '*.rpm' -exec mv {} /tmp/upload \; + - run: name: Build for EdgeRouter command: | From 9a7d3508846819320f761e638b154b6053c9bf05 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 11 Jun 2019 23:48:00 +0100 Subject: [PATCH 0099/1109] Fix expressions --- src/defaults/defaults_darwin.go | 4 ++-- src/defaults/defaults_linux.go | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/defaults/defaults_darwin.go b/src/defaults/defaults_darwin.go index 26fd1f2d..47683bf9 100644 --- a/src/defaults/defaults_darwin.go +++ b/src/defaults/defaults_darwin.go @@ -14,8 +14,8 @@ func GetDefaults() platformDefaultParameters { // Multicast interfaces DefaultMulticastInterfaces: []string{ - "en*", - "bridge*", + "en.*", + "bridge.*", }, // TUN/TAP diff --git a/src/defaults/defaults_linux.go b/src/defaults/defaults_linux.go index 67404e2d..b0aaf855 100644 --- a/src/defaults/defaults_linux.go +++ b/src/defaults/defaults_linux.go @@ -14,10 +14,7 @@ func GetDefaults() platformDefaultParameters { // Multicast interfaces DefaultMulticastInterfaces: []string{ - "en*", - "eth*", - "wlan*", - "br*", + ".*", }, // TUN/TAP From f545060e89bb53ff540391fa67273dba96526165 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 13 Jun 2019 23:37:53 +0100 Subject: [PATCH 0100/1109] Add notes on isSessionAllowed checks --- src/yggdrasil/session.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 46628c3e..22118476 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -219,6 +219,7 @@ func (ss *sessions) getByTheirSubnet(snet *address.Subnet) (*sessionInfo, bool) // includse initializing session info to sane defaults (e.g. lowest supported // MTU). func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { + // TODO: this check definitely needs to be moved if !ss.isSessionAllowed(theirPermKey, true) { return nil } @@ -393,6 +394,7 @@ func (ss *sessions) handlePing(ping *sessionPing) { // Get the corresponding session (or create a new session) sinfo, isIn := ss.getByTheirPerm(&ping.SendPermPub) // Check if the session is allowed + // TODO: this check may need to be moved if !isIn && !ss.isSessionAllowed(&ping.SendPermPub, false) { return } From 54f1804101ff45e0b02580a17edaeab882beb21f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 20 Jun 2019 15:11:55 +0100 Subject: [PATCH 0101/1109] Try and solidify multicast interface behavior --- src/multicast/admin.go | 2 +- src/multicast/multicast.go | 143 +++++++++++++++--------------- src/multicast/multicast_darwin.go | 4 +- 3 files changed, 76 insertions(+), 73 deletions(-) diff --git a/src/multicast/admin.go b/src/multicast/admin.go index 672b7ca4..40e28af4 100644 --- a/src/multicast/admin.go +++ b/src/multicast/admin.go @@ -5,7 +5,7 @@ import "github.com/yggdrasil-network/yggdrasil-go/src/admin" func (m *Multicast) SetupAdminHandlers(a *admin.AdminSocket) { a.AddHandler("getMulticastInterfaces", []string{}, func(in admin.Info) (admin.Info, error) { var intfs []string - for _, v := range m.interfaces() { + for _, v := range m.GetInterfaces() { intfs = append(intfs, v.Name) } return admin.Info{"multicast_interfaces": intfs}, nil diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index cbde7fd9..f0b1a9a1 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -5,6 +5,7 @@ import ( "fmt" "net" "regexp" + "sync" "time" "github.com/gologme/log" @@ -19,14 +20,16 @@ import ( // configured multicast interface, Yggdrasil will attempt to peer with that node // automatically. type Multicast struct { - core *yggdrasil.Core - config *config.NodeState - log *log.Logger - reconfigure chan chan error - sock *ipv6.PacketConn - groupAddr string - listeners map[string]*yggdrasil.TcpListener - listenPort uint16 + core *yggdrasil.Core + config *config.NodeState + log *log.Logger + sock *ipv6.PacketConn + groupAddr string + listeners map[string]*yggdrasil.TcpListener + listenPort uint16 + interfaces map[string]net.Interface + interfacesMutex sync.RWMutex + interfacesTime time.Time } // Init prepares the multicast interface for use. @@ -34,25 +37,24 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log m.core = core m.config = state m.log = log - m.reconfigure = make(chan chan error, 1) m.listeners = make(map[string]*yggdrasil.TcpListener) + m.interfaces = make(map[string]net.Interface) current, _ := m.config.Get() m.listenPort = current.LinkLocalTCPPort + m.groupAddr = "[ff02::114]:9001" + // Perform our first check for multicast interfaces + if count := m.UpdateInterfaces(); count != 0 { + m.log.Infoln("Found", count, "multicast interface(s)") + } else { + m.log.Infoln("Multicast is not enabled on any interfaces") + } + // Keep checking quietly every minute in case they change go func() { for { - e := <-m.reconfigure - // There's nothing particularly to do here because the multicast module - // already consults the config.NodeState when enumerating multicast - // interfaces on each pass. We just need to return nil so that the - // reconfiguration doesn't block indefinitely - e <- nil + time.Sleep(time.Minute) + m.UpdateInterfaces() } }() - m.groupAddr = "[ff02::114]:9001" - // Check if we've been given any expressions - if count := len(m.interfaces()); count != 0 { - m.log.Infoln("Found", count, "multicast interface(s)") - } return nil } @@ -60,32 +62,27 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log // listen for multicast beacons from other hosts and will advertise multicast // beacons out to the network. func (m *Multicast) Start() error { - current, _ := m.config.Get() - if len(current.MulticastInterfaces) == 0 { - m.log.Infoln("Multicast discovery is disabled") - } else { - m.log.Infoln("Multicast discovery is enabled") - addr, err := net.ResolveUDPAddr("udp", m.groupAddr) - if err != nil { - return err - } - listenString := fmt.Sprintf("[::]:%v", addr.Port) - lc := net.ListenConfig{ - Control: m.multicastReuse, - } - conn, err := lc.ListenPacket(context.Background(), "udp6", listenString) - if err != nil { - return err - } - m.sock = ipv6.NewPacketConn(conn) - if err = m.sock.SetControlMessage(ipv6.FlagDst, true); err != nil { - // Windows can't set this flag, so we need to handle it in other ways - } - - go m.multicastStarted() - go m.listen() - go m.announce() + addr, err := net.ResolveUDPAddr("udp", m.groupAddr) + if err != nil { + return err } + listenString := fmt.Sprintf("[::]:%v", addr.Port) + lc := net.ListenConfig{ + Control: m.multicastReuse, + } + conn, err := lc.ListenPacket(context.Background(), "udp6", listenString) + if err != nil { + return err + } + m.sock = ipv6.NewPacketConn(conn) + if err = m.sock.SetControlMessage(ipv6.FlagDst, true); err != nil { + // Windows can't set this flag, so we need to handle it in other ways + } + + go m.multicastStarted() + go m.listen() + go m.announce() + return nil } @@ -102,34 +99,37 @@ func (m *Multicast) UpdateConfig(config *config.NodeConfig) { m.config.Replace(*config) - errors := 0 + m.log.Infoln("Multicast configuration reloaded successfully") - components := []chan chan error{ - m.reconfigure, - } - - for _, component := range components { - response := make(chan error) - component <- response - if err := <-response; err != nil { - m.log.Errorln(err) - errors++ - } - } - - if errors > 0 { - m.log.Warnln(errors, "multicast module(s) reported errors during configuration reload") + if count := m.UpdateInterfaces(); count != 0 { + m.log.Infoln("Found", count, "multicast interface(s)") } else { - m.log.Infoln("Multicast configuration reloaded successfully") + m.log.Infoln("Multicast is not enabled on any interfaces") } } -func (m *Multicast) interfaces() map[string]net.Interface { +// GetInterfaces returns the currently known/enabled multicast interfaces. It is +// expected that UpdateInterfaces has been called at least once before calling +// this method. +func (m *Multicast) GetInterfaces() map[string]net.Interface { + m.interfacesMutex.RLock() + defer m.interfacesMutex.RUnlock() + return m.interfaces +} + +// UpdateInterfaces re-enumerates the available multicast interfaces on the +// system, using the current MulticastInterfaces config option as a template. +// The number of selected interfaces is returned. +func (m *Multicast) UpdateInterfaces() int { + m.interfacesMutex.Lock() + defer m.interfacesMutex.Unlock() // Get interface expressions from config current, _ := m.config.Get() exprs := current.MulticastInterfaces // Ask the system for network interfaces - interfaces := make(map[string]net.Interface) + for i := range m.interfaces { + delete(m.interfaces, i) + } allifaces, err := net.Interfaces() if err != nil { panic(err) @@ -156,11 +156,12 @@ func (m *Multicast) interfaces() map[string]net.Interface { } // Does the interface match the regular expression? Store it if so if e.MatchString(iface.Name) { - interfaces[iface.Name] = iface + m.interfaces[iface.Name] = iface } } } - return interfaces + m.interfacesTime = time.Now() + return len(m.interfaces) } func (m *Multicast) announce() { @@ -173,7 +174,7 @@ func (m *Multicast) announce() { panic(err) } for { - interfaces := m.interfaces() + interfaces := m.GetInterfaces() // There might be interfaces that we configured listeners for but are no // longer up - if that's the case then we should stop the listeners for name, listener := range m.listeners { @@ -307,9 +308,11 @@ func (m *Multicast) listen() { if addr.IP.String() != from.IP.String() { continue } - addr.Zone = "" - if err := m.core.CallPeer("tcp://"+addr.String(), from.Zone); err != nil { - m.log.Debugln("Call from multicast failed:", err) + if _, ok := m.GetInterfaces()[from.Zone]; ok { + addr.Zone = "" + if err := m.core.CallPeer("tcp://"+addr.String(), from.Zone); err != nil { + m.log.Debugln("Call from multicast failed:", err) + } } } } diff --git a/src/multicast/multicast_darwin.go b/src/multicast/multicast_darwin.go index 900354c7..213bff3a 100644 --- a/src/multicast/multicast_darwin.go +++ b/src/multicast/multicast_darwin.go @@ -35,12 +35,12 @@ func (m *Multicast) multicastStarted() { if awdlGoroutineStarted { return } - m.log.Infoln("Multicast discovery will wake up AWDL if required") awdlGoroutineStarted = true for { C.StopAWDLBrowsing() - for _, intf := range m.interfaces() { + for _, intf := range m.GetInterfaces() { if intf.Name == "awdl0" { + m.log.Infoln("Multicast discovery is using AWDL discovery") C.StartAWDLBrowsing() break } From 29a0f8b572f5db41e86af8d657a578c62890922c Mon Sep 17 00:00:00 2001 From: Arceliar Date: Tue, 25 Jun 2019 19:31:29 -0500 Subject: [PATCH 0102/1109] some minor refactoring to dht callbacks and searches, work in progress --- src/yggdrasil/api.go | 13 ++----- src/yggdrasil/conn.go | 2 +- src/yggdrasil/dht.go | 29 ++++++++++------ src/yggdrasil/search.go | 75 +++++++++++++++++++++-------------------- 4 files changed, 61 insertions(+), 58 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 25f9869c..b98df3bf 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -517,21 +517,14 @@ func (c *Core) DHTPing(keyString, coordString, targetString string) (DHTRes, err rq := dhtReqKey{info.key, target} sendPing := func() { c.dht.addCallback(&rq, func(res *dhtRes) { - defer func() { recover() }() - select { - case resCh <- res: - default: - } + resCh <- res }) c.dht.ping(&info, &target) } c.router.doAdmin(sendPing) - go func() { - time.Sleep(6 * time.Second) - close(resCh) - }() // TODO: do something better than the below... - for res := range resCh { + res := <-resCh + if res != nil { r := DHTRes{ Coords: append([]byte{}, res.Coords...), } diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 9ce5563d..7216be91 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -128,7 +128,7 @@ func (c *Conn) startSearch() { c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo) } // Continue the search - c.core.searches.continueSearch(sinfo) + sinfo.continueSearch() } // Take a copy of the session object, in case it changes later c.mutex.RLock() diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index b081c92d..b53e29c9 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -68,9 +68,9 @@ type dht struct { core *Core reconfigure chan chan error nodeID crypto.NodeID - peers chan *dhtInfo // other goroutines put incoming dht updates here - reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests - callbacks map[dhtReqKey]dht_callbackInfo // Search and admin lookup callbacks + peers chan *dhtInfo // other goroutines put incoming dht updates here + reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests + callbacks map[dhtReqKey][]dht_callbackInfo // Search and admin lookup callbacks // These next two could be replaced by a single linked list or similar... table map[crypto.NodeID]*dhtInfo imp []*dhtInfo @@ -88,7 +88,7 @@ func (t *dht) init(c *Core) { }() t.nodeID = *t.core.NodeID() t.peers = make(chan *dhtInfo, 1024) - t.callbacks = make(map[dhtReqKey]dht_callbackInfo) + t.callbacks = make(map[dhtReqKey][]dht_callbackInfo) t.reset() } @@ -244,15 +244,17 @@ type dht_callbackInfo struct { // Adds a callback and removes it after some timeout. func (t *dht) addCallback(rq *dhtReqKey, callback func(*dhtRes)) { info := dht_callbackInfo{callback, time.Now().Add(6 * time.Second)} - t.callbacks[*rq] = info + t.callbacks[*rq] = append(t.callbacks[*rq], info) } // Reads a lookup response, checks that we had sent a matching request, and processes the response info. // This mainly consists of updating the node we asked in our DHT (they responded, so we know they're still alive), and deciding if we want to do anything with their responses func (t *dht) handleRes(res *dhtRes) { rq := dhtReqKey{res.Key, res.Dest} - if callback, isIn := t.callbacks[rq]; isIn { - callback.f(res) + if callbacks, isIn := t.callbacks[rq]; isIn { + for _, callback := range callbacks { + callback.f(res) + } delete(t.callbacks, rq) } _, isIn := t.reqs[rq] @@ -326,10 +328,15 @@ func (t *dht) doMaintenance() { } } t.reqs = newReqs - newCallbacks := make(map[dhtReqKey]dht_callbackInfo, len(t.callbacks)) - for key, callback := range t.callbacks { - if now.Before(callback.time) { - newCallbacks[key] = callback + newCallbacks := make(map[dhtReqKey][]dht_callbackInfo, len(t.callbacks)) + for key, cs := range t.callbacks { + for _, c := range cs { + if now.Before(c.time) { + newCallbacks[key] = append(newCallbacks[key], c) + } else { + // Signal failure + c.f(nil) + } } } t.callbacks = newCallbacks diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 0a643363..576034bf 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -33,6 +33,7 @@ const search_RETRY_TIME = time.Second // Information about an ongoing search. // Includes the target NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited. type searchInfo struct { + core *Core dest crypto.NodeID mask crypto.NodeID time time.Time @@ -40,6 +41,7 @@ type searchInfo struct { toVisit []*dhtInfo visited map[crypto.NodeID]bool callback func(*sessionInfo, error) + // TODO context.Context for timeout and cancellation } // This stores a map of active searches. @@ -49,7 +51,7 @@ type searches struct { searches map[crypto.NodeID]*searchInfo } -// Intializes the searches struct. +// Initializes the searches struct. func (s *searches) init(core *Core) { s.core = core s.reconfigure = make(chan chan error, 1) @@ -65,12 +67,13 @@ func (s *searches) init(core *Core) { // Creates a new search info, adds it to the searches struct, and returns a pointer to the info. func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo { now := time.Now() - for dest, sinfo := range s.searches { - if now.Sub(sinfo.time) > time.Minute { - delete(s.searches, dest) - } - } + //for dest, sinfo := range s.searches { + // if now.Sub(sinfo.time) > time.Minute { + // delete(s.searches, dest) + // } + //} info := searchInfo{ + core: s.core, dest: *dest, mask: *mask, time: now.Add(-time.Second), @@ -82,30 +85,29 @@ func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callba //////////////////////////////////////////////////////////////////////////////// -// Checks if there's an ongoing search relaed to a dhtRes. +// Checks if there's an ongoing search related to a dhtRes. // If there is, it adds the response info to the search and triggers a new search step. // If there's no ongoing search, or we if the dhtRes finished the search (it was from the target node), then don't do anything more. -func (s *searches) handleDHTRes(res *dhtRes) { - sinfo, isIn := s.searches[res.Dest] - if !isIn || s.checkDHTRes(sinfo, res) { +func (sinfo *searchInfo) handleDHTRes(res *dhtRes) { + if res == nil || sinfo.checkDHTRes(res) { // Either we don't recognize this search, or we just finished it return } // Add to the search and continue - s.addToSearch(sinfo, res) - s.doSearchStep(sinfo) + sinfo.addToSearch(res) + sinfo.doSearchStep() } // Adds the information from a dhtRes to an ongoing search. // Info about a node that has already been visited is not re-added to the search. // Duplicate information about nodes toVisit is deduplicated (the newest information is kept). // The toVisit list is sorted in ascending order of keyspace distance from the destination. -func (s *searches) addToSearch(sinfo *searchInfo, res *dhtRes) { +func (sinfo *searchInfo) addToSearch(res *dhtRes) { // Add responses to toVisit if closer to dest than the res node from := dhtInfo{key: res.Key, coords: res.Coords} sinfo.visited[*from.getNodeID()] = true for _, info := range res.Infos { - if *info.getNodeID() == s.core.dht.nodeID || sinfo.visited[*info.getNodeID()] { + if *info.getNodeID() == sinfo.core.dht.nodeID || sinfo.visited[*info.getNodeID()] { continue } if dht_ordered(&sinfo.dest, info.getNodeID(), from.getNodeID()) { @@ -135,10 +137,10 @@ func (s *searches) addToSearch(sinfo *searchInfo, res *dhtRes) { // If there are no nodes left toVisit, then this cleans up the search. // Otherwise, it pops the closest node to the destination (in keyspace) off of the toVisit list and sends a dht ping. -func (s *searches) doSearchStep(sinfo *searchInfo) { +func (sinfo *searchInfo) doSearchStep() { if len(sinfo.toVisit) == 0 { // Dead end, do cleanup - delete(s.searches, sinfo.dest) + delete(sinfo.core.searches.searches, sinfo.dest) go sinfo.callback(nil, errors.New("search reached dead end")) return } @@ -146,31 +148,32 @@ func (s *searches) doSearchStep(sinfo *searchInfo) { var next *dhtInfo next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:] rq := dhtReqKey{next.key, sinfo.dest} - s.core.dht.addCallback(&rq, s.handleDHTRes) - s.core.dht.ping(next, &sinfo.dest) + sinfo.core.dht.addCallback(&rq, sinfo.handleDHTRes) + sinfo.core.dht.ping(next, &sinfo.dest) } // If we've recenty sent a ping for this search, do nothing. // Otherwise, doSearchStep and schedule another continueSearch to happen after search_RETRY_TIME. -func (s *searches) continueSearch(sinfo *searchInfo) { +func (sinfo *searchInfo) continueSearch() { if time.Since(sinfo.time) < search_RETRY_TIME { return } sinfo.time = time.Now() - s.doSearchStep(sinfo) + sinfo.doSearchStep() // In case the search dies, try to spawn another thread later // Note that this will spawn multiple parallel searches as time passes // Any that die aren't restarted, but a new one will start later retryLater := func() { - newSearchInfo := s.searches[sinfo.dest] + // FIXME this keeps the search alive forever if not for the searches map, fix that + newSearchInfo := sinfo.core.searches.searches[sinfo.dest] if newSearchInfo != sinfo { return } - s.continueSearch(sinfo) + sinfo.continueSearch() } go func() { time.Sleep(search_RETRY_TIME) - s.core.router.admin <- retryLater + sinfo.core.router.admin <- retryLater }() } @@ -185,37 +188,37 @@ func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callb // Checks if a dhtRes is good (called by handleDHTRes). // If the response is from the target, get/create a session, trigger a session ping, and return true. // Otherwise return false. -func (s *searches) checkDHTRes(info *searchInfo, res *dhtRes) bool { +func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { them := crypto.GetNodeID(&res.Key) var destMasked crypto.NodeID var themMasked crypto.NodeID for idx := 0; idx < crypto.NodeIDLen; idx++ { - destMasked[idx] = info.dest[idx] & info.mask[idx] - themMasked[idx] = them[idx] & info.mask[idx] + destMasked[idx] = sinfo.dest[idx] & sinfo.mask[idx] + themMasked[idx] = them[idx] & sinfo.mask[idx] } if themMasked != destMasked { return false } // They match, so create a session and send a sessionRequest - sinfo, isIn := s.core.sessions.getByTheirPerm(&res.Key) + sess, isIn := sinfo.core.sessions.getByTheirPerm(&res.Key) if !isIn { - sinfo = s.core.sessions.createSession(&res.Key) - if sinfo == nil { + sess = sinfo.core.sessions.createSession(&res.Key) + if sess == nil { // nil if the DHT search finished but the session wasn't allowed - go info.callback(nil, errors.New("session not allowed")) + go sinfo.callback(nil, errors.New("session not allowed")) return true } - _, isIn := s.core.sessions.getByTheirPerm(&res.Key) + _, isIn := sinfo.core.sessions.getByTheirPerm(&res.Key) if !isIn { panic("This should never happen") } } // FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)? - sinfo.coords = res.Coords - sinfo.packet = info.packet - s.core.sessions.ping(sinfo) - go info.callback(sinfo, nil) + sess.coords = res.Coords + sess.packet = sinfo.packet + sinfo.core.sessions.ping(sess) + go sinfo.callback(sess, nil) // Cleanup - delete(s.searches, res.Dest) + delete(sinfo.core.searches.searches, res.Dest) return true } From 93a323c62c52b156207922414e31a1890d235d26 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 28 Jun 2019 23:45:04 +0100 Subject: [PATCH 0103/1109] Add support for logging to file or syslog instead of stdout --- cmd/yggdrasil/main.go | 20 +++++++++++++++++++- src/yggdrasil/api.go | 2 +- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index fb6cccbd..32069c01 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -7,6 +7,7 @@ import ( "flag" "fmt" "io/ioutil" + "log/syslog" "os" "os/signal" "strings" @@ -114,6 +115,7 @@ func main() { autoconf := flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)") version := flag.Bool("version", false, "prints the version of this build") logging := flag.String("logging", "info,warn,error", "comma-separated list of logging levels to enable") + logto := flag.String("logto", "stdout", "file path to log to, \"syslog\" or \"stdout\"") flag.Parse() var cfg *config.NodeConfig @@ -161,7 +163,23 @@ func main() { return } // Create a new logger that logs output to stdout. - logger := log.New(os.Stdout, "", log.Flags()) + var logger *log.Logger + switch *logto { + case "stdout": + logger = log.New(os.Stdout, "", log.Flags()) + case "syslog": + if syslogwriter, err := syslog.New(syslog.LOG_INFO, yggdrasil.BuildName()); err == nil { + logger = log.New(syslogwriter, "", log.Flags()) + } + default: + if logfd, err := os.Create(*logto); err == nil { + logger = log.New(logfd, "", log.Flags()) + } + } + if logger == nil { + logger = log.New(os.Stdout, "", log.Flags()) + logger.Warnln("Logging defaulting to stdout") + } //logger.EnableLevel("error") //logger.EnableLevel("warn") //logger.EnableLevel("info") diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 25f9869c..462353c0 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -232,7 +232,7 @@ func (c *Core) GetSessions() []Session { // from git, or returns "unknown" otherwise. func BuildName() string { if buildName == "" { - return "unknown" + return "yggdrasil" } return buildName } From 27b3b9b49bab0da7814951310081f8b360b3a81f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 29 Jun 2019 00:12:56 +0100 Subject: [PATCH 0104/1109] Return new copy of interfaces on each Interfaces() call --- src/multicast/admin.go | 2 +- src/multicast/multicast.go | 66 ++++++------------------------- src/multicast/multicast_darwin.go | 4 +- 3 files changed, 16 insertions(+), 56 deletions(-) diff --git a/src/multicast/admin.go b/src/multicast/admin.go index 40e28af4..cafee07f 100644 --- a/src/multicast/admin.go +++ b/src/multicast/admin.go @@ -5,7 +5,7 @@ import "github.com/yggdrasil-network/yggdrasil-go/src/admin" func (m *Multicast) SetupAdminHandlers(a *admin.AdminSocket) { a.AddHandler("getMulticastInterfaces", []string{}, func(in admin.Info) (admin.Info, error) { var intfs []string - for _, v := range m.GetInterfaces() { + for _, v := range m.Interfaces() { intfs = append(intfs, v.Name) } return admin.Info{"multicast_interfaces": intfs}, nil diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index f0b1a9a1..3c0d8c0f 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -5,7 +5,6 @@ import ( "fmt" "net" "regexp" - "sync" "time" "github.com/gologme/log" @@ -20,16 +19,13 @@ import ( // configured multicast interface, Yggdrasil will attempt to peer with that node // automatically. type Multicast struct { - core *yggdrasil.Core - config *config.NodeState - log *log.Logger - sock *ipv6.PacketConn - groupAddr string - listeners map[string]*yggdrasil.TcpListener - listenPort uint16 - interfaces map[string]net.Interface - interfacesMutex sync.RWMutex - interfacesTime time.Time + core *yggdrasil.Core + config *config.NodeState + log *log.Logger + sock *ipv6.PacketConn + groupAddr string + listeners map[string]*yggdrasil.TcpListener + listenPort uint16 } // Init prepares the multicast interface for use. @@ -38,23 +34,9 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log m.config = state m.log = log m.listeners = make(map[string]*yggdrasil.TcpListener) - m.interfaces = make(map[string]net.Interface) current, _ := m.config.Get() m.listenPort = current.LinkLocalTCPPort m.groupAddr = "[ff02::114]:9001" - // Perform our first check for multicast interfaces - if count := m.UpdateInterfaces(); count != 0 { - m.log.Infoln("Found", count, "multicast interface(s)") - } else { - m.log.Infoln("Multicast is not enabled on any interfaces") - } - // Keep checking quietly every minute in case they change - go func() { - for { - time.Sleep(time.Minute) - m.UpdateInterfaces() - } - }() return nil } @@ -96,40 +78,19 @@ func (m *Multicast) Stop() error { // needed. func (m *Multicast) UpdateConfig(config *config.NodeConfig) { m.log.Debugln("Reloading multicast configuration...") - m.config.Replace(*config) - m.log.Infoln("Multicast configuration reloaded successfully") - - if count := m.UpdateInterfaces(); count != 0 { - m.log.Infoln("Found", count, "multicast interface(s)") - } else { - m.log.Infoln("Multicast is not enabled on any interfaces") - } } // GetInterfaces returns the currently known/enabled multicast interfaces. It is // expected that UpdateInterfaces has been called at least once before calling // this method. -func (m *Multicast) GetInterfaces() map[string]net.Interface { - m.interfacesMutex.RLock() - defer m.interfacesMutex.RUnlock() - return m.interfaces -} - -// UpdateInterfaces re-enumerates the available multicast interfaces on the -// system, using the current MulticastInterfaces config option as a template. -// The number of selected interfaces is returned. -func (m *Multicast) UpdateInterfaces() int { - m.interfacesMutex.Lock() - defer m.interfacesMutex.Unlock() +func (m *Multicast) Interfaces() map[string]net.Interface { + interfaces := make(map[string]net.Interface) // Get interface expressions from config current, _ := m.config.Get() exprs := current.MulticastInterfaces // Ask the system for network interfaces - for i := range m.interfaces { - delete(m.interfaces, i) - } allifaces, err := net.Interfaces() if err != nil { panic(err) @@ -156,12 +117,11 @@ func (m *Multicast) UpdateInterfaces() int { } // Does the interface match the regular expression? Store it if so if e.MatchString(iface.Name) { - m.interfaces[iface.Name] = iface + interfaces[iface.Name] = iface } } } - m.interfacesTime = time.Now() - return len(m.interfaces) + return interfaces } func (m *Multicast) announce() { @@ -174,7 +134,7 @@ func (m *Multicast) announce() { panic(err) } for { - interfaces := m.GetInterfaces() + interfaces := m.Interfaces() // There might be interfaces that we configured listeners for but are no // longer up - if that's the case then we should stop the listeners for name, listener := range m.listeners { @@ -308,7 +268,7 @@ func (m *Multicast) listen() { if addr.IP.String() != from.IP.String() { continue } - if _, ok := m.GetInterfaces()[from.Zone]; ok { + if _, ok := m.Interfaces()[from.Zone]; ok { addr.Zone = "" if err := m.core.CallPeer("tcp://"+addr.String(), from.Zone); err != nil { m.log.Debugln("Call from multicast failed:", err) diff --git a/src/multicast/multicast_darwin.go b/src/multicast/multicast_darwin.go index 213bff3a..c88b4a81 100644 --- a/src/multicast/multicast_darwin.go +++ b/src/multicast/multicast_darwin.go @@ -38,8 +38,8 @@ func (m *Multicast) multicastStarted() { awdlGoroutineStarted = true for { C.StopAWDLBrowsing() - for _, intf := range m.GetInterfaces() { - if intf.Name == "awdl0" { + for intf := range m.Interfaces() { + if intf == "awdl0" { m.log.Infoln("Multicast discovery is using AWDL discovery") C.StartAWDLBrowsing() break From 23108e268b2888c27d62687a2e8adb0a4bba722a Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 29 Jun 2019 00:32:23 +0100 Subject: [PATCH 0105/1109] Use go-syslog to fix builds on Windows --- cmd/yggdrasil/main.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 32069c01..6af27725 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -7,7 +7,6 @@ import ( "flag" "fmt" "io/ioutil" - "log/syslog" "os" "os/signal" "strings" @@ -16,6 +15,7 @@ import ( "golang.org/x/text/encoding/unicode" "github.com/gologme/log" + gsyslog "github.com/hashicorp/go-syslog" "github.com/hjson/hjson-go" "github.com/kardianos/minwinsvc" "github.com/mitchellh/mapstructure" @@ -168,8 +168,8 @@ func main() { case "stdout": logger = log.New(os.Stdout, "", log.Flags()) case "syslog": - if syslogwriter, err := syslog.New(syslog.LOG_INFO, yggdrasil.BuildName()); err == nil { - logger = log.New(syslogwriter, "", log.Flags()) + if syslogger, err := gsyslog.NewLogger(gsyslog.LOG_NOTICE, "DAEMON", yggdrasil.BuildName()); err == nil { + logger = log.New(syslogger, "", log.Flags()) } default: if logfd, err := os.Create(*logto); err == nil { From 5df110ac7985e87d1f11dc840cdb55dd6b69cbe4 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 28 Jun 2019 18:42:31 -0500 Subject: [PATCH 0106/1109] make Dial block until the search finishes, and use it as such --- src/tuntap/iface.go | 55 ++++++++--- src/tuntap/tun.go | 2 + src/yggdrasil/conn.go | 199 ++++++++++++---------------------------- src/yggdrasil/dialer.go | 5 + src/yggdrasil/search.go | 6 +- 5 files changed, 111 insertions(+), 156 deletions(-) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index d70a1305..f6cfec9c 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -134,7 +134,7 @@ func (tun *TunAdapter) reader() error { } // Then offset the buffer so that we can now just treat it as an IP // packet from now on - bs = bs[offset:] + bs = bs[offset:] // FIXME this breaks bs for the next read and means n is the wrong value } // From the IP header, work out what our source and destination addresses // and node IDs are. We will need these in order to work out where to send @@ -225,21 +225,46 @@ func (tun *TunAdapter) reader() error { panic("Given empty dstNodeID and dstNodeIDMask - this shouldn't happen") } // Dial to the remote node - if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { - // We've been given a connection so prepare the session wrapper - if s, err := tun.wrap(conn); err != nil { - // Something went wrong when storing the connection, typically that - // something already exists for this address or subnet - tun.log.Debugln("TUN/TAP iface wrap:", err) - } else { - // Update our reference to the connection - session, isIn = s, true + go func() { + // FIXME just spitting out a goroutine to do this is kind of ugly and means we drop packets until the dial finishes + tun.mutex.Lock() + _, known := tun.dials[*dstNodeID] + packet := append(util.GetBytes(), bs[:n]...) + tun.dials[*dstNodeID] = append(tun.dials[*dstNodeID], packet) + for len(tun.dials[*dstNodeID]) > 32 { + util.PutBytes(tun.dials[*dstNodeID][0]) + tun.dials[*dstNodeID] = tun.dials[*dstNodeID][1:] } - } else { - // We weren't able to dial for some reason so there's no point in - // continuing this iteration - skip to the next one - continue - } + tun.mutex.Unlock() + if known { + return + } + var tc *tunConn + if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { + // We've been given a connection so prepare the session wrapper + if tc, err = tun.wrap(conn); err != nil { + // Something went wrong when storing the connection, typically that + // something already exists for this address or subnet + tun.log.Debugln("TUN/TAP iface wrap:", err) + } + } + tun.mutex.Lock() + packets := tun.dials[*dstNodeID] + delete(tun.dials, *dstNodeID) + tun.mutex.Unlock() + if tc != nil { + for _, packet := range packets { + select { + case tc.send <- packet: + default: + util.PutBytes(packet) + } + } + } + }() + // While the dial is going on we can't do much else + // continuing this iteration - skip to the next one + continue } // If we have a connection now, try writing to it if isIn && session != nil { diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 683b83ac..a21f8711 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -49,6 +49,7 @@ type TunAdapter struct { mutex sync.RWMutex // Protects the below addrToConn map[address.Address]*tunConn subnetToConn map[address.Subnet]*tunConn + dials map[crypto.NodeID][][]byte // Buffer of packets to send after dialing finishes isOpen bool } @@ -113,6 +114,7 @@ func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener tun.dialer = dialer tun.addrToConn = make(map[address.Address]*tunConn) tun.subnetToConn = make(map[address.Subnet]*tunConn) + tun.dials = make(map[crypto.NodeID][][]byte) } // Start the setup process for the TUN/TAP adapter. If successful, starts the diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 7216be91..5c9a4136 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -45,23 +45,18 @@ type Conn struct { mutex sync.RWMutex closed bool session *sessionInfo - readDeadline atomic.Value // time.Time // TODO timer - writeDeadline atomic.Value // time.Time // TODO timer - searching atomic.Value // bool - searchwait chan struct{} // Never reset this, it's only used for the initial search - writebuf [][]byte // Packets to be sent if/when the search finishes + readDeadline atomic.Value // time.Time // TODO timer + writeDeadline atomic.Value // time.Time // TODO timer } // TODO func NewConn() that initializes additional fields as needed func newConn(core *Core, nodeID *crypto.NodeID, nodeMask *crypto.NodeID, session *sessionInfo) *Conn { conn := Conn{ - core: core, - nodeID: nodeID, - nodeMask: nodeMask, - session: session, - searchwait: make(chan struct{}), + core: core, + nodeID: nodeID, + nodeMask: nodeMask, + session: session, } - conn.searching.Store(false) return &conn } @@ -69,91 +64,38 @@ func (c *Conn) String() string { return fmt.Sprintf("conn=%p", c) } -// This method should only be called from the router goroutine -func (c *Conn) startSearch() { - // The searchCompleted callback is given to the search - searchCompleted := func(sinfo *sessionInfo, err error) { - defer c.searching.Store(false) - // If the search failed for some reason, e.g. it hit a dead end or timed - // out, then do nothing - if err != nil { - c.core.log.Debugln(c.String(), "DHT search failed:", err) - return - } - // Take the connection mutex - c.mutex.Lock() - defer c.mutex.Unlock() - // Were we successfully given a sessionInfo pointer? - if sinfo != nil { - // Store it, and update the nodeID and nodeMask (which may have been - // wildcarded before now) with their complete counterparts - c.core.log.Debugln(c.String(), "DHT search completed") - c.session = sinfo - c.nodeID = crypto.GetNodeID(&sinfo.theirPermPub) - for i := range c.nodeMask { - c.nodeMask[i] = 0xFF +// This should only be called from the router goroutine +func (c *Conn) search() error { + sinfo, isIn := c.core.searches.searches[*c.nodeID] + if !isIn { + done := make(chan struct{}, 1) + var sess *sessionInfo + var err error + searchCompleted := func(sinfo *sessionInfo, e error) { + sess = sinfo + err = e + // FIXME close can be called multiple times, do a non-blocking send instead + select { + case done <- struct{}{}: + default: } - // Make sure that any blocks on read/write operations are lifted - defer func() { recover() }() // So duplicate searches don't panic - close(c.searchwait) - } else { - // No session was returned - this shouldn't really happen because we - // should always return an error reason if we don't return a session - panic("DHT search didn't return an error or a sessionInfo") } - if c.closed { - // Things were closed before the search returned - // Go ahead and close it again to make sure the session is cleaned up - go c.Close() - } else { - // Send any messages we may have buffered - var msgs [][]byte - msgs, c.writebuf = c.writebuf, nil - go func() { - for _, msg := range msgs { - c.Write(msg) - util.PutBytes(msg) - } - }() - } - } - // doSearch will be called below in response to one or more conditions - doSearch := func() { - c.searching.Store(true) - // Check to see if there is a search already matching the destination - sinfo, isIn := c.core.searches.searches[*c.nodeID] - if !isIn { - // Nothing was found, so create a new search - sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) - c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo) - } - // Continue the search + sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) sinfo.continueSearch() - } - // Take a copy of the session object, in case it changes later - c.mutex.RLock() - sinfo := c.session - c.mutex.RUnlock() - if c.session == nil { - // No session object is present so previous searches, if we ran any, have - // not yielded a useful result (dead end, remote host not found) - doSearch() - } else { - sinfo.worker <- func() { - switch { - case !sinfo.init: - doSearch() - case time.Since(sinfo.time) > 6*time.Second: - if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second { - // TODO double check that the above condition is correct - doSearch() - } else { - c.core.sessions.ping(sinfo) - } - default: // Don't do anything, to keep traffic throttled - } + <-done + c.session = sess + if c.session == nil && err == nil { + panic("search failed but returend no error") } + c.nodeID = crypto.GetNodeID(&c.session.theirPermPub) + for i := range c.nodeMask { + c.nodeMask[i] = 0xFF + } + return err + } else { + return errors.New("search already exists") } + return nil } func getDeadlineTimer(value *atomic.Value) *time.Timer { @@ -167,30 +109,9 @@ func getDeadlineTimer(value *atomic.Value) *time.Timer { func (c *Conn) Read(b []byte) (int, error) { // Take a copy of the session object - c.mutex.RLock() sinfo := c.session - c.mutex.RUnlock() timer := getDeadlineTimer(&c.readDeadline) defer util.TimerStop(timer) - // If there is a search in progress then wait for the result - if sinfo == nil { - // Wait for the search to complete - select { - case <-c.searchwait: - case <-timer.C: - return 0, ConnError{errors.New("timeout"), true, false, 0} - } - // Retrieve our session info again - c.mutex.RLock() - sinfo = c.session - c.mutex.RUnlock() - // If sinfo is still nil at this point then the search failed and the - // searchwait channel has been recreated, so might as well give up and - // return an error code - if sinfo == nil { - return 0, errors.New("search failed") - } - } for { // Wait for some traffic to come through from the session select { @@ -253,32 +174,7 @@ func (c *Conn) Read(b []byte) (int, error) { } func (c *Conn) Write(b []byte) (bytesWritten int, err error) { - c.mutex.RLock() sinfo := c.session - c.mutex.RUnlock() - // If the session doesn't exist, or isn't initialised (which probably means - // that the search didn't complete successfully) then we may need to wait for - // the search to complete or start the search again - if sinfo == nil || !sinfo.init { - // Is a search already taking place? - if searching, sok := c.searching.Load().(bool); !sok || (sok && !searching) { - // No search was already taking place so start a new one - c.core.router.doAdmin(c.startSearch) - } - // Buffer the packet to be sent if/when the search is finished - c.mutex.Lock() - defer c.mutex.Unlock() - c.writebuf = append(c.writebuf, append(util.GetBytes(), b...)) - for len(c.writebuf) > 32 { - util.PutBytes(c.writebuf[0]) - c.writebuf = c.writebuf[1:] - } - return len(b), nil - } else { - // This triggers some session keepalive traffic - // FIXME this desparately needs to be refactored, since the ping case needlessly goes through the router goroutine just to have it pass a function to the session worker when it determines that a session already exists. - c.core.router.doAdmin(c.startSearch) - } var packet []byte done := make(chan struct{}) written := len(b) @@ -301,6 +197,34 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { } packet = p.encode() sinfo.bytesSent += uint64(len(b)) + // The rest of this work is session keep-alive traffic + doSearch := func() { + routerWork := func() { + // Check to see if there is a search already matching the destination + sinfo, isIn := c.core.searches.searches[*c.nodeID] + if !isIn { + // Nothing was found, so create a new search + searchCompleted := func(sinfo *sessionInfo, e error) {} + sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) + c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo) + } + // Continue the search + sinfo.continueSearch() + } + go func() { c.core.router.admin <- routerWork }() + } + switch { + case !sinfo.init: + doSearch() + case time.Since(sinfo.time) > 6*time.Second: + if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second { + // TODO double check that the above condition is correct + doSearch() + } else { + sinfo.core.sessions.ping(sinfo) + } + default: // Don't do anything, to keep traffic throttled + } } // Set up a timer so this doesn't block forever timer := getDeadlineTimer(&c.writeDeadline) @@ -327,7 +251,6 @@ func (c *Conn) Close() error { if c.session != nil { // Close the session, if it hasn't been closed already c.session.close() - c.session = nil } // This can't fail yet - TODO? c.closed = true diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index 1943c859..1e3e0d6e 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -14,6 +14,8 @@ type Dialer struct { core *Core } +// TODO DialContext that allows timeouts/cancellation, Dial should just call this with no timeout set in the context + // Dial opens a session to the given node. The first paramter should be "nodeid" // and the second parameter should contain a hexadecimal representation of the // target node ID. @@ -58,5 +60,8 @@ func (d *Dialer) Dial(network, address string) (*Conn, error) { // NodeID parameters. func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (*Conn, error) { conn := newConn(d.core, nodeID, nodeMask, nil) + if err := conn.search(); err != nil { + return nil, err + } return conn, nil } diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 576034bf..b43f0e46 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -141,7 +141,7 @@ func (sinfo *searchInfo) doSearchStep() { if len(sinfo.toVisit) == 0 { // Dead end, do cleanup delete(sinfo.core.searches.searches, sinfo.dest) - go sinfo.callback(nil, errors.New("search reached dead end")) + sinfo.callback(nil, errors.New("search reached dead end")) return } // Send to the next search target @@ -205,7 +205,7 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { sess = sinfo.core.sessions.createSession(&res.Key) if sess == nil { // nil if the DHT search finished but the session wasn't allowed - go sinfo.callback(nil, errors.New("session not allowed")) + sinfo.callback(nil, errors.New("session not allowed")) return true } _, isIn := sinfo.core.sessions.getByTheirPerm(&res.Key) @@ -217,7 +217,7 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { sess.coords = res.Coords sess.packet = sinfo.packet sinfo.core.sessions.ping(sess) - go sinfo.callback(sess, nil) + sinfo.callback(sess, nil) // Cleanup delete(sinfo.core.searches.searches, res.Dest) return true From c808be514ffea600ec8cf3ef02b48dd1612e142a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 28 Jun 2019 19:11:28 -0500 Subject: [PATCH 0107/1109] make tunAdapter.wrap return the right thing --- src/tuntap/tun.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index a21f8711..ed5d2d45 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -237,6 +237,7 @@ func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { stop: make(chan struct{}), alive: make(chan struct{}, 1), } + c = &s // Get the remote address and subnet of the other side remoteNodeID := conn.RemoteAddr() s.addr = *address.AddrForNodeID(&remoteNodeID) From e7cb76cea3cad1ac735c712b1b32b2a7b1d34b53 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 28 Jun 2019 19:21:44 -0500 Subject: [PATCH 0108/1109] clean up unused old session maps --- src/yggdrasil/session.go | 60 +--------------------------------------- 1 file changed, 1 insertion(+), 59 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 22118476..55b0ed43 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -118,12 +118,8 @@ type sessions struct { isAllowedHandler func(pubkey *crypto.BoxPubKey, initiator bool) bool // Returns true or false if session setup is allowed isAllowedMutex sync.RWMutex // Protects the above permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey // Maps known permanent keys to their shared key, used by DHT a lot - sinfos map[crypto.Handle]*sessionInfo // Maps (secret) handle onto session info - conns map[crypto.Handle]*Conn // Maps (secret) handle onto connections - byMySes map[crypto.BoxPubKey]*crypto.Handle // Maps mySesPub onto handle + sinfos map[crypto.Handle]*sessionInfo // Maps handle onto session info byTheirPerm map[crypto.BoxPubKey]*crypto.Handle // Maps theirPermPub onto handle - addrToPerm map[address.Address]*crypto.BoxPubKey - subnetToPerm map[address.Subnet]*crypto.BoxPubKey } // Initializes the session struct. @@ -149,10 +145,7 @@ func (ss *sessions) init(core *Core) { }() ss.permShared = make(map[crypto.BoxPubKey]*crypto.BoxSharedKey) ss.sinfos = make(map[crypto.Handle]*sessionInfo) - ss.byMySes = make(map[crypto.BoxPubKey]*crypto.Handle) ss.byTheirPerm = make(map[crypto.BoxPubKey]*crypto.Handle) - ss.addrToPerm = make(map[address.Address]*crypto.BoxPubKey) - ss.subnetToPerm = make(map[address.Subnet]*crypto.BoxPubKey) ss.lastCleanup = time.Now() } @@ -175,16 +168,6 @@ func (ss *sessions) getSessionForHandle(handle *crypto.Handle) (*sessionInfo, bo return sinfo, isIn } -// Gets a session corresponding to an ephemeral session key used by this node. -func (ss *sessions) getByMySes(key *crypto.BoxPubKey) (*sessionInfo, bool) { - h, isIn := ss.byMySes[*key] - if !isIn { - return nil, false - } - sinfo, isIn := ss.getSessionForHandle(h) - return sinfo, isIn -} - // Gets a session corresponding to a permanent key used by the remote node. func (ss *sessions) getByTheirPerm(key *crypto.BoxPubKey) (*sessionInfo, bool) { h, isIn := ss.byTheirPerm[*key] @@ -195,26 +178,6 @@ func (ss *sessions) getByTheirPerm(key *crypto.BoxPubKey) (*sessionInfo, bool) { return sinfo, isIn } -// Gets a session corresponding to an IPv6 address used by the remote node. -func (ss *sessions) getByTheirAddr(addr *address.Address) (*sessionInfo, bool) { - p, isIn := ss.addrToPerm[*addr] - if !isIn { - return nil, false - } - sinfo, isIn := ss.getByTheirPerm(p) - return sinfo, isIn -} - -// Gets a session corresponding to an IPv6 /64 subnet used by the remote node/network. -func (ss *sessions) getByTheirSubnet(snet *address.Subnet) (*sessionInfo, bool) { - p, isIn := ss.subnetToPerm[*snet] - if !isIn { - return nil, false - } - sinfo, isIn := ss.getByTheirPerm(p) - return sinfo, isIn -} - // Creates a new session and lazily cleans up old existing sessions. This // includse initializing session info to sane defaults (e.g. lowest supported // MTU). @@ -263,10 +226,7 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.worker = make(chan func(), 1) sinfo.recv = make(chan *wire_trafficPacket, 32) ss.sinfos[sinfo.myHandle] = &sinfo - ss.byMySes[sinfo.mySesPub] = &sinfo.myHandle ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle - ss.addrToPerm[sinfo.theirAddr] = &sinfo.theirPermPub - ss.subnetToPerm[sinfo.theirSubnet] = &sinfo.theirPermPub go sinfo.workerMain() return &sinfo } @@ -291,36 +251,18 @@ func (ss *sessions) cleanup() { sinfos[k] = v } ss.sinfos = sinfos - byMySes := make(map[crypto.BoxPubKey]*crypto.Handle, len(ss.byMySes)) - for k, v := range ss.byMySes { - byMySes[k] = v - } - ss.byMySes = byMySes byTheirPerm := make(map[crypto.BoxPubKey]*crypto.Handle, len(ss.byTheirPerm)) for k, v := range ss.byTheirPerm { byTheirPerm[k] = v } ss.byTheirPerm = byTheirPerm - addrToPerm := make(map[address.Address]*crypto.BoxPubKey, len(ss.addrToPerm)) - for k, v := range ss.addrToPerm { - addrToPerm[k] = v - } - ss.addrToPerm = addrToPerm - subnetToPerm := make(map[address.Subnet]*crypto.BoxPubKey, len(ss.subnetToPerm)) - for k, v := range ss.subnetToPerm { - subnetToPerm[k] = v - } - ss.subnetToPerm = subnetToPerm ss.lastCleanup = time.Now() } // Closes a session, removing it from sessions maps and killing the worker goroutine. func (sinfo *sessionInfo) close() { delete(sinfo.core.sessions.sinfos, sinfo.myHandle) - delete(sinfo.core.sessions.byMySes, sinfo.mySesPub) delete(sinfo.core.sessions.byTheirPerm, sinfo.theirPermPub) - delete(sinfo.core.sessions.addrToPerm, sinfo.theirAddr) - delete(sinfo.core.sessions.subnetToPerm, sinfo.theirSubnet) close(sinfo.worker) } From e88bef35c0be4b60577fb344f2c27fc6b54e4196 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 28 Jun 2019 20:02:58 -0500 Subject: [PATCH 0109/1109] get rid of old buffered session packets --- src/yggdrasil/conn.go | 2 +- src/yggdrasil/search.go | 2 -- src/yggdrasil/session.go | 14 ++------------ 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 5c9a4136..a4036c78 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -215,7 +215,7 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { } switch { case !sinfo.init: - doSearch() + sinfo.core.sessions.ping(sinfo) case time.Since(sinfo.time) > 6*time.Second: if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second { // TODO double check that the above condition is correct diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index b43f0e46..d8c9049a 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -37,7 +37,6 @@ type searchInfo struct { dest crypto.NodeID mask crypto.NodeID time time.Time - packet []byte toVisit []*dhtInfo visited map[crypto.NodeID]bool callback func(*sessionInfo, error) @@ -215,7 +214,6 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { } // FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)? sess.coords = res.Coords - sess.packet = sinfo.packet sinfo.core.sessions.ping(sess) sinfo.callback(sess, nil) // Cleanup diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 55b0ed43..dc3f01e8 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -39,7 +39,6 @@ type sessionInfo struct { pingTime time.Time // time the first ping was sent since the last received packet pingSend time.Time // time the last ping was sent coords []byte // coords of destination - packet []byte // a buffered packet, sent immediately on ping/pong init bool // Reset if coords change tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation bytesSent uint64 // Bytes of real traffic sent in this session @@ -325,8 +324,8 @@ func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) { } packet := p.encode() ss.core.router.out(packet) - if !isPong { - sinfo.pingSend = time.Now() + if sinfo.pingTime.Before(sinfo.time) { + sinfo.pingTime = time.Now() } } @@ -367,15 +366,6 @@ func (ss *sessions) handlePing(ping *sessionPing) { if !ping.IsPong { ss.sendPingPong(sinfo, true) } - if sinfo.packet != nil { - /* FIXME this needs to live in the net.Conn or something, needs work in Write - // send - var bs []byte - bs, sinfo.packet = sinfo.packet, nil - ss.core.router.sendPacket(bs) // FIXME this needs to live in the net.Conn or something, needs work in Write - */ - sinfo.packet = nil - } }) } From 784acba82398e411de2419231362e439f6f471e8 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 29 Jun 2019 12:14:44 -0500 Subject: [PATCH 0110/1109] I think this fixes the concurrent map read/write panic --- src/yggdrasil/conn.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index a4036c78..0357ccad 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -64,9 +64,11 @@ func (c *Conn) String() string { return fmt.Sprintf("conn=%p", c) } -// This should only be called from the router goroutine +// This should never be called from the router goroutine func (c *Conn) search() error { - sinfo, isIn := c.core.searches.searches[*c.nodeID] + var sinfo *searchInfo + var isIn bool + c.core.router.doAdmin(func() { sinfo, isIn = c.core.searches.searches[*c.nodeID] }) if !isIn { done := make(chan struct{}, 1) var sess *sessionInfo @@ -80,8 +82,10 @@ func (c *Conn) search() error { default: } } - sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) - sinfo.continueSearch() + c.core.router.doAdmin(func() { + sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) + sinfo.continueSearch() + }) <-done c.session = sess if c.session == nil && err == nil { From ca1f2bb0a27ec604067beb0d6361833194833e86 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 29 Jun 2019 12:33:00 -0500 Subject: [PATCH 0111/1109] add go-syslog to go.mod/go.sum --- go.mod | 1 + go.sum | 2 ++ 2 files changed, 3 insertions(+) diff --git a/go.mod b/go.mod index 995e54c2..eec583ae 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go require ( github.com/docker/libcontainer v2.2.1+incompatible github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 + github.com/hashicorp/go-syslog v1.0.0 github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/mitchellh/mapstructure v1.1.2 diff --git a/go.sum b/go.sum index 92dfe88c..4be88b29 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/docker/libcontainer v2.2.1+incompatible h1:++SbbkCw+X8vAd4j2gOCzZ2Nn7 github.com/docker/libcontainer v2.2.1+incompatible/go.mod h1:osvj61pYsqhNCMLGX31xr7klUBhHb/ZBuXS0o1Fvwbw= github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 h1:WD8iJ37bRNwvETMfVTusVSAi0WdXTpfNVGY2aHycNKY= github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= +github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 h1:xmvkbxXDeN1ffWq8kvrhyqVYAO2aXuRBsbpxVTR+JyU= github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk8851WTVWlqMC1ecJH07Ctz+Ezxx4u54g= From 818eca90dbbcc973852dfd4274fa6cf3a71db00a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 29 Jun 2019 16:10:02 -0500 Subject: [PATCH 0112/1109] fix nil pointer deref if searches fail, block dial until a search exceeds or a timeout passes (todo: replace timer with context) --- src/yggdrasil/conn.go | 14 ++++++++------ src/yggdrasil/dialer.go | 12 +++++++++++- src/yggdrasil/router.go | 2 +- src/yggdrasil/session.go | 17 +++++++++++++---- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 0357ccad..53d2551e 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -89,11 +89,13 @@ func (c *Conn) search() error { <-done c.session = sess if c.session == nil && err == nil { - panic("search failed but returend no error") + panic("search failed but returned no error") } - c.nodeID = crypto.GetNodeID(&c.session.theirPermPub) - for i := range c.nodeMask { - c.nodeMask[i] = 0xFF + if c.session != nil { + c.nodeID = crypto.GetNodeID(&c.session.theirPermPub) + for i := range c.nodeMask { + c.nodeMask[i] = 0xFF + } } return err } else { @@ -218,8 +220,6 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { go func() { c.core.router.admin <- routerWork }() } switch { - case !sinfo.init: - sinfo.core.sessions.ping(sinfo) case time.Since(sinfo.time) > 6*time.Second: if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second { // TODO double check that the above condition is correct @@ -227,6 +227,8 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { } else { sinfo.core.sessions.ping(sinfo) } + case sinfo.reset && sinfo.pingTime.Before(sinfo.time): + sinfo.core.sessions.ping(sinfo) default: // Don't do anything, to keep traffic throttled } } diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index 1e3e0d6e..6b24cfb4 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -5,6 +5,7 @@ import ( "errors" "strconv" "strings" + "time" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" ) @@ -61,7 +62,16 @@ func (d *Dialer) Dial(network, address string) (*Conn, error) { func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (*Conn, error) { conn := newConn(d.core, nodeID, nodeMask, nil) if err := conn.search(); err != nil { + conn.Close() return nil, err } - return conn, nil + t := time.NewTimer(6 * time.Second) // TODO use a context instead + defer t.Stop() + select { + case <-conn.session.init: + return conn, nil + case <-t.C: + conn.Close() + return nil, errors.New("session handshake timeout") + } } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 2e32fb6b..514d14fa 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -119,7 +119,7 @@ func (r *router) mainLoop() { case info := <-r.core.dht.peers: r.core.dht.insertPeer(info) case <-r.reset: - r.core.sessions.resetInits() + r.core.sessions.reset() r.core.dht.reset() case <-ticker.C: { diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index dc3f01e8..98a12c7b 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -39,12 +39,13 @@ type sessionInfo struct { pingTime time.Time // time the first ping was sent since the last received packet pingSend time.Time // time the last ping was sent coords []byte // coords of destination - init bool // Reset if coords change + reset bool // reset if coords change tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation bytesSent uint64 // Bytes of real traffic sent in this session bytesRecvd uint64 // Bytes of real traffic received in this session worker chan func() // Channel to send work to the session worker recv chan *wire_trafficPacket // Received packets go here, picked up by the associated Conn + init chan struct{} // Closed when the first session pong arrives, used to signal that the session is ready for initial use } func (sinfo *sessionInfo) doWorker(f func()) { @@ -101,7 +102,14 @@ func (s *sessionInfo) update(p *sessionPing) bool { } s.time = time.Now() s.tstamp = p.Tstamp - s.init = true + s.reset = false + defer func() { recover() }() // Recover if the below panics + select { + case <-s.init: + default: + // Unblock anything waiting for the session to initialize + close(s.init) + } return true } @@ -203,6 +211,7 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.mtuTime = now sinfo.pingTime = now sinfo.pingSend = now + sinfo.init = make(chan struct{}) higher := false for idx := range ss.core.boxPub { if ss.core.boxPub[idx] > sinfo.theirPermPub[idx] { @@ -410,10 +419,10 @@ func (sinfo *sessionInfo) updateNonce(theirNonce *crypto.BoxNonce) { // Resets all sessions to an uninitialized state. // Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change. -func (ss *sessions) resetInits() { +func (ss *sessions) reset() { for _, sinfo := range ss.sinfos { sinfo.doWorker(func() { - sinfo.init = false + sinfo.reset = true }) } } From 7d58a7ef3e8be7a0df386db7e79ed9c0de91ba62 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 29 Jun 2019 17:44:28 -0500 Subject: [PATCH 0113/1109] fix channel multiple close bug and concurrency bug in the way sessionInfo.close was being called --- src/yggdrasil/conn.go | 2 +- src/yggdrasil/session.go | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 53d2551e..38e4df9f 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -256,7 +256,7 @@ func (c *Conn) Close() error { defer c.mutex.Unlock() if c.session != nil { // Close the session, if it hasn't been closed already - c.session.close() + c.core.router.doAdmin(c.session.close) } // This can't fail yet - TODO? c.closed = true diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 98a12c7b..4cc059e6 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -269,8 +269,11 @@ func (ss *sessions) cleanup() { // Closes a session, removing it from sessions maps and killing the worker goroutine. func (sinfo *sessionInfo) close() { - delete(sinfo.core.sessions.sinfos, sinfo.myHandle) - delete(sinfo.core.sessions.byTheirPerm, sinfo.theirPermPub) + if s := sinfo.core.sessions.sinfos[sinfo.myHandle]; s == sinfo { + delete(sinfo.core.sessions.sinfos, sinfo.myHandle) + delete(sinfo.core.sessions.byTheirPerm, sinfo.theirPermPub) + } + defer func() { recover() }() close(sinfo.worker) } From 28db566b37b14224ffbf1505b89d8eab2a0d0f32 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 29 Jun 2019 18:44:24 -0500 Subject: [PATCH 0114/1109] fix concurrency bug in iface.go --- src/tuntap/iface.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index f6cfec9c..16a3b65d 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -225,11 +225,11 @@ func (tun *TunAdapter) reader() error { panic("Given empty dstNodeID and dstNodeIDMask - this shouldn't happen") } // Dial to the remote node + packet := append(util.GetBytes(), bs[:n]...) go func() { // FIXME just spitting out a goroutine to do this is kind of ugly and means we drop packets until the dial finishes tun.mutex.Lock() _, known := tun.dials[*dstNodeID] - packet := append(util.GetBytes(), bs[:n]...) tun.dials[*dstNodeID] = append(tun.dials[*dstNodeID], packet) for len(tun.dials[*dstNodeID]) > 32 { util.PutBytes(tun.dials[*dstNodeID][0]) From d39428735df66a39acef3da98c2ca3eac5237a15 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 29 Jun 2019 18:50:21 -0500 Subject: [PATCH 0115/1109] recover if we try to send to a closed session worker due to a race between a Conn.Write call and a Conn.Close call --- src/yggdrasil/conn.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 38e4df9f..b4f68e18 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -236,6 +236,11 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { timer := getDeadlineTimer(&c.writeDeadline) defer util.TimerStop(timer) // Hand over to the session worker + defer func() { + if recover() != nil { + err = errors.New("write failed") + } + }() // In case we're racing with a close select { // Send to worker case sinfo.worker <- workerFunc: case <-timer.C: From 40553a6a44e88c3df8e164f7b2386d92c3bc93cc Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 29 Jun 2019 18:56:26 -0500 Subject: [PATCH 0116/1109] make GetSessions use the session workers to avoid races --- src/yggdrasil/api.go | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 864e7f37..7270bc2e 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -211,16 +211,31 @@ func (c *Core) GetSessions() []Session { var sessions []Session getSessions := func() { for _, sinfo := range c.sessions.sinfos { - // TODO? skipped known but timed out sessions? - session := Session{ - Coords: append([]byte{}, sinfo.coords...), - MTU: sinfo.getMTU(), - BytesSent: sinfo.bytesSent, - BytesRecvd: sinfo.bytesRecvd, - Uptime: time.Now().Sub(sinfo.timeOpened), - WasMTUFixed: sinfo.wasMTUFixed, + var session Session + workerFunc := func() { + session := Session{ + Coords: append([]byte{}, sinfo.coords...), + MTU: sinfo.getMTU(), + BytesSent: sinfo.bytesSent, + BytesRecvd: sinfo.bytesRecvd, + Uptime: time.Now().Sub(sinfo.timeOpened), + WasMTUFixed: sinfo.wasMTUFixed, + } + copy(session.PublicKey[:], sinfo.theirPermPub[:]) } - copy(session.PublicKey[:], sinfo.theirPermPub[:]) + var skip bool + func() { + defer func() { + if recover() != nil { + skip = true + } + }() + sinfo.doWorker(workerFunc) + }() + if skip { + continue + } + // TODO? skipped known but timed out sessions? sessions = append(sessions, session) } } From fbe44ea97377fca5f8d5af7495b257e1abd954fa Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 29 Jun 2019 19:25:34 -0500 Subject: [PATCH 0117/1109] fix bug in session api code --- src/yggdrasil/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 7270bc2e..1bec9836 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -213,7 +213,7 @@ func (c *Core) GetSessions() []Session { for _, sinfo := range c.sessions.sinfos { var session Session workerFunc := func() { - session := Session{ + session = Session{ Coords: append([]byte{}, sinfo.coords...), MTU: sinfo.getMTU(), BytesSent: sinfo.bytesSent, From cd29fde178e554c190585d19fa3d12e18980601e Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 29 Jun 2019 19:32:15 -0500 Subject: [PATCH 0118/1109] temporary workaround to concurrency bug in sessions.getSharedKey --- src/yggdrasil/session.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 4cc059e6..53836c38 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -298,6 +298,8 @@ func (ss *sessions) getPing(sinfo *sessionInfo) sessionPing { // This comes up with dht req/res and session ping/pong traffic. func (ss *sessions) getSharedKey(myPriv *crypto.BoxPrivKey, theirPub *crypto.BoxPubKey) *crypto.BoxSharedKey { + return crypto.GetSharedKey(myPriv, theirPub) + // FIXME concurrency issues with the below, so for now we just burn the CPU every time if skey, isIn := ss.permShared[*theirPub]; isIn { return skey } From 86c30a1fc4f918463e70571befbc07bf6b10622b Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 1 Jul 2019 18:55:07 -0500 Subject: [PATCH 0119/1109] fix another panic from a send on a closed session worker channel, from races between Conn.Read/Write/Close --- src/yggdrasil/conn.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index b4f68e18..5d1e77ac 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -159,6 +159,12 @@ func (c *Conn) Read(b []byte) (int, error) { sinfo.bytesRecvd += uint64(len(b)) } // Hand over to the session worker + defer func() { + if recover() != nil { + err = errors.New("read failed, session already closed") + close(done) + } + }() // In case we're racing with a close select { // Send to worker case sinfo.worker <- workerFunc: case <-timer.C: @@ -238,7 +244,8 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { // Hand over to the session worker defer func() { if recover() != nil { - err = errors.New("write failed") + err = errors.New("write failed, session already closed") + close(done) } }() // In case we're racing with a close select { // Send to worker From 12486b055734f92c1622af9128f28f8376f75d02 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 6 Jul 2019 11:52:30 +0100 Subject: [PATCH 0120/1109] Try to more gracefully handle shutdowns on Windows --- cmd/yggdrasil/main.go | 16 +++++++++------- src/multicast/multicast.go | 7 +++++++ src/tuntap/iface.go | 6 ++++++ src/tuntap/tun.go | 10 ++++++++++ 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 6af27725..fd8828c8 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -231,11 +231,6 @@ func main() { } else { logger.Errorln("Unable to get Listener:", err) } - // The Stop function ensures that the TUN/TAP adapter is correctly shut down - // before the program exits. - defer func() { - n.core.Stop() - }() // Make some nice output that tells us what our IPv6 address and subnet are. // This is just logged to stdout for the user. address := n.core.Address() @@ -256,6 +251,8 @@ func main() { // deferred Stop function above will run which will shut down TUN/TAP. for { select { + case _ = <-c: + goto exit case _ = <-r: if *useconffile != "" { cfg = readConfig(useconf, useconffile, normaliseconf) @@ -265,11 +262,16 @@ func main() { } else { logger.Errorln("Reloading config at runtime is only possible with -useconffile") } - case _ = <-c: - goto exit } } exit: + // When gracefully shutting down we should try and clean up as much as + // possible, although not all of these functions are necessarily implemented + // yet + n.core.Stop() + n.admin.Stop() + n.multicast.Stop() + n.tuntap.Stop() } func (n *node) sessionFirewall(pubkey *crypto.BoxPubKey, initiator bool) bool { diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 3c0d8c0f..ba1f18fb 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -26,6 +26,7 @@ type Multicast struct { groupAddr string listeners map[string]*yggdrasil.TcpListener listenPort uint16 + isOpen bool } // Init prepares the multicast interface for use. @@ -61,6 +62,7 @@ func (m *Multicast) Start() error { // Windows can't set this flag, so we need to handle it in other ways } + m.isOpen = true go m.multicastStarted() go m.listen() go m.announce() @@ -70,6 +72,8 @@ func (m *Multicast) Start() error { // Stop is not implemented for multicast yet. func (m *Multicast) Stop() error { + m.isOpen = false + m.sock.Close() return nil } @@ -246,6 +250,9 @@ func (m *Multicast) listen() { for { nBytes, rcm, fromAddr, err := m.sock.ReadFrom(bs) if err != nil { + if !m.isOpen { + return + } panic(err) } if rcm != nil { diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 16a3b65d..60c814c2 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -98,6 +98,9 @@ func (tun *TunAdapter) writer() error { util.PutBytes(b) } if err != nil { + if !tun.isOpen { + return err + } tun.log.Errorln("TUN/TAP iface write error:", err) continue } @@ -114,6 +117,9 @@ func (tun *TunAdapter) reader() error { // Wait for a packet to be delivered to us through the TUN/TAP adapter n, err := tun.iface.Read(bs) if err != nil { + if !tun.isOpen { + return err + } panic(err) } if n == 0 { diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index ed5d2d45..b7b4cfa6 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -181,6 +181,16 @@ func (tun *TunAdapter) Start() error { return nil } +// Start the setup process for the TUN/TAP adapter. If successful, starts the +// read/write goroutines to handle packets on that interface. +func (tun *TunAdapter) Stop() error { + tun.isOpen = false + // TODO: we have nothing that cleanly stops all the various goroutines opened + // by TUN/TAP, e.g. readers/writers, sessions + tun.iface.Close() + return nil +} + // UpdateConfig updates the TUN/TAP module with the provided config.NodeConfig // and then signals the various module goroutines to reconfigure themselves if // needed. From 02c99d3e7d2ae9d66557533e365ecc5f79113ebe Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 6 Jul 2019 12:04:31 +0100 Subject: [PATCH 0121/1109] More directly define a minwinsvc exit handler --- cmd/yggdrasil/main.go | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index fd8828c8..79446849 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -172,7 +172,7 @@ func main() { logger = log.New(syslogger, "", log.Flags()) } default: - if logfd, err := os.Create(*logto); err == nil { + if logfd, err := os.OpenFile(*logto, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil { logger = log.New(logfd, "", log.Flags()) } } @@ -242,11 +242,16 @@ func main() { r := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) signal.Notify(r, os.Interrupt, syscall.SIGHUP) - // Create a function to capture the service being stopped on Windows. - winTerminate := func() { - c <- os.Interrupt + // Define what happens when we want to stop Yggdrasil. + terminate := func() { + n.core.Stop() + n.admin.Stop() + n.multicast.Stop() + n.tuntap.Stop() + os.Exit(0) } - minwinsvc.SetOnExit(winTerminate) + // Capture the service being stopped on Windows. + minwinsvc.SetOnExit(terminate) // Wait for the terminate/interrupt signal. Once a signal is received, the // deferred Stop function above will run which will shut down TUN/TAP. for { @@ -265,13 +270,7 @@ func main() { } } exit: - // When gracefully shutting down we should try and clean up as much as - // possible, although not all of these functions are necessarily implemented - // yet - n.core.Stop() - n.admin.Stop() - n.multicast.Stop() - n.tuntap.Stop() + terminate() } func (n *node) sessionFirewall(pubkey *crypto.BoxPubKey, initiator bool) bool { From 618d46a7b30bb7e491591ebeb3f15ad40e41021d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 6 Jul 2019 12:12:30 +0100 Subject: [PATCH 0122/1109] Don't block on adding peers in case one is unreachable and we are forced to wait for timeout --- src/yggdrasil/core.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 3a7f9f1b..62d89a8b 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -88,14 +88,14 @@ func (c *Core) addPeerLoop() { // Add peers from the Peers section for _, peer := range current.Peers { - c.AddPeer(peer, "") + go c.AddPeer(peer, "") time.Sleep(time.Second) } // Add peers from the InterfacePeers section for intf, intfpeers := range current.InterfacePeers { for _, peer := range intfpeers { - c.AddPeer(peer, intf) + go c.AddPeer(peer, intf) time.Sleep(time.Second) } } From 4804ce39afb250e2a5890182007e3b66e2a620a3 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 6 Jul 2019 12:17:40 +0100 Subject: [PATCH 0123/1109] Tidy up the terminate path a bit --- cmd/yggdrasil/main.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 79446849..129b01d5 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -242,16 +242,9 @@ func main() { r := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) signal.Notify(r, os.Interrupt, syscall.SIGHUP) - // Define what happens when we want to stop Yggdrasil. - terminate := func() { - n.core.Stop() - n.admin.Stop() - n.multicast.Stop() - n.tuntap.Stop() - os.Exit(0) - } // Capture the service being stopped on Windows. - minwinsvc.SetOnExit(terminate) + minwinsvc.SetOnExit(n.shutdown) + defer n.shutdown() // Wait for the terminate/interrupt signal. Once a signal is received, the // deferred Stop function above will run which will shut down TUN/TAP. for { @@ -270,7 +263,14 @@ func main() { } } exit: - terminate() +} + +func (n *node) shutdown() { + n.core.Stop() + n.admin.Stop() + n.multicast.Stop() + n.tuntap.Stop() + os.Exit(0) } func (n *node) sessionFirewall(pubkey *crypto.BoxPubKey, initiator bool) bool { From e8272926a422ce4deaae20f8fdf4ff8b4154740c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 6 Jul 2019 15:08:17 +0100 Subject: [PATCH 0124/1109] Fix TAP mode --- src/tuntap/ckr.go | 3 ++ src/tuntap/icmpv6.go | 94 +++++++++++++++++++++++++++++++++----------- src/tuntap/iface.go | 25 ++++++------ src/tuntap/tun.go | 19 ++------- 4 files changed, 88 insertions(+), 53 deletions(-) diff --git a/src/tuntap/ckr.go b/src/tuntap/ckr.go index c9233e64..00320391 100644 --- a/src/tuntap/ckr.go +++ b/src/tuntap/ckr.go @@ -48,8 +48,11 @@ func (c *cryptokey) init(tun *TunAdapter) { } }() + c.tun.log.Debugln("Configuring CKR...") if err := c.configure(); err != nil { c.tun.log.Errorln("CKR configuration failed:", err) + } else { + c.tun.log.Debugln("CKR configured") } } diff --git a/src/tuntap/icmpv6.go b/src/tuntap/icmpv6.go index 8159e0f9..ea1a785b 100644 --- a/src/tuntap/icmpv6.go +++ b/src/tuntap/icmpv6.go @@ -13,6 +13,7 @@ import ( "encoding/binary" "errors" "net" + "sync" "time" "golang.org/x/net/icmp" @@ -21,19 +22,18 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/address" ) -type macAddress [6]byte - const len_ETHER = 14 type ICMPv6 struct { - tun *TunAdapter - mylladdr net.IP - mymac macAddress - peermacs map[address.Address]neighbor + tun *TunAdapter + mylladdr net.IP + mymac net.HardwareAddr + peermacs map[address.Address]neighbor + peermacsmutex sync.RWMutex } type neighbor struct { - mac macAddress + mac net.HardwareAddr learned bool lastadvertisement time.Time lastsolicitation time.Time @@ -61,10 +61,12 @@ func ipv6Header_Marshal(h *ipv6.Header) ([]byte, error) { // addresses. func (i *ICMPv6) Init(t *TunAdapter) { i.tun = t + i.peermacsmutex.Lock() i.peermacs = make(map[address.Address]neighbor) + i.peermacsmutex.Unlock() // Our MAC address and link-local address - i.mymac = macAddress{ + i.mymac = net.HardwareAddr{ 0x02, 0x00, 0x00, 0x00, 0x00, 0x02} i.mylladdr = net.IP{ 0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -181,16 +183,30 @@ func (i *ICMPv6) UnmarshalPacket(datain []byte, datamac *[]byte) ([]byte, error) if datamac != nil { var addr address.Address var target address.Address - var mac macAddress + mac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00} copy(addr[:], ipv6Header.Src[:]) copy(target[:], datain[48:64]) copy(mac[:], (*datamac)[:]) - // fmt.Printf("Learning peer MAC %x for %x\n", mac, target) + i.peermacsmutex.Lock() neighbor := i.peermacs[target] neighbor.mac = mac neighbor.learned = true neighbor.lastadvertisement = time.Now() i.peermacs[target] = neighbor + i.peermacsmutex.Unlock() + i.tun.log.Debugln("Learned peer MAC", mac.String(), "for", net.IP(target[:]).String()) + /* + i.tun.log.Debugln("Peer MAC table:") + i.peermacsmutex.RLock() + for t, n := range i.peermacs { + if n.learned { + i.tun.log.Debugln("- Target", net.IP(t[:]).String(), "has MAC", n.mac.String()) + } else { + i.tun.log.Debugln("- Target", net.IP(t[:]).String(), "is not learned yet") + } + } + i.peermacsmutex.RUnlock() + */ } return nil, errors.New("No response needed") } @@ -201,7 +217,7 @@ func (i *ICMPv6) UnmarshalPacket(datain []byte, datamac *[]byte) ([]byte, error) // Creates an ICMPv6 packet based on the given icmp.MessageBody and other // parameters, complete with ethernet and IP headers, which can be written // directly to a TAP adapter. -func (i *ICMPv6) CreateICMPv6L2(dstmac macAddress, dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) { +func (i *ICMPv6) CreateICMPv6L2(dstmac net.HardwareAddr, dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) { // Pass through to CreateICMPv6 ipv6packet, err := CreateICMPv6(dst, src, mtype, mcode, mbody) if err != nil { @@ -264,13 +280,46 @@ func CreateICMPv6(dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody return responsePacket, nil } -func (i *ICMPv6) CreateNDPL2(dst address.Address) ([]byte, error) { +func (i *ICMPv6) Solicit(addr address.Address) { + retries := 5 + for retries > 0 { + retries-- + i.peermacsmutex.RLock() + if n, ok := i.peermacs[addr]; ok && n.learned { + i.tun.log.Debugln("MAC learned for", net.IP(addr[:]).String()) + i.peermacsmutex.RUnlock() + return + } + i.peermacsmutex.RUnlock() + i.tun.log.Debugln("Sending neighbor solicitation for", net.IP(addr[:]).String()) + i.peermacsmutex.Lock() + if n, ok := i.peermacs[addr]; !ok { + i.peermacs[addr] = neighbor{ + lastsolicitation: time.Now(), + } + } else { + n.lastsolicitation = time.Now() + } + i.peermacsmutex.Unlock() + request, err := i.createNDPL2(addr) + if err != nil { + panic(err) + } + if _, err := i.tun.iface.Write(request); err != nil { + panic(err) + } + i.tun.log.Debugln("Sent neighbor solicitation for", net.IP(addr[:]).String()) + time.Sleep(time.Second) + } +} + +func (i *ICMPv6) createNDPL2(dst address.Address) ([]byte, error) { // Create the ND payload var payload [28]byte - copy(payload[:4], []byte{0x00, 0x00, 0x00, 0x00}) - copy(payload[4:20], dst[:]) - copy(payload[20:22], []byte{0x01, 0x01}) - copy(payload[22:28], i.mymac[:6]) + copy(payload[:4], []byte{0x00, 0x00, 0x00, 0x00}) // Flags + copy(payload[4:20], dst[:]) // Destination + copy(payload[20:22], []byte{0x01, 0x01}) // Type & length + copy(payload[22:28], i.mymac[:6]) // Link layer address // Create the ICMPv6 solicited-node address var dstaddr address.Address @@ -281,7 +330,7 @@ func (i *ICMPv6) CreateNDPL2(dst address.Address) ([]byte, error) { copy(dstaddr[13:], dst[13:16]) // Create the multicast MAC - var dstmac macAddress + dstmac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00} copy(dstmac[:2], []byte{0x33, 0x33}) copy(dstmac[2:6], dstaddr[12:16]) @@ -293,9 +342,6 @@ func (i *ICMPv6) CreateNDPL2(dst address.Address) ([]byte, error) { if err != nil { return nil, err } - neighbor := i.peermacs[dstaddr] - neighbor.lastsolicitation = time.Now() - i.peermacs[dstaddr] = neighbor return requestPacket, nil } @@ -319,10 +365,10 @@ func (i *ICMPv6) HandleNDP(in []byte) ([]byte, error) { // Create our NDP message body response body := make([]byte, 28) - binary.BigEndian.PutUint32(body[:4], uint32(0x20000000)) - copy(body[4:20], in[8:24]) // Target address - body[20] = uint8(2) - body[21] = uint8(1) + binary.BigEndian.PutUint32(body[:4], uint32(0x40000000)) // Flags + copy(body[4:20], in[8:24]) // Target address + body[20] = uint8(2) // Type: Target link-layer address + body[21] = uint8(1) // Length: 1x address (8 bytes) copy(body[22:28], i.mymac[:6]) // Send it back diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 60c814c2..be3988a6 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -3,6 +3,7 @@ package tuntap import ( "bytes" "errors" + "net" "time" "github.com/songgao/packets/ethernet" @@ -43,19 +44,10 @@ func (tun *TunAdapter) writer() error { neigh, known := tun.icmpv6.peermacs[dstAddr] known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30) if !known { - request, err := tun.icmpv6.CreateNDPL2(dstAddr) - if err != nil { - panic(err) - } - if _, err := tun.iface.Write(request); err != nil { - panic(err) - } - tun.icmpv6.peermacs[dstAddr] = neighbor{ - lastsolicitation: time.Now(), - } + tun.icmpv6.Solicit(dstAddr) } } - var peermac macAddress + peermac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00} var peerknown bool if b[0]&0xf0 == 0x40 { dstAddr = tun.addr @@ -65,14 +57,19 @@ func (tun *TunAdapter) writer() error { } } if neighbor, ok := tun.icmpv6.peermacs[dstAddr]; ok && neighbor.learned { + // If we've learned the MAC of a 300::/7 address, for example, or a CKR + // address, use the MAC address of that peermac = neighbor.mac peerknown = true } else if neighbor, ok := tun.icmpv6.peermacs[tun.addr]; ok && neighbor.learned { + // Otherwise send directly to the MAC address of the host if that's + // known instead peermac = neighbor.mac peerknown = true - sendndp(dstAddr) } else { + // Nothing has been discovered, try to discover the destination sendndp(tun.addr) + } if peerknown { var proto ethernet.Ethertype @@ -92,6 +89,8 @@ func (tun *TunAdapter) writer() error { copy(frame[tun_ETHER_HEADER_LENGTH:], b[:n]) n += tun_ETHER_HEADER_LENGTH w, err = tun.iface.Write(frame[:n]) + } else { + tun.log.Errorln("TUN/TAP iface write error: no peer MAC known for", net.IP(dstAddr[:]).String(), "- dropping packet") } } else { w, err = tun.iface.Write(b[:n]) @@ -184,7 +183,7 @@ func (tun *TunAdapter) reader() error { // Unknown address length or protocol, so drop the packet and ignore it continue } - if !tun.ckr.isValidSource(srcAddr, addrlen) { + if tun.ckr.isEnabled() && !tun.ckr.isValidSource(srcAddr, addrlen) { // The packet had a source address that doesn't belong to us or our // configured crypto-key routing source subnets continue diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index b7b4cfa6..15530d23 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -14,7 +14,6 @@ import ( "fmt" "net" "sync" - "time" "github.com/gologme/log" "github.com/yggdrasil-network/water" @@ -152,21 +151,6 @@ func (tun *TunAdapter) Start() error { tun.send = make(chan []byte, 32) // TODO: is this a sensible value? tun.reconfigure = make(chan chan error) tun.mutex.Unlock() - if iftapmode { - go func() { - for { - if _, ok := tun.icmpv6.peermacs[tun.addr]; ok { - break - } - request, err := tun.icmpv6.CreateNDPL2(tun.addr) - if err != nil { - panic(err) - } - tun.send <- request - time.Sleep(time.Second) - } - }() - } go func() { for { e := <-tun.reconfigure @@ -177,6 +161,9 @@ func (tun *TunAdapter) Start() error { go tun.reader() go tun.writer() tun.icmpv6.Init(tun) + if iftapmode { + go tun.icmpv6.Solicit(tun.addr) + } tun.ckr.init(tun) return nil } From a10c141896d1fa292e0c74f7e6fd4df3a0eb7e93 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 6 Jul 2019 15:15:43 +0100 Subject: [PATCH 0125/1109] Fix data race on peermacs --- src/tuntap/icmpv6.go | 8 ++++++++ src/tuntap/iface.go | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/tuntap/icmpv6.go b/src/tuntap/icmpv6.go index ea1a785b..fe80dfbd 100644 --- a/src/tuntap/icmpv6.go +++ b/src/tuntap/icmpv6.go @@ -313,6 +313,14 @@ func (i *ICMPv6) Solicit(addr address.Address) { } } +func (i *ICMPv6) getNeighbor(addr address.Address) (neighbor, bool) { + i.peermacsmutex.RLock() + defer i.peermacsmutex.RUnlock() + + n, ok := i.peermacs[addr] + return n, ok +} + func (i *ICMPv6) createNDPL2(dst address.Address) ([]byte, error) { // Create the ND payload var payload [28]byte diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index be3988a6..9ffde85b 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -41,7 +41,7 @@ func (tun *TunAdapter) writer() error { return errors.New("Invalid address family") } sendndp := func(dstAddr address.Address) { - neigh, known := tun.icmpv6.peermacs[dstAddr] + neigh, known := tun.icmpv6.getNeighbor(dstAddr) known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30) if !known { tun.icmpv6.Solicit(dstAddr) @@ -56,12 +56,12 @@ func (tun *TunAdapter) writer() error { dstAddr = tun.addr } } - if neighbor, ok := tun.icmpv6.peermacs[dstAddr]; ok && neighbor.learned { + if neighbor, ok := tun.icmpv6.getNeighbor(dstAddr); ok && neighbor.learned { // If we've learned the MAC of a 300::/7 address, for example, or a CKR // address, use the MAC address of that peermac = neighbor.mac peerknown = true - } else if neighbor, ok := tun.icmpv6.peermacs[tun.addr]; ok && neighbor.learned { + } else if neighbor, ok := tun.icmpv6.getNeighbor(tun.addr); ok && neighbor.learned { // Otherwise send directly to the MAC address of the host if that's // known instead peermac = neighbor.mac From 30c03369cdd34c9064df3050cc0e68e6bf6f3892 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 6 Jul 2019 20:08:32 +0100 Subject: [PATCH 0126/1109] Try to fix CKR setup deadlock, fix some Windows output formatting --- src/tuntap/ckr.go | 13 ++++++------- src/tuntap/tun.go | 11 +++++------ src/tuntap/tun_windows.go | 16 ++++++++-------- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/tuntap/ckr.go b/src/tuntap/ckr.go index 00320391..52c11596 100644 --- a/src/tuntap/ckr.go +++ b/src/tuntap/ckr.go @@ -59,11 +59,10 @@ func (c *cryptokey) init(tun *TunAdapter) { // Configure the CKR routes - this must only ever be called from the router // goroutine, e.g. through router.doAdmin func (c *cryptokey) configure() error { - c.tun.config.Mutex.RLock() - defer c.tun.config.Mutex.RUnlock() + current, _ := c.tun.config.Get() // Set enabled/disabled state - c.setEnabled(c.tun.config.Current.TunnelRouting.Enable) + c.setEnabled(current.TunnelRouting.Enable) // Clear out existing routes c.mutexroutes.Lock() @@ -72,14 +71,14 @@ func (c *cryptokey) configure() error { c.mutexroutes.Unlock() // Add IPv6 routes - for ipv6, pubkey := range c.tun.config.Current.TunnelRouting.IPv6Destinations { + for ipv6, pubkey := range current.TunnelRouting.IPv6Destinations { if err := c.addRoute(ipv6, pubkey); err != nil { return err } } // Add IPv4 routes - for ipv4, pubkey := range c.tun.config.Current.TunnelRouting.IPv4Destinations { + for ipv4, pubkey := range current.TunnelRouting.IPv4Destinations { if err := c.addRoute(ipv4, pubkey); err != nil { return err } @@ -93,7 +92,7 @@ func (c *cryptokey) configure() error { // Add IPv6 sources c.ipv6sources = make([]net.IPNet, 0) - for _, source := range c.tun.config.Current.TunnelRouting.IPv6Sources { + for _, source := range current.TunnelRouting.IPv6Sources { if err := c.addSourceSubnet(source); err != nil { return err } @@ -101,7 +100,7 @@ func (c *cryptokey) configure() error { // Add IPv4 sources c.ipv4sources = make([]net.IPNet, 0) - for _, source := range c.tun.config.Current.TunnelRouting.IPv4Sources { + for _, source := range current.TunnelRouting.IPv4Sources { if err := c.addSourceSubnet(source); err != nil { return err } diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 15530d23..cc124971 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -119,13 +119,12 @@ func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener // Start the setup process for the TUN/TAP adapter. If successful, starts the // read/write goroutines to handle packets on that interface. func (tun *TunAdapter) Start() error { - tun.config.Mutex.RLock() - defer tun.config.Mutex.RUnlock() + current, _ := tun.config.Get() if tun.config == nil || tun.listener == nil || tun.dialer == nil { return errors.New("No configuration available to TUN/TAP") } var boxPub crypto.BoxPubKey - boxPubHex, err := hex.DecodeString(tun.config.Current.EncryptionPublicKey) + boxPubHex, err := hex.DecodeString(current.EncryptionPublicKey) if err != nil { return err } @@ -133,9 +132,9 @@ func (tun *TunAdapter) Start() error { nodeID := crypto.GetNodeID(&boxPub) tun.addr = *address.AddrForNodeID(nodeID) tun.subnet = *address.SubnetForNodeID(nodeID) - tun.mtu = tun.config.Current.IfMTU - ifname := tun.config.Current.IfName - iftapmode := tun.config.Current.IfTAPMode + tun.mtu = current.IfMTU + ifname := current.IfName + iftapmode := current.IfTAPMode addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1) if ifname != "none" { if err := tun.setup(ifname, iftapmode, addr, tun.mtu); err != nil { diff --git a/src/tuntap/tun_windows.go b/src/tuntap/tun_windows.go index 8a66ac62..002c354e 100644 --- a/src/tuntap/tun_windows.go +++ b/src/tuntap/tun_windows.go @@ -31,18 +31,18 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int } // Disable/enable the interface to resets its configuration (invalidating iface) cmd := exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=DISABLED") - tun.log.Printf("netsh command: %v", strings.Join(cmd.Args, " ")) + tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.log.Errorf("Windows netsh failed: %v.", err) + tun.log.Errorln("Windows netsh failed:", err) tun.log.Traceln(string(output)) return err } cmd = exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=ENABLED") - tun.log.Printf("netsh command: %v", strings.Join(cmd.Args, " ")) + tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " ")) output, err = cmd.CombinedOutput() if err != nil { - tun.log.Errorf("Windows netsh failed: %v.", err) + tun.log.Errorln("Windows netsh failed:", err) tun.log.Traceln(string(output)) return err } @@ -71,10 +71,10 @@ func (tun *TunAdapter) setupMTU(mtu int) error { fmt.Sprintf("interface=%s", tun.iface.Name()), fmt.Sprintf("mtu=%d", mtu), "store=active") - tun.log.Debugln("netsh command: %v", strings.Join(cmd.Args, " ")) + tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.log.Errorf("Windows netsh failed: %v.", err) + tun.log.Errorln("Windows netsh failed:", err) tun.log.Traceln(string(output)) return err } @@ -88,10 +88,10 @@ func (tun *TunAdapter) setupAddress(addr string) error { fmt.Sprintf("interface=%s", tun.iface.Name()), fmt.Sprintf("addr=%s", addr), "store=active") - tun.log.Debugln("netsh command: %v", strings.Join(cmd.Args, " ")) + tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.log.Errorf("Windows netsh failed: %v.", err) + tun.log.Errorln("Windows netsh failed:", err) tun.log.Traceln(string(output)) return err } From ea9d5db16d68d32268a05c8d288f67a9c3c2a2d1 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 7 Jul 2019 19:41:53 +0100 Subject: [PATCH 0127/1109] Make admin socket output a bit friendlier (fixes #385) --- src/admin/admin.go | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/admin/admin.go b/src/admin/admin.go index c3f140ae..4c933dd5 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -381,11 +381,11 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { if r != nil { send = Info{ "status": "error", - "error": "Unrecoverable error, possibly as a result of invalid input types or malformed syntax", + "error": "Check your syntax and input types", } - a.log.Errorln("Admin socket error:", r) + a.log.Debugln("Admin socket error:", r) if err := encoder.Encode(&send); err != nil { - a.log.Errorln("Admin socket JSON encode error:", err) + a.log.Debugln("Admin socket JSON encode error:", err) } conn.Close() } @@ -407,13 +407,14 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { send["request"] = recv send["status"] = "error" + n := strings.ToLower(recv["request"].(string)) + if _, ok := recv["request"]; !ok { send["error"] = "No request sent" - break + goto respond } - n := strings.ToLower(recv["request"].(string)) - if h, ok := a.handlers[strings.ToLower(n)]; ok { + if h, ok := a.handlers[n]; ok { // Check that we have all the required arguments for _, arg := range h.args { // An argument in [square brackets] is optional and not required, @@ -428,7 +429,7 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { "error": "Expected field missing: " + arg, "expecting": arg, } - break + goto respond } } @@ -439,16 +440,28 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { send["error"] = err.Error() if response != nil { send["response"] = response + goto respond } } else { send["status"] = "success" if response != nil { send["response"] = response + goto respond } } + } else { + // Start with a clean response on each request, which defaults to an error + // state. If a handler is found below then this will be overwritten + send = Info{ + "request": recv, + "status": "error", + "error": fmt.Sprintf("Unknown action '%s', try 'list' for help", recv["request"].(string)), + } + goto respond } // Send the response back + respond: if err := encoder.Encode(&send); err != nil { return } From 99aac19f98d6b09a3e636ae1a469e371e51e18a6 Mon Sep 17 00:00:00 2001 From: Leon Knauer Date: Tue, 9 Jul 2019 12:30:29 +0200 Subject: [PATCH 0128/1109] Correcting typo in headline --- doc/Whitepaper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Whitepaper.md b/doc/Whitepaper.md index 26d49a53..d7f13279 100644 --- a/doc/Whitepaper.md +++ b/doc/Whitepaper.md @@ -1,4 +1,4 @@ -# Yggdasil +# Yggdrasil Note: This is a very rough early draft. From 145a43e5f076cb65dbcf73d25fb29ea23625ff4d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 16 Jul 2019 09:49:28 +0100 Subject: [PATCH 0129/1109] Fix #413 by always generating public keys from private ones instead of trusting public keys supplied by config --- src/crypto/crypto.go | 18 ++++++++++++++++++ src/yggdrasil/core.go | 28 +++++++++++++++++++--------- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go index d5b467e9..75736ba7 100644 --- a/src/crypto/crypto.go +++ b/src/crypto/crypto.go @@ -15,6 +15,7 @@ import ( "crypto/sha512" "encoding/hex" + "golang.org/x/crypto/curve25519" "golang.org/x/crypto/ed25519" "golang.org/x/crypto/nacl/box" @@ -124,6 +125,15 @@ func Verify(pub *SigPubKey, msg []byte, sig *SigBytes) bool { return ed25519.Verify(pub[:], msg, sig[:]) } +func (p SigPrivKey) Public() SigPubKey { + priv := make(ed25519.PrivateKey, ed25519.PrivateKeySize) + copy(priv[:], p[:]) + pub := priv.Public().(ed25519.PublicKey) + var sigPub SigPubKey + copy(sigPub[:], pub[:]) + return sigPub +} + //////////////////////////////////////////////////////////////////////////////// // NaCl-like crypto "box" (curve25519+xsalsa20+poly1305) @@ -204,6 +214,14 @@ func (n *BoxNonce) Increment() { } } +func (p BoxPrivKey) Public() BoxPubKey { + var boxPub [BoxPubKeyLen]byte + var boxPriv [BoxPrivKeyLen]byte + copy(boxPriv[:BoxPrivKeyLen], p[:BoxPrivKeyLen]) + curve25519.ScalarBaseMult(&boxPub, &boxPriv) + return boxPub +} + // Used to subtract one nonce from another, staying in the range +- 64. // This is used by the nonce progression machinery to advance the bitmask of recently received packets (indexed by nonce), or to check the appropriate bit of the bitmask. // It's basically part of the machinery that prevents replays and duplicate packets. diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 62d89a8b..35a86df1 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -2,6 +2,7 @@ package yggdrasil import ( "encoding/hex" + "errors" "io/ioutil" "time" @@ -46,28 +47,37 @@ func (c *Core) init() error { current, _ := c.config.Get() - boxPubHex, err := hex.DecodeString(current.EncryptionPublicKey) - if err != nil { - return err - } boxPrivHex, err := hex.DecodeString(current.EncryptionPrivateKey) if err != nil { return err } - sigPubHex, err := hex.DecodeString(current.SigningPublicKey) - if err != nil { - return err + if len(boxPrivHex) < crypto.BoxPrivKeyLen { + return errors.New("EncryptionPrivateKey is incorrect length") } + sigPrivHex, err := hex.DecodeString(current.SigningPrivateKey) if err != nil { return err } + if len(sigPrivHex) < crypto.SigPrivKeyLen { + return errors.New("SigningPrivateKey is incorrect length") + } - copy(c.boxPub[:], boxPubHex) copy(c.boxPriv[:], boxPrivHex) - copy(c.sigPub[:], sigPubHex) copy(c.sigPriv[:], sigPrivHex) + boxPub, sigPub := c.boxPriv.Public(), c.sigPriv.Public() + + copy(c.boxPub[:], boxPub[:]) + copy(c.sigPub[:], sigPub[:]) + + if bp := hex.EncodeToString(c.boxPub[:]); current.EncryptionPublicKey != bp { + c.log.Warnln("EncryptionPublicKey in config is incorrect, should be", bp) + } + if sp := hex.EncodeToString(c.sigPub[:]); current.SigningPublicKey != sp { + c.log.Warnln("SigningPublicKey in config is incorrect, should be", sp) + } + c.searches.init(c) c.dht.init(c) c.sessions.init(c) From f3dd4320f779af35c097ae8409d431a86e54cf8a Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 16 Jul 2019 11:44:58 +0100 Subject: [PATCH 0130/1109] Try to set Conflicts in RPM properly --- .circleci/config.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 03244ce3..38b6b03d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,6 +17,11 @@ jobs: echo 'export CINAME=$(sh contrib/semver/name.sh)' >> $BASH_ENV echo 'export CIVERSION=$(sh contrib/semver/version.sh --bare)' >> $BASH_ENV echo 'export CIVERSIONRPM=$(sh contrib/semver/version.sh --bare | tr "-" ".")' >> $BASH_ENV + case "$CINAME" in \ + "yggdrasil") (echo 'export CICONFLICTS=yggdrasil-develop' >> $BASH_ENV) ;; \ + "yggdrasil-develop") (echo 'export CICONFLICTS=yggdrasil' >> $BASH_ENV) ;; \ + *) (echo 'export CICONFLICTS=yggdrasil yggdrasil-develop' >> $BASH_ENV) ;; \ + esac git config --global user.email "$(git log --format='%ae' HEAD -1)"; git config --global user.name "$(git log --format='%an' HEAD -1)"; @@ -53,6 +58,7 @@ jobs: sed -i "s/^PKGNAME=yggdrasil/PKGNAME=yggdrasil-$CIRCLE_BRANCH/" ~/rpmbuild/SPECS/yggdrasil.spec sed -i "s/^Name\:.*/Name\: $CINAME/" ~/rpmbuild/SPECS/yggdrasil.spec sed -i "s/^Version\:.*/Version\: $CIVERSIONRPM/" ~/rpmbuild/SPECS/yggdrasil.spec + sed -i "s/^Conflicts\:.*/Conflicts\: $CICONFLICTS/" ~/rpmbuild/SPECS/yggdrasil.spec cat ~/rpmbuild/SPECS/yggdrasil.spec GOARCH=amd64 rpmbuild -v --nodeps --target=x86_64 -ba ~/rpmbuild/SPECS/yggdrasil.spec #GOARCH=386 rpmbuild -v --nodeps --target=i386 -bb ~/rpmbuild/SPECS/yggdrasil.spec From 829a24a858a0d12a392e2ff04a1bc92d9e5107a2 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 16 Jul 2019 11:48:31 +0100 Subject: [PATCH 0131/1109] Fix default case --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 38b6b03d..1a2898c2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -20,7 +20,7 @@ jobs: case "$CINAME" in \ "yggdrasil") (echo 'export CICONFLICTS=yggdrasil-develop' >> $BASH_ENV) ;; \ "yggdrasil-develop") (echo 'export CICONFLICTS=yggdrasil' >> $BASH_ENV) ;; \ - *) (echo 'export CICONFLICTS=yggdrasil yggdrasil-develop' >> $BASH_ENV) ;; \ + *) (echo 'export CICONFLICTS="yggdrasil yggdrasil-develop"' >> $BASH_ENV) ;; \ esac git config --global user.email "$(git log --format='%ae' HEAD -1)"; git config --global user.name "$(git log --format='%an' HEAD -1)"; From d34600b5f92c6a243ffd3427ebd89118cf918ce1 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 17 Jul 2019 10:12:10 +0100 Subject: [PATCH 0132/1109] Try to fix TUN/TAP conn reader leakage --- src/tuntap/conn.go | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index d6bb7a7e..f45e4a14 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -11,6 +11,8 @@ import ( "golang.org/x/net/ipv6" ) +const tunConnTimeout = 2 * time.Minute + type tunConn struct { tun *TunAdapter conn *yggdrasil.Conn @@ -49,24 +51,37 @@ func (s *tunConn) reader() error { } default: } + s.tun.log.Debugln("Starting conn reader for", s) var n int var err error read := make(chan bool) b := make([]byte, 65535) - for { - go func() { - // TODO don't start a new goroutine for every packet read, this is probably a big part of the slowdowns we saw when refactoring + go func() { + s.tun.log.Debugln("Starting conn reader helper for", s) + for { + s.conn.SetReadDeadline(time.Now().Add(tunConnTimeout)) if n, err = s.conn.Read(b); err != nil { s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn read error:", err) - if e, eok := err.(yggdrasil.ConnError); eok && !e.Temporary() { - close(s.stop) - } else { - read <- false + if e, eok := err.(yggdrasil.ConnError); eok { + switch { + case e.Temporary(): + read <- false + continue + case e.Timeout(): + s.tun.log.Debugln("Conn reader for helper", s, "timed out") + fallthrough + default: + s.tun.log.Debugln("Stopping conn reader helper for", s) + s.close() + return + } } - return + read <- false } read <- true - }() + } + }() + for { select { case r := <-read: if r && n > 0 { @@ -93,6 +108,7 @@ func (s *tunConn) writer() error { } default: } + s.tun.log.Debugln("Starting conn writer for", s) for { select { case <-s.stop: @@ -134,8 +150,7 @@ func (s *tunConn) stillAlive() { } func (s *tunConn) checkForTimeouts() error { - const timeout = 2 * time.Minute - timer := time.NewTimer(timeout) + timer := time.NewTimer(tunConnTimeout) defer util.TimerStop(timer) defer s.close() for { @@ -145,7 +160,7 @@ func (s *tunConn) checkForTimeouts() error { return errors.New("connection closed") } util.TimerStop(timer) - timer.Reset(timeout) + timer.Reset(tunConnTimeout) case <-timer.C: return errors.New("timed out") } From 747b50bb7cfea29103873832a2e2828ad7bc5129 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 17 Jul 2019 11:13:53 +0100 Subject: [PATCH 0133/1109] Try to improve handling of timeouts --- src/tuntap/conn.go | 12 +++++++----- src/yggdrasil/conn.go | 22 ++++++++++++++-------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index f45e4a14..c9afa6e1 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -52,26 +52,29 @@ func (s *tunConn) reader() error { default: } s.tun.log.Debugln("Starting conn reader for", s) + defer s.tun.log.Debugln("Stopping conn reader for", s) var n int var err error read := make(chan bool) b := make([]byte, 65535) go func() { s.tun.log.Debugln("Starting conn reader helper for", s) + defer s.tun.log.Debugln("Stopping conn reader helper for", s) for { s.conn.SetReadDeadline(time.Now().Add(tunConnTimeout)) if n, err = s.conn.Read(b); err != nil { s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn read error:", err) if e, eok := err.(yggdrasil.ConnError); eok { + s.tun.log.Debugln("Conn reader helper", s, "error:", e) switch { case e.Temporary(): + fallthrough + case e.Timeout(): read <- false continue - case e.Timeout(): - s.tun.log.Debugln("Conn reader for helper", s, "timed out") + case e.Closed(): fallthrough default: - s.tun.log.Debugln("Stopping conn reader helper for", s) s.close() return } @@ -94,7 +97,6 @@ func (s *tunConn) reader() error { } s.stillAlive() // TODO? Only stay alive if we read >0 bytes? case <-s.stop: - s.tun.log.Debugln("Stopping conn reader for", s) return nil } } @@ -109,10 +111,10 @@ func (s *tunConn) writer() error { default: } s.tun.log.Debugln("Starting conn writer for", s) + defer s.tun.log.Debugln("Stopping conn writer for", s) for { select { case <-s.stop: - s.tun.log.Debugln("Stopping conn writer for", s) return nil case b, ok := <-s.send: if !ok { diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 5d1e77ac..2a286b04 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -16,6 +16,7 @@ type ConnError struct { error timeout bool temporary bool + closed bool maxsize int } @@ -38,6 +39,11 @@ func (e *ConnError) PacketTooBig() (bool, int) { return e.maxsize > 0, e.maxsize } +// Closed returns if the session is already closed and is now unusable. +func (e *ConnError) Closed() bool { + return e.closed +} + type Conn struct { core *Core nodeID *crypto.NodeID @@ -122,11 +128,11 @@ func (c *Conn) Read(b []byte) (int, error) { // Wait for some traffic to come through from the session select { case <-timer.C: - return 0, ConnError{errors.New("timeout"), true, false, 0} + return 0, ConnError{errors.New("timeout"), true, false, false, 0} case p, ok := <-sinfo.recv: // If the session is closed then do nothing if !ok { - return 0, errors.New("session is closed") + return 0, ConnError{errors.New("session is closed"), false, false, true, 0} } defer util.PutBytes(p.Payload) var err error @@ -135,7 +141,7 @@ func (c *Conn) Read(b []byte) (int, error) { defer close(done) // If the nonce is bad then drop the packet and return an error if !sinfo.nonceIsOK(&p.Nonce) { - err = ConnError{errors.New("packet dropped due to invalid nonce"), false, true, 0} + err = ConnError{errors.New("packet dropped due to invalid nonce"), false, true, false, 0} return } // Decrypt the packet @@ -144,7 +150,7 @@ func (c *Conn) Read(b []byte) (int, error) { // Check if we were unable to decrypt the packet for some reason and // return an error if we couldn't if !isOK { - err = ConnError{errors.New("packet dropped due to decryption failure"), false, true, 0} + err = ConnError{errors.New("packet dropped due to decryption failure"), false, true, false, 0} return } // Return the newly decrypted buffer back to the slice we were given @@ -168,7 +174,7 @@ func (c *Conn) Read(b []byte) (int, error) { select { // Send to worker case sinfo.worker <- workerFunc: case <-timer.C: - return 0, ConnError{errors.New("timeout"), true, false, 0} + return 0, ConnError{errors.New("timeout"), true, false, false, 0} } <-done // Wait for the worker to finish, failing this can cause memory errors (util.[Get||Put]Bytes stuff) // Something went wrong in the session worker so abort @@ -194,7 +200,7 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { defer close(done) // Does the packet exceed the permitted size for the session? if uint16(len(b)) > sinfo.getMTU() { - written, err = 0, ConnError{errors.New("packet too big"), true, false, int(sinfo.getMTU())} + written, err = 0, ConnError{errors.New("packet too big"), true, false, false, int(sinfo.getMTU())} return } // Encrypt the packet @@ -244,14 +250,14 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { // Hand over to the session worker defer func() { if recover() != nil { - err = errors.New("write failed, session already closed") + err = ConnError{errors.New("write failed, session already closed"), false, false, true, 0} close(done) } }() // In case we're racing with a close select { // Send to worker case sinfo.worker <- workerFunc: case <-timer.C: - return 0, ConnError{errors.New("timeout"), true, false, 0} + return 0, ConnError{errors.New("timeout"), true, false, false, 0} } // Wait for the worker to finish, otherwise there are memory errors ([Get||Put]Bytes stuff) <-done From 7d1c03d2ac5f21f6e6d8a894c547dee7eaa1a145 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 17 Jul 2019 12:07:16 +0100 Subject: [PATCH 0134/1109] Only call stillAlive if channel read succeeds --- src/tuntap/conn.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index c9afa6e1..c5e6e81b 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -86,7 +86,7 @@ func (s *tunConn) reader() error { }() for { select { - case r := <-read: + case r, ok := <-read: if r && n > 0 { bs := append(util.GetBytes(), b[:n]...) select { @@ -95,7 +95,9 @@ func (s *tunConn) reader() error { util.PutBytes(bs) } } - s.stillAlive() // TODO? Only stay alive if we read >0 bytes? + if ok { + s.stillAlive() // TODO? Only stay alive if we read >0 bytes? + } case <-s.stop: return nil } From eec70bf2f22028a1cae5fe60f8a89dfbb84bc1b0 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 17 Jul 2019 13:53:16 +0100 Subject: [PATCH 0135/1109] Remove stillAlive code from TUN/TAP conn as it is no longer required with the new deadlines --- src/tuntap/conn.go | 45 ++++++++++----------------------------------- src/tuntap/tun.go | 1 - 2 files changed, 10 insertions(+), 36 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index c5e6e81b..4cc880c1 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -64,16 +64,19 @@ func (s *tunConn) reader() error { s.conn.SetReadDeadline(time.Now().Add(tunConnTimeout)) if n, err = s.conn.Read(b); err != nil { s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn read error:", err) - if e, eok := err.(yggdrasil.ConnError); eok { + if e, eok := err.(yggdrasil.ConnError); eok && !e.Temporary() { s.tun.log.Debugln("Conn reader helper", s, "error:", e) switch { - case e.Temporary(): - fallthrough + // The timeout probably means we've waited for the timeout period and + // nothing has happened so close the connection case e.Timeout(): - read <- false + s.close() continue + // The connection is already closed, so we anticipate that the main + // reader goroutine has already exited. Also stop in that case case e.Closed(): - fallthrough + return + // Some other case that we don't know about - close the connection default: s.close() return @@ -86,7 +89,7 @@ func (s *tunConn) reader() error { }() for { select { - case r, ok := <-read: + case r := <-read: if r && n > 0 { bs := append(util.GetBytes(), b[:n]...) select { @@ -95,9 +98,6 @@ func (s *tunConn) reader() error { util.PutBytes(bs) } } - if ok { - s.stillAlive() // TODO? Only stay alive if we read >0 bytes? - } case <-s.stop: return nil } @@ -123,6 +123,7 @@ func (s *tunConn) writer() error { return errors.New("send closed") } // TODO write timeout and close + s.conn.SetWriteDeadline(time.Now().Add(tunConnTimeout)) if _, err := s.conn.Write(b); err != nil { e, eok := err.(yggdrasil.ConnError) if !eok { @@ -141,32 +142,6 @@ func (s *tunConn) writer() error { } } util.PutBytes(b) - s.stillAlive() - } - } -} - -func (s *tunConn) stillAlive() { - select { - case s.alive <- struct{}{}: - default: - } -} - -func (s *tunConn) checkForTimeouts() error { - timer := time.NewTimer(tunConnTimeout) - defer util.TimerStop(timer) - defer s.close() - for { - select { - case _, ok := <-s.alive: - if !ok { - return errors.New("connection closed") - } - util.TimerStop(timer) - timer.Reset(tunConnTimeout) - case <-timer.C: - return errors.New("timed out") } } } diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index cc124971..72bdd2f0 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -259,7 +259,6 @@ func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { // Start the connection goroutines go s.reader() go s.writer() - go s.checkForTimeouts() // Return return c, err } From 1bf1c6eb3626f3f442838a9057c7a2ce636af0bc Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 17 Jul 2019 19:43:29 +0100 Subject: [PATCH 0136/1109] Revert "Remove stillAlive code from TUN/TAP conn as it is no longer required with the new deadlines" This reverts commit eec70bf2f22028a1cae5fe60f8a89dfbb84bc1b0. --- src/tuntap/conn.go | 45 +++++++++++++++++++++++++++++++++++---------- src/tuntap/tun.go | 1 + 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index 4cc880c1..c5e6e81b 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -64,19 +64,16 @@ func (s *tunConn) reader() error { s.conn.SetReadDeadline(time.Now().Add(tunConnTimeout)) if n, err = s.conn.Read(b); err != nil { s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn read error:", err) - if e, eok := err.(yggdrasil.ConnError); eok && !e.Temporary() { + if e, eok := err.(yggdrasil.ConnError); eok { s.tun.log.Debugln("Conn reader helper", s, "error:", e) switch { - // The timeout probably means we've waited for the timeout period and - // nothing has happened so close the connection + case e.Temporary(): + fallthrough case e.Timeout(): - s.close() + read <- false continue - // The connection is already closed, so we anticipate that the main - // reader goroutine has already exited. Also stop in that case case e.Closed(): - return - // Some other case that we don't know about - close the connection + fallthrough default: s.close() return @@ -89,7 +86,7 @@ func (s *tunConn) reader() error { }() for { select { - case r := <-read: + case r, ok := <-read: if r && n > 0 { bs := append(util.GetBytes(), b[:n]...) select { @@ -98,6 +95,9 @@ func (s *tunConn) reader() error { util.PutBytes(bs) } } + if ok { + s.stillAlive() // TODO? Only stay alive if we read >0 bytes? + } case <-s.stop: return nil } @@ -123,7 +123,6 @@ func (s *tunConn) writer() error { return errors.New("send closed") } // TODO write timeout and close - s.conn.SetWriteDeadline(time.Now().Add(tunConnTimeout)) if _, err := s.conn.Write(b); err != nil { e, eok := err.(yggdrasil.ConnError) if !eok { @@ -142,6 +141,32 @@ func (s *tunConn) writer() error { } } util.PutBytes(b) + s.stillAlive() + } + } +} + +func (s *tunConn) stillAlive() { + select { + case s.alive <- struct{}{}: + default: + } +} + +func (s *tunConn) checkForTimeouts() error { + timer := time.NewTimer(tunConnTimeout) + defer util.TimerStop(timer) + defer s.close() + for { + select { + case _, ok := <-s.alive: + if !ok { + return errors.New("connection closed") + } + util.TimerStop(timer) + timer.Reset(tunConnTimeout) + case <-timer.C: + return errors.New("timed out") } } } diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 72bdd2f0..cc124971 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -259,6 +259,7 @@ func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { // Start the connection goroutines go s.reader() go s.writer() + go s.checkForTimeouts() // Return return c, err } From 307b24d8cb505143cb56372a3ff81118d6f1d158 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 17 Jul 2019 21:42:17 +0100 Subject: [PATCH 0137/1109] Fix Conn.Read/Conn.Write behavior after Conn.Close, get rid of second TUN/TAP conn reader goroutine, no longer use deadlines --- src/tuntap/conn.go | 66 +++++++++++++----------------------------- src/yggdrasil/conn.go | 42 +++++++++++++++++++-------- src/yggdrasil/debug.go | 7 +++-- 3 files changed, 54 insertions(+), 61 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index c5e6e81b..24d862e8 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -43,7 +43,7 @@ func (s *tunConn) _close_nomutex() { }() } -func (s *tunConn) reader() error { +func (s *tunConn) reader() (err error) { select { case _, ok := <-s.stop: if !ok { @@ -51,55 +51,29 @@ func (s *tunConn) reader() error { } default: } - s.tun.log.Debugln("Starting conn reader for", s) - defer s.tun.log.Debugln("Stopping conn reader for", s) + s.tun.log.Debugln("Starting conn reader for", s.conn.String()) + defer s.tun.log.Debugln("Stopping conn reader for", s.conn.String()) var n int - var err error - read := make(chan bool) b := make([]byte, 65535) - go func() { - s.tun.log.Debugln("Starting conn reader helper for", s) - defer s.tun.log.Debugln("Stopping conn reader helper for", s) - for { - s.conn.SetReadDeadline(time.Now().Add(tunConnTimeout)) - if n, err = s.conn.Read(b); err != nil { - s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn read error:", err) - if e, eok := err.(yggdrasil.ConnError); eok { - s.tun.log.Debugln("Conn reader helper", s, "error:", e) - switch { - case e.Temporary(): - fallthrough - case e.Timeout(): - read <- false - continue - case e.Closed(): - fallthrough - default: - s.close() - return - } - } - read <- false - } - read <- true - } - }() for { select { - case r, ok := <-read: - if r && n > 0 { - bs := append(util.GetBytes(), b[:n]...) - select { - case s.tun.send <- bs: - default: - util.PutBytes(bs) - } - } - if ok { - s.stillAlive() // TODO? Only stay alive if we read >0 bytes? - } case <-s.stop: return nil + default: + } + if n, err = s.conn.Read(b); err != nil { + if e, eok := err.(yggdrasil.ConnError); eok && !e.Temporary() { + s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn read error:", err) + return e + } + } else if n > 0 { + bs := append(util.GetBytes(), b[:n]...) + select { + case s.tun.send <- bs: + default: + util.PutBytes(bs) + } + s.stillAlive() } } } @@ -112,8 +86,8 @@ func (s *tunConn) writer() error { } default: } - s.tun.log.Debugln("Starting conn writer for", s) - defer s.tun.log.Debugln("Stopping conn writer for", s) + s.tun.log.Debugln("Starting conn writer for", s.conn.String()) + defer s.tun.log.Debugln("Stopping conn writer for", s.conn.String()) for { select { case <-s.stop: diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 2a286b04..0e18078e 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -49,7 +49,7 @@ type Conn struct { nodeID *crypto.NodeID nodeMask *crypto.NodeID mutex sync.RWMutex - closed bool + close chan bool session *sessionInfo readDeadline atomic.Value // time.Time // TODO timer writeDeadline atomic.Value // time.Time // TODO timer @@ -62,6 +62,7 @@ func newConn(core *Core, nodeID *crypto.NodeID, nodeMask *crypto.NodeID, session nodeID: nodeID, nodeMask: nodeMask, session: session, + close: make(chan bool), } return &conn } @@ -127,12 +128,14 @@ func (c *Conn) Read(b []byte) (int, error) { for { // Wait for some traffic to come through from the session select { + case <-c.close: + return 0, ConnError{errors.New("session closed"), false, false, true, 0} case <-timer.C: - return 0, ConnError{errors.New("timeout"), true, false, false, 0} + return 0, ConnError{errors.New("read timeout"), true, false, false, 0} case p, ok := <-sinfo.recv: // If the session is closed then do nothing if !ok { - return 0, ConnError{errors.New("session is closed"), false, false, true, 0} + return 0, ConnError{errors.New("session closed"), false, false, true, 0} } defer util.PutBytes(p.Payload) var err error @@ -167,16 +170,26 @@ func (c *Conn) Read(b []byte) (int, error) { // Hand over to the session worker defer func() { if recover() != nil { - err = errors.New("read failed, session already closed") + err = ConnError{errors.New("read failed, session already closed"), false, false, true, 0} close(done) } }() // In case we're racing with a close - select { // Send to worker + // Send to worker + select { case sinfo.worker <- workerFunc: + case <-c.close: + return 0, ConnError{errors.New("session closed"), false, false, true, 0} case <-timer.C: - return 0, ConnError{errors.New("timeout"), true, false, false, 0} + return 0, ConnError{errors.New("read timeout"), true, false, false, 0} + } + // Wait for the worker to finish + select { + case <-done: // Wait for the worker to finish, failing this can cause memory errors (util.[Get||Put]Bytes stuff) + case <-c.close: + return 0, ConnError{errors.New("session closed"), false, false, true, 0} + case <-timer.C: + return 0, ConnError{errors.New("read timeout"), true, false, false, 0} } - <-done // Wait for the worker to finish, failing this can cause memory errors (util.[Get||Put]Bytes stuff) // Something went wrong in the session worker so abort if err != nil { if ce, ok := err.(*ConnError); ok && ce.Temporary() { @@ -257,7 +270,7 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { select { // Send to worker case sinfo.worker <- workerFunc: case <-timer.C: - return 0, ConnError{errors.New("timeout"), true, false, false, 0} + return 0, ConnError{errors.New("write timeout"), true, false, false, 0} } // Wait for the worker to finish, otherwise there are memory errors ([Get||Put]Bytes stuff) <-done @@ -269,16 +282,21 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { return written, err } -func (c *Conn) Close() error { +func (c *Conn) Close() (err error) { c.mutex.Lock() defer c.mutex.Unlock() if c.session != nil { // Close the session, if it hasn't been closed already c.core.router.doAdmin(c.session.close) } - // This can't fail yet - TODO? - c.closed = true - return nil + func() { + defer func() { + recover() + err = ConnError{errors.New("close failed, session already closed"), false, false, true, 0} + }() + close(c.close) // Closes reader/writer goroutines + }() + return } func (c *Conn) LocalAddr() crypto.NodeID { diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index c4eed639..5cb7c46a 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -439,14 +439,14 @@ func (c *Core) DEBUG_maybeSendUDPKeys(saddr string) { */ //////////////////////////////////////////////////////////////////////////////// - +/* func (c *Core) DEBUG_addPeer(addr string) { err := c.admin.addPeer(addr, "") if err != nil { panic(err) } } - +*/ /* func (c *Core) DEBUG_addSOCKSConn(socksaddr, peeraddr string) { go func() { @@ -541,13 +541,14 @@ func (c *Core) DEBUG_setIfceExpr(expr *regexp.Regexp) { c.log.Println("DEBUG_setIfceExpr no longer implemented") } +/* func (c *Core) DEBUG_addAllowedEncryptionPublicKey(boxStr string) { err := c.admin.addAllowedEncryptionPublicKey(boxStr) if err != nil { panic(err) } } - +*/ //////////////////////////////////////////////////////////////////////////////// func DEBUG_simLinkPeers(p, q *peer) { From 311c612f2e7147e4b99376329c39cfb022d73113 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 17 Jul 2019 23:23:19 +0100 Subject: [PATCH 0138/1109] Only flag stillAlive on successful write --- src/tuntap/conn.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index 24d862e8..0d63fde3 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -113,9 +113,10 @@ func (s *tunConn) writer() error { } else { s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn write error:", err) } + } else { + s.stillAlive() } util.PutBytes(b) - s.stillAlive() } } } From 06330f503f7b9a5751edbaeb059f1e332d7c8580 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 18 Jul 2019 00:02:16 +0100 Subject: [PATCH 0139/1109] Recover if stillAlive fails --- src/tuntap/conn.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index 0d63fde3..234c34f0 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -122,6 +122,7 @@ func (s *tunConn) writer() error { } func (s *tunConn) stillAlive() { + defer func() { recover() }() select { case s.alive <- struct{}{}: default: From 53012074800b911d91aec2799bdefccc793fecf1 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 17 Jul 2019 18:25:38 -0500 Subject: [PATCH 0140/1109] fix possible unsafe memory use in Conn.Read --- src/yggdrasil/conn.go | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 0e18078e..1d686f83 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -125,6 +125,7 @@ func (c *Conn) Read(b []byte) (int, error) { sinfo := c.session timer := getDeadlineTimer(&c.readDeadline) defer util.TimerStop(timer) + var bs []byte for { // Wait for some traffic to come through from the session select { @@ -148,24 +149,18 @@ func (c *Conn) Read(b []byte) (int, error) { return } // Decrypt the packet - bs, isOK := crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce) - defer util.PutBytes(bs) // FIXME commenting this out leads to illegal buffer reuse, this implies there's a memory error somewhere and that this is just flooding things out of the finite pool of old slices that get reused + var isOK bool + bs, isOK = crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce) // Check if we were unable to decrypt the packet for some reason and // return an error if we couldn't if !isOK { err = ConnError{errors.New("packet dropped due to decryption failure"), false, true, false, 0} return } - // Return the newly decrypted buffer back to the slice we were given - copy(b, bs) - // Trim the slice down to size based on the data we received - if len(bs) < len(b) { - b = b[:len(bs)] - } // Update the session sinfo.updateNonce(&p.Nonce) sinfo.time = time.Now() - sinfo.bytesRecvd += uint64(len(b)) + sinfo.bytesRecvd += uint64(len(bs)) } // Hand over to the session worker defer func() { @@ -197,9 +192,12 @@ func (c *Conn) Read(b []byte) (int, error) { } return 0, err } + // Copy results to the output slice and clean up + copy(b, bs) + util.PutBytes(bs) // If we've reached this point then everything went to plan, return the // number of bytes we populated back into the given slice - return len(b), nil + return len(bs), nil } } } From 06e8403aafb8c1004558ffffa9ff0775625e660c Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 17 Jul 2019 21:09:22 -0500 Subject: [PATCH 0141/1109] add cancellation code to util, like context but just the cancellation parts + some error logic --- src/util/cancellation.go | 83 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 src/util/cancellation.go diff --git a/src/util/cancellation.go b/src/util/cancellation.go new file mode 100644 index 00000000..1433c734 --- /dev/null +++ b/src/util/cancellation.go @@ -0,0 +1,83 @@ +package util + +import ( + "errors" + "sync" + "time" + "runtime" +) + +type Cancellation interface { + Finished() <-chan struct{} + Cancel(error) error + Error() error +} + +func CancellationFinalizer(c Cancellation) { + c.Cancel(errors.New("finalizer called")) +} + +type cancellation struct { + signal chan error + cancel chan struct{} + errMtx sync.RWMutex + err error +} + +func (c *cancellation) worker() { + // Launch this in a separate goroutine when creating a cancellation + err := <-c.signal + c.errMtx.Lock() + c.err = err + c.errMtx.Unlock() + close(c.cancel) +} + +func NewCancellation() Cancellation { + c := cancellation{ + signal: make(chan error), + cancel: make(chan struct{}), + } + runtime.SetFinalizer(&c, CancellationFinalizer) + go c.worker() + return &c +} + +func (c *cancellation) Finished() <-chan struct{} { + return c.cancel +} + +func (c *cancellation) Cancel(err error) error { + select { + case c.signal<-err: + return nil + case <-c.cancel: + return c.Error() + } +} + +func (c *cancellation) Error() error { + c.errMtx.RLock() + err := c.err + c.errMtx.RUnlock() + return err +} + +func CancellationWithTimeout(parent Cancellation, timeout time.Duration) Cancellation { + child := NewCancellation() + go func() { + timer := time.NewTimer(timeout) + defer TimerStop(timer) + select { + case <-parent.Finished(): + child.Cancel(parent.Error()) + case <-timer.C: + child.Cancel(errors.New("timeout")) + } + }() + return child +} + +func CancellationWithDeadline(parent Cancellation, deadline time.Time) Cancellation { + return CancellationWithTimeout(parent, deadline.Sub(time.Now())) +} From 6bf182e341db390d3e05db9de72dba08b8d73c44 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 17 Jul 2019 21:15:02 -0500 Subject: [PATCH 0142/1109] add util.CancellationChild() and run gofmt --- src/util/cancellation.go | 109 +++++++++++++++++++++------------------ 1 file changed, 60 insertions(+), 49 deletions(-) diff --git a/src/util/cancellation.go b/src/util/cancellation.go index 1433c734..fa980084 100644 --- a/src/util/cancellation.go +++ b/src/util/cancellation.go @@ -1,83 +1,94 @@ package util import ( - "errors" - "sync" - "time" - "runtime" + "errors" + "runtime" + "sync" + "time" ) type Cancellation interface { - Finished() <-chan struct{} - Cancel(error) error - Error() error + Finished() <-chan struct{} + Cancel(error) error + Error() error } func CancellationFinalizer(c Cancellation) { - c.Cancel(errors.New("finalizer called")) + c.Cancel(errors.New("finalizer called")) } type cancellation struct { - signal chan error - cancel chan struct{} - errMtx sync.RWMutex - err error + signal chan error + cancel chan struct{} + errMtx sync.RWMutex + err error } func (c *cancellation) worker() { - // Launch this in a separate goroutine when creating a cancellation - err := <-c.signal - c.errMtx.Lock() - c.err = err - c.errMtx.Unlock() - close(c.cancel) + // Launch this in a separate goroutine when creating a cancellation + err := <-c.signal + c.errMtx.Lock() + c.err = err + c.errMtx.Unlock() + close(c.cancel) } func NewCancellation() Cancellation { - c := cancellation{ - signal: make(chan error), - cancel: make(chan struct{}), - } - runtime.SetFinalizer(&c, CancellationFinalizer) - go c.worker() - return &c + c := cancellation{ + signal: make(chan error), + cancel: make(chan struct{}), + } + runtime.SetFinalizer(&c, CancellationFinalizer) + go c.worker() + return &c } func (c *cancellation) Finished() <-chan struct{} { - return c.cancel + return c.cancel } func (c *cancellation) Cancel(err error) error { - select { - case c.signal<-err: - return nil - case <-c.cancel: - return c.Error() - } + select { + case c.signal <- err: + return nil + case <-c.cancel: + return c.Error() + } } func (c *cancellation) Error() error { - c.errMtx.RLock() - err := c.err - c.errMtx.RUnlock() - return err + c.errMtx.RLock() + err := c.err + c.errMtx.RUnlock() + return err +} + +func CancellationChild(parent Cancellation) Cancellation { + child := NewCancellation() + go func() { + select { + case <-child.Finished(): + case <-parent.Finished(): + child.Cancel(parent.Error()) + } + }() + return child } func CancellationWithTimeout(parent Cancellation, timeout time.Duration) Cancellation { - child := NewCancellation() - go func() { - timer := time.NewTimer(timeout) - defer TimerStop(timer) - select { - case <-parent.Finished(): - child.Cancel(parent.Error()) - case <-timer.C: - child.Cancel(errors.New("timeout")) - } - }() - return child + child := CancellationChild(parent) + go func() { + timer := time.NewTimer(timeout) + defer TimerStop(timer) + select { + case <-child.Finished(): + case <-timer.C: + child.Cancel(errors.New("timeout")) + } + }() + return child } func CancellationWithDeadline(parent Cancellation, deadline time.Time) Cancellation { - return CancellationWithTimeout(parent, deadline.Sub(time.Now())) + return CancellationWithTimeout(parent, deadline.Sub(time.Now())) } From cf3ebe04a7b26c96895958075942087c45afcebd Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 17 Jul 2019 21:37:45 -0500 Subject: [PATCH 0143/1109] have Conn use Cancellation instead of manually setting up timers --- src/util/cancellation.go | 4 +- src/yggdrasil/conn.go | 81 ++++++++++++++++++++++------------------ 2 files changed, 48 insertions(+), 37 deletions(-) diff --git a/src/util/cancellation.go b/src/util/cancellation.go index fa980084..2a78c19d 100644 --- a/src/util/cancellation.go +++ b/src/util/cancellation.go @@ -75,6 +75,8 @@ func CancellationChild(parent Cancellation) Cancellation { return child } +var CancellationTimeoutError = errors.New("timeout") + func CancellationWithTimeout(parent Cancellation, timeout time.Duration) Cancellation { child := CancellationChild(parent) go func() { @@ -83,7 +85,7 @@ func CancellationWithTimeout(parent Cancellation, timeout time.Duration) Cancell select { case <-child.Finished(): case <-timer.C: - child.Cancel(errors.New("timeout")) + child.Cancel(CancellationTimeoutError) } }() return child diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 1d686f83..bc884fb3 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -46,13 +46,13 @@ func (e *ConnError) Closed() bool { type Conn struct { core *Core - nodeID *crypto.NodeID - nodeMask *crypto.NodeID - mutex sync.RWMutex - close chan bool - session *sessionInfo readDeadline atomic.Value // time.Time // TODO timer writeDeadline atomic.Value // time.Time // TODO timer + cancel util.Cancellation + mutex sync.RWMutex // protects the below + nodeID *crypto.NodeID + nodeMask *crypto.NodeID + session *sessionInfo } // TODO func NewConn() that initializes additional fields as needed @@ -62,12 +62,14 @@ func newConn(core *Core, nodeID *crypto.NodeID, nodeMask *crypto.NodeID, session nodeID: nodeID, nodeMask: nodeMask, session: session, - close: make(chan bool), + cancel: util.NewCancellation(), } return &conn } func (c *Conn) String() string { + c.mutex.RLock() + defer c.mutex.RUnlock() return fmt.Sprintf("conn=%p", c) } @@ -111,28 +113,31 @@ func (c *Conn) search() error { return nil } -func getDeadlineTimer(value *atomic.Value) *time.Timer { - timer := time.NewTimer(24 * 365 * time.Hour) // FIXME for some reason setting this to 0 doesn't always let it stop and drain the channel correctly - util.TimerStop(timer) +func (c *Conn) getDeadlineCancellation(value *atomic.Value) util.Cancellation { if deadline, ok := value.Load().(time.Time); ok { - timer.Reset(time.Until(deadline)) + // A deadline is set, so return a Cancellation that uses it + return util.CancellationWithDeadline(c.cancel, deadline) + } else { + // No cancellation was set, so return a child cancellation with no timeout + return util.CancellationChild(c.cancel) } - return timer } func (c *Conn) Read(b []byte) (int, error) { // Take a copy of the session object sinfo := c.session - timer := getDeadlineTimer(&c.readDeadline) - defer util.TimerStop(timer) + cancel := c.getDeadlineCancellation(&c.readDeadline) + defer cancel.Cancel(nil) var bs []byte for { // Wait for some traffic to come through from the session select { - case <-c.close: - return 0, ConnError{errors.New("session closed"), false, false, true, 0} - case <-timer.C: - return 0, ConnError{errors.New("read timeout"), true, false, false, 0} + case <-cancel.Finished(): + if cancel.Error() == util.CancellationTimeoutError { + return 0, ConnError{errors.New("read timeout"), true, false, false, 0} + } else { + return 0, ConnError{errors.New("session closed"), false, false, true, 0} + } case p, ok := <-sinfo.recv: // If the session is closed then do nothing if !ok { @@ -172,18 +177,22 @@ func (c *Conn) Read(b []byte) (int, error) { // Send to worker select { case sinfo.worker <- workerFunc: - case <-c.close: - return 0, ConnError{errors.New("session closed"), false, false, true, 0} - case <-timer.C: - return 0, ConnError{errors.New("read timeout"), true, false, false, 0} + case <-cancel.Finished(): + if cancel.Error() == util.CancellationTimeoutError { + return 0, ConnError{errors.New("read timeout"), true, false, false, 0} + } else { + return 0, ConnError{errors.New("session closed"), false, false, true, 0} + } } // Wait for the worker to finish select { case <-done: // Wait for the worker to finish, failing this can cause memory errors (util.[Get||Put]Bytes stuff) - case <-c.close: - return 0, ConnError{errors.New("session closed"), false, false, true, 0} - case <-timer.C: - return 0, ConnError{errors.New("read timeout"), true, false, false, 0} + case <-cancel.Finished(): + if cancel.Error() == util.CancellationTimeoutError { + return 0, ConnError{errors.New("read timeout"), true, false, false, 0} + } else { + return 0, ConnError{errors.New("session closed"), false, false, true, 0} + } } // Something went wrong in the session worker so abort if err != nil { @@ -256,8 +265,8 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { } } // Set up a timer so this doesn't block forever - timer := getDeadlineTimer(&c.writeDeadline) - defer util.TimerStop(timer) + cancel := c.getDeadlineCancellation(&c.writeDeadline) + defer cancel.Cancel(nil) // Hand over to the session worker defer func() { if recover() != nil { @@ -267,8 +276,12 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { }() // In case we're racing with a close select { // Send to worker case sinfo.worker <- workerFunc: - case <-timer.C: - return 0, ConnError{errors.New("write timeout"), true, false, false, 0} + case <-cancel.Finished(): + if cancel.Error() == util.CancellationTimeoutError { + return 0, ConnError{errors.New("write timeout"), true, false, false, 0} + } else { + return 0, ConnError{errors.New("session closed"), false, false, true, 0} + } } // Wait for the worker to finish, otherwise there are memory errors ([Get||Put]Bytes stuff) <-done @@ -287,13 +300,9 @@ func (c *Conn) Close() (err error) { // Close the session, if it hasn't been closed already c.core.router.doAdmin(c.session.close) } - func() { - defer func() { - recover() - err = ConnError{errors.New("close failed, session already closed"), false, false, true, 0} - }() - close(c.close) // Closes reader/writer goroutines - }() + if e := c.cancel.Cancel(errors.New("connection closed")); e != nil { + err = ConnError{errors.New("close failed, session already closed"), false, false, true, 0} + } return } From 1a5c2a49426dcb1e4f1ac0538b8cb29ca77a0a6b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 19 Jul 2019 22:21:30 +0100 Subject: [PATCH 0144/1109] Update Windows module a bit - capture TAP setup errors earlier, refer to newer version of water which should fix #456 --- go.mod | 11 +++++----- go.sum | 13 ++++++++++++ src/tuntap/tun_windows.go | 44 ++++++++++++++++++++++++++------------- 3 files changed, 48 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index eec583ae..28044531 100644 --- a/go.mod +++ b/go.mod @@ -8,9 +8,10 @@ require ( github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/mitchellh/mapstructure v1.1.2 github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 - github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae - golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 - golang.org/x/net v0.0.0-20181207154023-610586996380 - golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e - golang.org/x/text v0.3.0 + github.com/yggdrasil-network/water v0.0.0-20190719211521-a76871ea954b + golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 + golang.org/x/net v0.0.0-20190628185345-da137c7871d7 + golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 + golang.org/x/text v0.3.2 + golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386 // indirect ) diff --git a/go.sum b/go.sum index 4be88b29..c4b8686c 100644 --- a/go.sum +++ b/go.sum @@ -14,11 +14,24 @@ github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 h1:1zN6ImoqhSJhN8h github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091/go.mod h1:N20Z5Y8oye9a7HmytmZ+tr8Q2vlP0tAHP13kTHzwvQY= github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae h1:MYCANF1kehCG6x6G+/9txLfq6n3lS5Vp0Mxn1hdiBAc= github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= +github.com/yggdrasil-network/water v0.0.0-20190719211521-a76871ea954b/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20181207154023-610586996380 h1:zPQexyRtNYBc7bcHmehl1dH6TB3qn8zytv8cBGLDNY0= golang.org/x/net v0.0.0-20181207154023-610586996380/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e h1:njOxP/wVblhCLIUhjHXf6X+dzTt5OQ3vMQo9mkOIKIo= golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= diff --git a/src/tuntap/tun_windows.go b/src/tuntap/tun_windows.go index 002c354e..98508efd 100644 --- a/src/tuntap/tun_windows.go +++ b/src/tuntap/tun_windows.go @@ -1,6 +1,7 @@ package tuntap import ( + "errors" "fmt" "os/exec" "strings" @@ -27,23 +28,13 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int } iface, err := water.New(config) if err != nil { - panic(err) - } - // Disable/enable the interface to resets its configuration (invalidating iface) - cmd := exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=DISABLED") - tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " ")) - output, err := cmd.CombinedOutput() - if err != nil { - tun.log.Errorln("Windows netsh failed:", err) - tun.log.Traceln(string(output)) return err } - cmd = exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=ENABLED") - tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " ")) - output, err = cmd.CombinedOutput() - if err != nil { - tun.log.Errorln("Windows netsh failed:", err) - tun.log.Traceln(string(output)) + if iface.Name() == "" { + return errors.New("unable to find TAP adapter with component ID " + config.PlatformSpecificParams.ComponentID) + } + // Reset the adapter - this invalidates iface so we'll need to get a new one + if err := tun.resetAdapter(); err != nil { return err } // Get a new iface @@ -64,6 +55,29 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int return tun.setupAddress(addr) } +// Disable/enable the interface to reset its configuration (invalidating iface). +func (tun *TunAdapter) resetAdapter() error { + // Bring down the interface first + cmd := exec.Command("netsh", "interface", "set", "interface", tun.iface.Name(), "admin=DISABLED") + tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " ")) + output, err := cmd.CombinedOutput() + if err != nil { + tun.log.Errorln("Windows netsh failed:", err) + tun.log.Traceln(string(output)) + return err + } + // Bring the interface back up + cmd = exec.Command("netsh", "interface", "set", "interface", tun.iface.Name(), "admin=ENABLED") + tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " ")) + output, err = cmd.CombinedOutput() + if err != nil { + tun.log.Errorln("Windows netsh failed:", err) + tun.log.Traceln(string(output)) + return err + } + return nil +} + // Sets the MTU of the TAP adapter. func (tun *TunAdapter) setupMTU(mtu int) error { // Set MTU From 613468e6a76a02509e183a8a69b5d416f87d97ce Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 19 Jul 2019 22:30:59 +0100 Subject: [PATCH 0145/1109] Update go.mod/go.sum again for BSD tweaks in Water due to failed CI build --- go.mod | 2 +- go.sum | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 28044531..d9430455 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/mitchellh/mapstructure v1.1.2 github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 - github.com/yggdrasil-network/water v0.0.0-20190719211521-a76871ea954b + github.com/yggdrasil-network/water v0.0.0-20190719213007-b160316e362e golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 golang.org/x/net v0.0.0-20190628185345-da137c7871d7 golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 diff --git a/go.sum b/go.sum index c4b8686c..28b5fb35 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,7 @@ github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091/go.mod h1:N20Z5Y8o github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae h1:MYCANF1kehCG6x6G+/9txLfq6n3lS5Vp0Mxn1hdiBAc= github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= github.com/yggdrasil-network/water v0.0.0-20190719211521-a76871ea954b/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= +github.com/yggdrasil-network/water v0.0.0-20190719213007-b160316e362e/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= From 52080aa41e2eb4ee93f46f3b4ddb7503cac939d0 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 19 Jul 2019 22:34:18 +0100 Subject: [PATCH 0146/1109] Build with Go 1.12.7 --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1a2898c2..2bd5b03f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ version: 2.1 jobs: build-linux: docker: - - image: circleci/golang:1.12 + - image: circleci/golang:1.12.7 steps: - checkout @@ -141,7 +141,7 @@ jobs: build-other: docker: - - image: circleci/golang:1.12 + - image: circleci/golang:1.12.7 steps: - checkout From f3e3e4bca1202eb0cb07b2aa9c818d15e8009535 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 20 Jul 2019 11:14:42 +0100 Subject: [PATCH 0147/1109] Update go.mod/go.sum again for Windows interface selection tweaks --- go.mod | 2 +- go.sum | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index d9430455..0d58244f 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/mitchellh/mapstructure v1.1.2 github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 - github.com/yggdrasil-network/water v0.0.0-20190719213007-b160316e362e + github.com/yggdrasil-network/water v0.0.0-20190720101301-5db94379a5eb golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 golang.org/x/net v0.0.0-20190628185345-da137c7871d7 golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 diff --git a/go.sum b/go.sum index 28b5fb35..4335302f 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,7 @@ github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae h1:MYCANF1 github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= github.com/yggdrasil-network/water v0.0.0-20190719211521-a76871ea954b/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= github.com/yggdrasil-network/water v0.0.0-20190719213007-b160316e362e/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= +github.com/yggdrasil-network/water v0.0.0-20190720101301-5db94379a5eb/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= From 2582df752d668ddc25b5b2692e6bc8ce2461e60d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 20 Jul 2019 11:43:30 +0100 Subject: [PATCH 0148/1109] Fix resetting Windows adapter (reverting previous change) --- src/tuntap/tun_windows.go | 39 +++++++++++++++------------------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/src/tuntap/tun_windows.go b/src/tuntap/tun_windows.go index 98508efd..d46b7e5b 100644 --- a/src/tuntap/tun_windows.go +++ b/src/tuntap/tun_windows.go @@ -34,7 +34,21 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int return errors.New("unable to find TAP adapter with component ID " + config.PlatformSpecificParams.ComponentID) } // Reset the adapter - this invalidates iface so we'll need to get a new one - if err := tun.resetAdapter(); err != nil { + cmd := exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=DISABLED") + tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " ")) + output, err := cmd.CombinedOutput() + if err != nil { + tun.log.Errorln("Windows netsh failed:", err) + tun.log.Traceln(string(output)) + return err + } + // Bring the interface back up + cmd = exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=ENABLED") + tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " ")) + output, err = cmd.CombinedOutput() + if err != nil { + tun.log.Errorln("Windows netsh failed:", err) + tun.log.Traceln(string(output)) return err } // Get a new iface @@ -55,29 +69,6 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int return tun.setupAddress(addr) } -// Disable/enable the interface to reset its configuration (invalidating iface). -func (tun *TunAdapter) resetAdapter() error { - // Bring down the interface first - cmd := exec.Command("netsh", "interface", "set", "interface", tun.iface.Name(), "admin=DISABLED") - tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " ")) - output, err := cmd.CombinedOutput() - if err != nil { - tun.log.Errorln("Windows netsh failed:", err) - tun.log.Traceln(string(output)) - return err - } - // Bring the interface back up - cmd = exec.Command("netsh", "interface", "set", "interface", tun.iface.Name(), "admin=ENABLED") - tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " ")) - output, err = cmd.CombinedOutput() - if err != nil { - tun.log.Errorln("Windows netsh failed:", err) - tun.log.Traceln(string(output)) - return err - } - return nil -} - // Sets the MTU of the TAP adapter. func (tun *TunAdapter) setupMTU(mtu int) error { // Set MTU From 36201895e7285859aa9e9c0fb70386b15cde65b3 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 20 Jul 2019 12:10:05 +0100 Subject: [PATCH 0149/1109] Don't mangle bs slice in TAP mode --- src/tuntap/iface.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 9ffde85b..4ddaf0b9 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -111,10 +111,10 @@ func (tun *TunAdapter) writer() error { } func (tun *TunAdapter) reader() error { - bs := make([]byte, 65535) + recvd := make([]byte, 65535) for { // Wait for a packet to be delivered to us through the TUN/TAP adapter - n, err := tun.iface.Read(bs) + n, err := tun.iface.Read(recvd) if err != nil { if !tun.isOpen { return err @@ -130,17 +130,21 @@ func (tun *TunAdapter) reader() error { if tun.iface.IsTAP() { // Set our offset to beyond the ethernet headers offset = tun_ETHER_HEADER_LENGTH + // Check first of all that we can go beyond the ethernet headers + if len(recvd) <= offset { + continue + } // If we detect an ICMP packet then hand it to the ICMPv6 module - if bs[offset+6] == 58 { + if recvd[offset+6] == 58 { // Found an ICMPv6 packet b := make([]byte, n) - copy(b, bs) + copy(b, recvd) go tun.icmpv6.ParsePacket(b) } - // Then offset the buffer so that we can now just treat it as an IP - // packet from now on - bs = bs[offset:] // FIXME this breaks bs for the next read and means n is the wrong value } + // Offset the buffer from now on so that we can ignore ethernet frames if + // they are present + bs := recvd[offset:] // From the IP header, work out what our source and destination addresses // and node IDs are. We will need these in order to work out where to send // the packet From 48ad3c5d7fc053c66dfafc20a074a636725c7afe Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 20 Jul 2019 16:13:54 +0100 Subject: [PATCH 0150/1109] Update water go.mod references, fix some bugs in TAP mode (which should hopefully fix Windows support too) --- go.mod | 2 +- go.sum | 1 + src/tuntap/icmpv6.go | 14 ++++++++------ src/tuntap/iface.go | 28 +++++++++++++++++----------- src/tuntap/tun_windows.go | 6 ++++++ 5 files changed, 33 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 0d58244f..f0769054 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/mitchellh/mapstructure v1.1.2 github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 - github.com/yggdrasil-network/water v0.0.0-20190720101301-5db94379a5eb + github.com/yggdrasil-network/water v0.0.0-20190720145626-28ccb9101d55 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 golang.org/x/net v0.0.0-20190628185345-da137c7871d7 golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 diff --git a/go.sum b/go.sum index 4335302f..c6fc16c2 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,7 @@ github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae/go.mod h1: github.com/yggdrasil-network/water v0.0.0-20190719211521-a76871ea954b/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= github.com/yggdrasil-network/water v0.0.0-20190719213007-b160316e362e/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= github.com/yggdrasil-network/water v0.0.0-20190720101301-5db94379a5eb/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= +github.com/yggdrasil-network/water v0.0.0-20190720145626-28ccb9101d55/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/src/tuntap/icmpv6.go b/src/tuntap/icmpv6.go index fe80dfbd..e601acb5 100644 --- a/src/tuntap/icmpv6.go +++ b/src/tuntap/icmpv6.go @@ -78,8 +78,9 @@ func (i *ICMPv6) Init(t *TunAdapter) { // Parses an incoming ICMPv6 packet. The packet provided may be either an // ethernet frame containing an IP packet, or the IP packet alone. This is // determined by whether the TUN/TAP adapter is running in TUN (layer 3) or -// TAP (layer 2) mode. -func (i *ICMPv6) ParsePacket(datain []byte) { +// TAP (layer 2) mode. Returns an error condition which is nil if the ICMPv6 +// module handled the packet or contains the error if not. +func (i *ICMPv6) ParsePacket(datain []byte) error { var response []byte var err error @@ -91,11 +92,12 @@ func (i *ICMPv6) ParsePacket(datain []byte) { } if err != nil { - return + return err } // Write the packet to TUN/TAP i.tun.iface.Write(response) + return nil } // Unwraps the ethernet headers of an incoming ICMPv6 packet and hands off @@ -105,7 +107,7 @@ func (i *ICMPv6) ParsePacket(datain []byte) { func (i *ICMPv6) UnmarshalPacketL2(datain []byte) ([]byte, error) { // Ignore non-IPv6 frames if binary.BigEndian.Uint16(datain[12:14]) != uint16(0x86DD) { - return nil, nil + return nil, errors.New("Ignoring non-IPv6 frame") } // Hand over to ParsePacket to interpret the IPv6 packet @@ -141,12 +143,12 @@ func (i *ICMPv6) UnmarshalPacket(datain []byte, datamac *[]byte) ([]byte, error) // Check if the packet is IPv6 if ipv6Header.Version != ipv6.Version { - return nil, err + return nil, errors.New("Ignoring non-IPv6 packet") } // Check if the packet is ICMPv6 if ipv6Header.NextHeader != 58 { - return nil, err + return nil, errors.New("Ignoring non-ICMPv6 packet") } // Parse the ICMPv6 message contents diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 4ddaf0b9..637715d6 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -111,7 +111,7 @@ func (tun *TunAdapter) writer() error { } func (tun *TunAdapter) reader() error { - recvd := make([]byte, 65535) + recvd := make([]byte, 65535+tun_ETHER_HEADER_LENGTH) for { // Wait for a packet to be delivered to us through the TUN/TAP adapter n, err := tun.iface.Read(recvd) @@ -134,17 +134,22 @@ func (tun *TunAdapter) reader() error { if len(recvd) <= offset { continue } - // If we detect an ICMP packet then hand it to the ICMPv6 module - if recvd[offset+6] == 58 { - // Found an ICMPv6 packet - b := make([]byte, n) - copy(b, recvd) - go tun.icmpv6.ParsePacket(b) - } } // Offset the buffer from now on so that we can ignore ethernet frames if // they are present - bs := recvd[offset:] + bs := recvd[offset : offset+n] + n -= offset + // If we detect an ICMP packet then hand it to the ICMPv6 module + if bs[6] == 58 { + // Found an ICMPv6 packet - we need to make sure to give ICMPv6 the full + // Ethernet frame rather than just the IPv6 packet as this is needed for + // NDP to work correctly + if err := tun.icmpv6.ParsePacket(recvd); err == nil { + // We acted on the packet in the ICMPv6 module so don't forward or do + // anything else with it + continue + } + } // From the IP header, work out what our source and destination addresses // and node IDs are. We will need these in order to work out where to send // the packet @@ -162,7 +167,7 @@ func (tun *TunAdapter) reader() error { continue } // Check the packet size - if n != 256*int(bs[4])+int(bs[5])+offset+tun_IPv6_HEADER_LENGTH { + if n-tun_IPv6_HEADER_LENGTH != 256*int(bs[4])+int(bs[5]) { continue } // IPv6 address @@ -176,7 +181,7 @@ func (tun *TunAdapter) reader() error { continue } // Check the packet size - if n != 256*int(bs[2])+int(bs[3])+offset { + if n != 256*int(bs[2])+int(bs[3]) { continue } // IPv4 address @@ -185,6 +190,7 @@ func (tun *TunAdapter) reader() error { copy(dstAddr[:addrlen], bs[16:]) } else { // Unknown address length or protocol, so drop the packet and ignore it + tun.log.Traceln("Unknown packet type, dropping") continue } if tun.ckr.isEnabled() && !tun.ckr.isValidSource(srcAddr, addrlen) { diff --git a/src/tuntap/tun_windows.go b/src/tuntap/tun_windows.go index d46b7e5b..a826c7ad 100644 --- a/src/tuntap/tun_windows.go +++ b/src/tuntap/tun_windows.go @@ -71,6 +71,9 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int // Sets the MTU of the TAP adapter. func (tun *TunAdapter) setupMTU(mtu int) error { + if tun.iface == nil || tun.iface.Name() == "" { + return errors.New("Can't configure MTU as TAP adapter is not present") + } // Set MTU cmd := exec.Command("netsh", "interface", "ipv6", "set", "subinterface", fmt.Sprintf("interface=%s", tun.iface.Name()), @@ -88,6 +91,9 @@ func (tun *TunAdapter) setupMTU(mtu int) error { // Sets the IPv6 address of the TAP adapter. func (tun *TunAdapter) setupAddress(addr string) error { + if tun.iface == nil || tun.iface.Name() == "" { + return errors.New("Can't configure IPv6 address as TAP adapter is not present") + } // Set address cmd := exec.Command("netsh", "interface", "ipv6", "add", "address", fmt.Sprintf("interface=%s", tun.iface.Name()), From 34ac5c9197242b3c2a025a5d7634534185b410af Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 20 Jul 2019 21:56:53 +0100 Subject: [PATCH 0151/1109] Send PPROF output text to stderr instead of stdout so that it doesn't break -genconf --- src/yggdrasil/debug.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index 5cb7c46a..ff3bbe70 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -35,9 +35,9 @@ func init() { hostPort := os.Getenv(envVarName) switch { case hostPort == "": - fmt.Printf("DEBUG: %s not set, profiler not started.\n", envVarName) + fmt.Fprintf(os.Stderr, "DEBUG: %s not set, profiler not started.\n", envVarName) default: - fmt.Printf("DEBUG: Starting pprof on %s\n", hostPort) + fmt.Fprintf(os.Stderr, "DEBUG: Starting pprof on %s\n", hostPort) go func() { fmt.Println(http.ListenAndServe(hostPort, nil)) }() } } From 8669091a0882627ad1aa6cc78081267c595a36d1 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 22 Jul 2019 19:45:48 +0100 Subject: [PATCH 0152/1109] Don't send IP back twice with getPeers --- src/admin/admin.go | 1 - 1 file changed, 1 deletion(-) diff --git a/src/admin/admin.go b/src/admin/admin.go index 4c933dd5..fc8a6a59 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -103,7 +103,6 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. addr := *address.AddrForNodeID(crypto.GetNodeID(&p.PublicKey)) so := net.IP(addr[:]).String() peers[so] = Info{ - "ip": so, "port": p.Port, "uptime": p.Uptime.Seconds(), "bytes_sent": p.BytesSent, From de9d0a6cf10ef6485b684d3b59cc3326c165ed6a Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 22 Jul 2019 22:41:55 +0100 Subject: [PATCH 0153/1109] Redirect Conn session closure errors to debug channel --- src/tuntap/conn.go | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index 234c34f0..193c6236 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -63,7 +63,11 @@ func (s *tunConn) reader() (err error) { } if n, err = s.conn.Read(b); err != nil { if e, eok := err.(yggdrasil.ConnError); eok && !e.Temporary() { - s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn read error:", err) + if e.Closed() { + s.tun.log.Debugln(s.conn.String(), "TUN/TAP conn read debug:", err) + } else { + s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn read error:", err) + } return e } } else if n > 0 { @@ -98,9 +102,12 @@ func (s *tunConn) writer() error { } // TODO write timeout and close if _, err := s.conn.Write(b); err != nil { - e, eok := err.(yggdrasil.ConnError) - if !eok { - s.tun.log.Errorln(s.conn.String(), "TUN/TAP generic write error:", err) + if e, eok := err.(yggdrasil.ConnError); !eok { + if e.Closed() { + s.tun.log.Debugln(s.conn.String(), "TUN/TAP generic write debug:", err) + } else { + s.tun.log.Errorln(s.conn.String(), "TUN/TAP generic write error:", err) + } } else if ispackettoobig, maxsize := e.PacketTooBig(); ispackettoobig { // TODO: This currently isn't aware of IPv4 for CKR ptb := &icmp.PacketTooBig{ @@ -111,7 +118,11 @@ func (s *tunConn) writer() error { s.tun.send <- packet } } else { - s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn write error:", err) + if e.Closed() { + s.tun.log.Debugln(s.conn.String(), "TUN/TAP conn write debug:", err) + } else { + s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn write error:", err) + } } } else { s.stillAlive() From 9b99f0b5e487a7ab025c3eb72d2ced21adf52f0a Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 25 Jul 2019 08:40:45 +0100 Subject: [PATCH 0154/1109] Update go.mod/go.sum references --- go.mod | 6 +++--- go.sum | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f0769054..dc2d5222 100644 --- a/go.mod +++ b/go.mod @@ -8,10 +8,10 @@ require ( github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/mitchellh/mapstructure v1.1.2 github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 - github.com/yggdrasil-network/water v0.0.0-20190720145626-28ccb9101d55 + github.com/yggdrasil-network/water v0.0.0-20190725073841-250edb919f8a golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 - golang.org/x/net v0.0.0-20190628185345-da137c7871d7 + golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 golang.org/x/text v0.3.2 - golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386 // indirect + golang.org/x/tools v0.0.0-20190724185037-8aa4eac1a7c1 // indirect ) diff --git a/go.sum b/go.sum index c6fc16c2..059d8459 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,7 @@ github.com/yggdrasil-network/water v0.0.0-20190719211521-a76871ea954b/go.mod h1: github.com/yggdrasil-network/water v0.0.0-20190719213007-b160316e362e/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= github.com/yggdrasil-network/water v0.0.0-20190720101301-5db94379a5eb/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= github.com/yggdrasil-network/water v0.0.0-20190720145626-28ccb9101d55/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= +github.com/yggdrasil-network/water v0.0.0-20190725073841-250edb919f8a/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -27,6 +28,7 @@ golang.org/x/net v0.0.0-20181207154023-610586996380/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e h1:njOxP/wVblhCLIUhjHXf6X+dzTt5OQ3vMQo9mkOIKIo= golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -38,3 +40,4 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190724185037-8aa4eac1a7c1/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= From e5bb9bcb8dadc3cadd46df7f4d5651fbebbd17ce Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 26 Jul 2019 17:44:40 -0500 Subject: [PATCH 0155/1109] change how searches are initialized so we actually send a dhtReq to ourself and get a response, in case we're the destination --- src/yggdrasil/search.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index d8c9049a..4c31fd6b 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -179,8 +179,12 @@ func (sinfo *searchInfo) continueSearch() { // Calls create search, and initializes the iterative search parts of the struct before returning it. func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo { sinfo := s.createSearch(dest, mask, callback) - sinfo.toVisit = s.core.dht.lookup(dest, true) sinfo.visited = make(map[crypto.NodeID]bool) + loc := s.core.switchTable.getLocator() + sinfo.toVisit = append(sinfo.toVisit, &dhtInfo{ + key: s.core.boxPub, + coords: loc.getCoords(), + }) // Start the search by asking ourself, useful if we're the destination return sinfo } From 195d577151dde9c3aa01cdfc82161ac9250e9c1e Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 27 Jul 2019 13:30:47 +0100 Subject: [PATCH 0156/1109] Add IFF_NODAD/IFF_SECURED, define consts --- src/tuntap/tun_darwin.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/tuntap/tun_darwin.go b/src/tuntap/tun_darwin.go index 5dfca137..d7b46537 100644 --- a/src/tuntap/tun_darwin.go +++ b/src/tuntap/tun_darwin.go @@ -30,7 +30,12 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int return tun.setupAddress(addr) } -const darwin_SIOCAIFADDR_IN6 = 2155899162 +const ( + darwin_SIOCAIFADDR_IN6 = 2155899162 // netinet6/in6_var.h + darwin_IN6_IFF_NODAD = 0x0020 // netinet6/in6_var.h + darwin_IN6_IFF_SECURED = 0x0400 // netinet6/in6_var.h + darwin_ND6_INFINITE_LIFETIME = 0xFFFFFFFF // netinet6/nd6.h +) type in6_addrlifetime struct { ia6t_expire float64 @@ -91,8 +96,11 @@ func (tun *TunAdapter) setupAddress(addr string) error { ar.ifra_addr.sin6_addr[i] = uint16(binary.BigEndian.Uint16(b)) } - ar.ifra_lifetime.ia6t_vltime = 0xFFFFFFFF - ar.ifra_lifetime.ia6t_pltime = 0xFFFFFFFF + ar.ifra_flags |= darwin_IN6_IFF_NODAD + ar.ifra_flags |= darwin_IN6_IFF_SECURED + + ar.ifra_lifetime.ia6t_vltime = darwin_ND6_INFINITE_LIFETIME + ar.ifra_lifetime.ia6t_pltime = darwin_ND6_INFINITE_LIFETIME var ir ifreq copy(ir.ifr_name[:], tun.iface.Name()) From 4d4fa84123c2b8c47ff71ce569165c3c38e8d59d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 27 Jul 2019 13:57:19 +0100 Subject: [PATCH 0157/1109] Hopefully fix CircleCI builds on PRs --- .circleci/config.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2bd5b03f..0bf62c5f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,6 +17,7 @@ jobs: echo 'export CINAME=$(sh contrib/semver/name.sh)' >> $BASH_ENV echo 'export CIVERSION=$(sh contrib/semver/version.sh --bare)' >> $BASH_ENV echo 'export CIVERSIONRPM=$(sh contrib/semver/version.sh --bare | tr "-" ".")' >> $BASH_ENV + echo 'export CIBRANCH=$(echo $CIRCLE_BRANCH | tr -d "/")' case "$CINAME" in \ "yggdrasil") (echo 'export CICONFLICTS=yggdrasil-develop' >> $BASH_ENV) ;; \ "yggdrasil-develop") (echo 'export CICONFLICTS=yggdrasil' >> $BASH_ENV) ;; \ @@ -53,9 +54,9 @@ jobs: name: Build for Linux (RPM packages) command: | git clone https://github.com/yggdrasil-network/yggdrasil-package-rpm ~/rpmbuild/SPECS - cd ../ && tar -czvf ~/rpmbuild/SOURCES/v$CIVERSIONRPM --transform "s/project/yggdrasil-go-$CIRCLE_BRANCH-$CIVERSIONRPM/" project - sed -i "s/yggdrasil-go/yggdrasil-go-$CIRCLE_BRANCH/" ~/rpmbuild/SPECS/yggdrasil.spec - sed -i "s/^PKGNAME=yggdrasil/PKGNAME=yggdrasil-$CIRCLE_BRANCH/" ~/rpmbuild/SPECS/yggdrasil.spec + cd ../ && tar -czvf ~/rpmbuild/SOURCES/v$CIVERSIONRPM --transform "s/project/yggdrasil-go-$CIBRANCH-$CIVERSIONRPM/" project + sed -i "s/yggdrasil-go/yggdrasil-go-$CIBRANCH/" ~/rpmbuild/SPECS/yggdrasil.spec + sed -i "s/^PKGNAME=yggdrasil/PKGNAME=yggdrasil-$CIBRANCH/" ~/rpmbuild/SPECS/yggdrasil.spec sed -i "s/^Name\:.*/Name\: $CINAME/" ~/rpmbuild/SPECS/yggdrasil.spec sed -i "s/^Version\:.*/Version\: $CIVERSIONRPM/" ~/rpmbuild/SPECS/yggdrasil.spec sed -i "s/^Conflicts\:.*/Conflicts\: $CICONFLICTS/" ~/rpmbuild/SPECS/yggdrasil.spec From d81310538602ad716631b811c9cf9073ee667972 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 27 Jul 2019 14:11:03 +0100 Subject: [PATCH 0158/1109] Export CIBRANCH to Bash env --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0bf62c5f..36fd6783 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,7 +17,7 @@ jobs: echo 'export CINAME=$(sh contrib/semver/name.sh)' >> $BASH_ENV echo 'export CIVERSION=$(sh contrib/semver/version.sh --bare)' >> $BASH_ENV echo 'export CIVERSIONRPM=$(sh contrib/semver/version.sh --bare | tr "-" ".")' >> $BASH_ENV - echo 'export CIBRANCH=$(echo $CIRCLE_BRANCH | tr -d "/")' + echo 'export CIBRANCH=$(echo $CIRCLE_BRANCH | tr -d "/")' >> $BASH_ENV case "$CINAME" in \ "yggdrasil") (echo 'export CICONFLICTS=yggdrasil-develop' >> $BASH_ENV) ;; \ "yggdrasil-develop") (echo 'export CICONFLICTS=yggdrasil' >> $BASH_ENV) ;; \ From ad4ba6871e8a536e31abb7a43e93497fb44ba413 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 27 Jul 2019 14:15:07 +0100 Subject: [PATCH 0159/1109] Use Go 1.12.7 for macOS builds --- .circleci/config.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 36fd6783..f9cd0720 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -32,11 +32,11 @@ jobs: sudo apt-get install -y rpm file mkdir -p ~/rpmbuild/BUILD ~/rpmbuild/RPMS ~/rpmbuild/SOURCES ~/rpmbuild/SPECS ~/rpmbuild/SRPMS - # - run: - # name: Test debug builds - # command: | - # ./build -d - # test -f yggdrasil && test -f yggdrasilctl + - run: + name: Test debug builds + command: | + ./build -d + test -f yggdrasil && test -f yggdrasilctl - run: name: Build for Linux (including Debian packages) @@ -102,11 +102,11 @@ jobs: echo -e "Host *\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config - run: - name: Install Go 1.12 + name: Install Go 1.12.7 command: | cd /tmp - curl -LO https://dl.google.com/go/go1.12.darwin-amd64.pkg - sudo installer -pkg /tmp/go1.12.darwin-amd64.pkg -target / + curl -LO https://dl.google.com/go/go1.12.7.darwin-amd64.pkg + sudo installer -pkg /tmp/go1.12.7.darwin-amd64.pkg -target / #- run: # name: Install Gomobile From de1005e4fa95405073993eafb17f99d25d35246c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 27 Jul 2019 15:00:09 +0100 Subject: [PATCH 0160/1109] Various API changes and simplifications to fix mobile builds --- src/admin/admin.go | 4 +-- src/config/config.go | 32 ++++++++++++++++---- src/dummy/dummy.go | 61 -------------------------------------- src/mobile/mobile.go | 10 +++---- src/multicast/multicast.go | 4 +-- src/tuntap/ckr.go | 2 +- src/tuntap/conn.go | 4 +-- src/tuntap/tun.go | 2 +- src/tuntap/tun_dummy.go | 19 ------------ src/yggdrasil/api.go | 28 +++++++---------- src/yggdrasil/conn.go | 13 ++++++-- src/yggdrasil/core.go | 4 +-- src/yggdrasil/router.go | 2 +- 13 files changed, 63 insertions(+), 122 deletions(-) delete mode 100644 src/dummy/dummy.go delete mode 100644 src/tuntap/tun_dummy.go diff --git a/src/admin/admin.go b/src/admin/admin.go index fc8a6a59..7d6f16b6 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -60,7 +60,7 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. go func() { for { e := <-a.reconfigure - current, previous := state.Get() + current, previous := state.GetCurrent(), state.GetPrevious() if current.AdminListen != previous.AdminListen { a.listenaddr = current.AdminListen a.Stop() @@ -69,7 +69,7 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. e <- nil } }() - current, _ := state.Get() + current := state.GetCurrent() a.listenaddr = current.AdminListen a.AddHandler("list", []string{}, func(in Info) (Info, error) { handlers := make(map[string]interface{}) diff --git a/src/config/config.go b/src/config/config.go index 9f8f3f54..a7bbbacf 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -16,21 +16,27 @@ type NodeState struct { Mutex sync.RWMutex } -// Get returns both the current and previous node configs -func (s *NodeState) Get() (NodeConfig, NodeConfig) { +// Current returns the current node config +func (s *NodeState) GetCurrent() NodeConfig { s.Mutex.RLock() defer s.Mutex.RUnlock() - return s.Current, s.Previous + return s.Current +} + +// Previous returns the previous node config +func (s *NodeState) GetPrevious() NodeConfig { + s.Mutex.RLock() + defer s.Mutex.RUnlock() + return s.Previous } // Replace the node configuration with new configuration. This method returns // both the new and the previous node configs -func (s *NodeState) Replace(n NodeConfig) (NodeConfig, NodeConfig) { +func (s *NodeState) Replace(n NodeConfig) { s.Mutex.Lock() defer s.Mutex.Unlock() s.Previous = s.Current s.Current = n - return s.Current, s.Previous } // NodeConfig defines all configuration values needed to run a signle yggdrasil node @@ -115,3 +121,19 @@ func GenerateConfig() *NodeConfig { return &cfg } + +// NewEncryptionKeys generates a new encryption keypair. The encryption keys are +// used to encrypt traffic and to derive the IPv6 address/subnet of the node. +func (cfg *NodeConfig) NewEncryptionKeys() { + bpub, bpriv := crypto.NewBoxKeys() + cfg.EncryptionPublicKey = hex.EncodeToString(bpub[:]) + cfg.EncryptionPrivateKey = hex.EncodeToString(bpriv[:]) +} + +// NewSigningKeys generates a new signing keypair. The signing keys are used to +// derive the structure of the spanning tree. +func (cfg *NodeConfig) NewSigningKeys() { + spub, spriv := crypto.NewSigKeys() + cfg.SigningPublicKey = hex.EncodeToString(spub[:]) + cfg.SigningPrivateKey = hex.EncodeToString(spriv[:]) +} diff --git a/src/dummy/dummy.go b/src/dummy/dummy.go deleted file mode 100644 index ca6bb7b7..00000000 --- a/src/dummy/dummy.go +++ /dev/null @@ -1,61 +0,0 @@ -package dummy - -import ( - "github.com/gologme/log" - "github.com/yggdrasil-network/yggdrasil-go/src/address" - "github.com/yggdrasil-network/yggdrasil-go/src/config" - "github.com/yggdrasil-network/yggdrasil-go/src/defaults" - "github.com/yggdrasil-network/yggdrasil-go/src/util" - "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" -) - -// DummyAdapter is a non-specific adapter that is used by the mobile APIs. -// You can also use it to send or receive custom traffic over Yggdrasil. -type DummyAdapter struct { - yggdrasil.Adapter -} - -// Init initialises the dummy adapter. -func (m *DummyAdapter) Init(config *config.NodeState, log *log.Logger, send chan<- []byte, recv <-chan []byte, reject <-chan yggdrasil.RejectedPacket) { - m.Adapter.Init(config, log, send, recv, reject) -} - -// Name returns the name of the adapter. This is always "dummy" for dummy -// adapters. -func (m *DummyAdapter) Name() string { - return "dummy" -} - -// MTU gets the adapter's MTU. This returns your platform's maximum MTU for -// dummy adapters. -func (m *DummyAdapter) MTU() int { - return defaults.GetDefaults().MaximumIfMTU -} - -// IsTAP always returns false for dummy adapters. -func (m *DummyAdapter) IsTAP() bool { - return false -} - -// Recv waits for and returns for a packet from the router. -func (m *DummyAdapter) Recv() ([]byte, error) { - packet := <-m.Adapter.Recv - return packet, nil -} - -// Send a packet to the router. -func (m *DummyAdapter) Send(buf []byte) error { - packet := append(util.GetBytes(), buf[:]...) - m.Adapter.Send <- packet - return nil -} - -// Start is not implemented for dummy adapters. -func (m *DummyAdapter) Start(address.Address, address.Subnet) error { - return nil -} - -// Close is not implemented for dummy adapters. -func (m *DummyAdapter) Close() error { - return nil -} diff --git a/src/mobile/mobile.go b/src/mobile/mobile.go index 02bd30fe..08a8c1ac 100644 --- a/src/mobile/mobile.go +++ b/src/mobile/mobile.go @@ -10,7 +10,6 @@ import ( hjson "github.com/hjson/hjson-go" "github.com/mitchellh/mapstructure" "github.com/yggdrasil-network/yggdrasil-go/src/config" - "github.com/yggdrasil-network/yggdrasil-go/src/dummy" "github.com/yggdrasil-network/yggdrasil-go/src/multicast" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) @@ -24,7 +23,6 @@ type Yggdrasil struct { core yggdrasil.Core multicast multicast.Multicast log MobileLogger - dummy.DummyAdapter } func (m *Yggdrasil) addStaticPeers(cfg *config.NodeConfig) { @@ -59,10 +57,10 @@ func (m *Yggdrasil) StartAutoconfigure() error { if hostname, err := os.Hostname(); err == nil { nc.NodeInfo = map[string]interface{}{"name": hostname} } - if err := m.core.SetRouterAdapter(m); err != nil { + /*if err := m.core.SetRouterAdapter(m); err != nil { logger.Errorln("An error occured setting router adapter:", err) return err - } + }*/ state, err := m.core.Start(nc, logger) if err != nil { logger.Errorln("An error occured starting Yggdrasil:", err) @@ -92,10 +90,10 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error { return err } nc.IfName = "dummy" - if err := m.core.SetRouterAdapter(m); err != nil { + /*if err := m.core.SetRouterAdapter(m); err != nil { logger.Errorln("An error occured setting router adapter:", err) return err - } + }*/ state, err := m.core.Start(nc, logger) if err != nil { logger.Errorln("An error occured starting Yggdrasil:", err) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index ba1f18fb..5ab9673e 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -35,7 +35,7 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log m.config = state m.log = log m.listeners = make(map[string]*yggdrasil.TcpListener) - current, _ := m.config.Get() + current := m.config.GetCurrent() m.listenPort = current.LinkLocalTCPPort m.groupAddr = "[ff02::114]:9001" return nil @@ -92,7 +92,7 @@ func (m *Multicast) UpdateConfig(config *config.NodeConfig) { func (m *Multicast) Interfaces() map[string]net.Interface { interfaces := make(map[string]net.Interface) // Get interface expressions from config - current, _ := m.config.Get() + current := m.config.GetCurrent() exprs := current.MulticastInterfaces // Ask the system for network interfaces allifaces, err := net.Interfaces() diff --git a/src/tuntap/ckr.go b/src/tuntap/ckr.go index 52c11596..80e3e42f 100644 --- a/src/tuntap/ckr.go +++ b/src/tuntap/ckr.go @@ -59,7 +59,7 @@ func (c *cryptokey) init(tun *TunAdapter) { // Configure the CKR routes - this must only ever be called from the router // goroutine, e.g. through router.doAdmin func (c *cryptokey) configure() error { - current, _ := c.tun.config.Get() + current := c.tun.config.GetCurrent() // Set enabled/disabled state c.setEnabled(current.TunnelRouting.Enable) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index 193c6236..1d47b378 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -108,10 +108,10 @@ func (s *tunConn) writer() error { } else { s.tun.log.Errorln(s.conn.String(), "TUN/TAP generic write error:", err) } - } else if ispackettoobig, maxsize := e.PacketTooBig(); ispackettoobig { + } else if e.PacketTooBig() { // TODO: This currently isn't aware of IPv4 for CKR ptb := &icmp.PacketTooBig{ - MTU: int(maxsize), + MTU: int(e.PacketMaximumSize()), Data: b[:900], } if packet, err := CreateICMPv6(b[8:24], b[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb); err == nil { diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index cc124971..eef05b87 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -119,7 +119,7 @@ func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener // Start the setup process for the TUN/TAP adapter. If successful, starts the // read/write goroutines to handle packets on that interface. func (tun *TunAdapter) Start() error { - current, _ := tun.config.Get() + current := tun.config.GetCurrent() if tun.config == nil || tun.listener == nil || tun.dialer == nil { return errors.New("No configuration available to TUN/TAP") } diff --git a/src/tuntap/tun_dummy.go b/src/tuntap/tun_dummy.go deleted file mode 100644 index 04e65257..00000000 --- a/src/tuntap/tun_dummy.go +++ /dev/null @@ -1,19 +0,0 @@ -// +build mobile - -package tuntap - -// This is to catch unsupported platforms -// If your platform supports tun devices, you could try configuring it manually - -// Creates the TUN/TAP adapter, if supported by the Water library. Note that -// no guarantees are made at this point on an unsupported platform. -func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { - tun.mtu = getSupportedMTU(mtu) - return tun.setupAddress(addr) -} - -// We don't know how to set the IPv6 address on an unknown platform, therefore -// write about it to stdout and don't try to do anything further. -func (tun *TunAdapter) setupAddress(addr string) error { - return nil -} diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 1bec9836..bd0d0983 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -290,18 +290,6 @@ func (c *Core) ListenTCP(uri string) (*TcpListener, error) { return c.link.tcp.listen(uri) } -// NewEncryptionKeys generates a new encryption keypair. The encryption keys are -// used to encrypt traffic and to derive the IPv6 address/subnet of the node. -func (c *Core) NewEncryptionKeys() (*crypto.BoxPubKey, *crypto.BoxPrivKey) { - return crypto.NewBoxKeys() -} - -// NewSigningKeys generates a new signing keypair. The signing keys are used to -// derive the structure of the spanning tree. -func (c *Core) NewSigningKeys() (*crypto.SigPubKey, *crypto.SigPrivKey) { - return crypto.NewSigKeys() -} - // NodeID gets the node ID. func (c *Core) NodeID() *crypto.NodeID { return crypto.GetNodeID(&c.boxPub) @@ -343,12 +331,6 @@ func (c *Core) Subnet() *net.IPNet { return &net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)} } -// RouterAddresses returns the raw address and subnet types as used by the -// router -func (c *Core) RouterAddresses() (address.Address, address.Subnet) { - return c.router.addr, c.router.subnet -} - // NodeInfo gets the currently configured nodeinfo. func (c *Core) MyNodeInfo() nodeinfoPayload { return c.router.nodeinfo.getNodeInfo() @@ -473,6 +455,16 @@ func (c *Core) DisconnectPeer(port uint64) error { return nil } +// RouterAddress returns the raw address as used by the router. +func (c *Core) RouterAddress() address.Address { + return c.router.addr +} + +// RouterSubnet returns the raw address as used by the router. +func (c *Core) RouterSubnet() address.Subnet { + return c.router.subnet +} + // GetAllowedEncryptionPublicKeys returns the public keys permitted for incoming // peer connections. func (c *Core) GetAllowedEncryptionPublicKeys() []string { diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index bc884fb3..cd61e248 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -35,8 +35,17 @@ func (e *ConnError) Temporary() bool { // PacketTooBig returns in response to sending a packet that is too large, and // if so, the maximum supported packet size that should be used for the // connection. -func (e *ConnError) PacketTooBig() (bool, int) { - return e.maxsize > 0, e.maxsize +func (e *ConnError) PacketTooBig() bool { + return e.maxsize > 0 +} + +// PacketMaximumSize returns the maximum supported packet size. This will only +// return a non-zero value if ConnError.PacketTooBig() returns true. +func (e *ConnError) PacketMaximumSize() int { + if !e.PacketTooBig() { + return 0 + } + return e.maxsize } // Closed returns if the session is already closed and is now unusable. diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 35a86df1..2aa78347 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -45,7 +45,7 @@ func (c *Core) init() error { c.log = log.New(ioutil.Discard, "", 0) } - current, _ := c.config.Get() + current := c.config.GetCurrent() boxPrivHex, err := hex.DecodeString(current.EncryptionPrivateKey) if err != nil { @@ -94,7 +94,7 @@ func (c *Core) init() error { func (c *Core) addPeerLoop() { for { // the peers from the config - these could change! - current, _ := c.config.Get() + current := c.config.GetCurrent() // Add peers from the Peers section for _, peer := range current.Peers { diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 514d14fa..abd31cb9 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -132,7 +132,7 @@ func (r *router) mainLoop() { case f := <-r.admin: f() case e := <-r.reconfigure: - current, _ := r.core.config.Get() + current := r.core.config.GetCurrent() e <- r.nodeinfo.setNodeInfo(current.NodeInfo, current.NodeInfoPrivacy) } } From 377f88512b3f9fa7912421966112ea79f72e4edf Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 27 Jul 2019 15:57:19 +0100 Subject: [PATCH 0161/1109] Remove commented out router function --- src/yggdrasil/router.go | 175 ---------------------------------------- 1 file changed, 175 deletions(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index abd31cb9..c5e1dde0 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -138,181 +138,6 @@ func (r *router) mainLoop() { } } -/* -// Checks a packet's to/from address to make sure it's in the allowed range. -// If a session to the destination exists, gets the session and passes the packet to it. -// If no session exists, it triggers (or continues) a search. -// If the session hasn't responded recently, it triggers a ping or search to keep things alive or deal with broken coords *relatively* quickly. -// It also deals with oversized packets if there are MTU issues by calling into icmpv6.go to spoof PacketTooBig traffic, or DestinationUnreachable if the other side has their adapter disabled. -func (r *router) sendPacket(bs []byte) { - var sourceAddr address.Address - var destAddr address.Address - var destSnet address.Subnet - var destPubKey *crypto.BoxPubKey - var destNodeID *crypto.NodeID - var addrlen int - if bs[0]&0xf0 == 0x60 { - // Check if we have a fully-sized header - if len(bs) < 40 { - panic("Tried to send a packet shorter than an IPv6 header...") - } - // IPv6 address - addrlen = 16 - copy(sourceAddr[:addrlen], bs[8:]) - copy(destAddr[:addrlen], bs[24:]) - copy(destSnet[:addrlen/2], bs[24:]) - } else if bs[0]&0xf0 == 0x40 { - // Check if we have a fully-sized header - if len(bs) < 20 { - panic("Tried to send a packet shorter than an IPv4 header...") - } - // IPv4 address - addrlen = 4 - copy(sourceAddr[:addrlen], bs[12:]) - copy(destAddr[:addrlen], bs[16:]) - } else { - // Unknown address length - return - } - if !r.cryptokey.isValidSource(sourceAddr, addrlen) { - // The packet had a source address that doesn't belong to us or our - // configured crypto-key routing source subnets - return - } - if !destAddr.IsValid() && !destSnet.IsValid() { - // The addresses didn't match valid Yggdrasil node addresses so let's see - // whether it matches a crypto-key routing range instead - if key, err := r.cryptokey.getPublicKeyForAddress(destAddr, addrlen); err == nil { - // A public key was found, get the node ID for the search - destPubKey = &key - destNodeID = crypto.GetNodeID(destPubKey) - // Do a quick check to ensure that the node ID refers to a vaild Yggdrasil - // address or subnet - this might be superfluous - addr := *address.AddrForNodeID(destNodeID) - copy(destAddr[:], addr[:]) - copy(destSnet[:], addr[:]) - if !destAddr.IsValid() && !destSnet.IsValid() { - return - } - } else { - // No public key was found in the CKR table so we've exhausted our options - return - } - } - searchCompleted := func(sinfo *sessionInfo, err error) { - if err != nil { - r.core.log.Debugln("DHT search failed:", err) - return - } - } - doSearch := func(packet []byte) { - var nodeID, mask *crypto.NodeID - switch { - case destNodeID != nil: - // We already know the full node ID, probably because it's from a CKR - // route in which the public key is known ahead of time - nodeID = destNodeID - var m crypto.NodeID - for i := range m { - m[i] = 0xFF - } - mask = &m - case destAddr.IsValid(): - // We don't know the full node ID - try and use the address to generate - // a truncated node ID - nodeID, mask = destAddr.GetNodeIDandMask() - case destSnet.IsValid(): - // We don't know the full node ID - try and use the subnet to generate - // a truncated node ID - nodeID, mask = destSnet.GetNodeIDandMask() - default: - return - } - sinfo, isIn := r.core.searches.searches[*nodeID] - if !isIn { - sinfo = r.core.searches.newIterSearch(nodeID, mask, searchCompleted) - } - if packet != nil { - sinfo.packet = packet - } - r.core.searches.continueSearch(sinfo) - } - var sinfo *sessionInfo - var isIn bool - if destAddr.IsValid() { - sinfo, isIn = r.core.sessions.getByTheirAddr(&destAddr) - } - if destSnet.IsValid() { - sinfo, isIn = r.core.sessions.getByTheirSubnet(&destSnet) - } - sTime := sinfo.time.Load().(time.Time) - pingTime := sinfo.pingTime.Load().(time.Time) - pingSend := sinfo.pingSend.Load().(time.Time) - switch { - case !isIn || !sinfo.init.Load().(bool): - // No or unintiialized session, so we need to search first - doSearch(bs) - case time.Since(sTime) > 6*time.Second: - if sTime.Before(pingTime) && time.Since(pingTime) > 6*time.Second { - // We haven't heard from the dest in a while - // We tried pinging but didn't get a response - // They may have changed coords - // Try searching to discover new coords - // Note that search spam is throttled internally - doSearch(nil) - } else { - // We haven't heard about the dest in a while - now := time.Now() - - if !sTime.Before(pingTime) { - // Update pingTime to start the clock for searches (above) - sinfo.pingTime.Store(now) - } - if time.Since(pingSend) > time.Second { - // Send at most 1 ping per second - sinfo.pingSend.Store(now) - r.core.sessions.sendPingPong(sinfo, false) - } - } - fallthrough // Also send the packet - default: - // If we know the public key ahead of time (i.e. a CKR route) then check - // if the session perm pub key matches before we send the packet to it - if destPubKey != nil { - if !bytes.Equal((*destPubKey)[:], sinfo.theirPermPub[:]) { - return - } - } - - // Drop packets if the session MTU is 0 - this means that one or other - // side probably has their TUN adapter disabled - if sinfo.getMTU() == 0 { - // Don't continue - drop the packet - return - } - // Generate an ICMPv6 Packet Too Big for packets larger than session MTU - if len(bs) > int(sinfo.getMTU()) { - // Get the size of the oversized payload, up to a max of 900 bytes - window := 900 - if int(sinfo.getMTU()) < window { - window = int(sinfo.getMTU()) - } - - // Send the error back to the adapter - r.reject <- RejectedPacket{ - Reason: PacketTooBig, - Packet: bs[:window], - Detail: int(sinfo.getMTU()), - } - - // Don't continue - drop the packet - return - } - sinfo.send <- bs - } -} -*/ - // Checks incoming traffic type and passes it to the appropriate handler. func (r *router) handleIn(packet []byte) { pType, pTypeLen := wire_decode_uint64(packet) From e0a3055c2ff306463f515b0d7c3a8bafb2e09113 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 27 Jul 2019 18:10:32 -0500 Subject: [PATCH 0162/1109] get rid of session workers, new util.PutBytes/GetBytes logic --- go.sum | 5 ++ src/util/cancellation.go | 38 ++++++-------- src/util/util.go | 31 ++++++------ src/yggdrasil/api.go | 2 +- src/yggdrasil/conn.go | 104 +++++++++++++++++++++------------------ src/yggdrasil/session.go | 29 +++-------- 6 files changed, 100 insertions(+), 109 deletions(-) diff --git a/go.sum b/go.sum index 059d8459..050a3e5e 100644 --- a/go.sum +++ b/go.sum @@ -18,25 +18,30 @@ github.com/yggdrasil-network/water v0.0.0-20190719211521-a76871ea954b/go.mod h1: github.com/yggdrasil-network/water v0.0.0-20190719213007-b160316e362e/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= github.com/yggdrasil-network/water v0.0.0-20190720101301-5db94379a5eb/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= github.com/yggdrasil-network/water v0.0.0-20190720145626-28ccb9101d55/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= +github.com/yggdrasil-network/water v0.0.0-20190725073841-250edb919f8a h1:mQ0mPD+dyB/vaDPyVkCBiXUQu9Or7/cRSTjPlV8tXvw= github.com/yggdrasil-network/water v0.0.0-20190725073841-250edb919f8a/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20181207154023-610586996380 h1:zPQexyRtNYBc7bcHmehl1dH6TB3qn8zytv8cBGLDNY0= golang.org/x/net v0.0.0-20181207154023-610586996380/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e h1:njOxP/wVblhCLIUhjHXf6X+dzTt5OQ3vMQo9mkOIKIo= golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI= golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= diff --git a/src/util/cancellation.go b/src/util/cancellation.go index 2a78c19d..af4721bb 100644 --- a/src/util/cancellation.go +++ b/src/util/cancellation.go @@ -13,33 +13,25 @@ type Cancellation interface { Error() error } +var CancellationFinalized = errors.New("finalizer called") +var CancellationTimeoutError = errors.New("timeout") + func CancellationFinalizer(c Cancellation) { - c.Cancel(errors.New("finalizer called")) + c.Cancel(CancellationFinalized) } type cancellation struct { - signal chan error cancel chan struct{} - errMtx sync.RWMutex + mutex sync.RWMutex err error -} - -func (c *cancellation) worker() { - // Launch this in a separate goroutine when creating a cancellation - err := <-c.signal - c.errMtx.Lock() - c.err = err - c.errMtx.Unlock() - close(c.cancel) + done bool } func NewCancellation() Cancellation { c := cancellation{ - signal: make(chan error), cancel: make(chan struct{}), } runtime.SetFinalizer(&c, CancellationFinalizer) - go c.worker() return &c } @@ -48,18 +40,22 @@ func (c *cancellation) Finished() <-chan struct{} { } func (c *cancellation) Cancel(err error) error { - select { - case c.signal <- err: + c.mutex.Lock() + defer c.mutex.Unlock() + if c.done { + return c.err + } else { + c.err = err + c.done = true + close(c.cancel) return nil - case <-c.cancel: - return c.Error() } } func (c *cancellation) Error() error { - c.errMtx.RLock() + c.mutex.RLock() err := c.err - c.errMtx.RUnlock() + c.mutex.RUnlock() return err } @@ -75,8 +71,6 @@ func CancellationChild(parent Cancellation) Cancellation { return child } -var CancellationTimeoutError = errors.New("timeout") - func CancellationWithTimeout(parent Cancellation, timeout time.Duration) Cancellation { child := CancellationChild(parent) go func() { diff --git a/src/util/util.go b/src/util/util.go index 94bd5d6a..4596474e 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -3,6 +3,7 @@ package util // These are misc. utility functions that didn't really fit anywhere else import "runtime" +import "sync" import "time" // A wrapper around runtime.Gosched() so it doesn't need to be imported elsewhere. @@ -21,29 +22,27 @@ func UnlockThread() { } // This is used to buffer recently used slices of bytes, to prevent allocations in the hot loops. -// It's used like a sync.Pool, but with a fixed size and typechecked without type casts to/from interface{} (which were making the profiles look ugly). -var byteStore chan []byte +var byteStoreMutex sync.Mutex +var byteStore [][]byte -func init() { - byteStore = make(chan []byte, 32) -} - -// Gets an empty slice from the byte store, if one is available, or else returns a new nil slice. +// Gets an empty slice from the byte store. func GetBytes() []byte { - select { - case bs := <-byteStore: - return bs[:0] - default: + byteStoreMutex.Lock() + defer byteStoreMutex.Unlock() + if len(byteStore) > 0 { + var bs []byte + bs, byteStore = byteStore[len(byteStore)-1][:0], byteStore[:len(byteStore)-1] + return bs + } else { return nil } } -// Puts a slice in the store, if there's room, or else returns and lets the slice get collected. +// Puts a slice in the store. func PutBytes(bs []byte) { - select { - case byteStore <- bs: - default: - } + byteStoreMutex.Lock() + defer byteStoreMutex.Unlock() + byteStore = append(byteStore, bs) } // This is a workaround to go's broken timer implementation diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 1bec9836..2bad5a0c 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -230,7 +230,7 @@ func (c *Core) GetSessions() []Session { skip = true } }() - sinfo.doWorker(workerFunc) + sinfo.doFunc(workerFunc) }() if skip { continue diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index bc884fb3..ee9cbb30 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -145,9 +145,9 @@ func (c *Conn) Read(b []byte) (int, error) { } defer util.PutBytes(p.Payload) var err error - done := make(chan struct{}) + //done := make(chan struct{}) workerFunc := func() { - defer close(done) + //defer close(done) // If the nonce is bad then drop the packet and return an error if !sinfo.nonceIsOK(&p.Nonce) { err = ConnError{errors.New("packet dropped due to invalid nonce"), false, true, false, 0} @@ -167,33 +167,36 @@ func (c *Conn) Read(b []byte) (int, error) { sinfo.time = time.Now() sinfo.bytesRecvd += uint64(len(bs)) } - // Hand over to the session worker - defer func() { - if recover() != nil { - err = ConnError{errors.New("read failed, session already closed"), false, false, true, 0} - close(done) + sinfo.doFunc(workerFunc) + /* + // Hand over to the session worker + defer func() { + if recover() != nil { + err = ConnError{errors.New("read failed, session already closed"), false, false, true, 0} + close(done) + } + }() // In case we're racing with a close + // Send to worker + select { + case sinfo.worker <- workerFunc: + case <-cancel.Finished(): + if cancel.Error() == util.CancellationTimeoutError { + return 0, ConnError{errors.New("read timeout"), true, false, false, 0} + } else { + return 0, ConnError{errors.New("session closed"), false, false, true, 0} + } } - }() // In case we're racing with a close - // Send to worker - select { - case sinfo.worker <- workerFunc: - case <-cancel.Finished(): - if cancel.Error() == util.CancellationTimeoutError { - return 0, ConnError{errors.New("read timeout"), true, false, false, 0} - } else { - return 0, ConnError{errors.New("session closed"), false, false, true, 0} + // Wait for the worker to finish + select { + case <-done: // Wait for the worker to finish, failing this can cause memory errors (util.[Get||Put]Bytes stuff) + case <-cancel.Finished(): + if cancel.Error() == util.CancellationTimeoutError { + return 0, ConnError{errors.New("read timeout"), true, false, false, 0} + } else { + return 0, ConnError{errors.New("session closed"), false, false, true, 0} + } } - } - // Wait for the worker to finish - select { - case <-done: // Wait for the worker to finish, failing this can cause memory errors (util.[Get||Put]Bytes stuff) - case <-cancel.Finished(): - if cancel.Error() == util.CancellationTimeoutError { - return 0, ConnError{errors.New("read timeout"), true, false, false, 0} - } else { - return 0, ConnError{errors.New("session closed"), false, false, true, 0} - } - } + */ // Something went wrong in the session worker so abort if err != nil { if ce, ok := err.(*ConnError); ok && ce.Temporary() { @@ -214,10 +217,10 @@ func (c *Conn) Read(b []byte) (int, error) { func (c *Conn) Write(b []byte) (bytesWritten int, err error) { sinfo := c.session var packet []byte - done := make(chan struct{}) + //done := make(chan struct{}) written := len(b) workerFunc := func() { - defer close(done) + //defer close(done) // Does the packet exceed the permitted size for the session? if uint16(len(b)) > sinfo.getMTU() { written, err = 0, ConnError{errors.New("packet too big"), true, false, false, int(sinfo.getMTU())} @@ -264,27 +267,30 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { default: // Don't do anything, to keep traffic throttled } } - // Set up a timer so this doesn't block forever - cancel := c.getDeadlineCancellation(&c.writeDeadline) - defer cancel.Cancel(nil) - // Hand over to the session worker - defer func() { - if recover() != nil { - err = ConnError{errors.New("write failed, session already closed"), false, false, true, 0} - close(done) + sinfo.doFunc(workerFunc) + /* + // Set up a timer so this doesn't block forever + cancel := c.getDeadlineCancellation(&c.writeDeadline) + defer cancel.Cancel(nil) + // Hand over to the session worker + defer func() { + if recover() != nil { + err = ConnError{errors.New("write failed, session already closed"), false, false, true, 0} + close(done) + } + }() // In case we're racing with a close + select { // Send to worker + case sinfo.worker <- workerFunc: + case <-cancel.Finished(): + if cancel.Error() == util.CancellationTimeoutError { + return 0, ConnError{errors.New("write timeout"), true, false, false, 0} + } else { + return 0, ConnError{errors.New("session closed"), false, false, true, 0} + } } - }() // In case we're racing with a close - select { // Send to worker - case sinfo.worker <- workerFunc: - case <-cancel.Finished(): - if cancel.Error() == util.CancellationTimeoutError { - return 0, ConnError{errors.New("write timeout"), true, false, false, 0} - } else { - return 0, ConnError{errors.New("session closed"), false, false, true, 0} - } - } - // Wait for the worker to finish, otherwise there are memory errors ([Get||Put]Bytes stuff) - <-done + // Wait for the worker to finish, otherwise there are memory errors ([Get||Put]Bytes stuff) + <-done + */ // Give the packet to the router if written > 0 { sinfo.core.router.out(packet) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 53836c38..eca3bb00 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -16,6 +16,7 @@ import ( // All the information we know about an active session. // This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API. type sessionInfo struct { + mutex sync.Mutex // Protects all of the below, use it any time you read/chance the contents of a session core *Core // reconfigure chan chan error // theirAddr address.Address // @@ -43,24 +44,14 @@ type sessionInfo struct { tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation bytesSent uint64 // Bytes of real traffic sent in this session bytesRecvd uint64 // Bytes of real traffic received in this session - worker chan func() // Channel to send work to the session worker recv chan *wire_trafficPacket // Received packets go here, picked up by the associated Conn init chan struct{} // Closed when the first session pong arrives, used to signal that the session is ready for initial use } -func (sinfo *sessionInfo) doWorker(f func()) { - done := make(chan struct{}) - sinfo.worker <- func() { - f() - close(done) - } - <-done -} - -func (sinfo *sessionInfo) workerMain() { - for f := range sinfo.worker { - f() - } +func (sinfo *sessionInfo) doFunc(f func()) { + sinfo.mutex.Lock() + defer sinfo.mutex.Unlock() + f() } // Represents a session ping/pong packet, andincludes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU. @@ -231,11 +222,9 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.myHandle = *crypto.NewHandle() sinfo.theirAddr = *address.AddrForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) - sinfo.worker = make(chan func(), 1) sinfo.recv = make(chan *wire_trafficPacket, 32) ss.sinfos[sinfo.myHandle] = &sinfo ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle - go sinfo.workerMain() return &sinfo } @@ -267,14 +256,12 @@ func (ss *sessions) cleanup() { ss.lastCleanup = time.Now() } -// Closes a session, removing it from sessions maps and killing the worker goroutine. +// Closes a session, removing it from sessions maps. func (sinfo *sessionInfo) close() { if s := sinfo.core.sessions.sinfos[sinfo.myHandle]; s == sinfo { delete(sinfo.core.sessions.sinfos, sinfo.myHandle) delete(sinfo.core.sessions.byTheirPerm, sinfo.theirPermPub) } - defer func() { recover() }() - close(sinfo.worker) } // Returns a session ping appropriate for the given session info. @@ -372,7 +359,7 @@ func (ss *sessions) handlePing(ping *sessionPing) { } ss.listenerMutex.Unlock() } - sinfo.doWorker(func() { + sinfo.doFunc(func() { // Update the session if !sinfo.update(ping) { /*panic("Should not happen in testing")*/ return @@ -426,7 +413,7 @@ func (sinfo *sessionInfo) updateNonce(theirNonce *crypto.BoxNonce) { // Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change. func (ss *sessions) reset() { for _, sinfo := range ss.sinfos { - sinfo.doWorker(func() { + sinfo.doFunc(func() { sinfo.reset = true }) } From 9e118884d48d924b396c81943a2ef3f9aafe8891 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 27 Jul 2019 18:12:06 -0500 Subject: [PATCH 0163/1109] remove some commented code --- src/yggdrasil/conn.go | 56 ------------------------------------------- 1 file changed, 56 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index ee9cbb30..e04108f6 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -145,9 +145,7 @@ func (c *Conn) Read(b []byte) (int, error) { } defer util.PutBytes(p.Payload) var err error - //done := make(chan struct{}) workerFunc := func() { - //defer close(done) // If the nonce is bad then drop the packet and return an error if !sinfo.nonceIsOK(&p.Nonce) { err = ConnError{errors.New("packet dropped due to invalid nonce"), false, true, false, 0} @@ -168,35 +166,6 @@ func (c *Conn) Read(b []byte) (int, error) { sinfo.bytesRecvd += uint64(len(bs)) } sinfo.doFunc(workerFunc) - /* - // Hand over to the session worker - defer func() { - if recover() != nil { - err = ConnError{errors.New("read failed, session already closed"), false, false, true, 0} - close(done) - } - }() // In case we're racing with a close - // Send to worker - select { - case sinfo.worker <- workerFunc: - case <-cancel.Finished(): - if cancel.Error() == util.CancellationTimeoutError { - return 0, ConnError{errors.New("read timeout"), true, false, false, 0} - } else { - return 0, ConnError{errors.New("session closed"), false, false, true, 0} - } - } - // Wait for the worker to finish - select { - case <-done: // Wait for the worker to finish, failing this can cause memory errors (util.[Get||Put]Bytes stuff) - case <-cancel.Finished(): - if cancel.Error() == util.CancellationTimeoutError { - return 0, ConnError{errors.New("read timeout"), true, false, false, 0} - } else { - return 0, ConnError{errors.New("session closed"), false, false, true, 0} - } - } - */ // Something went wrong in the session worker so abort if err != nil { if ce, ok := err.(*ConnError); ok && ce.Temporary() { @@ -217,10 +186,8 @@ func (c *Conn) Read(b []byte) (int, error) { func (c *Conn) Write(b []byte) (bytesWritten int, err error) { sinfo := c.session var packet []byte - //done := make(chan struct{}) written := len(b) workerFunc := func() { - //defer close(done) // Does the packet exceed the permitted size for the session? if uint16(len(b)) > sinfo.getMTU() { written, err = 0, ConnError{errors.New("packet too big"), true, false, false, int(sinfo.getMTU())} @@ -268,29 +235,6 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { } } sinfo.doFunc(workerFunc) - /* - // Set up a timer so this doesn't block forever - cancel := c.getDeadlineCancellation(&c.writeDeadline) - defer cancel.Cancel(nil) - // Hand over to the session worker - defer func() { - if recover() != nil { - err = ConnError{errors.New("write failed, session already closed"), false, false, true, 0} - close(done) - } - }() // In case we're racing with a close - select { // Send to worker - case sinfo.worker <- workerFunc: - case <-cancel.Finished(): - if cancel.Error() == util.CancellationTimeoutError { - return 0, ConnError{errors.New("write timeout"), true, false, false, 0} - } else { - return 0, ConnError{errors.New("session closed"), false, false, true, 0} - } - } - // Wait for the worker to finish, otherwise there are memory errors ([Get||Put]Bytes stuff) - <-done - */ // Give the packet to the router if written > 0 { sinfo.core.router.out(packet) From b66bea813b05e49c441593fb4c57e23b526d7b01 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 27 Jul 2019 18:23:55 -0500 Subject: [PATCH 0164/1109] rename a couple of things and move a PutBytes so it happens sooner --- src/yggdrasil/conn.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index e04108f6..30519f6f 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -143,9 +143,9 @@ func (c *Conn) Read(b []byte) (int, error) { if !ok { return 0, ConnError{errors.New("session closed"), false, false, true, 0} } - defer util.PutBytes(p.Payload) var err error - workerFunc := func() { + sessionFunc := func() { + defer util.PutBytes(p.Payload) // If the nonce is bad then drop the packet and return an error if !sinfo.nonceIsOK(&p.Nonce) { err = ConnError{errors.New("packet dropped due to invalid nonce"), false, true, false, 0} @@ -165,7 +165,7 @@ func (c *Conn) Read(b []byte) (int, error) { sinfo.time = time.Now() sinfo.bytesRecvd += uint64(len(bs)) } - sinfo.doFunc(workerFunc) + sinfo.doFunc(sessionFunc) // Something went wrong in the session worker so abort if err != nil { if ce, ok := err.(*ConnError); ok && ce.Temporary() { @@ -187,7 +187,7 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { sinfo := c.session var packet []byte written := len(b) - workerFunc := func() { + sessionFunc := func() { // Does the packet exceed the permitted size for the session? if uint16(len(b)) > sinfo.getMTU() { written, err = 0, ConnError{errors.New("packet too big"), true, false, false, int(sinfo.getMTU())} @@ -234,7 +234,7 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { default: // Don't do anything, to keep traffic throttled } } - sinfo.doFunc(workerFunc) + sinfo.doFunc(sessionFunc) // Give the packet to the router if written > 0 { sinfo.core.router.out(packet) From 38e1503b28562a04865807f2bf11dd98c0c75954 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 27 Jul 2019 20:09:43 -0500 Subject: [PATCH 0165/1109] split up some of the tun reader logic into a separate worker, so the main loop can be mostly just syscalls --- src/tuntap/iface.go | 313 ++++++++++++++++++++++---------------------- 1 file changed, 160 insertions(+), 153 deletions(-) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 637715d6..00cf2dfd 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -112,6 +112,164 @@ func (tun *TunAdapter) writer() error { func (tun *TunAdapter) reader() error { recvd := make([]byte, 65535+tun_ETHER_HEADER_LENGTH) + toWorker := make(chan []byte, 32) + defer close(toWorker) + go func() { + for bs := range toWorker { + // If we detect an ICMP packet then hand it to the ICMPv6 module + if bs[6] == 58 { + // Found an ICMPv6 packet - we need to make sure to give ICMPv6 the full + // Ethernet frame rather than just the IPv6 packet as this is needed for + // NDP to work correctly + if err := tun.icmpv6.ParsePacket(recvd); err == nil { + // We acted on the packet in the ICMPv6 module so don't forward or do + // anything else with it + continue + } + } + // From the IP header, work out what our source and destination addresses + // and node IDs are. We will need these in order to work out where to send + // the packet + var srcAddr address.Address + var dstAddr address.Address + var dstNodeID *crypto.NodeID + var dstNodeIDMask *crypto.NodeID + var dstSnet address.Subnet + var addrlen int + n := len(bs) + // Check the IP protocol - if it doesn't match then we drop the packet and + // do nothing with it + if bs[0]&0xf0 == 0x60 { + // Check if we have a fully-sized IPv6 header + if len(bs) < 40 { + continue + } + // Check the packet size + if n-tun_IPv6_HEADER_LENGTH != 256*int(bs[4])+int(bs[5]) { + continue + } + // IPv6 address + addrlen = 16 + copy(srcAddr[:addrlen], bs[8:]) + copy(dstAddr[:addrlen], bs[24:]) + copy(dstSnet[:addrlen/2], bs[24:]) + } else if bs[0]&0xf0 == 0x40 { + // Check if we have a fully-sized IPv4 header + if len(bs) < 20 { + continue + } + // Check the packet size + if n != 256*int(bs[2])+int(bs[3]) { + continue + } + // IPv4 address + addrlen = 4 + copy(srcAddr[:addrlen], bs[12:]) + copy(dstAddr[:addrlen], bs[16:]) + } else { + // Unknown address length or protocol, so drop the packet and ignore it + tun.log.Traceln("Unknown packet type, dropping") + continue + } + if tun.ckr.isEnabled() && !tun.ckr.isValidSource(srcAddr, addrlen) { + // The packet had a source address that doesn't belong to us or our + // configured crypto-key routing source subnets + continue + } + if !dstAddr.IsValid() && !dstSnet.IsValid() { + if key, err := tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { + // A public key was found, get the node ID for the search + dstNodeID = crypto.GetNodeID(&key) + // Do a quick check to ensure that the node ID refers to a vaild + // Yggdrasil address or subnet - this might be superfluous + addr := *address.AddrForNodeID(dstNodeID) + copy(dstAddr[:], addr[:]) + copy(dstSnet[:], addr[:]) + // Are we certain we looked up a valid node? + if !dstAddr.IsValid() && !dstSnet.IsValid() { + continue + } + } else { + // No public key was found in the CKR table so we've exhausted our options + continue + } + } + // Do we have an active connection for this node address? + tun.mutex.RLock() + session, isIn := tun.addrToConn[dstAddr] + if !isIn || session == nil { + session, isIn = tun.subnetToConn[dstSnet] + if !isIn || session == nil { + // Neither an address nor a subnet mapping matched, therefore populate + // the node ID and mask to commence a search + if dstAddr.IsValid() { + dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() + } else { + dstNodeID, dstNodeIDMask = dstSnet.GetNodeIDandMask() + } + } + } + tun.mutex.RUnlock() + // If we don't have a connection then we should open one + if !isIn || session == nil { + // Check we haven't been given empty node ID, really this shouldn't ever + // happen but just to be sure... + if dstNodeID == nil || dstNodeIDMask == nil { + panic("Given empty dstNodeID and dstNodeIDMask - this shouldn't happen") + } + // Dial to the remote node + packet := bs + go func() { + // FIXME just spitting out a goroutine to do this is kind of ugly and means we drop packets until the dial finishes + tun.mutex.Lock() + _, known := tun.dials[*dstNodeID] + tun.dials[*dstNodeID] = append(tun.dials[*dstNodeID], packet) + for len(tun.dials[*dstNodeID]) > 32 { + util.PutBytes(tun.dials[*dstNodeID][0]) + tun.dials[*dstNodeID] = tun.dials[*dstNodeID][1:] + } + tun.mutex.Unlock() + if known { + return + } + var tc *tunConn + if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { + // We've been given a connection so prepare the session wrapper + if tc, err = tun.wrap(conn); err != nil { + // Something went wrong when storing the connection, typically that + // something already exists for this address or subnet + tun.log.Debugln("TUN/TAP iface wrap:", err) + } + } + tun.mutex.Lock() + packets := tun.dials[*dstNodeID] + delete(tun.dials, *dstNodeID) + tun.mutex.Unlock() + if tc != nil { + for _, packet := range packets { + select { + case tc.send <- packet: + default: + util.PutBytes(packet) + } + } + } + }() + // While the dial is going on we can't do much else + // continuing this iteration - skip to the next one + continue + } + // If we have a connection now, try writing to it + if isIn && session != nil { + packet := bs + select { + case session.send <- packet: + default: + util.PutBytes(packet) + } + } + } + }() for { // Wait for a packet to be delivered to us through the TUN/TAP adapter n, err := tun.iface.Read(recvd) @@ -137,158 +295,7 @@ func (tun *TunAdapter) reader() error { } // Offset the buffer from now on so that we can ignore ethernet frames if // they are present - bs := recvd[offset : offset+n] - n -= offset - // If we detect an ICMP packet then hand it to the ICMPv6 module - if bs[6] == 58 { - // Found an ICMPv6 packet - we need to make sure to give ICMPv6 the full - // Ethernet frame rather than just the IPv6 packet as this is needed for - // NDP to work correctly - if err := tun.icmpv6.ParsePacket(recvd); err == nil { - // We acted on the packet in the ICMPv6 module so don't forward or do - // anything else with it - continue - } - } - // From the IP header, work out what our source and destination addresses - // and node IDs are. We will need these in order to work out where to send - // the packet - var srcAddr address.Address - var dstAddr address.Address - var dstNodeID *crypto.NodeID - var dstNodeIDMask *crypto.NodeID - var dstSnet address.Subnet - var addrlen int - // Check the IP protocol - if it doesn't match then we drop the packet and - // do nothing with it - if bs[0]&0xf0 == 0x60 { - // Check if we have a fully-sized IPv6 header - if len(bs) < 40 { - continue - } - // Check the packet size - if n-tun_IPv6_HEADER_LENGTH != 256*int(bs[4])+int(bs[5]) { - continue - } - // IPv6 address - addrlen = 16 - copy(srcAddr[:addrlen], bs[8:]) - copy(dstAddr[:addrlen], bs[24:]) - copy(dstSnet[:addrlen/2], bs[24:]) - } else if bs[0]&0xf0 == 0x40 { - // Check if we have a fully-sized IPv4 header - if len(bs) < 20 { - continue - } - // Check the packet size - if n != 256*int(bs[2])+int(bs[3]) { - continue - } - // IPv4 address - addrlen = 4 - copy(srcAddr[:addrlen], bs[12:]) - copy(dstAddr[:addrlen], bs[16:]) - } else { - // Unknown address length or protocol, so drop the packet and ignore it - tun.log.Traceln("Unknown packet type, dropping") - continue - } - if tun.ckr.isEnabled() && !tun.ckr.isValidSource(srcAddr, addrlen) { - // The packet had a source address that doesn't belong to us or our - // configured crypto-key routing source subnets - continue - } - if !dstAddr.IsValid() && !dstSnet.IsValid() { - if key, err := tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { - // A public key was found, get the node ID for the search - dstNodeID = crypto.GetNodeID(&key) - // Do a quick check to ensure that the node ID refers to a vaild - // Yggdrasil address or subnet - this might be superfluous - addr := *address.AddrForNodeID(dstNodeID) - copy(dstAddr[:], addr[:]) - copy(dstSnet[:], addr[:]) - // Are we certain we looked up a valid node? - if !dstAddr.IsValid() && !dstSnet.IsValid() { - continue - } - } else { - // No public key was found in the CKR table so we've exhausted our options - continue - } - } - // Do we have an active connection for this node address? - tun.mutex.RLock() - session, isIn := tun.addrToConn[dstAddr] - if !isIn || session == nil { - session, isIn = tun.subnetToConn[dstSnet] - if !isIn || session == nil { - // Neither an address nor a subnet mapping matched, therefore populate - // the node ID and mask to commence a search - if dstAddr.IsValid() { - dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() - } else { - dstNodeID, dstNodeIDMask = dstSnet.GetNodeIDandMask() - } - } - } - tun.mutex.RUnlock() - // If we don't have a connection then we should open one - if !isIn || session == nil { - // Check we haven't been given empty node ID, really this shouldn't ever - // happen but just to be sure... - if dstNodeID == nil || dstNodeIDMask == nil { - panic("Given empty dstNodeID and dstNodeIDMask - this shouldn't happen") - } - // Dial to the remote node - packet := append(util.GetBytes(), bs[:n]...) - go func() { - // FIXME just spitting out a goroutine to do this is kind of ugly and means we drop packets until the dial finishes - tun.mutex.Lock() - _, known := tun.dials[*dstNodeID] - tun.dials[*dstNodeID] = append(tun.dials[*dstNodeID], packet) - for len(tun.dials[*dstNodeID]) > 32 { - util.PutBytes(tun.dials[*dstNodeID][0]) - tun.dials[*dstNodeID] = tun.dials[*dstNodeID][1:] - } - tun.mutex.Unlock() - if known { - return - } - var tc *tunConn - if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { - // We've been given a connection so prepare the session wrapper - if tc, err = tun.wrap(conn); err != nil { - // Something went wrong when storing the connection, typically that - // something already exists for this address or subnet - tun.log.Debugln("TUN/TAP iface wrap:", err) - } - } - tun.mutex.Lock() - packets := tun.dials[*dstNodeID] - delete(tun.dials, *dstNodeID) - tun.mutex.Unlock() - if tc != nil { - for _, packet := range packets { - select { - case tc.send <- packet: - default: - util.PutBytes(packet) - } - } - } - }() - // While the dial is going on we can't do much else - // continuing this iteration - skip to the next one - continue - } - // If we have a connection now, try writing to it - if isIn && session != nil { - packet := append(util.GetBytes(), bs[:n]...) - select { - case session.send <- packet: - default: - util.PutBytes(packet) - } - } + bs := append(util.GetBytes(), recvd[offset:offset+n]...) + toWorker <- bs } } From 24f4754f2b145ca0d5c7eb5a1ec50ee91470e56f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 28 Jul 2019 11:30:24 +0100 Subject: [PATCH 0166/1109] Export NodeInfoPayload type, rename some API functions --- src/admin/admin.go | 5 +++-- src/yggdrasil/api.go | 36 +++++++++++++----------------------- src/yggdrasil/nodeinfo.go | 22 ++++++++++------------ src/yggdrasil/wire.go | 2 +- 4 files changed, 27 insertions(+), 38 deletions(-) diff --git a/src/admin/admin.go b/src/admin/admin.go index 7d6f16b6..2b73764c 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -85,14 +85,15 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. */ a.AddHandler("getSelf", []string{}, func(in Info) (Info, error) { ip := c.Address().String() + subnet := c.Subnet() return Info{ "self": Info{ ip: Info{ - "box_pub_key": c.BoxPubKey(), + "box_pub_key": c.EncryptionPublicKey(), "build_name": yggdrasil.BuildName(), "build_version": yggdrasil.BuildVersion(), "coords": fmt.Sprintf("%v", c.Coords()), - "subnet": c.Subnet().String(), + "subnet": subnet.String(), }, }, }, nil diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index bd0d0983..f42a2083 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -59,7 +59,7 @@ type DHTRes struct { } // NodeInfoPayload represents a RequestNodeInfo response, in bytes. -type NodeInfoPayload nodeinfoPayload +type NodeInfoPayload []byte // SwitchQueues represents information from the switch related to link // congestion and a list of switch queues created in response to congestion on a @@ -301,12 +301,12 @@ func (c *Core) TreeID() *crypto.TreeID { } // SigPubKey gets the node's signing public key. -func (c *Core) SigPubKey() string { +func (c *Core) SigningPublicKey() string { return hex.EncodeToString(c.sigPub[:]) } // BoxPubKey gets the node's encryption public key. -func (c *Core) BoxPubKey() string { +func (c *Core) EncryptionPublicKey() string { return hex.EncodeToString(c.boxPub[:]) } @@ -318,21 +318,21 @@ func (c *Core) Coords() []byte { // Address gets the IPv6 address of the Yggdrasil node. This is always a /128 // address. -func (c *Core) Address() *net.IP { +func (c *Core) Address() net.IP { address := net.IP(address.AddrForNodeID(c.NodeID())[:]) - return &address + return address } // Subnet gets the routed IPv6 subnet of the Yggdrasil node. This is always a // /64 subnet. -func (c *Core) Subnet() *net.IPNet { +func (c *Core) Subnet() net.IPNet { subnet := address.SubnetForNodeID(c.NodeID())[:] subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0) - return &net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)} + return net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)} } -// NodeInfo gets the currently configured nodeinfo. -func (c *Core) MyNodeInfo() nodeinfoPayload { +// MyNodeInfo gets the currently configured nodeinfo. +func (c *Core) MyNodeInfo() NodeInfoPayload { return c.router.nodeinfo.getNodeInfo() } @@ -355,7 +355,7 @@ func (c *Core) GetNodeInfo(keyString, coordString string, nocache bool) (NodeInf } if !nocache { if response, err := c.router.nodeinfo.getCachedNodeInfo(key); err == nil { - return NodeInfoPayload(response), nil + return response, nil } } var coords []byte @@ -370,9 +370,9 @@ func (c *Core) GetNodeInfo(keyString, coordString string, nocache bool) (NodeInf coords = append(coords, uint8(u64)) } } - response := make(chan *nodeinfoPayload, 1) + response := make(chan *NodeInfoPayload, 1) sendNodeInfoRequest := func() { - c.router.nodeinfo.addCallback(key, func(nodeinfo *nodeinfoPayload) { + c.router.nodeinfo.addCallback(key, func(nodeinfo *NodeInfoPayload) { defer func() { recover() }() select { case response <- nodeinfo: @@ -387,7 +387,7 @@ func (c *Core) GetNodeInfo(keyString, coordString string, nocache bool) (NodeInf close(response) }() for res := range response { - return NodeInfoPayload(*res), nil + return *res, nil } return NodeInfoPayload{}, errors.New(fmt.Sprintf("getNodeInfo timeout: %s", keyString)) } @@ -455,16 +455,6 @@ func (c *Core) DisconnectPeer(port uint64) error { return nil } -// RouterAddress returns the raw address as used by the router. -func (c *Core) RouterAddress() address.Address { - return c.router.addr -} - -// RouterSubnet returns the raw address as used by the router. -func (c *Core) RouterSubnet() address.Subnet { - return c.router.subnet -} - // GetAllowedEncryptionPublicKeys returns the public keys permitted for incoming // peer connections. func (c *Core) GetAllowedEncryptionPublicKeys() []string { diff --git a/src/yggdrasil/nodeinfo.go b/src/yggdrasil/nodeinfo.go index 89b8b89f..f1c7ed07 100644 --- a/src/yggdrasil/nodeinfo.go +++ b/src/yggdrasil/nodeinfo.go @@ -13,7 +13,7 @@ import ( type nodeinfo struct { core *Core - myNodeInfo nodeinfoPayload + myNodeInfo NodeInfoPayload myNodeInfoMutex sync.RWMutex callbacks map[crypto.BoxPubKey]nodeinfoCallback callbacksMutex sync.Mutex @@ -21,15 +21,13 @@ type nodeinfo struct { cacheMutex sync.RWMutex } -type nodeinfoPayload []byte - type nodeinfoCached struct { - payload nodeinfoPayload + payload NodeInfoPayload created time.Time } type nodeinfoCallback struct { - call func(nodeinfo *nodeinfoPayload) + call func(nodeinfo *NodeInfoPayload) created time.Time } @@ -38,7 +36,7 @@ type nodeinfoReqRes struct { SendPermPub crypto.BoxPubKey // Sender's permanent key SendCoords []byte // Sender's coords IsResponse bool - NodeInfo nodeinfoPayload + NodeInfo NodeInfoPayload } // Initialises the nodeinfo cache/callback maps, and starts a goroutine to keep @@ -70,7 +68,7 @@ func (m *nodeinfo) init(core *Core) { } // Add a callback for a nodeinfo lookup -func (m *nodeinfo) addCallback(sender crypto.BoxPubKey, call func(nodeinfo *nodeinfoPayload)) { +func (m *nodeinfo) addCallback(sender crypto.BoxPubKey, call func(nodeinfo *NodeInfoPayload)) { m.callbacksMutex.Lock() defer m.callbacksMutex.Unlock() m.callbacks[sender] = nodeinfoCallback{ @@ -80,7 +78,7 @@ func (m *nodeinfo) addCallback(sender crypto.BoxPubKey, call func(nodeinfo *node } // Handles the callback, if there is one -func (m *nodeinfo) callback(sender crypto.BoxPubKey, nodeinfo nodeinfoPayload) { +func (m *nodeinfo) callback(sender crypto.BoxPubKey, nodeinfo NodeInfoPayload) { m.callbacksMutex.Lock() defer m.callbacksMutex.Unlock() if callback, ok := m.callbacks[sender]; ok { @@ -90,7 +88,7 @@ func (m *nodeinfo) callback(sender crypto.BoxPubKey, nodeinfo nodeinfoPayload) { } // Get the current node's nodeinfo -func (m *nodeinfo) getNodeInfo() nodeinfoPayload { +func (m *nodeinfo) getNodeInfo() NodeInfoPayload { m.myNodeInfoMutex.RLock() defer m.myNodeInfoMutex.RUnlock() return m.myNodeInfo @@ -135,7 +133,7 @@ func (m *nodeinfo) setNodeInfo(given interface{}, privacy bool) error { } // Add nodeinfo into the cache for a node -func (m *nodeinfo) addCachedNodeInfo(key crypto.BoxPubKey, payload nodeinfoPayload) { +func (m *nodeinfo) addCachedNodeInfo(key crypto.BoxPubKey, payload NodeInfoPayload) { m.cacheMutex.Lock() defer m.cacheMutex.Unlock() m.cache[key] = nodeinfoCached{ @@ -145,13 +143,13 @@ func (m *nodeinfo) addCachedNodeInfo(key crypto.BoxPubKey, payload nodeinfoPaylo } // Get a nodeinfo entry from the cache -func (m *nodeinfo) getCachedNodeInfo(key crypto.BoxPubKey) (nodeinfoPayload, error) { +func (m *nodeinfo) getCachedNodeInfo(key crypto.BoxPubKey) (NodeInfoPayload, error) { m.cacheMutex.RLock() defer m.cacheMutex.RUnlock() if nodeinfo, ok := m.cache[key]; ok { return nodeinfo.payload, nil } - return nodeinfoPayload{}, errors.New("No cache entry found") + return NodeInfoPayload{}, errors.New("No cache entry found") } // Handles a nodeinfo request/response - called from the router diff --git a/src/yggdrasil/wire.go b/src/yggdrasil/wire.go index 1bb4d90f..5aa354dc 100644 --- a/src/yggdrasil/wire.go +++ b/src/yggdrasil/wire.go @@ -394,7 +394,7 @@ func (p *nodeinfoReqRes) decode(bs []byte) bool { if len(bs) == 0 { return false } - p.NodeInfo = make(nodeinfoPayload, len(bs)) + p.NodeInfo = make(NodeInfoPayload, len(bs)) if !wire_chop_slice(p.NodeInfo[:], &bs) { return false } From c9554f82be40c6a1a991b953ad9879efef660d6f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 28 Jul 2019 11:35:16 +0100 Subject: [PATCH 0167/1109] Formatting tweaks in api.go --- src/yggdrasil/api.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index f42a2083..bf64324a 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -261,7 +261,7 @@ func BuildVersion() string { return buildVersion } -// ListenConn returns a listener for Yggdrasil session connections. +// ConnListen returns a listener for Yggdrasil session connections. func (c *Core) ConnListen() (*Listener, error) { c.sessions.listenerMutex.Lock() defer c.sessions.listenerMutex.Unlock() @@ -300,12 +300,12 @@ func (c *Core) TreeID() *crypto.TreeID { return crypto.GetTreeID(&c.sigPub) } -// SigPubKey gets the node's signing public key. +// SigningPublicKey gets the node's signing public key. func (c *Core) SigningPublicKey() string { return hex.EncodeToString(c.sigPub[:]) } -// BoxPubKey gets the node's encryption public key. +// EncryptionPublicKey gets the node's encryption public key. func (c *Core) EncryptionPublicKey() string { return hex.EncodeToString(c.boxPub[:]) } @@ -389,7 +389,7 @@ func (c *Core) GetNodeInfo(keyString, coordString string, nocache bool) (NodeInf for res := range response { return *res, nil } - return NodeInfoPayload{}, errors.New(fmt.Sprintf("getNodeInfo timeout: %s", keyString)) + return NodeInfoPayload{}, fmt.Errorf("getNodeInfo timeout: %s", keyString) } // SetSessionGatekeeper allows you to configure a handler function for deciding @@ -475,7 +475,8 @@ func (c *Core) RemoveAllowedEncryptionPublicKey(bstr string) (err error) { return nil } -// Send a DHT ping to the node with the provided key and coords, optionally looking up the specified target NodeID. +// DHTPing sends a DHT ping to the node with the provided key and coords, +// optionally looking up the specified target NodeID. func (c *Core) DHTPing(keyString, coordString, targetString string) (DHTRes, error) { var key crypto.BoxPubKey if keyBytes, err := hex.DecodeString(keyString); err != nil { @@ -535,5 +536,5 @@ func (c *Core) DHTPing(keyString, coordString, targetString string) (DHTRes, err } return r, nil } - return DHTRes{}, errors.New(fmt.Sprintf("DHT ping timeout: %s", keyString)) + return DHTRes{}, fmt.Errorf("DHT ping timeout: %s", keyString) } From cbc8711dd3022d6204e7f2e59e2c49857769429d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 28 Jul 2019 13:39:29 +0100 Subject: [PATCH 0168/1109] Remove mobile module, since it can now be moved into another repository --- src/mobile/awdl.go | 108 ------------------------- src/mobile/mobile.go | 147 ----------------------------------- src/mobile/mobile_android.go | 12 --- src/mobile/mobile_ios.go | 61 --------------- 4 files changed, 328 deletions(-) delete mode 100644 src/mobile/awdl.go delete mode 100644 src/mobile/mobile.go delete mode 100644 src/mobile/mobile_android.go delete mode 100644 src/mobile/mobile_ios.go diff --git a/src/mobile/awdl.go b/src/mobile/awdl.go deleted file mode 100644 index 9a073ef7..00000000 --- a/src/mobile/awdl.go +++ /dev/null @@ -1,108 +0,0 @@ -package mobile - -/* -import ( - "errors" - "io" - "sync" -) - -type awdl struct { - link *link - reconfigure chan chan error - mutex sync.RWMutex // protects interfaces below - interfaces map[string]*awdlInterface -} - -type awdlInterface struct { - linkif *linkInterface - rwc awdlReadWriteCloser - peer *peer - stream stream -} - -type awdlReadWriteCloser struct { - fromAWDL chan []byte - toAWDL chan []byte -} - -func (c awdlReadWriteCloser) Read(p []byte) (n int, err error) { - if packet, ok := <-c.fromAWDL; ok { - n = copy(p, packet) - return n, nil - } - return 0, io.EOF -} - -func (c awdlReadWriteCloser) Write(p []byte) (n int, err error) { - var pc []byte - pc = append(pc, p...) - c.toAWDL <- pc - return len(pc), nil -} - -func (c awdlReadWriteCloser) Close() error { - close(c.fromAWDL) - close(c.toAWDL) - return nil -} - -func (a *awdl) init(l *link) error { - a.link = l - a.mutex.Lock() - a.interfaces = make(map[string]*awdlInterface) - a.reconfigure = make(chan chan error, 1) - a.mutex.Unlock() - - go func() { - for e := range a.reconfigure { - e <- nil - } - }() - - return nil -} - -func (a *awdl) create(name, local, remote string, incoming bool) (*awdlInterface, error) { - rwc := awdlReadWriteCloser{ - fromAWDL: make(chan []byte, 1), - toAWDL: make(chan []byte, 1), - } - s := stream{} - s.init(rwc) - linkif, err := a.link.create(&s, name, "awdl", local, remote, incoming, true) - if err != nil { - return nil, err - } - intf := awdlInterface{ - linkif: linkif, - rwc: rwc, - } - a.mutex.Lock() - a.interfaces[name] = &intf - a.mutex.Unlock() - go intf.linkif.handler() - return &intf, nil -} - -func (a *awdl) getInterface(identity string) *awdlInterface { - a.mutex.RLock() - defer a.mutex.RUnlock() - if intf, ok := a.interfaces[identity]; ok { - return intf - } - return nil -} - -func (a *awdl) shutdown(identity string) error { - if intf, ok := a.interfaces[identity]; ok { - close(intf.linkif.closed) - intf.rwc.Close() - a.mutex.Lock() - delete(a.interfaces, identity) - a.mutex.Unlock() - return nil - } - return errors.New("Interface not found or already closed") -} -*/ diff --git a/src/mobile/mobile.go b/src/mobile/mobile.go deleted file mode 100644 index 08a8c1ac..00000000 --- a/src/mobile/mobile.go +++ /dev/null @@ -1,147 +0,0 @@ -package mobile - -import ( - "encoding/json" - "os" - "time" - - "github.com/gologme/log" - - hjson "github.com/hjson/hjson-go" - "github.com/mitchellh/mapstructure" - "github.com/yggdrasil-network/yggdrasil-go/src/config" - "github.com/yggdrasil-network/yggdrasil-go/src/multicast" - "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" -) - -// Yggdrasil mobile package is meant to "plug the gap" for mobile support, as -// Gomobile will not create headers for Swift/Obj-C etc if they have complex -// (non-native) types. Therefore for iOS we will expose some nice simple -// functions. Note that in the case of iOS we handle reading/writing to/from TUN -// in Swift therefore we use the "dummy" TUN interface instead. -type Yggdrasil struct { - core yggdrasil.Core - multicast multicast.Multicast - log MobileLogger -} - -func (m *Yggdrasil) addStaticPeers(cfg *config.NodeConfig) { - if len(cfg.Peers) == 0 && len(cfg.InterfacePeers) == 0 { - return - } - for { - for _, peer := range cfg.Peers { - m.core.AddPeer(peer, "") - time.Sleep(time.Second) - } - for intf, intfpeers := range cfg.InterfacePeers { - for _, peer := range intfpeers { - m.core.AddPeer(peer, intf) - time.Sleep(time.Second) - } - } - time.Sleep(time.Minute) - } -} - -// StartAutoconfigure starts a node with a randomly generated config -func (m *Yggdrasil) StartAutoconfigure() error { - logger := log.New(m.log, "", 0) - logger.EnableLevel("error") - logger.EnableLevel("warn") - logger.EnableLevel("info") - nc := config.GenerateConfig() - nc.IfName = "dummy" - nc.AdminListen = "tcp://localhost:9001" - nc.Peers = []string{} - if hostname, err := os.Hostname(); err == nil { - nc.NodeInfo = map[string]interface{}{"name": hostname} - } - /*if err := m.core.SetRouterAdapter(m); err != nil { - logger.Errorln("An error occured setting router adapter:", err) - return err - }*/ - state, err := m.core.Start(nc, logger) - if err != nil { - logger.Errorln("An error occured starting Yggdrasil:", err) - return err - } - m.multicast.Init(&m.core, state, logger, nil) - if err := m.multicast.Start(); err != nil { - logger.Errorln("An error occurred starting multicast:", err) - } - go m.addStaticPeers(nc) - return nil -} - -// StartJSON starts a node with the given JSON config. You can get JSON config -// (rather than HJSON) by using the GenerateConfigJSON() function -func (m *Yggdrasil) StartJSON(configjson []byte) error { - logger := log.New(m.log, "", 0) - logger.EnableLevel("error") - logger.EnableLevel("warn") - logger.EnableLevel("info") - nc := config.GenerateConfig() - var dat map[string]interface{} - if err := hjson.Unmarshal(configjson, &dat); err != nil { - return err - } - if err := mapstructure.Decode(dat, &nc); err != nil { - return err - } - nc.IfName = "dummy" - /*if err := m.core.SetRouterAdapter(m); err != nil { - logger.Errorln("An error occured setting router adapter:", err) - return err - }*/ - state, err := m.core.Start(nc, logger) - if err != nil { - logger.Errorln("An error occured starting Yggdrasil:", err) - return err - } - m.multicast.Init(&m.core, state, logger, nil) - if err := m.multicast.Start(); err != nil { - logger.Errorln("An error occurred starting multicast:", err) - } - go m.addStaticPeers(nc) - return nil -} - -// Stop the mobile Yggdrasil instance -func (m *Yggdrasil) Stop() error { - m.core.Stop() - if err := m.Stop(); err != nil { - return err - } - return nil -} - -// GenerateConfigJSON generates mobile-friendly configuration in JSON format -func GenerateConfigJSON() []byte { - nc := config.GenerateConfig() - nc.IfName = "dummy" - if json, err := json.Marshal(nc); err == nil { - return json - } - return nil -} - -// GetAddressString gets the node's IPv6 address -func (m *Yggdrasil) GetAddressString() string { - return m.core.Address().String() -} - -// GetSubnetString gets the node's IPv6 subnet in CIDR notation -func (m *Yggdrasil) GetSubnetString() string { - return m.core.Subnet().String() -} - -// GetBoxPubKeyString gets the node's public encryption key -func (m *Yggdrasil) GetBoxPubKeyString() string { - return m.core.BoxPubKey() -} - -// GetSigPubKeyString gets the node's public signing key -func (m *Yggdrasil) GetSigPubKeyString() string { - return m.core.SigPubKey() -} diff --git a/src/mobile/mobile_android.go b/src/mobile/mobile_android.go deleted file mode 100644 index f3206aca..00000000 --- a/src/mobile/mobile_android.go +++ /dev/null @@ -1,12 +0,0 @@ -// +build android - -package mobile - -import "log" - -type MobileLogger struct{} - -func (nsl MobileLogger) Write(p []byte) (n int, err error) { - log.Println(string(p)) - return len(p), nil -} diff --git a/src/mobile/mobile_ios.go b/src/mobile/mobile_ios.go deleted file mode 100644 index 26b219cc..00000000 --- a/src/mobile/mobile_ios.go +++ /dev/null @@ -1,61 +0,0 @@ -// +build darwin - -package mobile - -/* -#cgo CFLAGS: -x objective-c -#cgo LDFLAGS: -framework Foundation -#import -void Log(const char *text) { - NSString *nss = [NSString stringWithUTF8String:text]; - NSLog(@"%@", nss); -} -*/ -import "C" -import ( - "unsafe" -) - -type MobileLogger struct { -} - -func (nsl MobileLogger) Write(p []byte) (n int, err error) { - p = append(p, 0) - cstr := (*C.char)(unsafe.Pointer(&p[0])) - C.Log(cstr) - return len(p), nil -} - -/* -func (c *Core) AWDLCreateInterface(name, local, remote string, incoming bool) error { - if intf, err := c.link.awdl.create(name, local, remote, incoming); err != nil || intf == nil { - c.log.Println("c.link.awdl.create:", err) - return err - } - return nil -} - -func (c *Core) AWDLShutdownInterface(name string) error { - return c.link.awdl.shutdown(name) -} - -func (c *Core) AWDLRecvPacket(identity string) ([]byte, error) { - if intf := c.link.awdl.getInterface(identity); intf != nil { - read, ok := <-intf.rwc.toAWDL - if !ok { - return nil, errors.New("AWDLRecvPacket: channel closed") - } - return read, nil - } - return nil, errors.New("AWDLRecvPacket identity not known: " + identity) -} - -func (c *Core) AWDLSendPacket(identity string, buf []byte) error { - packet := append(util.GetBytes(), buf[:]...) - if intf := c.link.awdl.getInterface(identity); intf != nil { - intf.rwc.fromAWDL <- packet - return nil - } - return errors.New("AWDLSendPacket identity not known: " + identity) -} -*/ From bb4abf575b9f4487b6d072ef0f69f56979cf888e Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 28 Jul 2019 13:51:22 +0100 Subject: [PATCH 0169/1109] Fix build -i --- build | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build b/build index bad287fa..383b91a0 100755 --- a/build +++ b/build @@ -31,14 +31,14 @@ if [ $IOS ]; then echo "Building framework for iOS" gomobile bind -target ios -tags mobile -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \ github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil \ - github.com/yggdrasil-network/yggdrasil-go/src/mobile \ - github.com/yggdrasil-network/yggdrasil-go/src/config + github.com/yggdrasil-network/yggdrasil-go/src/config \ + github.com/yggdrasil-network/yggdrasil-extras/src/mobile elif [ $ANDROID ]; then echo "Building aar for Android" gomobile bind -target android -tags mobile -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \ github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil \ - github.com/yggdrasil-network/yggdrasil-go/src/mobile \ - github.com/yggdrasil-network/yggdrasil-go/src/config + github.com/yggdrasil-network/yggdrasil-go/src/config \ + github.com/yggdrasil-network/yggdrasil-extras/src/mobile else for CMD in `ls cmd/` ; do echo "Building: $CMD" From 406e143f7ff8cafb36d19daf5a155487f83b71cb Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 28 Jul 2019 23:33:04 -0500 Subject: [PATCH 0170/1109] move some logic from TunAdapter.reader into a new function, TunAdapter.readerPacketHandler --- src/tuntap/iface.go | 351 ++++++++++++++++++++++---------------------- 1 file changed, 178 insertions(+), 173 deletions(-) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 00cf2dfd..a95dfae4 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -110,178 +110,10 @@ func (tun *TunAdapter) writer() error { } } -func (tun *TunAdapter) reader() error { - recvd := make([]byte, 65535+tun_ETHER_HEADER_LENGTH) - toWorker := make(chan []byte, 32) - defer close(toWorker) - go func() { - for bs := range toWorker { - // If we detect an ICMP packet then hand it to the ICMPv6 module - if bs[6] == 58 { - // Found an ICMPv6 packet - we need to make sure to give ICMPv6 the full - // Ethernet frame rather than just the IPv6 packet as this is needed for - // NDP to work correctly - if err := tun.icmpv6.ParsePacket(recvd); err == nil { - // We acted on the packet in the ICMPv6 module so don't forward or do - // anything else with it - continue - } - } - // From the IP header, work out what our source and destination addresses - // and node IDs are. We will need these in order to work out where to send - // the packet - var srcAddr address.Address - var dstAddr address.Address - var dstNodeID *crypto.NodeID - var dstNodeIDMask *crypto.NodeID - var dstSnet address.Subnet - var addrlen int - n := len(bs) - // Check the IP protocol - if it doesn't match then we drop the packet and - // do nothing with it - if bs[0]&0xf0 == 0x60 { - // Check if we have a fully-sized IPv6 header - if len(bs) < 40 { - continue - } - // Check the packet size - if n-tun_IPv6_HEADER_LENGTH != 256*int(bs[4])+int(bs[5]) { - continue - } - // IPv6 address - addrlen = 16 - copy(srcAddr[:addrlen], bs[8:]) - copy(dstAddr[:addrlen], bs[24:]) - copy(dstSnet[:addrlen/2], bs[24:]) - } else if bs[0]&0xf0 == 0x40 { - // Check if we have a fully-sized IPv4 header - if len(bs) < 20 { - continue - } - // Check the packet size - if n != 256*int(bs[2])+int(bs[3]) { - continue - } - // IPv4 address - addrlen = 4 - copy(srcAddr[:addrlen], bs[12:]) - copy(dstAddr[:addrlen], bs[16:]) - } else { - // Unknown address length or protocol, so drop the packet and ignore it - tun.log.Traceln("Unknown packet type, dropping") - continue - } - if tun.ckr.isEnabled() && !tun.ckr.isValidSource(srcAddr, addrlen) { - // The packet had a source address that doesn't belong to us or our - // configured crypto-key routing source subnets - continue - } - if !dstAddr.IsValid() && !dstSnet.IsValid() { - if key, err := tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { - // A public key was found, get the node ID for the search - dstNodeID = crypto.GetNodeID(&key) - // Do a quick check to ensure that the node ID refers to a vaild - // Yggdrasil address or subnet - this might be superfluous - addr := *address.AddrForNodeID(dstNodeID) - copy(dstAddr[:], addr[:]) - copy(dstSnet[:], addr[:]) - // Are we certain we looked up a valid node? - if !dstAddr.IsValid() && !dstSnet.IsValid() { - continue - } - } else { - // No public key was found in the CKR table so we've exhausted our options - continue - } - } - // Do we have an active connection for this node address? - tun.mutex.RLock() - session, isIn := tun.addrToConn[dstAddr] - if !isIn || session == nil { - session, isIn = tun.subnetToConn[dstSnet] - if !isIn || session == nil { - // Neither an address nor a subnet mapping matched, therefore populate - // the node ID and mask to commence a search - if dstAddr.IsValid() { - dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() - } else { - dstNodeID, dstNodeIDMask = dstSnet.GetNodeIDandMask() - } - } - } - tun.mutex.RUnlock() - // If we don't have a connection then we should open one - if !isIn || session == nil { - // Check we haven't been given empty node ID, really this shouldn't ever - // happen but just to be sure... - if dstNodeID == nil || dstNodeIDMask == nil { - panic("Given empty dstNodeID and dstNodeIDMask - this shouldn't happen") - } - // Dial to the remote node - packet := bs - go func() { - // FIXME just spitting out a goroutine to do this is kind of ugly and means we drop packets until the dial finishes - tun.mutex.Lock() - _, known := tun.dials[*dstNodeID] - tun.dials[*dstNodeID] = append(tun.dials[*dstNodeID], packet) - for len(tun.dials[*dstNodeID]) > 32 { - util.PutBytes(tun.dials[*dstNodeID][0]) - tun.dials[*dstNodeID] = tun.dials[*dstNodeID][1:] - } - tun.mutex.Unlock() - if known { - return - } - var tc *tunConn - if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { - // We've been given a connection so prepare the session wrapper - if tc, err = tun.wrap(conn); err != nil { - // Something went wrong when storing the connection, typically that - // something already exists for this address or subnet - tun.log.Debugln("TUN/TAP iface wrap:", err) - } - } - tun.mutex.Lock() - packets := tun.dials[*dstNodeID] - delete(tun.dials, *dstNodeID) - tun.mutex.Unlock() - if tc != nil { - for _, packet := range packets { - select { - case tc.send <- packet: - default: - util.PutBytes(packet) - } - } - } - }() - // While the dial is going on we can't do much else - // continuing this iteration - skip to the next one - continue - } - // If we have a connection now, try writing to it - if isIn && session != nil { - packet := bs - select { - case session.send <- packet: - default: - util.PutBytes(packet) - } - } - } - }() - for { - // Wait for a packet to be delivered to us through the TUN/TAP adapter - n, err := tun.iface.Read(recvd) - if err != nil { - if !tun.isOpen { - return err - } - panic(err) - } - if n == 0 { - continue - } +// Run in a separate goroutine by the reader +// Does all of the per-packet ICMP checks, passes packets to the right Conn worker +func (tun *TunAdapter) readerPacketHandler(ch chan []byte) { + for recvd := range ch { // If it's a TAP adapter, update the buffer slice so that we no longer // include the ethernet headers offset := 0 @@ -295,7 +127,180 @@ func (tun *TunAdapter) reader() error { } // Offset the buffer from now on so that we can ignore ethernet frames if // they are present - bs := append(util.GetBytes(), recvd[offset:offset+n]...) + bs := recvd[offset:] + // If we detect an ICMP packet then hand it to the ICMPv6 module + if bs[6] == 58 { + // Found an ICMPv6 packet - we need to make sure to give ICMPv6 the full + // Ethernet frame rather than just the IPv6 packet as this is needed for + // NDP to work correctly + if err := tun.icmpv6.ParsePacket(recvd); err == nil { + // We acted on the packet in the ICMPv6 module so don't forward or do + // anything else with it + continue + } + } + // Shift forward to avoid leaking bytes off the front of the slide when we eventually store it + bs = append(recvd[:0], bs...) + // From the IP header, work out what our source and destination addresses + // and node IDs are. We will need these in order to work out where to send + // the packet + var srcAddr address.Address + var dstAddr address.Address + var dstNodeID *crypto.NodeID + var dstNodeIDMask *crypto.NodeID + var dstSnet address.Subnet + var addrlen int + n := len(bs) + // Check the IP protocol - if it doesn't match then we drop the packet and + // do nothing with it + if bs[0]&0xf0 == 0x60 { + // Check if we have a fully-sized IPv6 header + if len(bs) < 40 { + continue + } + // Check the packet size + if n-tun_IPv6_HEADER_LENGTH != 256*int(bs[4])+int(bs[5]) { + continue + } + // IPv6 address + addrlen = 16 + copy(srcAddr[:addrlen], bs[8:]) + copy(dstAddr[:addrlen], bs[24:]) + copy(dstSnet[:addrlen/2], bs[24:]) + } else if bs[0]&0xf0 == 0x40 { + // Check if we have a fully-sized IPv4 header + if len(bs) < 20 { + continue + } + // Check the packet size + if n != 256*int(bs[2])+int(bs[3]) { + continue + } + // IPv4 address + addrlen = 4 + copy(srcAddr[:addrlen], bs[12:]) + copy(dstAddr[:addrlen], bs[16:]) + } else { + // Unknown address length or protocol, so drop the packet and ignore it + tun.log.Traceln("Unknown packet type, dropping") + continue + } + if tun.ckr.isEnabled() && !tun.ckr.isValidSource(srcAddr, addrlen) { + // The packet had a source address that doesn't belong to us or our + // configured crypto-key routing source subnets + continue + } + if !dstAddr.IsValid() && !dstSnet.IsValid() { + if key, err := tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { + // A public key was found, get the node ID for the search + dstNodeID = crypto.GetNodeID(&key) + // Do a quick check to ensure that the node ID refers to a vaild + // Yggdrasil address or subnet - this might be superfluous + addr := *address.AddrForNodeID(dstNodeID) + copy(dstAddr[:], addr[:]) + copy(dstSnet[:], addr[:]) + // Are we certain we looked up a valid node? + if !dstAddr.IsValid() && !dstSnet.IsValid() { + continue + } + } else { + // No public key was found in the CKR table so we've exhausted our options + continue + } + } + // Do we have an active connection for this node address? + tun.mutex.RLock() + session, isIn := tun.addrToConn[dstAddr] + if !isIn || session == nil { + session, isIn = tun.subnetToConn[dstSnet] + if !isIn || session == nil { + // Neither an address nor a subnet mapping matched, therefore populate + // the node ID and mask to commence a search + if dstAddr.IsValid() { + dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() + } else { + dstNodeID, dstNodeIDMask = dstSnet.GetNodeIDandMask() + } + } + } + tun.mutex.RUnlock() + // If we don't have a connection then we should open one + if !isIn || session == nil { + // Check we haven't been given empty node ID, really this shouldn't ever + // happen but just to be sure... + if dstNodeID == nil || dstNodeIDMask == nil { + panic("Given empty dstNodeID and dstNodeIDMask - this shouldn't happen") + } + // Dial to the remote node + go func() { + // FIXME just spitting out a goroutine to do this is kind of ugly and means we drop packets until the dial finishes + tun.mutex.Lock() + _, known := tun.dials[*dstNodeID] + tun.dials[*dstNodeID] = append(tun.dials[*dstNodeID], bs) + for len(tun.dials[*dstNodeID]) > 32 { + util.PutBytes(tun.dials[*dstNodeID][0]) + tun.dials[*dstNodeID] = tun.dials[*dstNodeID][1:] + } + tun.mutex.Unlock() + if known { + return + } + var tc *tunConn + if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { + // We've been given a connection so prepare the session wrapper + if tc, err = tun.wrap(conn); err != nil { + // Something went wrong when storing the connection, typically that + // something already exists for this address or subnet + tun.log.Debugln("TUN/TAP iface wrap:", err) + } + } + tun.mutex.Lock() + packets := tun.dials[*dstNodeID] + delete(tun.dials, *dstNodeID) + tun.mutex.Unlock() + if tc != nil { + for _, packet := range packets { + select { + case tc.send <- packet: + default: + util.PutBytes(packet) + } + } + } + }() + // While the dial is going on we can't do much else + // continuing this iteration - skip to the next one + continue + } + // If we have a connection now, try writing to it + if isIn && session != nil { + select { + case session.send <- bs: + default: + util.PutBytes(bs) + } + } + } +} + +func (tun *TunAdapter) reader() error { + recvd := make([]byte, 65535+tun_ETHER_HEADER_LENGTH) + toWorker := make(chan []byte, 32) + defer close(toWorker) + go tun.readerPacketHandler(toWorker) + for { + // Wait for a packet to be delivered to us through the TUN/TAP adapter + n, err := tun.iface.Read(recvd) + if err != nil { + if !tun.isOpen { + return err + } + panic(err) + } + if n == 0 { + continue + } + bs := append(util.GetBytes(), recvd[:n]...) toWorker <- bs } } From 750a79eb094340d4939e1fe2f116ce8722598666 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 29 Jul 2019 23:45:47 +0100 Subject: [PATCH 0171/1109] Update build script --- build | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/build b/build index 383b91a0..a787965f 100755 --- a/build +++ b/build @@ -7,24 +7,26 @@ PKGNAME=${PKGNAME:-$(sh contrib/semver/name.sh)} PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)} LDFLAGS="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER" +ARGS="-v" -while getopts "udaitc:l:r" option +while getopts "uaitc:l:dro:" option do - case "${option}" + case "$option" in u) UPX=true;; - d) DEBUG=true;; i) IOS=true;; a) ANDROID=true;; t) TABLES=true;; c) GCFLAGS="$GCFLAGS $OPTARG";; l) LDFLAGS="$LDFLAGS $OPTARG";; - r) RACE="-race";; + d) ARGS="$ARGS -tags debug";; + r) ARGS="$ARGS -race";; + o) ARGS="$ARGS -o $OPTARG";; esac done if [ -z $TABLES ]; then - STRIP="-s -w" + LDFLAGS="$LDFLAGS -s -w" fi if [ $IOS ]; then @@ -42,12 +44,8 @@ elif [ $ANDROID ]; then else for CMD in `ls cmd/` ; do echo "Building: $CMD" + go build $ARGS -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" ./cmd/$CMD - if [ $DEBUG ]; then - go build $RACE -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" -tags debug -v ./cmd/$CMD - else - go build $RACE -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" -v ./cmd/$CMD - fi if [ $UPX ]; then upx --brute $CMD fi From cafa20074c0910224c56953a7aec072974efd446 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 29 Jul 2019 23:50:00 +0100 Subject: [PATCH 0172/1109] Don't strip debug builds --- build | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build b/build index a787965f..046296eb 100755 --- a/build +++ b/build @@ -26,7 +26,9 @@ do done if [ -z $TABLES ]; then - LDFLAGS="$LDFLAGS -s -w" + if [ "$ARGS" == "${ARGS/-tags debug/}" ]; then + LDFLAGS="$LDFLAGS -s -w" + fi fi if [ $IOS ]; then From b4d08f9273fd6ec6c13bc1e1377fb5315d6c2c85 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 30 Jul 2019 00:03:17 +0100 Subject: [PATCH 0173/1109] Try to be more POSIX-compliant --- build | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/build b/build index 046296eb..80a971f9 100755 --- a/build +++ b/build @@ -19,16 +19,14 @@ do t) TABLES=true;; c) GCFLAGS="$GCFLAGS $OPTARG";; l) LDFLAGS="$LDFLAGS $OPTARG";; - d) ARGS="$ARGS -tags debug";; + d) ARGS="$ARGS -tags debug" DEBUG=true;; r) ARGS="$ARGS -race";; o) ARGS="$ARGS -o $OPTARG";; esac done -if [ -z $TABLES ]; then - if [ "$ARGS" == "${ARGS/-tags debug/}" ]; then - LDFLAGS="$LDFLAGS -s -w" - fi +if [ -z $TABLES ] && [ -z $DEBUG ]; then + LDFLAGS="$LDFLAGS -s -w" fi if [ $IOS ]; then From a3099894bd542c0560f15d0e50b781fa72e4b286 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 30 Jul 2019 10:15:06 +0100 Subject: [PATCH 0174/1109] Update CHANGELOG.md --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5894598c..ee50dcc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,39 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> +## [0.3.6] - 2019-08-03 +### Added +- Yggdrasil now has a public API with interfaces such as `yggdrasil.ConnDialer`, `yggdrasil.ConnListener` and `yggdrasil.Conn` for using Yggdrasil as a transport directly within applications +- Session gatekeeper functions, part of the API, which can be used to control whether to allow or reject incoming or outgoing sessions dynamically (compared to the previous fixed whitelist/blacklist approach) +- Support for logging to files or syslog (where supported) +- Platform defaults now include the ability to set sane defaults for multicast interfaces + +### Changed +- Following a massive refactoring exercise, Yggdrasil's codebase has now been broken out into modules +- Core node functionality in the `yggdrasil` package with a public API + - This allows Yggdrasil to be integrated directly into other applications and used as a transport + - IP-specific code has now been moved out of the core `yggdrasil` package, making Yggdrasil effectively protocol-agnostic +- Multicast peer discovery functionality is now in the `multicast` package +- Admin socket functionality is now in the `admin` package and uses the Yggdrasil public API +- TUN/TAP, ICMPv6 and all IP-specific functionality is now in the `tuntap` package +- `PPROF` debug output is now sent to `stderr` instead of `stdout` +- Node IPv6 addresses on macOS are now configured as `secured` +- Upstream dependency references have been updated, which includes a number of fixes in the Water library + +### Fixed +- Multicast discovery is no longer disabled if the nominated interfaces aren't available on the system yet, e.g. during boot +- Multicast interfaces are now re-evaluated more frequently so that Yggdrasil doesn't need to be restarted to use interfaces that have become available since startup +- Admin socket error cases are now handled better +- Various fixes in the TUN/TAP module, particularly surrounding Windows platform support +- Invalid keys will now cause the node to fail to start, rather than starting but silently not working as before +- Session MTUs are now always calculated correctly, in some cases they were incorrectly defaulting to 1280 before +- Multiple searches now don't take place for a single connection +- Concurrency bugs fixed +- Fixed a number of bugs in the ICMPv6 neighbor solicitation in the TUN/TAP code +- A case where peers weren't always added correctly if one or more peers were unreachable has been fixed +- Searches which include the local node are now handled correctly +- Lots of small bug tweaks and clean-ups throughout the codebase + ## [0.3.5] - 2019-03-13 ### Fixed - The `AllowedEncryptionPublicKeys` option has now been fixed to handle incoming connections properly and no longer blocks outgoing connections (this was broken in v0.3.4) From e6bca895bc56683a2b0d008ed92ae3b0bed7f19d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 30 Jul 2019 11:52:30 +0100 Subject: [PATCH 0175/1109] Update go.mod/go.sum --- go.mod | 6 +++--- go.sum | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index dc2d5222..325f55c7 100644 --- a/go.mod +++ b/go.mod @@ -8,10 +8,10 @@ require ( github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/mitchellh/mapstructure v1.1.2 github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 - github.com/yggdrasil-network/water v0.0.0-20190725073841-250edb919f8a + github.com/yggdrasil-network/water v0.0.0-20190725123504-a16161896c34 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 - golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 + golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e golang.org/x/text v0.3.2 - golang.org/x/tools v0.0.0-20190724185037-8aa4eac1a7c1 // indirect + golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a // indirect ) diff --git a/go.sum b/go.sum index 050a3e5e..409913e0 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,7 @@ github.com/yggdrasil-network/water v0.0.0-20190720101301-5db94379a5eb/go.mod h1: github.com/yggdrasil-network/water v0.0.0-20190720145626-28ccb9101d55/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= github.com/yggdrasil-network/water v0.0.0-20190725073841-250edb919f8a h1:mQ0mPD+dyB/vaDPyVkCBiXUQu9Or7/cRSTjPlV8tXvw= github.com/yggdrasil-network/water v0.0.0-20190725073841-250edb919f8a/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= +github.com/yggdrasil-network/water v0.0.0-20190725123504-a16161896c34/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -39,6 +40,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI= golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= @@ -46,3 +48,4 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190724185037-8aa4eac1a7c1/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= From 68769efdc9be1e51d7c61913f068e99992986257 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 2 Aug 2019 20:05:15 +0100 Subject: [PATCH 0176/1109] Update go.mod/go.sum --- go.mod | 4 ++-- go.sum | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 325f55c7..84025df3 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/yggdrasil-network/water v0.0.0-20190725123504-a16161896c34 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 - golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e + golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 golang.org/x/text v0.3.2 - golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a // indirect + golang.org/x/tools v0.0.0-20190802003818-e9bb7d36c060 // indirect ) diff --git a/go.sum b/go.sum index 409913e0..2e81ccc3 100644 --- a/go.sum +++ b/go.sum @@ -41,6 +41,7 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI= golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= @@ -49,3 +50,4 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190724185037-8aa4eac1a7c1/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190802003818-e9bb7d36c060/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= From 1eabf88782bf25eb33d72743eef9664a3d96caa0 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 2 Aug 2019 20:48:07 -0500 Subject: [PATCH 0177/1109] more updates to go.sum --- go.sum | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.sum b/go.sum index 2e81ccc3..81d337e1 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,7 @@ github.com/yggdrasil-network/water v0.0.0-20190720101301-5db94379a5eb/go.mod h1: github.com/yggdrasil-network/water v0.0.0-20190720145626-28ccb9101d55/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= github.com/yggdrasil-network/water v0.0.0-20190725073841-250edb919f8a h1:mQ0mPD+dyB/vaDPyVkCBiXUQu9Or7/cRSTjPlV8tXvw= github.com/yggdrasil-network/water v0.0.0-20190725073841-250edb919f8a/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= +github.com/yggdrasil-network/water v0.0.0-20190725123504-a16161896c34 h1:Qh5FE+Q5iGqpmR/FPMYHuoZLN921au/nxAlmKe+Hdbo= github.com/yggdrasil-network/water v0.0.0-20190725123504-a16161896c34/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -41,6 +42,7 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI= golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From df0090e32a5262897fe35271788da98016abc5f1 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 3 Aug 2019 21:46:18 -0500 Subject: [PATCH 0178/1109] Add per-session read/write workers, work in progress, they still unfortunately need to take a mutex for safety --- src/yggdrasil/conn.go | 59 ++++-------------------- src/yggdrasil/dialer.go | 1 + src/yggdrasil/router.go | 2 +- src/yggdrasil/session.go | 98 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 106 insertions(+), 54 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index d1cb7609..e47ea2fb 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -137,7 +137,6 @@ func (c *Conn) Read(b []byte) (int, error) { sinfo := c.session cancel := c.getDeadlineCancellation(&c.readDeadline) defer cancel.Cancel(nil) - var bs []byte for { // Wait for some traffic to come through from the session select { @@ -147,54 +146,25 @@ func (c *Conn) Read(b []byte) (int, error) { } else { return 0, ConnError{errors.New("session closed"), false, false, true, 0} } - case p, ok := <-sinfo.recv: - // If the session is closed then do nothing - if !ok { - return 0, ConnError{errors.New("session closed"), false, false, true, 0} - } + case bs := <-sinfo.recv: var err error - sessionFunc := func() { - defer util.PutBytes(p.Payload) - // If the nonce is bad then drop the packet and return an error - if !sinfo.nonceIsOK(&p.Nonce) { - err = ConnError{errors.New("packet dropped due to invalid nonce"), false, true, false, 0} - return - } - // Decrypt the packet - var isOK bool - bs, isOK = crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce) - // Check if we were unable to decrypt the packet for some reason and - // return an error if we couldn't - if !isOK { - err = ConnError{errors.New("packet dropped due to decryption failure"), false, true, false, 0} - return - } - // Update the session - sinfo.updateNonce(&p.Nonce) - sinfo.time = time.Now() - sinfo.bytesRecvd += uint64(len(bs)) - } - sinfo.doFunc(sessionFunc) - // Something went wrong in the session worker so abort - if err != nil { - if ce, ok := err.(*ConnError); ok && ce.Temporary() { - continue - } - return 0, err + n := len(bs) + if len(bs) > len(b) { + n = len(b) + err = ConnError{errors.New("read buffer too small for entire packet"), false, true, false, 0} } // Copy results to the output slice and clean up copy(b, bs) util.PutBytes(bs) // If we've reached this point then everything went to plan, return the // number of bytes we populated back into the given slice - return len(bs), nil + return n, err } } } func (c *Conn) Write(b []byte) (bytesWritten int, err error) { sinfo := c.session - var packet []byte written := len(b) sessionFunc := func() { // Does the packet exceed the permitted size for the session? @@ -202,18 +172,6 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { written, err = 0, ConnError{errors.New("packet too big"), true, false, false, int(sinfo.getMTU())} return } - // Encrypt the packet - payload, nonce := crypto.BoxSeal(&sinfo.sharedSesKey, b, &sinfo.myNonce) - defer util.PutBytes(payload) - // Construct the wire packet to send to the router - p := wire_trafficPacket{ - Coords: sinfo.coords, - Handle: sinfo.theirHandle, - Nonce: *nonce, - Payload: payload, - } - packet = p.encode() - sinfo.bytesSent += uint64(len(b)) // The rest of this work is session keep-alive traffic doSearch := func() { routerWork := func() { @@ -244,11 +202,10 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { } } sinfo.doFunc(sessionFunc) - // Give the packet to the router if written > 0 { - sinfo.core.router.out(packet) + bs := append(util.GetBytes(), b...) + sinfo.send <- bs } - // Finally return the number of bytes we wrote return written, err } diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index 6b24cfb4..6ce2e8ac 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -69,6 +69,7 @@ func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (*Conn, er defer t.Stop() select { case <-conn.session.init: + conn.session.startWorkers(conn.cancel) return conn, nil case <-t.C: conn.Close() diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index c5e1dde0..77012863 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -166,7 +166,7 @@ func (r *router) handleTraffic(packet []byte) { return } select { - case sinfo.recv <- &p: // FIXME ideally this should be front drop + case sinfo.fromRouter <- &p: // FIXME ideally this should be front drop default: util.PutBytes(p.Payload) } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index eca3bb00..0552ec1b 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -6,11 +6,13 @@ package yggdrasil import ( "bytes" + "errors" "sync" "time" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/util" ) // All the information we know about an active session. @@ -44,8 +46,11 @@ type sessionInfo struct { tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation bytesSent uint64 // Bytes of real traffic sent in this session bytesRecvd uint64 // Bytes of real traffic received in this session - recv chan *wire_trafficPacket // Received packets go here, picked up by the associated Conn + fromRouter chan *wire_trafficPacket // Received packets go here, picked up by the associated Conn init chan struct{} // Closed when the first session pong arrives, used to signal that the session is ready for initial use + cancel util.Cancellation // Used to terminate workers + recv chan []byte + send chan []byte } func (sinfo *sessionInfo) doFunc(f func()) { @@ -222,7 +227,9 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.myHandle = *crypto.NewHandle() sinfo.theirAddr = *address.AddrForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) - sinfo.recv = make(chan *wire_trafficPacket, 32) + sinfo.fromRouter = make(chan *wire_trafficPacket, 32) + sinfo.recv = make(chan []byte, 32) + sinfo.send = make(chan []byte, 32) ss.sinfos[sinfo.myHandle] = &sinfo ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle return &sinfo @@ -355,6 +362,7 @@ func (ss *sessions) handlePing(ping *sessionPing) { for i := range conn.nodeMask { conn.nodeMask[i] = 0xFF } + conn.session.startWorkers(conn.cancel) ss.listener.conn <- conn } ss.listenerMutex.Unlock() @@ -418,3 +426,89 @@ func (ss *sessions) reset() { }) } } + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////// Worker Functions Below //////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +func (sinfo *sessionInfo) startWorkers(cancel util.Cancellation) { + sinfo.cancel = cancel + go sinfo.recvWorker() + go sinfo.sendWorker() +} + +func (sinfo *sessionInfo) recvWorker() { + // TODO move theirNonce etc into a struct that gets stored here, passed in over a channel + // Since there's no reason for anywhere else in the session code to need to *read* it... + // Only needs to be updated from the outside if a ping resets it... + // That would get rid of the need to take a mutex for the sessionFunc + for { + select { + case <-sinfo.cancel.Finished(): + return + case p := <-sinfo.fromRouter: + var bs []byte + var err error + sessionFunc := func() { + defer util.PutBytes(p.Payload) + // If the nonce is bad then drop the packet and return an error + if !sinfo.nonceIsOK(&p.Nonce) { + err = ConnError{errors.New("packet dropped due to invalid nonce"), false, true, false, 0} + return + } + // Decrypt the packet + var isOK bool + bs, isOK = crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce) + // Check if we were unable to decrypt the packet for some reason and + // return an error if we couldn't + if !isOK { + err = ConnError{errors.New("packet dropped due to decryption failure"), false, true, false, 0} + return + } + // Update the session + sinfo.updateNonce(&p.Nonce) + sinfo.time = time.Now() + sinfo.bytesRecvd += uint64(len(bs)) + } + sinfo.doFunc(sessionFunc) + if len(bs) > 0 { + if err != nil { + // Bad packet, drop it + util.PutBytes(bs) + } else { + // Pass the packet to the buffer for Conn.Read + sinfo.recv <- bs + } + } + } + } +} + +func (sinfo *sessionInfo) sendWorker() { + // TODO move info that this worker needs here, send updates via a channel + // Otherwise we need to take a mutex to avoid races with update() + for { + select { + case <-sinfo.cancel.Finished(): + return + case bs := <-sinfo.send: + // TODO + var packet []byte + sessionFunc := func() { + sinfo.bytesSent += uint64(len(bs)) + payload, nonce := crypto.BoxSeal(&sinfo.sharedSesKey, bs, &sinfo.myNonce) + defer util.PutBytes(payload) + // Construct the wire packet to send to the router + p := wire_trafficPacket{ + Coords: sinfo.coords, + Handle: sinfo.theirHandle, + Nonce: *nonce, + Payload: payload, + } + packet = p.encode() + } + sinfo.doFunc(sessionFunc) + sinfo.core.router.out(packet) + } + } +} From 5dfc71e1ee26cca6343efaa4eeaa0dafb09b8c1e Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 3 Aug 2019 22:00:47 -0500 Subject: [PATCH 0179/1109] put bytes back when done --- src/yggdrasil/session.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 0552ec1b..f60e81a2 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -495,6 +495,7 @@ func (sinfo *sessionInfo) sendWorker() { // TODO var packet []byte sessionFunc := func() { + defer util.PutBytes(bs) sinfo.bytesSent += uint64(len(bs)) payload, nonce := crypto.BoxSeal(&sinfo.sharedSesKey, bs, &sinfo.myNonce) defer util.PutBytes(payload) From 72ed541bf3cec1f8e0b9a23bcbd845b8f479749e Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 3 Aug 2019 22:07:38 -0500 Subject: [PATCH 0180/1109] a little cleanup to Conn functions --- src/yggdrasil/conn.go | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index e47ea2fb..f72bdd2f 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -82,7 +82,7 @@ func (c *Conn) String() string { return fmt.Sprintf("conn=%p", c) } -// This should never be called from the router goroutine +// This should never be called from the router goroutine, used in the dial functions func (c *Conn) search() error { var sinfo *searchInfo var isIn bool @@ -122,6 +122,23 @@ func (c *Conn) search() error { return nil } +// Used in session keep-alive traffic in Conn.Write +func (c *Conn) doSearch() { + routerWork := func() { + // Check to see if there is a search already matching the destination + sinfo, isIn := c.core.searches.searches[*c.nodeID] + if !isIn { + // Nothing was found, so create a new search + searchCompleted := func(sinfo *sessionInfo, e error) {} + sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) + c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo) + } + // Continue the search + sinfo.continueSearch() + } + go func() { c.core.router.admin <- routerWork }() +} + func (c *Conn) getDeadlineCancellation(value *atomic.Value) util.Cancellation { if deadline, ok := value.Load().(time.Time); ok { // A deadline is set, so return a Cancellation that uses it @@ -173,26 +190,11 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { return } // The rest of this work is session keep-alive traffic - doSearch := func() { - routerWork := func() { - // Check to see if there is a search already matching the destination - sinfo, isIn := c.core.searches.searches[*c.nodeID] - if !isIn { - // Nothing was found, so create a new search - searchCompleted := func(sinfo *sessionInfo, e error) {} - sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) - c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo) - } - // Continue the search - sinfo.continueSearch() - } - go func() { c.core.router.admin <- routerWork }() - } switch { case time.Since(sinfo.time) > 6*time.Second: if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second { // TODO double check that the above condition is correct - doSearch() + c.doSearch() } else { sinfo.core.sessions.ping(sinfo) } From 099bd3ae1e00d14344964ce8199654331a89339e Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 3 Aug 2019 22:35:10 -0500 Subject: [PATCH 0181/1109] reduce part of sendWorker that needs to keep a mutex --- src/yggdrasil/session.go | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index f60e81a2..993dc52b 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -492,23 +492,27 @@ func (sinfo *sessionInfo) sendWorker() { case <-sinfo.cancel.Finished(): return case bs := <-sinfo.send: - // TODO - var packet []byte + var p wire_trafficPacket + var k crypto.BoxSharedKey sessionFunc := func() { - defer util.PutBytes(bs) sinfo.bytesSent += uint64(len(bs)) - payload, nonce := crypto.BoxSeal(&sinfo.sharedSesKey, bs, &sinfo.myNonce) - defer util.PutBytes(payload) - // Construct the wire packet to send to the router - p := wire_trafficPacket{ - Coords: sinfo.coords, - Handle: sinfo.theirHandle, - Nonce: *nonce, - Payload: payload, + p = wire_trafficPacket{ + Coords: append([]byte(nil), sinfo.coords...), + Handle: sinfo.theirHandle, + Nonce: sinfo.myNonce, } - packet = p.encode() + sinfo.myNonce.Increment() + k = sinfo.sharedSesKey } + // Get the mutex-protected info needed to encrypt the packet sinfo.doFunc(sessionFunc) + // Encrypt the packet + p.Payload, _ = crypto.BoxSeal(&k, bs, &p.Nonce) + packet := p.encode() + // Cleanup + util.PutBytes(bs) + util.PutBytes(p.Payload) + // Send the packet sinfo.core.router.out(packet) } } From b9987b4fdc92d3d19eaa9fec83a88681b2ef359b Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 3 Aug 2019 22:47:10 -0500 Subject: [PATCH 0182/1109] reduce time spent with a mutex held in sessionInfo.recvWorker --- src/yggdrasil/session.go | 42 +++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 993dc52b..a48f44b1 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -449,36 +449,42 @@ func (sinfo *sessionInfo) recvWorker() { case p := <-sinfo.fromRouter: var bs []byte var err error + var k crypto.BoxSharedKey sessionFunc := func() { - defer util.PutBytes(p.Payload) - // If the nonce is bad then drop the packet and return an error if !sinfo.nonceIsOK(&p.Nonce) { err = ConnError{errors.New("packet dropped due to invalid nonce"), false, true, false, 0} return } - // Decrypt the packet - var isOK bool - bs, isOK = crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce) - // Check if we were unable to decrypt the packet for some reason and - // return an error if we couldn't - if !isOK { - err = ConnError{errors.New("packet dropped due to decryption failure"), false, true, false, 0} + k = sinfo.sharedSesKey + } + sinfo.doFunc(sessionFunc) + if err != nil { + util.PutBytes(p.Payload) + continue + } + var isOK bool + bs, isOK = crypto.BoxOpen(&k, p.Payload, &p.Nonce) + if !isOK { + util.PutBytes(bs) + continue + } + sessionFunc = func() { + if k != sinfo.sharedSesKey || !sinfo.nonceIsOK(&p.Nonce) { + // The session updated in the mean time, so return an error + err = ConnError{errors.New("session updated during crypto operation"), false, true, false, 0} return } - // Update the session sinfo.updateNonce(&p.Nonce) sinfo.time = time.Now() sinfo.bytesRecvd += uint64(len(bs)) } sinfo.doFunc(sessionFunc) - if len(bs) > 0 { - if err != nil { - // Bad packet, drop it - util.PutBytes(bs) - } else { - // Pass the packet to the buffer for Conn.Read - sinfo.recv <- bs - } + if err != nil { + // Not sure what else to do with this packet, I guess just drop it + util.PutBytes(bs) + } else { + // Pass the packet to the buffer for Conn.Read + sinfo.recv <- bs } } } From 7a9ad0c8ccbe0cdd4210dada7b52c54c04428714 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 3 Aug 2019 23:10:37 -0500 Subject: [PATCH 0183/1109] add workerpool to util --- src/util/workerpool.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/util/workerpool.go diff --git a/src/util/workerpool.go b/src/util/workerpool.go new file mode 100644 index 00000000..fd37f397 --- /dev/null +++ b/src/util/workerpool.go @@ -0,0 +1,29 @@ +package util + +import "runtime" + +var workerPool chan func() + +func init() { + maxProcs := runtime.GOMAXPROCS(0) + if maxProcs < 1 { + maxProcs = 1 + } + workerPool = make(chan func(), maxProcs) + for idx := 0; idx < maxProcs; idx++ { + go func() { + for f := range workerPool { + f() + } + }() + } +} + +// WorkerGo submits a job to a pool of GOMAXPROCS worker goroutines. +// This is meant for short non-blocking functions f() where you could just go f(), +// but you want some kind of backpressure to prevent spawning endless goroutines. +// WorkerGo returns as soon as the function is queued to run, not when it finishes. +// In Yggdrasil, these workers are used for certain cryptographic operations. +func WorkerGo(f func()) { + workerPool <- f +} From befd1b43a03cc9fdb8d53ef60e413fce4ccb0b33 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 3 Aug 2019 23:14:51 -0500 Subject: [PATCH 0184/1109] refactor session worker code slightly --- src/yggdrasil/session.go | 128 ++++++++++++++++++++------------------- 1 file changed, 67 insertions(+), 61 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index a48f44b1..d39f129d 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -442,50 +442,53 @@ func (sinfo *sessionInfo) recvWorker() { // Since there's no reason for anywhere else in the session code to need to *read* it... // Only needs to be updated from the outside if a ping resets it... // That would get rid of the need to take a mutex for the sessionFunc + doRecv := func(p *wire_trafficPacket) { + var bs []byte + var err error + var k crypto.BoxSharedKey + sessionFunc := func() { + if !sinfo.nonceIsOK(&p.Nonce) { + err = ConnError{errors.New("packet dropped due to invalid nonce"), false, true, false, 0} + return + } + k = sinfo.sharedSesKey + } + sinfo.doFunc(sessionFunc) + if err != nil { + util.PutBytes(p.Payload) + return + } + var isOK bool + bs, isOK = crypto.BoxOpen(&k, p.Payload, &p.Nonce) + if !isOK { + util.PutBytes(bs) + return + } + sessionFunc = func() { + if k != sinfo.sharedSesKey || !sinfo.nonceIsOK(&p.Nonce) { + // The session updated in the mean time, so return an error + err = ConnError{errors.New("session updated during crypto operation"), false, true, false, 0} + return + } + sinfo.updateNonce(&p.Nonce) + sinfo.time = time.Now() + sinfo.bytesRecvd += uint64(len(bs)) + } + sinfo.doFunc(sessionFunc) + if err != nil { + // Not sure what else to do with this packet, I guess just drop it + util.PutBytes(bs) + } else { + // Pass the packet to the buffer for Conn.Read + sinfo.recv <- bs + } + } for { select { case <-sinfo.cancel.Finished(): return case p := <-sinfo.fromRouter: - var bs []byte - var err error - var k crypto.BoxSharedKey - sessionFunc := func() { - if !sinfo.nonceIsOK(&p.Nonce) { - err = ConnError{errors.New("packet dropped due to invalid nonce"), false, true, false, 0} - return - } - k = sinfo.sharedSesKey - } - sinfo.doFunc(sessionFunc) - if err != nil { - util.PutBytes(p.Payload) - continue - } - var isOK bool - bs, isOK = crypto.BoxOpen(&k, p.Payload, &p.Nonce) - if !isOK { - util.PutBytes(bs) - continue - } - sessionFunc = func() { - if k != sinfo.sharedSesKey || !sinfo.nonceIsOK(&p.Nonce) { - // The session updated in the mean time, so return an error - err = ConnError{errors.New("session updated during crypto operation"), false, true, false, 0} - return - } - sinfo.updateNonce(&p.Nonce) - sinfo.time = time.Now() - sinfo.bytesRecvd += uint64(len(bs)) - } - sinfo.doFunc(sessionFunc) - if err != nil { - // Not sure what else to do with this packet, I guess just drop it - util.PutBytes(bs) - } else { - // Pass the packet to the buffer for Conn.Read - sinfo.recv <- bs - } + doRecv(p) } } } @@ -493,33 +496,36 @@ func (sinfo *sessionInfo) recvWorker() { func (sinfo *sessionInfo) sendWorker() { // TODO move info that this worker needs here, send updates via a channel // Otherwise we need to take a mutex to avoid races with update() + doSend := func(bs []byte) { + var p wire_trafficPacket + var k crypto.BoxSharedKey + sessionFunc := func() { + sinfo.bytesSent += uint64(len(bs)) + p = wire_trafficPacket{ + Coords: append([]byte(nil), sinfo.coords...), + Handle: sinfo.theirHandle, + Nonce: sinfo.myNonce, + } + sinfo.myNonce.Increment() + k = sinfo.sharedSesKey + } + // Get the mutex-protected info needed to encrypt the packet + sinfo.doFunc(sessionFunc) + // Encrypt the packet + p.Payload, _ = crypto.BoxSeal(&k, bs, &p.Nonce) + packet := p.encode() + // Cleanup + util.PutBytes(bs) + util.PutBytes(p.Payload) + // Send the packet + sinfo.core.router.out(packet) + } for { select { case <-sinfo.cancel.Finished(): return case bs := <-sinfo.send: - var p wire_trafficPacket - var k crypto.BoxSharedKey - sessionFunc := func() { - sinfo.bytesSent += uint64(len(bs)) - p = wire_trafficPacket{ - Coords: append([]byte(nil), sinfo.coords...), - Handle: sinfo.theirHandle, - Nonce: sinfo.myNonce, - } - sinfo.myNonce.Increment() - k = sinfo.sharedSesKey - } - // Get the mutex-protected info needed to encrypt the packet - sinfo.doFunc(sessionFunc) - // Encrypt the packet - p.Payload, _ = crypto.BoxSeal(&k, bs, &p.Nonce) - packet := p.encode() - // Cleanup - util.PutBytes(bs) - util.PutBytes(p.Payload) - // Send the packet - sinfo.core.router.out(packet) + doSend(bs) } } } From 00e9c3dbd9ba9246b598ca3b8f340f9d6c909e51 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 3 Aug 2019 23:27:52 -0500 Subject: [PATCH 0185/1109] do session crypto work using the worker pool --- src/yggdrasil/session.go | 98 ++++++++++++++++++++++++++++------------ 1 file changed, 69 insertions(+), 29 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index d39f129d..ea0b1a12 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -442,6 +442,7 @@ func (sinfo *sessionInfo) recvWorker() { // Since there's no reason for anywhere else in the session code to need to *read* it... // Only needs to be updated from the outside if a ping resets it... // That would get rid of the need to take a mutex for the sessionFunc + var callbacks []chan func() doRecv := func(p *wire_trafficPacket) { var bs []byte var err error @@ -459,31 +460,51 @@ func (sinfo *sessionInfo) recvWorker() { return } var isOK bool - bs, isOK = crypto.BoxOpen(&k, p.Payload, &p.Nonce) - if !isOK { - util.PutBytes(bs) - return - } - sessionFunc = func() { - if k != sinfo.sharedSesKey || !sinfo.nonceIsOK(&p.Nonce) { - // The session updated in the mean time, so return an error - err = ConnError{errors.New("session updated during crypto operation"), false, true, false, 0} - return + ch := make(chan func(), 1) + poolFunc := func() { + bs, isOK = crypto.BoxOpen(&k, p.Payload, &p.Nonce) + callback := func() { + if !isOK { + util.PutBytes(bs) + return + } + sessionFunc = func() { + if k != sinfo.sharedSesKey || !sinfo.nonceIsOK(&p.Nonce) { + // The session updated in the mean time, so return an error + err = ConnError{errors.New("session updated during crypto operation"), false, true, false, 0} + return + } + sinfo.updateNonce(&p.Nonce) + sinfo.time = time.Now() + sinfo.bytesRecvd += uint64(len(bs)) + } + sinfo.doFunc(sessionFunc) + if err != nil { + // Not sure what else to do with this packet, I guess just drop it + util.PutBytes(bs) + } else { + // Pass the packet to the buffer for Conn.Read + sinfo.recv <- bs + } } - sinfo.updateNonce(&p.Nonce) - sinfo.time = time.Now() - sinfo.bytesRecvd += uint64(len(bs)) - } - sinfo.doFunc(sessionFunc) - if err != nil { - // Not sure what else to do with this packet, I guess just drop it - util.PutBytes(bs) - } else { - // Pass the packet to the buffer for Conn.Read - sinfo.recv <- bs + ch <- callback } + // Send to the worker and wait for it to finish + util.WorkerGo(poolFunc) + callbacks = append(callbacks, ch) } for { + for len(callbacks) > 0 { + select { + case f := <-callbacks[0]: + callbacks = callbacks[1:] + f() + case <-sinfo.cancel.Finished(): + return + case p := <-sinfo.fromRouter: + doRecv(p) + } + } select { case <-sinfo.cancel.Finished(): return @@ -496,6 +517,7 @@ func (sinfo *sessionInfo) recvWorker() { func (sinfo *sessionInfo) sendWorker() { // TODO move info that this worker needs here, send updates via a channel // Otherwise we need to take a mutex to avoid races with update() + var callbacks []chan func() doSend := func(bs []byte) { var p wire_trafficPacket var k crypto.BoxSharedKey @@ -511,16 +533,34 @@ func (sinfo *sessionInfo) sendWorker() { } // Get the mutex-protected info needed to encrypt the packet sinfo.doFunc(sessionFunc) - // Encrypt the packet - p.Payload, _ = crypto.BoxSeal(&k, bs, &p.Nonce) - packet := p.encode() - // Cleanup - util.PutBytes(bs) - util.PutBytes(p.Payload) - // Send the packet - sinfo.core.router.out(packet) + ch := make(chan func(), 1) + poolFunc := func() { + // Encrypt the packet + p.Payload, _ = crypto.BoxSeal(&k, bs, &p.Nonce) + packet := p.encode() + // Cleanup + util.PutBytes(bs) + util.PutBytes(p.Payload) + // The callback will send the packet + callback := func() { sinfo.core.router.out(packet) } + ch <- callback + } + // Send to the worker and wait for it to finish + util.WorkerGo(poolFunc) + callbacks = append(callbacks, ch) } for { + for len(callbacks) > 0 { + select { + case f := <-callbacks[0]: + callbacks = callbacks[1:] + f() + case <-sinfo.cancel.Finished(): + return + case bs := <-sinfo.send: + doSend(bs) + } + } select { case <-sinfo.cancel.Finished(): return From cbbb61b01978c811ef9c10f9598bee1c23343029 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 4 Aug 2019 00:00:41 -0500 Subject: [PATCH 0186/1109] fix another drain on the bytestore --- src/yggdrasil/session.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index ea0b1a12..161d8eda 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -463,6 +463,7 @@ func (sinfo *sessionInfo) recvWorker() { ch := make(chan func(), 1) poolFunc := func() { bs, isOK = crypto.BoxOpen(&k, p.Payload, &p.Nonce) + util.PutBytes(p.Payload) callback := func() { if !isOK { util.PutBytes(bs) From 144c823beea1163d5e91ce6c9ee97daf7e2b4382 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 4 Aug 2019 00:28:13 -0500 Subject: [PATCH 0187/1109] just use a sync.Pool as the bytestore to not overcomplicate things, the allocations from interface{} casting don't seem to actually hurt in practice right now --- src/util/util.go | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/util/util.go b/src/util/util.go index 4596474e..7f03ad05 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -22,27 +22,16 @@ func UnlockThread() { } // This is used to buffer recently used slices of bytes, to prevent allocations in the hot loops. -var byteStoreMutex sync.Mutex -var byteStore [][]byte +var byteStore = sync.Pool{New: func() interface{} { return []byte(nil) }} // Gets an empty slice from the byte store. func GetBytes() []byte { - byteStoreMutex.Lock() - defer byteStoreMutex.Unlock() - if len(byteStore) > 0 { - var bs []byte - bs, byteStore = byteStore[len(byteStore)-1][:0], byteStore[:len(byteStore)-1] - return bs - } else { - return nil - } + return byteStore.Get().([]byte)[:0] } // Puts a slice in the store. func PutBytes(bs []byte) { - byteStoreMutex.Lock() - defer byteStoreMutex.Unlock() - byteStore = append(byteStore, bs) + byteStore.Put(bs) } // This is a workaround to go's broken timer implementation From 6da5802ae56b0d348661763c5dc7b091d77b72d1 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 4 Aug 2019 02:08:47 -0500 Subject: [PATCH 0188/1109] don't block forever in Write if the session is cancelled, cleanup Conn.Read slightly --- src/yggdrasil/conn.go | 54 +++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index f72bdd2f..d00db5c5 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -154,29 +154,27 @@ func (c *Conn) Read(b []byte) (int, error) { sinfo := c.session cancel := c.getDeadlineCancellation(&c.readDeadline) defer cancel.Cancel(nil) - for { - // Wait for some traffic to come through from the session - select { - case <-cancel.Finished(): - if cancel.Error() == util.CancellationTimeoutError { - return 0, ConnError{errors.New("read timeout"), true, false, false, 0} - } else { - return 0, ConnError{errors.New("session closed"), false, false, true, 0} - } - case bs := <-sinfo.recv: - var err error - n := len(bs) - if len(bs) > len(b) { - n = len(b) - err = ConnError{errors.New("read buffer too small for entire packet"), false, true, false, 0} - } - // Copy results to the output slice and clean up - copy(b, bs) - util.PutBytes(bs) - // If we've reached this point then everything went to plan, return the - // number of bytes we populated back into the given slice - return n, err + // Wait for some traffic to come through from the session + select { + case <-cancel.Finished(): + if cancel.Error() == util.CancellationTimeoutError { + return 0, ConnError{errors.New("read timeout"), true, false, false, 0} + } else { + return 0, ConnError{errors.New("session closed"), false, false, true, 0} } + case bs := <-sinfo.recv: + var err error + n := len(bs) + if len(bs) > len(b) { + n = len(b) + err = ConnError{errors.New("read buffer too small for entire packet"), false, true, false, 0} + } + // Copy results to the output slice and clean up + copy(b, bs) + util.PutBytes(bs) + // If we've reached this point then everything went to plan, return the + // number of bytes we populated back into the given slice + return n, err } } @@ -206,7 +204,17 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { sinfo.doFunc(sessionFunc) if written > 0 { bs := append(util.GetBytes(), b...) - sinfo.send <- bs + cancel := c.getDeadlineCancellation(&c.writeDeadline) + defer cancel.Cancel(nil) + select { + case <-cancel.Finished(): + if cancel.Error() == util.CancellationTimeoutError { + return 0, ConnError{errors.New("write timeout"), true, false, false, 0} + } else { + return 0, ConnError{errors.New("session closed"), false, false, true, 0} + } + case sinfo.send <- bs: + } } return written, err } From 7bf5884ac1af2df80df503ccecd3fca629065218 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 4 Aug 2019 02:14:45 -0500 Subject: [PATCH 0189/1109] remove some lossy channel sends that should be safe to allow to block --- src/tuntap/conn.go | 6 +----- src/tuntap/iface.go | 13 +++---------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index 1d47b378..0bb4efdc 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -72,11 +72,7 @@ func (s *tunConn) reader() (err error) { } } else if n > 0 { bs := append(util.GetBytes(), b[:n]...) - select { - case s.tun.send <- bs: - default: - util.PutBytes(bs) - } + s.tun.send <- bs s.stillAlive() } } diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index a95dfae4..1cee9b45 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -260,11 +260,8 @@ func (tun *TunAdapter) readerPacketHandler(ch chan []byte) { tun.mutex.Unlock() if tc != nil { for _, packet := range packets { - select { - case tc.send <- packet: - default: - util.PutBytes(packet) - } + p := packet // Possibly required because of how range + tc.send <- p } } }() @@ -274,11 +271,7 @@ func (tun *TunAdapter) readerPacketHandler(ch chan []byte) { } // If we have a connection now, try writing to it if isIn && session != nil { - select { - case session.send <- bs: - default: - util.PutBytes(bs) - } + session.send <- bs } } } From 1e6a6d216022f30af4b589a2432af4b09e311f22 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 4 Aug 2019 02:21:41 -0500 Subject: [PATCH 0190/1109] use session.cancel in the router to make blocking safe, reduce size of fromRouter buffer so the drops in the switch are closer to the intended front-drop behavior --- src/yggdrasil/router.go | 4 ++-- src/yggdrasil/session.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 77012863..4eaa56d8 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -166,8 +166,8 @@ func (r *router) handleTraffic(packet []byte) { return } select { - case sinfo.fromRouter <- &p: // FIXME ideally this should be front drop - default: + case sinfo.fromRouter <- &p: + case <-sinfo.cancel.Finished(): util.PutBytes(p.Payload) } } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 161d8eda..c17fb045 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -227,7 +227,7 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.myHandle = *crypto.NewHandle() sinfo.theirAddr = *address.AddrForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) - sinfo.fromRouter = make(chan *wire_trafficPacket, 32) + sinfo.fromRouter = make(chan *wire_trafficPacket, 1) sinfo.recv = make(chan []byte, 32) sinfo.send = make(chan []byte, 32) ss.sinfos[sinfo.myHandle] = &sinfo From f52955ee0fd17f5adb7960f9602e1e498c056c9c Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 4 Aug 2019 14:18:59 -0500 Subject: [PATCH 0191/1109] WARNING: CRYPTO DISABLED while speeding up stream writeMsg --- src/crypto/crypto.go | 2 ++ src/util/util.go | 9 +++++++++ src/yggdrasil/router.go | 1 - src/yggdrasil/stream.go | 27 ++++++++++++++++----------- src/yggdrasil/switch.go | 2 +- 5 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go index 75736ba7..14b12186 100644 --- a/src/crypto/crypto.go +++ b/src/crypto/crypto.go @@ -172,6 +172,7 @@ func BoxOpen(shared *BoxSharedKey, boxed []byte, nonce *BoxNonce) ([]byte, bool) { out := util.GetBytes() + return append(out, boxed...), true s := (*[BoxSharedKeyLen]byte)(shared) n := (*[BoxNonceLen]byte)(nonce) unboxed, success := box.OpenAfterPrecomputation(out, boxed, n, s) @@ -184,6 +185,7 @@ func BoxSeal(shared *BoxSharedKey, unboxed []byte, nonce *BoxNonce) ([]byte, *Bo } nonce.Increment() out := util.GetBytes() + return append(out, unboxed...), nonce s := (*[BoxSharedKeyLen]byte)(shared) n := (*[BoxNonceLen]byte)(nonce) boxed := box.SealAfterPrecomputation(out, unboxed, n, s) diff --git a/src/util/util.go b/src/util/util.go index 7f03ad05..b5e6ccc6 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -34,6 +34,15 @@ func PutBytes(bs []byte) { byteStore.Put(bs) } +// Gets a slice of the appropriate length, reusing existing slice capacity when possible +func ResizeBytes(bs []byte, length int) []byte { + if cap(bs) >= length { + return bs[:length] + } else { + return make([]byte, length) + } +} + // This is a workaround to go's broken timer implementation func TimerStop(t *time.Timer) bool { stopped := t.Stop() diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 4eaa56d8..2df7684f 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -127,7 +127,6 @@ func (r *router) mainLoop() { r.core.switchTable.doMaintenance() r.core.dht.doMaintenance() r.core.sessions.cleanup() - util.GetBytes() // To slowly drain things } case f := <-r.admin: f() diff --git a/src/yggdrasil/stream.go b/src/yggdrasil/stream.go index 4d73844f..30dd9244 100644 --- a/src/yggdrasil/stream.go +++ b/src/yggdrasil/stream.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io" + "net" "github.com/yggdrasil-network/yggdrasil-go/src/util" ) @@ -12,10 +13,11 @@ import ( var _ = linkInterfaceMsgIO(&stream{}) type stream struct { - rwc io.ReadWriteCloser - inputBuffer []byte // Incoming packet stream - frag [2 * streamMsgSize]byte // Temporary data read off the underlying rwc, on its way to the inputBuffer - outputBuffer [2 * streamMsgSize]byte // Temporary data about to be written to the rwc + rwc io.ReadWriteCloser + inputBuffer []byte // Incoming packet stream + frag [2 * streamMsgSize]byte // Temporary data read off the underlying rwc, on its way to the inputBuffer + //outputBuffer [2 * streamMsgSize]byte // Temporary data about to be written to the rwc + outputBuffer net.Buffers } func (s *stream) close() error { @@ -35,14 +37,17 @@ func (s *stream) init(rwc io.ReadWriteCloser) { // writeMsg writes a message with stream padding, and is *not* thread safe. func (s *stream) writeMsg(bs []byte) (int, error) { buf := s.outputBuffer[:0] - buf = append(buf, streamMsg[:]...) - buf = wire_put_uint64(uint64(len(bs)), buf) - padLen := len(buf) - buf = append(buf, bs...) + buf = append(buf, streamMsg[:]) + l := wire_put_uint64(uint64(len(bs)), util.GetBytes()) + defer util.PutBytes(l) + buf = append(buf, l) + padLen := len(buf[0]) + len(buf[1]) + buf = append(buf, bs) + totalLen := padLen + len(bs) var bn int - for bn < len(buf) { - n, err := s.rwc.Write(buf[bn:]) - bn += n + for bn < totalLen { + n, err := buf.WriteTo(s.rwc) + bn += int(n) if err != nil { l := bn - padLen if l < 0 { diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 1bc40501..0e11593f 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -820,7 +820,7 @@ func (t *switchTable) doWorker() { select { case bs := <-t.toRouter: buf = append(buf, bs) - for len(buf) > 32 { + for len(buf) > 32768 { // FIXME realistically don't drop anything, just for testing util.PutBytes(buf[0]) buf = buf[1:] } From 75b931f37ea562198fbc372dd9a44789036ddaec Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 4 Aug 2019 14:50:19 -0500 Subject: [PATCH 0192/1109] eliminate some more copying between slices --- src/tuntap/conn.go | 7 ++++--- src/tuntap/iface.go | 14 +++++++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index 0bb4efdc..1881bdea 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -54,13 +54,13 @@ func (s *tunConn) reader() (err error) { s.tun.log.Debugln("Starting conn reader for", s.conn.String()) defer s.tun.log.Debugln("Stopping conn reader for", s.conn.String()) var n int - b := make([]byte, 65535) for { select { case <-s.stop: return nil default: } + b := util.ResizeBytes(util.GetBytes(), 65535) if n, err = s.conn.Read(b); err != nil { if e, eok := err.(yggdrasil.ConnError); eok && !e.Temporary() { if e.Closed() { @@ -71,9 +71,10 @@ func (s *tunConn) reader() (err error) { return e } } else if n > 0 { - bs := append(util.GetBytes(), b[:n]...) - s.tun.send <- bs + s.tun.send <- b[:n] s.stillAlive() + } else { + util.PutBytes(b) } } } diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 1cee9b45..670f7829 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -139,8 +139,10 @@ func (tun *TunAdapter) readerPacketHandler(ch chan []byte) { continue } } - // Shift forward to avoid leaking bytes off the front of the slide when we eventually store it - bs = append(recvd[:0], bs...) + if offset != 0 { + // Shift forward to avoid leaking bytes off the front of the slice when we eventually store it + bs = append(recvd[:0], bs...) + } // From the IP header, work out what our source and destination addresses // and node IDs are. We will need these in order to work out where to send // the packet @@ -277,11 +279,12 @@ func (tun *TunAdapter) readerPacketHandler(ch chan []byte) { } func (tun *TunAdapter) reader() error { - recvd := make([]byte, 65535+tun_ETHER_HEADER_LENGTH) toWorker := make(chan []byte, 32) defer close(toWorker) go tun.readerPacketHandler(toWorker) for { + // Get a slice to store the packet in + recvd := util.ResizeBytes(util.GetBytes(), 65535+tun_ETHER_HEADER_LENGTH) // Wait for a packet to be delivered to us through the TUN/TAP adapter n, err := tun.iface.Read(recvd) if err != nil { @@ -291,9 +294,10 @@ func (tun *TunAdapter) reader() error { panic(err) } if n == 0 { + util.PutBytes(recvd) continue } - bs := append(util.GetBytes(), recvd[:n]...) - toWorker <- bs + // Send the packet to the worker + toWorker <- recvd[:n] } } From 0ba8c6a34f3bfe5eb919e5fd1fe5a0d138c45b6d Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 4 Aug 2019 15:21:04 -0500 Subject: [PATCH 0193/1109] have the stream code use bufio instead of copying manually to an input buffer, slightly reduces total uses of memmove --- src/crypto/crypto.go | 4 +-- src/yggdrasil/stream.go | 75 +++++++++++++++-------------------------- 2 files changed, 30 insertions(+), 49 deletions(-) diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go index 14b12186..44d2d8e8 100644 --- a/src/crypto/crypto.go +++ b/src/crypto/crypto.go @@ -172,7 +172,7 @@ func BoxOpen(shared *BoxSharedKey, boxed []byte, nonce *BoxNonce) ([]byte, bool) { out := util.GetBytes() - return append(out, boxed...), true + return append(out, boxed...), true //FIXME disabled crypto for benchmarking s := (*[BoxSharedKeyLen]byte)(shared) n := (*[BoxNonceLen]byte)(nonce) unboxed, success := box.OpenAfterPrecomputation(out, boxed, n, s) @@ -185,7 +185,7 @@ func BoxSeal(shared *BoxSharedKey, unboxed []byte, nonce *BoxNonce) ([]byte, *Bo } nonce.Increment() out := util.GetBytes() - return append(out, unboxed...), nonce + return append(out, unboxed...), nonce // FIXME disabled crypto for benchmarking s := (*[BoxSharedKeyLen]byte)(shared) n := (*[BoxNonceLen]byte)(nonce) boxed := box.SealAfterPrecomputation(out, unboxed, n, s) diff --git a/src/yggdrasil/stream.go b/src/yggdrasil/stream.go index 30dd9244..011943f5 100644 --- a/src/yggdrasil/stream.go +++ b/src/yggdrasil/stream.go @@ -1,6 +1,7 @@ package yggdrasil import ( + "bufio" "errors" "fmt" "io" @@ -13,10 +14,8 @@ import ( var _ = linkInterfaceMsgIO(&stream{}) type stream struct { - rwc io.ReadWriteCloser - inputBuffer []byte // Incoming packet stream - frag [2 * streamMsgSize]byte // Temporary data read off the underlying rwc, on its way to the inputBuffer - //outputBuffer [2 * streamMsgSize]byte // Temporary data about to be written to the rwc + rwc io.ReadWriteCloser + inputBuffer *bufio.Reader outputBuffer net.Buffers } @@ -32,6 +31,7 @@ func (s *stream) init(rwc io.ReadWriteCloser) { // TODO have this also do the metadata handshake and create the peer struct s.rwc = rwc // TODO call something to do the metadata exchange + s.inputBuffer = bufio.NewReaderSize(s.rwc, 2*streamMsgSize) } // writeMsg writes a message with stream padding, and is *not* thread safe. @@ -62,26 +62,11 @@ func (s *stream) writeMsg(bs []byte) (int, error) { // readMsg reads a message from the stream, accounting for stream padding, and is *not* thread safe. func (s *stream) readMsg() ([]byte, error) { for { - buf := s.inputBuffer - msg, ok, err := stream_chopMsg(&buf) - switch { - case err != nil: - // Something in the stream format is corrupt + bs, err := s.readMsgFromBuffer() + if err != nil { return nil, fmt.Errorf("message error: %v", err) - case ok: - // Copy the packet into bs, shift the buffer, and return - msg = append(util.GetBytes(), msg...) - s.inputBuffer = append(s.inputBuffer[:0], buf...) - return msg, nil - default: - // Wait for the underlying reader to return enough info for us to proceed - n, err := s.rwc.Read(s.frag[:]) - if n > 0 { - s.inputBuffer = append(s.inputBuffer, s.frag[:n]...) - } else if err != nil { - return nil, err - } } + return bs, err } } @@ -113,34 +98,30 @@ func (s *stream) _recvMetaBytes() ([]byte, error) { return metaBytes, nil } -// This takes a pointer to a slice as an argument. It checks if there's a -// complete message and, if so, slices out those parts and returns the message, -// true, and nil. If there's no error, but also no complete message, it returns -// nil, false, and nil. If there's an error, it returns nil, false, and the -// error, which the reader then handles (currently, by returning from the -// reader, which causes the connection to close). -func stream_chopMsg(bs *[]byte) ([]byte, bool, error) { - // Returns msg, ok, err - if len(*bs) < len(streamMsg) { - return nil, false, nil +// Reads bytes from the underlying rwc and returns 1 full message +func (s *stream) readMsgFromBuffer() ([]byte, error) { + pad := streamMsg // Copy + _, err := io.ReadFull(s.inputBuffer, pad[:]) + if err != nil { + return nil, err + } else if pad != streamMsg { + return nil, errors.New("bad message") } - for idx := range streamMsg { - if (*bs)[idx] != streamMsg[idx] { - return nil, false, errors.New("bad message") + lenSlice := make([]byte, 0, 10) + // FIXME this nextByte stuff depends on wire.go format, kind of ugly to have it here + nextByte := byte(0xff) + for nextByte > 127 { + nextByte, err = s.inputBuffer.ReadByte() + if err != nil { + return nil, err } + lenSlice = append(lenSlice, nextByte) } - msgLen, msgLenLen := wire_decode_uint64((*bs)[len(streamMsg):]) + msgLen, _ := wire_decode_uint64(lenSlice) if msgLen > streamMsgSize { - return nil, false, errors.New("oversized message") + return nil, errors.New("oversized message") } - msgBegin := len(streamMsg) + msgLenLen - msgEnd := msgBegin + int(msgLen) - if msgLenLen == 0 || len(*bs) < msgEnd { - // We don't have the full message - // Need to buffer this and wait for the rest to come in - return nil, false, nil - } - msg := (*bs)[msgBegin:msgEnd] - (*bs) = (*bs)[msgEnd:] - return msg, true, nil + msg := util.ResizeBytes(util.GetBytes(), int(msgLen)) + _, err = io.ReadFull(s.inputBuffer, msg) + return msg, err } From 07f14f92ed4f79a062946bda6e858164437f16cf Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 4 Aug 2019 15:25:14 -0500 Subject: [PATCH 0194/1109] disable crypto and switch buffer changes from testing --- src/crypto/crypto.go | 4 ++-- src/yggdrasil/switch.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go index 44d2d8e8..e85493f0 100644 --- a/src/crypto/crypto.go +++ b/src/crypto/crypto.go @@ -172,7 +172,7 @@ func BoxOpen(shared *BoxSharedKey, boxed []byte, nonce *BoxNonce) ([]byte, bool) { out := util.GetBytes() - return append(out, boxed...), true //FIXME disabled crypto for benchmarking + //return append(out, boxed...), true //FIXME disabled crypto for benchmarking s := (*[BoxSharedKeyLen]byte)(shared) n := (*[BoxNonceLen]byte)(nonce) unboxed, success := box.OpenAfterPrecomputation(out, boxed, n, s) @@ -185,7 +185,7 @@ func BoxSeal(shared *BoxSharedKey, unboxed []byte, nonce *BoxNonce) ([]byte, *Bo } nonce.Increment() out := util.GetBytes() - return append(out, unboxed...), nonce // FIXME disabled crypto for benchmarking + //return append(out, unboxed...), nonce // FIXME disabled crypto for benchmarking s := (*[BoxSharedKeyLen]byte)(shared) n := (*[BoxNonceLen]byte)(nonce) boxed := box.SealAfterPrecomputation(out, unboxed, n, s) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 0e11593f..1bc40501 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -820,7 +820,7 @@ func (t *switchTable) doWorker() { select { case bs := <-t.toRouter: buf = append(buf, bs) - for len(buf) > 32768 { // FIXME realistically don't drop anything, just for testing + for len(buf) > 32 { util.PutBytes(buf[0]) buf = buf[1:] } From 5d5486049b6d6ea9b9103bd0a86dece142618290 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 4 Aug 2019 15:53:34 -0500 Subject: [PATCH 0195/1109] add Conn.ReadNoCopy and Conn.WriteNoCopy that transfer ownership of a slice instead of copying, have Read and Write use the NoCopy versions under the hood and just manage copying as needed --- src/yggdrasil/conn.go | 85 ++++++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 34 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index d00db5c5..1a05bd83 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -149,73 +149,90 @@ func (c *Conn) getDeadlineCancellation(value *atomic.Value) util.Cancellation { } } -func (c *Conn) Read(b []byte) (int, error) { - // Take a copy of the session object - sinfo := c.session +// Used internally by Read, the caller is responsible for util.PutBytes when they're done. +func (c *Conn) ReadNoCopy() ([]byte, error) { cancel := c.getDeadlineCancellation(&c.readDeadline) defer cancel.Cancel(nil) // Wait for some traffic to come through from the session select { case <-cancel.Finished(): if cancel.Error() == util.CancellationTimeoutError { - return 0, ConnError{errors.New("read timeout"), true, false, false, 0} + return nil, ConnError{errors.New("read timeout"), true, false, false, 0} } else { - return 0, ConnError{errors.New("session closed"), false, false, true, 0} + return nil, ConnError{errors.New("session closed"), false, false, true, 0} } - case bs := <-sinfo.recv: - var err error - n := len(bs) - if len(bs) > len(b) { - n = len(b) - err = ConnError{errors.New("read buffer too small for entire packet"), false, true, false, 0} - } - // Copy results to the output slice and clean up - copy(b, bs) - util.PutBytes(bs) - // If we've reached this point then everything went to plan, return the - // number of bytes we populated back into the given slice - return n, err + case bs := <-c.session.recv: + return bs, nil } } -func (c *Conn) Write(b []byte) (bytesWritten int, err error) { - sinfo := c.session - written := len(b) +// Implements net.Conn.Read +func (c *Conn) Read(b []byte) (int, error) { + bs, err := c.ReadNoCopy() + if err != nil { + return 0, err + } + n := len(bs) + if len(bs) > len(b) { + n = len(b) + err = ConnError{errors.New("read buffer too small for entire packet"), false, true, false, 0} + } + // Copy results to the output slice and clean up + copy(b, bs) + util.PutBytes(bs) + // Return the number of bytes copied to the slice, along with any error + return n, err +} + +// Used internally by Write, the caller must not reuse the argument bytes when no error occurs +func (c *Conn) WriteNoCopy(bs []byte) error { + var err error sessionFunc := func() { // Does the packet exceed the permitted size for the session? - if uint16(len(b)) > sinfo.getMTU() { - written, err = 0, ConnError{errors.New("packet too big"), true, false, false, int(sinfo.getMTU())} + if uint16(len(bs)) > c.session.getMTU() { + err = ConnError{errors.New("packet too big"), true, false, false, int(c.session.getMTU())} return } // The rest of this work is session keep-alive traffic switch { - case time.Since(sinfo.time) > 6*time.Second: - if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second { + case time.Since(c.session.time) > 6*time.Second: + if c.session.time.Before(c.session.pingTime) && time.Since(c.session.pingTime) > 6*time.Second { // TODO double check that the above condition is correct c.doSearch() } else { - sinfo.core.sessions.ping(sinfo) + c.core.sessions.ping(c.session) } - case sinfo.reset && sinfo.pingTime.Before(sinfo.time): - sinfo.core.sessions.ping(sinfo) + case c.session.reset && c.session.pingTime.Before(c.session.time): + c.core.sessions.ping(c.session) default: // Don't do anything, to keep traffic throttled } } - sinfo.doFunc(sessionFunc) - if written > 0 { - bs := append(util.GetBytes(), b...) + c.session.doFunc(sessionFunc) + if err == nil { cancel := c.getDeadlineCancellation(&c.writeDeadline) defer cancel.Cancel(nil) select { case <-cancel.Finished(): if cancel.Error() == util.CancellationTimeoutError { - return 0, ConnError{errors.New("write timeout"), true, false, false, 0} + err = ConnError{errors.New("write timeout"), true, false, false, 0} } else { - return 0, ConnError{errors.New("session closed"), false, false, true, 0} + err = ConnError{errors.New("session closed"), false, false, true, 0} } - case sinfo.send <- bs: + case c.session.send <- bs: } } + return err +} + +// Implements net.Conn.Write +func (c *Conn) Write(b []byte) (int, error) { + written := len(b) + bs := append(util.GetBytes(), b...) + err := c.WriteNoCopy(bs) + if err != nil { + util.PutBytes(bs) + written = 0 + } return written, err } From 6803f209b0a353195a4645f2b008ea9881aa36da Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 4 Aug 2019 15:59:51 -0500 Subject: [PATCH 0196/1109] have tuntap code use Conn.ReadNoCopy and Conn.WriteNoCopy to avoid copying between slices --- src/tuntap/conn.go | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index 1881bdea..ab3179bf 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -53,15 +53,14 @@ func (s *tunConn) reader() (err error) { } s.tun.log.Debugln("Starting conn reader for", s.conn.String()) defer s.tun.log.Debugln("Stopping conn reader for", s.conn.String()) - var n int for { select { case <-s.stop: return nil default: } - b := util.ResizeBytes(util.GetBytes(), 65535) - if n, err = s.conn.Read(b); err != nil { + var bs []byte + if bs, err = s.conn.ReadNoCopy(); err != nil { if e, eok := err.(yggdrasil.ConnError); eok && !e.Temporary() { if e.Closed() { s.tun.log.Debugln(s.conn.String(), "TUN/TAP conn read debug:", err) @@ -70,11 +69,11 @@ func (s *tunConn) reader() (err error) { } return e } - } else if n > 0 { - s.tun.send <- b[:n] + } else if len(bs) > 0 { + s.tun.send <- bs s.stillAlive() } else { - util.PutBytes(b) + util.PutBytes(bs) } } } @@ -93,12 +92,12 @@ func (s *tunConn) writer() error { select { case <-s.stop: return nil - case b, ok := <-s.send: + case bs, ok := <-s.send: if !ok { return errors.New("send closed") } // TODO write timeout and close - if _, err := s.conn.Write(b); err != nil { + if err := s.conn.WriteNoCopy(bs); err != nil { if e, eok := err.(yggdrasil.ConnError); !eok { if e.Closed() { s.tun.log.Debugln(s.conn.String(), "TUN/TAP generic write debug:", err) @@ -109,9 +108,9 @@ func (s *tunConn) writer() error { // TODO: This currently isn't aware of IPv4 for CKR ptb := &icmp.PacketTooBig{ MTU: int(e.PacketMaximumSize()), - Data: b[:900], + Data: bs[:900], } - if packet, err := CreateICMPv6(b[8:24], b[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb); err == nil { + if packet, err := CreateICMPv6(bs[8:24], bs[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb); err == nil { s.tun.send <- packet } } else { @@ -124,7 +123,6 @@ func (s *tunConn) writer() error { } else { s.stillAlive() } - util.PutBytes(b) } } } From c55d7b4705e61ba8f78b893e72a3f1ee31b618d0 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 4 Aug 2019 16:16:49 -0500 Subject: [PATCH 0197/1109] have the switch queue drop packts to ourself when the total size of all packets is at least queueTotalMaxSize, instead of an arbitrary unconfigurable packet count --- src/yggdrasil/switch.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 1bc40501..b53229cc 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -814,17 +814,23 @@ func (t *switchTable) doWorker() { go func() { // Keep taking packets from the idle worker and sending them to the above whenever it's idle, keeping anything extra in a (fifo, head-drop) buffer var buf [][]byte + var size int for { - buf = append(buf, <-t.toRouter) + bs := <-t.toRouter + size += len(bs) + buf = append(buf, bs) for len(buf) > 0 { select { case bs := <-t.toRouter: + size += len(bs) buf = append(buf, bs) - for len(buf) > 32 { + for size > int(t.queueTotalMaxSize) { + size -= len(buf[0]) util.PutBytes(buf[0]) buf = buf[1:] } case sendingToRouter <- buf[0]: + size -= len(buf[0]) buf = buf[1:] } } From 979c3d4c07250afbda4dc3faa38f788e31300282 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 4 Aug 2019 16:29:58 -0500 Subject: [PATCH 0198/1109] move some potentially blocking operations out of session pool workers, minor cleanup --- src/crypto/crypto.go | 2 -- src/yggdrasil/session.go | 13 ++++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go index e85493f0..75736ba7 100644 --- a/src/crypto/crypto.go +++ b/src/crypto/crypto.go @@ -172,7 +172,6 @@ func BoxOpen(shared *BoxSharedKey, boxed []byte, nonce *BoxNonce) ([]byte, bool) { out := util.GetBytes() - //return append(out, boxed...), true //FIXME disabled crypto for benchmarking s := (*[BoxSharedKeyLen]byte)(shared) n := (*[BoxNonceLen]byte)(nonce) unboxed, success := box.OpenAfterPrecomputation(out, boxed, n, s) @@ -185,7 +184,6 @@ func BoxSeal(shared *BoxSharedKey, unboxed []byte, nonce *BoxNonce) ([]byte, *Bo } nonce.Increment() out := util.GetBytes() - //return append(out, unboxed...), nonce // FIXME disabled crypto for benchmarking s := (*[BoxSharedKeyLen]byte)(shared) n := (*[BoxNonceLen]byte)(nonce) boxed := box.SealAfterPrecomputation(out, unboxed, n, s) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index c17fb045..c39f60de 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -463,8 +463,8 @@ func (sinfo *sessionInfo) recvWorker() { ch := make(chan func(), 1) poolFunc := func() { bs, isOK = crypto.BoxOpen(&k, p.Payload, &p.Nonce) - util.PutBytes(p.Payload) callback := func() { + util.PutBytes(p.Payload) if !isOK { util.PutBytes(bs) return @@ -539,11 +539,14 @@ func (sinfo *sessionInfo) sendWorker() { // Encrypt the packet p.Payload, _ = crypto.BoxSeal(&k, bs, &p.Nonce) packet := p.encode() - // Cleanup - util.PutBytes(bs) - util.PutBytes(p.Payload) // The callback will send the packet - callback := func() { sinfo.core.router.out(packet) } + callback := func() { + // Cleanup + util.PutBytes(bs) + util.PutBytes(p.Payload) + // Send the packet + sinfo.core.router.out(packet) + } ch <- callback } // Send to the worker and wait for it to finish From 37533f157d6ee118ae087cb6a28b7869704eda21 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 5 Aug 2019 00:30:12 +0100 Subject: [PATCH 0199/1109] Make some API changes (currently broken) --- src/admin/admin.go | 42 +++++++++++++++------- src/util/util.go | 23 ++++++++++-- src/yggdrasil/api.go | 79 ++++++++--------------------------------- src/yggdrasil/switch.go | 21 +++++++++++ 4 files changed, 84 insertions(+), 81 deletions(-) diff --git a/src/admin/admin.go b/src/admin/admin.go index 2b73764c..db4b64b0 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -17,6 +17,7 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) @@ -243,31 +244,46 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. } }) a.AddHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in Info) (Info, error) { + var reserr error + var result yggdrasil.DHTRes if in["target"] == nil { in["target"] = "none" } - result, err := a.core.DHTPing(in["box_pub_key"].(string), in["coords"].(string), in["target"].(string)) - if err == nil { - infos := make(map[string]map[string]string, len(result.Infos)) - for _, dinfo := range result.Infos { - info := map[string]string{ - "box_pub_key": hex.EncodeToString(dinfo.PublicKey[:]), - "coords": fmt.Sprintf("%v", dinfo.Coords), - } - addr := net.IP(address.AddrForNodeID(crypto.GetNodeID(&dinfo.PublicKey))[:]).String() - infos[addr] = info + coords := util.DecodeCoordString(in["coords"].(string)) + var boxPubKey crypto.BoxPubKey + if b, err := hex.DecodeString(in["box_pub_key"].(string)); err == nil { + copy(boxPubKey[:], b[:]) + if n, err := hex.DecodeString(in["target"].(string)); err == nil { + var targetNodeID crypto.NodeID + copy(targetNodeID[:], n[:]) + result, reserr = a.core.DHTPing(boxPubKey, coords, &targetNodeID) + } else { + result, reserr = a.core.DHTPing(boxPubKey, coords, nil) } - return Info{"nodes": infos}, nil } else { return Info{}, err } + if reserr != nil { + return Info{}, reserr + } + infos := make(map[string]map[string]string, len(result.Infos)) + for _, dinfo := range result.Infos { + info := map[string]string{ + "box_pub_key": hex.EncodeToString(dinfo.PublicKey[:]), + "coords": fmt.Sprintf("%v", dinfo.Coords), + } + addr := net.IP(address.AddrForNodeID(crypto.GetNodeID(&dinfo.PublicKey))[:]).String() + infos[addr] = info + } + return Info{"nodes": infos}, nil }) a.AddHandler("getNodeInfo", []string{"[box_pub_key]", "[coords]", "[nocache]"}, func(in Info) (Info, error) { var nocache bool if in["nocache"] != nil { nocache = in["nocache"].(string) == "true" } - var box_pub_key, coords string + var box_pub_key string + var coords []uint64 if in["box_pub_key"] == nil && in["coords"] == nil { nodeinfo := a.core.MyNodeInfo() var jsoninfo interface{} @@ -280,7 +296,7 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. return Info{}, errors.New("Expecting both box_pub_key and coords") } else { box_pub_key = in["box_pub_key"].(string) - coords = in["coords"].(string) + coords = util.DecodeCoordString(in["coords"].(string)) } result, err := a.core.GetNodeInfo(box_pub_key, coords, nocache) if err == nil { diff --git a/src/util/util.go b/src/util/util.go index 4596474e..5be97603 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -2,9 +2,13 @@ package util // These are misc. utility functions that didn't really fit anywhere else -import "runtime" -import "sync" -import "time" +import ( + "runtime" + "strconv" + "strings" + "sync" + "time" +) // A wrapper around runtime.Gosched() so it doesn't need to be imported elsewhere. func Yield() { @@ -91,3 +95,16 @@ func Difference(a, b []string) []string { } return ab } + +// DecodeCoordString decodes a string representing coordinates in [1 2 3] format +// and returns a []byte. +func DecodeCoordString(in string) (out []uint64) { + s := strings.Trim(in, "[]") + t := strings.Split(s, " ") + for _, a := range t { + if u, err := strconv.ParseUint(a, 0, 64); err == nil { + out = append(out, u) + } + } + return out +} diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 014c370f..f1cfc18e 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -6,8 +6,6 @@ import ( "fmt" "net" "sort" - "strconv" - "strings" "sync/atomic" "time" @@ -46,14 +44,14 @@ type SwitchPeer struct { // DHT searches. type DHTEntry struct { PublicKey crypto.BoxPubKey - Coords []byte + Coords []uint64 LastSeen time.Duration } // DHTRes represents a DHT response, as returned by DHTPing. type DHTRes struct { PublicKey crypto.BoxPubKey // key of the sender - Coords []byte // coords of the sender + Coords []uint64 // coords of the sender Dest crypto.NodeID // the destination node ID Infos []DHTEntry // response } @@ -166,7 +164,7 @@ func (c *Core) GetDHT() []DHTEntry { }) for _, v := range dhtentry { info := DHTEntry{ - Coords: append([]byte{}, v.coords...), + Coords: append([]uint64{}, coordsBytestoUint64s(v.coords)...), LastSeen: now.Sub(v.recv), } copy(info.PublicKey[:], v.key[:]) @@ -346,30 +344,7 @@ func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) { // key and coordinates specified. The third parameter specifies whether a cached // result is acceptable - this results in less traffic being generated than is // necessary when, e.g. crawling the network. -func (c *Core) GetNodeInfo(keyString, coordString string, nocache bool) (NodeInfoPayload, error) { - var key crypto.BoxPubKey - if keyBytes, err := hex.DecodeString(keyString); err != nil { - return NodeInfoPayload{}, err - } else { - copy(key[:], keyBytes) - } - if !nocache { - if response, err := c.router.nodeinfo.getCachedNodeInfo(key); err == nil { - return response, nil - } - } - var coords []byte - for _, cstr := range strings.Split(strings.Trim(coordString, "[]"), " ") { - if cstr == "" { - // Special case, happens if trimmed is the empty string, e.g. this is the root - continue - } - if u64, err := strconv.ParseUint(cstr, 10, 8); err != nil { - return NodeInfoPayload{}, err - } else { - coords = append(coords, uint8(u64)) - } - } +func (c *Core) GetNodeInfo(key crypto.BoxPubKey, coords []byte, nocache bool) (NodeInfoPayload, error) { response := make(chan *NodeInfoPayload, 1) sendNodeInfoRequest := func() { c.router.nodeinfo.addCallback(key, func(nodeinfo *NodeInfoPayload) { @@ -389,7 +364,7 @@ func (c *Core) GetNodeInfo(keyString, coordString string, nocache bool) (NodeInf for res := range response { return *res, nil } - return NodeInfoPayload{}, fmt.Errorf("getNodeInfo timeout: %s", keyString) + return NodeInfoPayload{}, fmt.Errorf("getNodeInfo timeout: %s", hex.EncodeToString(key[:])) } // SetSessionGatekeeper allows you to configure a handler function for deciding @@ -477,64 +452,38 @@ func (c *Core) RemoveAllowedEncryptionPublicKey(bstr string) (err error) { // DHTPing sends a DHT ping to the node with the provided key and coords, // optionally looking up the specified target NodeID. -func (c *Core) DHTPing(keyString, coordString, targetString string) (DHTRes, error) { - var key crypto.BoxPubKey - if keyBytes, err := hex.DecodeString(keyString); err != nil { - return DHTRes{}, err - } else { - copy(key[:], keyBytes) - } - var coords []byte - for _, cstr := range strings.Split(strings.Trim(coordString, "[]"), " ") { - if cstr == "" { - // Special case, happens if trimmed is the empty string, e.g. this is the root - continue - } - if u64, err := strconv.ParseUint(cstr, 10, 8); err != nil { - return DHTRes{}, err - } else { - coords = append(coords, uint8(u64)) - } - } +func (c *Core) DHTPing(key crypto.BoxPubKey, coords []uint64, target *crypto.NodeID) (DHTRes, error) { resCh := make(chan *dhtRes, 1) info := dhtInfo{ key: key, - coords: coords, + coords: coordsUint64stoBytes(coords), } - target := *info.getNodeID() - if targetString == "none" { - // Leave the default target in place - } else if targetBytes, err := hex.DecodeString(targetString); err != nil { - return DHTRes{}, err - } else if len(targetBytes) != len(target) { - return DHTRes{}, errors.New("Incorrect target NodeID length") - } else { - var target crypto.NodeID - copy(target[:], targetBytes) + if target == nil { + target = info.getNodeID() } - rq := dhtReqKey{info.key, target} + rq := dhtReqKey{info.key, *target} sendPing := func() { c.dht.addCallback(&rq, func(res *dhtRes) { resCh <- res }) - c.dht.ping(&info, &target) + c.dht.ping(&info, &rq.dest) } c.router.doAdmin(sendPing) // TODO: do something better than the below... res := <-resCh if res != nil { r := DHTRes{ - Coords: append([]byte{}, res.Coords...), + Coords: append([]uint64{}, coordsBytestoUint64s(res.Coords)...), } copy(r.PublicKey[:], res.Key[:]) for _, i := range res.Infos { e := DHTEntry{ - Coords: append([]byte{}, i.coords...), + Coords: append([]uint64{}, coordsBytestoUint64s(i.coords)...), } copy(e.PublicKey[:], i.key[:]) r.Infos = append(r.Infos, e) } return r, nil } - return DHTRes{}, fmt.Errorf("DHT ping timeout: %s", keyString) + return DHTRes{}, fmt.Errorf("DHT ping timeout: %s", hex.EncodeToString(key[:])) } diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 1bc40501..34b57aa6 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -137,6 +137,27 @@ type peerInfo struct { // This is just a uint64 with a named type for clarity reasons. type switchPort uint64 +func coordsUint64stoBytes(in []uint64) (out []byte) { + for _, coord := range in { + c := wire_encode_uint64(coord) + out = append(out, c...) + } + return out +} + +func coordsBytestoUint64s(in []byte) (out []uint64) { + offset := 0 + for { + coord, length := wire_decode_uint64(in[offset:]) + if length == 0 { + break + } + out = append(out, coord) + offset += length + } + return out +} + // This is the subset of the information about a peer needed to make routing decisions, and it stored separately in an atomically accessed table, which gets hammered in the "hot loop" of the routing logic (see: peer.handleTraffic in peers.go). type tableElem struct { port switchPort From 3a2ae9d902bdcdffe5a6567ed0abe990fc8ba09e Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 5 Aug 2019 10:17:19 +0100 Subject: [PATCH 0200/1109] Update API to represent coords as []uint64 --- src/.DS_Store | Bin 0 -> 6148 bytes src/admin/admin.go | 10 +++++++--- src/util/util.go | 2 +- src/yggdrasil/api.go | 28 ++++++++++++++-------------- src/yggdrasil/switch.go | 21 --------------------- src/yggdrasil/wire.go | 23 +++++++++++++++++++++++ 6 files changed, 45 insertions(+), 39 deletions(-) create mode 100644 src/.DS_Store diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 Date: Mon, 5 Aug 2019 10:21:40 +0100 Subject: [PATCH 0201/1109] Return box_pub_key as hex string in JSON (replaces #481) --- src/admin/admin.go | 143 ++------------------------------------------- 1 file changed, 4 insertions(+), 139 deletions(-) diff --git a/src/admin/admin.go b/src/admin/admin.go index 59d0f954..9789bca2 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -79,11 +79,6 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. } return Info{"list": handlers}, nil }) - /* - a.AddHandler("dot", []string{}, func(in Info) (Info, error) { - return Info{"dot": string(a.getResponse_dot())}, nil - }) - */ a.AddHandler("getSelf", []string{}, func(in Info) (Info, error) { ip := c.Address().String() subnet := c.Subnet() @@ -111,7 +106,7 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. "bytes_recvd": p.BytesRecvd, "proto": p.Protocol, "endpoint": p.Endpoint, - "box_pub_key": p.PublicKey, + "box_pub_key": hex.EncodeToString(p.PublicKey[:]), } } return Info{"peers": peers}, nil @@ -129,7 +124,7 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. "bytes_recvd": s.BytesRecvd, "proto": s.Protocol, "endpoint": s.Endpoint, - "box_pub_key": s.PublicKey, + "box_pub_key": hex.EncodeToString(s.PublicKey[:]), } } return Info{"switchpeers": switchpeers}, nil @@ -148,7 +143,7 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. dht[so] = Info{ "coords": fmt.Sprintf("%v", d.Coords), "last_seen": d.LastSeen.Seconds(), - "box_pub_key": d.PublicKey, + "box_pub_key": hex.EncodeToString(d.PublicKey[:]), } } return Info{"dht": dht}, nil @@ -165,7 +160,7 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. "mtu": s.MTU, "uptime": s.Uptime.Seconds(), "was_mtu_fixed": s.WasMTUFixed, - "box_pub_key": s.PublicKey, + "box_pub_key": hex.EncodeToString(s.PublicKey[:]), } } return Info{"sessions": sessions}, nil @@ -492,133 +487,3 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { } } } - -// getResponse_dot returns a response for a graphviz dot formatted -// representation of the known parts of the network. This is color-coded and -// labeled, and includes the self node, switch peers, nodes known to the DHT, -// and nodes with open sessions. The graph is structured as a tree with directed -// links leading away from the root. -/* -func (a *AdminSocket) getResponse_dot() []byte { - //self := a.getData_getSelf() - peers := a.core.GetSwitchPeers() - dht := a.core.GetDHT() - sessions := a.core.GetSessions() - // Start building a tree from all known nodes - type nodeInfo struct { - name string - key string - parent string - port uint64 - options string - } - infos := make(map[string]nodeInfo) - // Get coords as a slice of strings, FIXME? this looks very fragile - coordSlice := func(coords string) []string { - tmp := strings.Replace(coords, "[", "", -1) - tmp = strings.Replace(tmp, "]", "", -1) - return strings.Split(tmp, " ") - } - // First fill the tree with all known nodes, no parents - addInfo := func(nodes []admin_nodeInfo, options string, tag string) { - for _, node := range nodes { - n := node.asMap() - info := nodeInfo{ - key: n["coords"].(string), - options: options, - } - if len(tag) > 0 { - info.name = fmt.Sprintf("%s\n%s", n["ip"].(string), tag) - } else { - info.name = n["ip"].(string) - } - coordsSplit := coordSlice(info.key) - if len(coordsSplit) != 0 { - portStr := coordsSplit[len(coordsSplit)-1] - portUint, err := strconv.ParseUint(portStr, 10, 64) - if err == nil { - info.port = portUint - } - } - infos[info.key] = info - } - } - addInfo(dht, "fillcolor=\"#ffffff\" style=filled fontname=\"sans serif\"", "Known in DHT") // white - addInfo(sessions, "fillcolor=\"#acf3fd\" style=filled fontname=\"sans serif\"", "Open session") // blue - addInfo(peers, "fillcolor=\"#ffffb5\" style=filled fontname=\"sans serif\"", "Connected peer") // yellow - addInfo(append([]admin_nodeInfo(nil), *self), "fillcolor=\"#a5ff8a\" style=filled fontname=\"sans serif\"", "This node") // green - // Now go through and create placeholders for any missing nodes - for _, info := range infos { - // This is ugly string manipulation - coordsSplit := coordSlice(info.key) - for idx := range coordsSplit { - key := fmt.Sprintf("[%v]", strings.Join(coordsSplit[:idx], " ")) - newInfo, isIn := infos[key] - if isIn { - continue - } - newInfo.name = "?" - newInfo.key = key - newInfo.options = "fontname=\"sans serif\" style=dashed color=\"#999999\" fontcolor=\"#999999\"" - - coordsSplit := coordSlice(newInfo.key) - if len(coordsSplit) != 0 { - portStr := coordsSplit[len(coordsSplit)-1] - portUint, err := strconv.ParseUint(portStr, 10, 64) - if err == nil { - newInfo.port = portUint - } - } - - infos[key] = newInfo - } - } - // Now go through and attach parents - for _, info := range infos { - pSplit := coordSlice(info.key) - if len(pSplit) > 0 { - pSplit = pSplit[:len(pSplit)-1] - } - info.parent = fmt.Sprintf("[%v]", strings.Join(pSplit, " ")) - infos[info.key] = info - } - // Finally, get a sorted list of keys, which we use to organize the output - var keys []string - for _, info := range infos { - keys = append(keys, info.key) - } - // sort - sort.SliceStable(keys, func(i, j int) bool { - return keys[i] < keys[j] - }) - sort.SliceStable(keys, func(i, j int) bool { - return infos[keys[i]].port < infos[keys[j]].port - }) - // Now print it all out - var out []byte - put := func(s string) { - out = append(out, []byte(s)...) - } - put("digraph {\n") - // First set the labels - for _, key := range keys { - info := infos[key] - put(fmt.Sprintf("\"%v\" [ label = \"%v\" %v ];\n", info.key, info.name, info.options)) - } - // Then print the tree structure - for _, key := range keys { - info := infos[key] - if info.key == info.parent { - continue - } // happens for the root, skip it - port := fmt.Sprint(info.port) - style := "fontname=\"sans serif\"" - if infos[info.parent].name == "?" || infos[info.key].name == "?" { - style = "fontname=\"sans serif\" style=dashed color=\"#999999\" fontcolor=\"#999999\"" - } - put(fmt.Sprintf(" \"%+v\" -> \"%+v\" [ label = \"%v\" %s ];\n", info.parent, info.key, port, style)) - } - put("}\n") - return out -} -*/ From 84a4f54217e9ad34ca4a5beab8fb0c9b7b84c370 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 5 Aug 2019 18:49:15 -0500 Subject: [PATCH 0202/1109] temporary fix to nil pointer, better to make sure it's never nil --- src/yggdrasil/router.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 2df7684f..161ae60a 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -161,7 +161,9 @@ func (r *router) handleTraffic(packet []byte) { return } sinfo, isIn := r.core.sessions.getSessionForHandle(&p.Handle) - if !isIn { + if !isIn || sinfo.cancel == nil { + // FIXME make sure sinfo.cancel can never be nil + util.PutBytes(p.Payload) return } select { From 8a8514981769eacff68f226150cbcd6a3fa9ff34 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 5 Aug 2019 18:50:08 -0500 Subject: [PATCH 0203/1109] remove src/.DS_Store --- src/.DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/.DS_Store diff --git a/src/.DS_Store b/src/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 Date: Mon, 5 Aug 2019 19:11:28 -0500 Subject: [PATCH 0204/1109] have createSession fill the sessionInfo.cancel field, have Conn use Conn.session.cancel instead of storing its own cancellation, this should prevent any of these things from being both nil and reachable at the same time --- src/yggdrasil/conn.go | 13 +++++-------- src/yggdrasil/dialer.go | 2 +- src/yggdrasil/router.go | 3 +-- src/yggdrasil/session.go | 11 ++++++++--- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 1a05bd83..2452a3d6 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -57,7 +57,6 @@ type Conn struct { core *Core readDeadline atomic.Value // time.Time // TODO timer writeDeadline atomic.Value // time.Time // TODO timer - cancel util.Cancellation mutex sync.RWMutex // protects the below nodeID *crypto.NodeID nodeMask *crypto.NodeID @@ -71,7 +70,6 @@ func newConn(core *Core, nodeID *crypto.NodeID, nodeMask *crypto.NodeID, session nodeID: nodeID, nodeMask: nodeMask, session: session, - cancel: util.NewCancellation(), } return &conn } @@ -142,10 +140,10 @@ func (c *Conn) doSearch() { func (c *Conn) getDeadlineCancellation(value *atomic.Value) util.Cancellation { if deadline, ok := value.Load().(time.Time); ok { // A deadline is set, so return a Cancellation that uses it - return util.CancellationWithDeadline(c.cancel, deadline) + return util.CancellationWithDeadline(c.session.cancel, deadline) } else { // No cancellation was set, so return a child cancellation with no timeout - return util.CancellationChild(c.cancel) + return util.CancellationChild(c.session.cancel) } } @@ -241,10 +239,9 @@ func (c *Conn) Close() (err error) { defer c.mutex.Unlock() if c.session != nil { // Close the session, if it hasn't been closed already - c.core.router.doAdmin(c.session.close) - } - if e := c.cancel.Cancel(errors.New("connection closed")); e != nil { - err = ConnError{errors.New("close failed, session already closed"), false, false, true, 0} + if e := c.session.cancel.Cancel(errors.New("connection closed")); e != nil { + err = ConnError{errors.New("close failed, session already closed"), false, false, true, 0} + } } return } diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index 6ce2e8ac..db5d5a4d 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -69,7 +69,7 @@ func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (*Conn, er defer t.Stop() select { case <-conn.session.init: - conn.session.startWorkers(conn.cancel) + conn.session.startWorkers() return conn, nil case <-t.C: conn.Close() diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 161ae60a..a11f6ae5 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -161,8 +161,7 @@ func (r *router) handleTraffic(packet []byte) { return } sinfo, isIn := r.core.sessions.getSessionForHandle(&p.Handle) - if !isIn || sinfo.cancel == nil { - // FIXME make sure sinfo.cancel can never be nil + if !isIn { util.PutBytes(p.Payload) return } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index c39f60de..f9c38faa 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -208,6 +208,7 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.pingTime = now sinfo.pingSend = now sinfo.init = make(chan struct{}) + sinfo.cancel = util.NewCancellation() higher := false for idx := range ss.core.boxPub { if ss.core.boxPub[idx] > sinfo.theirPermPub[idx] { @@ -232,6 +233,11 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.send = make(chan []byte, 32) ss.sinfos[sinfo.myHandle] = &sinfo ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle + go func() { + // Run cleanup when the session is canceled + <-sinfo.cancel.Finished() + sinfo.core.router.doAdmin(sinfo.close) + }() return &sinfo } @@ -362,7 +368,7 @@ func (ss *sessions) handlePing(ping *sessionPing) { for i := range conn.nodeMask { conn.nodeMask[i] = 0xFF } - conn.session.startWorkers(conn.cancel) + conn.session.startWorkers() ss.listener.conn <- conn } ss.listenerMutex.Unlock() @@ -431,8 +437,7 @@ func (ss *sessions) reset() { //////////////////////////// Worker Functions Below //////////////////////////// //////////////////////////////////////////////////////////////////////////////// -func (sinfo *sessionInfo) startWorkers(cancel util.Cancellation) { - sinfo.cancel = cancel +func (sinfo *sessionInfo) startWorkers() { go sinfo.recvWorker() go sinfo.sendWorker() } From 790524bd1c6afa4a4a64f4551ec019d8e77ff86a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Tue, 6 Aug 2019 19:25:55 -0500 Subject: [PATCH 0205/1109] copy/paste old flowkey logic into a util function, add a struct of key and packet, make WriteNoCopy accept this instead of a slice --- src/tuntap/conn.go | 7 ++- src/util/util.go | 38 ++++++++++++++++ src/yggdrasil/conn.go | 12 ++--- src/yggdrasil/router.go | 2 +- src/yggdrasil/session.go | 98 ++++++++++++++++++++++------------------ 5 files changed, 105 insertions(+), 52 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index ab3179bf..61cdb2b4 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -96,8 +96,11 @@ func (s *tunConn) writer() error { if !ok { return errors.New("send closed") } - // TODO write timeout and close - if err := s.conn.WriteNoCopy(bs); err != nil { + msg := yggdrasil.FlowKeyMessage{ + FlowKey: util.GetFlowKey(bs), + Message: bs, + } + if err := s.conn.WriteNoCopy(msg); err != nil { if e, eok := err.(yggdrasil.ConnError); !eok { if e.Closed() { s.tun.log.Debugln(s.conn.String(), "TUN/TAP generic write debug:", err) diff --git a/src/util/util.go b/src/util/util.go index 1158156c..a588a35c 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -106,3 +106,41 @@ func DecodeCoordString(in string) (out []uint64) { } return out } + +// GetFlowLabel takes an IP packet as an argument and returns some information about the traffic flow. +// For IPv4 packets, this is derived from the source and destination protocol and port numbers. +// For IPv6 packets, this is derived from the FlowLabel field of the packet if this was set, otherwise it's handled like IPv4. +// The FlowKey is then used internally by Yggdrasil for congestion control. +func GetFlowKey(bs []byte) uint64 { + // Work out the flowkey - this is used to determine which switch queue + // traffic will be pushed to in the event of congestion + var flowkey uint64 + // Get the IP protocol version from the packet + switch bs[0] & 0xf0 { + case 0x40: // IPv4 packet + // Check the packet meets minimum UDP packet length + if len(bs) >= 24 { + // Is the protocol TCP, UDP or SCTP? + if bs[9] == 0x06 || bs[9] == 0x11 || bs[9] == 0x84 { + ihl := bs[0] & 0x0f * 4 // Header length + flowkey = uint64(bs[9])<<32 /* proto */ | + uint64(bs[ihl+0])<<24 | uint64(bs[ihl+1])<<16 /* sport */ | + uint64(bs[ihl+2])<<8 | uint64(bs[ihl+3]) /* dport */ + } + } + case 0x60: // IPv6 packet + // Check if the flowlabel was specified in the packet header + flowkey = uint64(bs[1]&0x0f)<<16 | uint64(bs[2])<<8 | uint64(bs[3]) + // If the flowlabel isn't present, make protokey from proto | sport | dport + // if the packet meets minimum UDP packet length + if flowkey == 0 && len(bs) >= 48 { + // Is the protocol TCP, UDP or SCTP? + if bs[6] == 0x06 || bs[6] == 0x11 || bs[6] == 0x84 { + flowkey = uint64(bs[6])<<32 /* proto */ | + uint64(bs[40])<<24 | uint64(bs[41])<<16 /* sport */ | + uint64(bs[42])<<8 | uint64(bs[43]) /* dport */ + } + } + } + return flowkey +} diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 2452a3d6..20db931d 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -183,11 +183,11 @@ func (c *Conn) Read(b []byte) (int, error) { } // Used internally by Write, the caller must not reuse the argument bytes when no error occurs -func (c *Conn) WriteNoCopy(bs []byte) error { +func (c *Conn) WriteNoCopy(msg FlowKeyMessage) error { var err error sessionFunc := func() { // Does the packet exceed the permitted size for the session? - if uint16(len(bs)) > c.session.getMTU() { + if uint16(len(msg.Message)) > c.session.getMTU() { err = ConnError{errors.New("packet too big"), true, false, false, int(c.session.getMTU())} return } @@ -216,7 +216,7 @@ func (c *Conn) WriteNoCopy(bs []byte) error { } else { err = ConnError{errors.New("session closed"), false, false, true, 0} } - case c.session.send <- bs: + case c.session.send <- msg: } } return err @@ -225,10 +225,10 @@ func (c *Conn) WriteNoCopy(bs []byte) error { // Implements net.Conn.Write func (c *Conn) Write(b []byte) (int, error) { written := len(b) - bs := append(util.GetBytes(), b...) - err := c.WriteNoCopy(bs) + msg := FlowKeyMessage{Message: append(util.GetBytes(), b...)} + err := c.WriteNoCopy(msg) if err != nil { - util.PutBytes(bs) + util.PutBytes(msg.Message) written = 0 } return written, err diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index a11f6ae5..7e2a325a 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -166,7 +166,7 @@ func (r *router) handleTraffic(packet []byte) { return } select { - case sinfo.fromRouter <- &p: + case sinfo.fromRouter <- p: case <-sinfo.cancel.Finished(): util.PutBytes(p.Payload) } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index f9c38faa..517947e8 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -18,39 +18,39 @@ import ( // All the information we know about an active session. // This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API. type sessionInfo struct { - mutex sync.Mutex // Protects all of the below, use it any time you read/chance the contents of a session - core *Core // - reconfigure chan chan error // - theirAddr address.Address // - theirSubnet address.Subnet // - theirPermPub crypto.BoxPubKey // - theirSesPub crypto.BoxPubKey // - mySesPub crypto.BoxPubKey // - mySesPriv crypto.BoxPrivKey // - sharedSesKey crypto.BoxSharedKey // derived from session keys - theirHandle crypto.Handle // - myHandle crypto.Handle // - theirNonce crypto.BoxNonce // - theirNonceMask uint64 // - myNonce crypto.BoxNonce // - theirMTU uint16 // - myMTU uint16 // - wasMTUFixed bool // Was the MTU fixed by a receive error? - timeOpened time.Time // Time the sessino was opened - time time.Time // Time we last received a packet - mtuTime time.Time // time myMTU was last changed - pingTime time.Time // time the first ping was sent since the last received packet - pingSend time.Time // time the last ping was sent - coords []byte // coords of destination - reset bool // reset if coords change - tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation - bytesSent uint64 // Bytes of real traffic sent in this session - bytesRecvd uint64 // Bytes of real traffic received in this session - fromRouter chan *wire_trafficPacket // Received packets go here, picked up by the associated Conn - init chan struct{} // Closed when the first session pong arrives, used to signal that the session is ready for initial use - cancel util.Cancellation // Used to terminate workers - recv chan []byte - send chan []byte + mutex sync.Mutex // Protects all of the below, use it any time you read/chance the contents of a session + core *Core // + reconfigure chan chan error // + theirAddr address.Address // + theirSubnet address.Subnet // + theirPermPub crypto.BoxPubKey // + theirSesPub crypto.BoxPubKey // + mySesPub crypto.BoxPubKey // + mySesPriv crypto.BoxPrivKey // + sharedSesKey crypto.BoxSharedKey // derived from session keys + theirHandle crypto.Handle // + myHandle crypto.Handle // + theirNonce crypto.BoxNonce // + theirNonceMask uint64 // + myNonce crypto.BoxNonce // + theirMTU uint16 // + myMTU uint16 // + wasMTUFixed bool // Was the MTU fixed by a receive error? + timeOpened time.Time // Time the sessino was opened + time time.Time // Time we last received a packet + mtuTime time.Time // time myMTU was last changed + pingTime time.Time // time the first ping was sent since the last received packet + pingSend time.Time // time the last ping was sent + coords []byte // coords of destination + reset bool // reset if coords change + tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation + bytesSent uint64 // Bytes of real traffic sent in this session + bytesRecvd uint64 // Bytes of real traffic received in this session + init chan struct{} // Closed when the first session pong arrives, used to signal that the session is ready for initial use + cancel util.Cancellation // Used to terminate workers + fromRouter chan wire_trafficPacket // Received packets go here, to be decrypted by the session + recv chan []byte // Decrypted packets go here, picked up by the associated Conn + send chan FlowKeyMessage // Packets with optional flow key go here, to be encrypted and sent } func (sinfo *sessionInfo) doFunc(f func()) { @@ -228,9 +228,9 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.myHandle = *crypto.NewHandle() sinfo.theirAddr = *address.AddrForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) - sinfo.fromRouter = make(chan *wire_trafficPacket, 1) + sinfo.fromRouter = make(chan wire_trafficPacket, 1) sinfo.recv = make(chan []byte, 32) - sinfo.send = make(chan []byte, 32) + sinfo.send = make(chan FlowKeyMessage, 32) ss.sinfos[sinfo.myHandle] = &sinfo ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle go func() { @@ -442,13 +442,18 @@ func (sinfo *sessionInfo) startWorkers() { go sinfo.sendWorker() } +type FlowKeyMessage struct { + FlowKey uint64 + Message []byte +} + func (sinfo *sessionInfo) recvWorker() { // TODO move theirNonce etc into a struct that gets stored here, passed in over a channel // Since there's no reason for anywhere else in the session code to need to *read* it... // Only needs to be updated from the outside if a ping resets it... // That would get rid of the need to take a mutex for the sessionFunc var callbacks []chan func() - doRecv := func(p *wire_trafficPacket) { + doRecv := func(p wire_trafficPacket) { var bs []byte var err error var k crypto.BoxSharedKey @@ -524,16 +529,22 @@ func (sinfo *sessionInfo) sendWorker() { // TODO move info that this worker needs here, send updates via a channel // Otherwise we need to take a mutex to avoid races with update() var callbacks []chan func() - doSend := func(bs []byte) { + doSend := func(msg FlowKeyMessage) { var p wire_trafficPacket var k crypto.BoxSharedKey sessionFunc := func() { - sinfo.bytesSent += uint64(len(bs)) + sinfo.bytesSent += uint64(len(msg.Message)) p = wire_trafficPacket{ Coords: append([]byte(nil), sinfo.coords...), Handle: sinfo.theirHandle, Nonce: sinfo.myNonce, } + if msg.FlowKey != 0 { + // Helps ensure that traffic from this flow ends up in a separate queue from other flows + // The zero padding relies on the fact that the self-peer is always on port 0 + p.Coords = append(p.Coords, 0) + p.Coords = wire_put_uint64(msg.FlowKey, p.Coords) + } sinfo.myNonce.Increment() k = sinfo.sharedSesKey } @@ -542,12 +553,13 @@ func (sinfo *sessionInfo) sendWorker() { ch := make(chan func(), 1) poolFunc := func() { // Encrypt the packet - p.Payload, _ = crypto.BoxSeal(&k, bs, &p.Nonce) - packet := p.encode() + p.Payload, _ = crypto.BoxSeal(&k, msg.Message, &p.Nonce) // The callback will send the packet callback := func() { + // Encoding may block on a util.GetBytes(), so kept out of the worker pool + packet := p.encode() // Cleanup - util.PutBytes(bs) + util.PutBytes(msg.Message) util.PutBytes(p.Payload) // Send the packet sinfo.core.router.out(packet) @@ -566,8 +578,8 @@ func (sinfo *sessionInfo) sendWorker() { f() case <-sinfo.cancel.Finished(): return - case bs := <-sinfo.send: - doSend(bs) + case msg := <-sinfo.send: + doSend(msg) } } select { From d795ab1b650e5437ba3b7ca91dbccfcf1b6e00f5 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Tue, 6 Aug 2019 20:51:38 -0500 Subject: [PATCH 0206/1109] minor allocation fix --- src/yggdrasil/stream.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yggdrasil/stream.go b/src/yggdrasil/stream.go index 011943f5..56d4754a 100644 --- a/src/yggdrasil/stream.go +++ b/src/yggdrasil/stream.go @@ -44,6 +44,7 @@ func (s *stream) writeMsg(bs []byte) (int, error) { padLen := len(buf[0]) + len(buf[1]) buf = append(buf, bs) totalLen := padLen + len(bs) + s.outputBuffer = buf[:0] // So we can reuse the same underlying array later var bn int for bn < totalLen { n, err := buf.WriteTo(s.rwc) From bbb35d72094ddb912c1ff5a660bf9fe36aee34b9 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 7 Aug 2019 10:52:19 +0100 Subject: [PATCH 0207/1109] Transform Listen statement to new format if needed --- cmd/yggdrasil/main.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 129b01d5..ee8ebe8e 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -74,6 +74,12 @@ func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *config if err := hjson.Unmarshal(conf, &dat); err != nil { panic(err) } + // Check for fields that have changed type recently, e.g. the Listen config + // option is now a []string rather than a string + if listen, ok := dat["Listen"].(string); ok { + dat["Listen"] = []string{listen} + } + // Sanitise the config confJson, err := json.Marshal(dat) if err != nil { panic(err) From 9ab08446ff07e77ba25941c0be55aebe2bc372c0 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 7 Aug 2019 17:40:50 -0500 Subject: [PATCH 0208/1109] make sure the sessionInfo.recvWorker doesn't block if sinfo.recv somehow fills --- src/yggdrasil/session.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 517947e8..d8b7e9b3 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -495,7 +495,11 @@ func (sinfo *sessionInfo) recvWorker() { util.PutBytes(bs) } else { // Pass the packet to the buffer for Conn.Read - sinfo.recv <- bs + select { + case <-sinfo.cancel.Finished(): + util.PutBytes(bs) + case sinfo.recv <- bs: + } } } ch <- callback From 5e81a0c42167f4ff882e89fb2d0f5b17433c3d3f Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 7 Aug 2019 18:08:31 -0500 Subject: [PATCH 0209/1109] Use a separate buffer per session for incoming packets, so 1 session that floods won't block other sessions --- src/yggdrasil/session.go | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index d8b7e9b3..da66ad59 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -508,6 +508,32 @@ func (sinfo *sessionInfo) recvWorker() { util.WorkerGo(poolFunc) callbacks = append(callbacks, ch) } + fromHelper := make(chan wire_trafficPacket, 1) + go func() { + var buf []wire_trafficPacket + for { + for len(buf) > 0 { + select { + case <-sinfo.cancel.Finished(): + return + case p := <-sinfo.fromRouter: + buf = append(buf, p) + for len(buf) > 64 { // Based on nonce window size + util.PutBytes(buf[0].Payload) + buf = buf[1:] + } + case fromHelper <- buf[0]: + buf = buf[1:] + } + } + select { + case <-sinfo.cancel.Finished(): + return + case p := <-sinfo.fromRouter: + buf = append(buf, p) + } + } + }() for { for len(callbacks) > 0 { select { @@ -516,14 +542,14 @@ func (sinfo *sessionInfo) recvWorker() { f() case <-sinfo.cancel.Finished(): return - case p := <-sinfo.fromRouter: + case p := <-fromHelper: doRecv(p) } } select { case <-sinfo.cancel.Finished(): return - case p := <-sinfo.fromRouter: + case p := <-fromHelper: doRecv(p) } } From 589ad638eab4a38060f39061c00ad81be7cc2fbe Mon Sep 17 00:00:00 2001 From: Slex Date: Sun, 11 Aug 2019 00:31:22 +0300 Subject: [PATCH 0210/1109] Implement feature from https://github.com/yggdrasil-network/yggdrasil-go/issues/488 --- build | 2 +- cmd/yggdrasil/main.go | 11 ++++++----- cmd/yggdrasilctl/main.go | 9 +++++++++ src/admin/admin.go | 5 +++-- src/version/version.go | 22 ++++++++++++++++++++++ src/yggdrasil/api.go | 18 ------------------ src/yggdrasil/core.go | 8 +++----- src/yggdrasil/nodeinfo.go | 5 +++-- 8 files changed, 47 insertions(+), 33 deletions(-) create mode 100644 src/version/version.go diff --git a/build b/build index 80a971f9..c1e5f863 100755 --- a/build +++ b/build @@ -2,7 +2,7 @@ set -ef -PKGSRC=${PKGSRC:-github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil} +PKGSRC=${PKGSRC:-github.com/yggdrasil-network/yggdrasil-go/src/version} PKGNAME=${PKGNAME:-$(sh contrib/semver/name.sh)} PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)} diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index ee8ebe8e..36494c40 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -25,6 +25,7 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/multicast" "github.com/yggdrasil-network/yggdrasil-go/src/tuntap" + "github.com/yggdrasil-network/yggdrasil-go/src/version" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) @@ -119,7 +120,7 @@ func main() { normaliseconf := flag.Bool("normaliseconf", false, "use in combination with either -useconf or -useconffile, outputs your configuration normalised") confjson := flag.Bool("json", false, "print configuration from -genconf or -normaliseconf as JSON instead of HJSON") autoconf := flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)") - version := flag.Bool("version", false, "prints the version of this build") + ver := flag.Bool("version", false, "prints the version of this build") logging := flag.String("logging", "info,warn,error", "comma-separated list of logging levels to enable") logto := flag.String("logto", "stdout", "file path to log to, \"syslog\" or \"stdout\"") flag.Parse() @@ -127,9 +128,9 @@ func main() { var cfg *config.NodeConfig var err error switch { - case *version: - fmt.Println("Build name:", yggdrasil.BuildName()) - fmt.Println("Build version:", yggdrasil.BuildVersion()) + case *ver: + fmt.Println("Build name:", version.BuildName()) + fmt.Println("Build version:", version.BuildVersion()) os.Exit(0) case *autoconf: // Use an autoconf-generated config, this will give us random keys and @@ -174,7 +175,7 @@ func main() { case "stdout": logger = log.New(os.Stdout, "", log.Flags()) case "syslog": - if syslogger, err := gsyslog.NewLogger(gsyslog.LOG_NOTICE, "DAEMON", yggdrasil.BuildName()); err == nil { + if syslogger, err := gsyslog.NewLogger(gsyslog.LOG_NOTICE, "DAEMON", version.BuildName()); err == nil { logger = log.New(syslogger, "", log.Flags()) } default: diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index 51f4fa51..4cc9745d 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -19,6 +19,7 @@ import ( "github.com/hjson/hjson-go" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" + "github.com/yggdrasil-network/yggdrasil-go/src/version" ) type admin_info map[string]interface{} @@ -53,9 +54,17 @@ func main() { server := flag.String("endpoint", endpoint, "Admin socket endpoint") injson := flag.Bool("json", false, "Output in JSON format (as opposed to pretty-print)") verbose := flag.Bool("v", false, "Verbose output (includes public keys)") + ver := flag.Bool("version", false, "Prints the version of this build") flag.Parse() args := flag.Args() + if *ver { + fmt.Println(os.Args[0], "build name:", version.BuildName()) + fmt.Println(os.Args[0], "version:", version.BuildVersion()) + fmt.Println("\nFor get yggdrasil version use\n - ", os.Args[0], "getSelf") + os.Exit(0) + } + if len(args) == 0 { flag.Usage() return diff --git a/src/admin/admin.go b/src/admin/admin.go index 9789bca2..c7fc151d 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -18,6 +18,7 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/util" + "github.com/yggdrasil-network/yggdrasil-go/src/version" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) @@ -86,8 +87,8 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. "self": Info{ ip: Info{ "box_pub_key": c.EncryptionPublicKey(), - "build_name": yggdrasil.BuildName(), - "build_version": yggdrasil.BuildVersion(), + "build_name": version.BuildName(), + "build_version": version.BuildVersion(), "coords": fmt.Sprintf("%v", c.Coords()), "subnet": subnet.String(), }, diff --git a/src/version/version.go b/src/version/version.go new file mode 100644 index 00000000..4cc7a8f2 --- /dev/null +++ b/src/version/version.go @@ -0,0 +1,22 @@ +package version + +var buildName string +var buildVersion string + +// BuildName gets the current build name. This is usually injected if built +// from git, or returns "unknown" otherwise. +func BuildName() string { + if buildName == "" { + return "yggdrasilctl" + } + return buildName +} + +// BuildVersion gets the current build version. This is usually injected if +// built from git, or returns "unknown" otherwise. +func BuildVersion() string { + if buildVersion == "" { + return "unknown" + } + return buildVersion +} diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 98f90130..57ee5c65 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -241,24 +241,6 @@ func (c *Core) GetSessions() []Session { return sessions } -// BuildName gets the current build name. This is usually injected if built -// from git, or returns "unknown" otherwise. -func BuildName() string { - if buildName == "" { - return "yggdrasil" - } - return buildName -} - -// BuildVersion gets the current build version. This is usually injected if -// built from git, or returns "unknown" otherwise. -func BuildVersion() string { - if buildVersion == "" { - return "unknown" - } - return buildVersion -} - // ConnListen returns a listener for Yggdrasil session connections. func (c *Core) ConnListen() (*Listener, error) { c.sessions.listenerMutex.Lock() diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 2aa78347..0921ab9f 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -10,11 +10,9 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/version" ) -var buildName string -var buildVersion string - // The Core object represents the Yggdrasil node. You should create a Core // object for each Yggdrasil node you plan to run. type Core struct { @@ -164,10 +162,10 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, Previous: *nc, } - if name := BuildName(); name != "unknown" { + if name := version.BuildName(); name != "unknown" { c.log.Infoln("Build name:", name) } - if version := BuildVersion(); version != "unknown" { + if version := version.BuildVersion(); version != "unknown" { c.log.Infoln("Build version:", version) } diff --git a/src/yggdrasil/nodeinfo.go b/src/yggdrasil/nodeinfo.go index f1c7ed07..73d4e115 100644 --- a/src/yggdrasil/nodeinfo.go +++ b/src/yggdrasil/nodeinfo.go @@ -9,6 +9,7 @@ import ( "time" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/version" ) type nodeinfo struct { @@ -99,8 +100,8 @@ func (m *nodeinfo) setNodeInfo(given interface{}, privacy bool) error { m.myNodeInfoMutex.Lock() defer m.myNodeInfoMutex.Unlock() defaults := map[string]interface{}{ - "buildname": BuildName(), - "buildversion": BuildVersion(), + "buildname": version.BuildName(), + "buildversion": version.BuildVersion(), "buildplatform": runtime.GOOS, "buildarch": runtime.GOARCH, } From 7a28eb787ee860e1555af52673b046ccb4b0d56c Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 11 Aug 2019 13:00:19 -0500 Subject: [PATCH 0211/1109] try to fix a few edge cases with searches that could lead them to ending without the callback being run or without cleaning up the old search info --- src/yggdrasil/conn.go | 4 ++-- src/yggdrasil/search.go | 14 ++------------ 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 20db931d..134f3cd2 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -130,9 +130,9 @@ func (c *Conn) doSearch() { searchCompleted := func(sinfo *sessionInfo, e error) {} sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo) + // Start the search + sinfo.continueSearch() } - // Continue the search - sinfo.continueSearch() } go func() { c.core.router.admin <- routerWork }() } diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 4c31fd6b..676ac4f9 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -36,7 +36,6 @@ type searchInfo struct { core *Core dest crypto.NodeID mask crypto.NodeID - time time.Time toVisit []*dhtInfo visited map[crypto.NodeID]bool callback func(*sessionInfo, error) @@ -65,17 +64,10 @@ func (s *searches) init(core *Core) { // Creates a new search info, adds it to the searches struct, and returns a pointer to the info. func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo { - now := time.Now() - //for dest, sinfo := range s.searches { - // if now.Sub(sinfo.time) > time.Minute { - // delete(s.searches, dest) - // } - //} info := searchInfo{ core: s.core, dest: *dest, mask: *mask, - time: now.Add(-time.Second), callback: callback, } s.searches[*dest] = &info @@ -154,10 +146,6 @@ func (sinfo *searchInfo) doSearchStep() { // If we've recenty sent a ping for this search, do nothing. // Otherwise, doSearchStep and schedule another continueSearch to happen after search_RETRY_TIME. func (sinfo *searchInfo) continueSearch() { - if time.Since(sinfo.time) < search_RETRY_TIME { - return - } - sinfo.time = time.Now() sinfo.doSearchStep() // In case the search dies, try to spawn another thread later // Note that this will spawn multiple parallel searches as time passes @@ -209,6 +197,8 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { if sess == nil { // nil if the DHT search finished but the session wasn't allowed sinfo.callback(nil, errors.New("session not allowed")) + // Cleanup + delete(sinfo.core.searches.searches, res.Dest) return true } _, isIn := sinfo.core.sessions.getByTheirPerm(&res.Key) From 277da1fe60fb6e32bfdbe572ff23f3fc585dbd99 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 11 Aug 2019 13:11:14 -0500 Subject: [PATCH 0212/1109] make sure searches don't end if try to continue (in parallel) with nowhere left to send, but we just sent a search and are still waiting for a response --- src/yggdrasil/search.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 676ac4f9..b970fe55 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -36,6 +36,7 @@ type searchInfo struct { core *Core dest crypto.NodeID mask crypto.NodeID + time time.Time toVisit []*dhtInfo visited map[crypto.NodeID]bool callback func(*sessionInfo, error) @@ -68,6 +69,7 @@ func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callba core: s.core, dest: *dest, mask: *mask, + time: time.Now(), callback: callback, } s.searches[*dest] = &info @@ -130,9 +132,11 @@ func (sinfo *searchInfo) addToSearch(res *dhtRes) { // Otherwise, it pops the closest node to the destination (in keyspace) off of the toVisit list and sends a dht ping. func (sinfo *searchInfo) doSearchStep() { if len(sinfo.toVisit) == 0 { - // Dead end, do cleanup - delete(sinfo.core.searches.searches, sinfo.dest) - sinfo.callback(nil, errors.New("search reached dead end")) + if time.Since(sinfo.time) > search_RETRY_TIME { + // Dead end and no response in too long, do cleanup + delete(sinfo.core.searches.searches, sinfo.dest) + sinfo.callback(nil, errors.New("search reached dead end")) + } return } // Send to the next search target @@ -141,6 +145,7 @@ func (sinfo *searchInfo) doSearchStep() { rq := dhtReqKey{next.key, sinfo.dest} sinfo.core.dht.addCallback(&rq, sinfo.handleDHTRes) sinfo.core.dht.ping(next, &sinfo.dest) + sinfo.time = time.Now() } // If we've recenty sent a ping for this search, do nothing. From 70a118ae98164e93ad122d187a18f1a0d0403b90 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 12 Aug 2019 11:41:29 +0100 Subject: [PATCH 0213/1109] Update go.mod/go.sum --- go.mod | 6 +++--- go.sum | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 84025df3..c7773cc4 100644 --- a/go.mod +++ b/go.mod @@ -8,10 +8,10 @@ require ( github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/mitchellh/mapstructure v1.1.2 github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 - github.com/yggdrasil-network/water v0.0.0-20190725123504-a16161896c34 + github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 - golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 + golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e golang.org/x/text v0.3.2 - golang.org/x/tools v0.0.0-20190802003818-e9bb7d36c060 // indirect + golang.org/x/tools v0.0.0-20190809145639-6d4652c779c4 // indirect ) diff --git a/go.sum b/go.sum index 81d337e1..0369a785 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,7 @@ github.com/yggdrasil-network/water v0.0.0-20190725073841-250edb919f8a h1:mQ0mPD+ github.com/yggdrasil-network/water v0.0.0-20190725073841-250edb919f8a/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= github.com/yggdrasil-network/water v0.0.0-20190725123504-a16161896c34 h1:Qh5FE+Q5iGqpmR/FPMYHuoZLN921au/nxAlmKe+Hdbo= github.com/yggdrasil-network/water v0.0.0-20190725123504-a16161896c34/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= +github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -44,6 +45,7 @@ golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= @@ -53,3 +55,5 @@ golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDq golang.org/x/tools v0.0.0-20190724185037-8aa4eac1a7c1/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190802003818-e9bb7d36c060/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190809145639-6d4652c779c4/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From c15976e4dc50e1a2c8308513bcbc426f98995e03 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 12 Aug 2019 18:08:02 -0500 Subject: [PATCH 0214/1109] go.sum --- go.sum | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.sum b/go.sum index 0369a785..c786f5ea 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,7 @@ github.com/yggdrasil-network/water v0.0.0-20190725073841-250edb919f8a h1:mQ0mPD+ github.com/yggdrasil-network/water v0.0.0-20190725073841-250edb919f8a/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= github.com/yggdrasil-network/water v0.0.0-20190725123504-a16161896c34 h1:Qh5FE+Q5iGqpmR/FPMYHuoZLN921au/nxAlmKe+Hdbo= github.com/yggdrasil-network/water v0.0.0-20190725123504-a16161896c34/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= +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= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -45,6 +46,7 @@ golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e h1:TsjK5I7fXk8f2FQrgu6NS7i5Qih3knl2FL1htyguLRE= golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From b2cb1d965cd3951a224397a7a1d421c411e6fc8a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 12 Aug 2019 18:22:30 -0500 Subject: [PATCH 0215/1109] avoid leaking sessions when no listener exists, or blocking if it's busy --- src/yggdrasil/session.go | 50 ++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index da66ad59..179273e6 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -348,40 +348,40 @@ func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) { func (ss *sessions) handlePing(ping *sessionPing) { // Get the corresponding session (or create a new session) sinfo, isIn := ss.getByTheirPerm(&ping.SendPermPub) - // Check if the session is allowed - // TODO: this check may need to be moved - if !isIn && !ss.isSessionAllowed(&ping.SendPermPub, false) { - return - } - // Create the session if it doesn't already exist - if !isIn { - ss.createSession(&ping.SendPermPub) - sinfo, isIn = ss.getByTheirPerm(&ping.SendPermPub) - if !isIn { - panic("This should not happen") - } + switch { + case isIn: // Session already exists + case !ss.isSessionAllowed(&ping.SendPermPub, false): // Session is not allowed + case ping.IsPong: // This is a response, not an initial ping, so ignore it. + default: ss.listenerMutex.Lock() - // Check and see if there's a Listener waiting to accept connections - // TODO: this should not block if nothing is accepting - if !ping.IsPong && ss.listener != nil { + if ss.listener != nil { + // This is a ping from an allowed node for which no session exists, and we have a listener ready to handle sessions. + // We need to create a session and pass it to the listener. + sinfo = ss.createSession(&ping.SendPermPub) + if s, _ := ss.getByTheirPerm(&ping.SendPermPub); s != sinfo { + panic("This should not happen") + } conn := newConn(ss.core, crypto.GetNodeID(&sinfo.theirPermPub), &crypto.NodeID{}, sinfo) for i := range conn.nodeMask { conn.nodeMask[i] = 0xFF } conn.session.startWorkers() - ss.listener.conn <- conn + c := ss.listener.conn + go func() { c <- conn }() } ss.listenerMutex.Unlock() } - sinfo.doFunc(func() { - // Update the session - if !sinfo.update(ping) { /*panic("Should not happen in testing")*/ - return - } - if !ping.IsPong { - ss.sendPingPong(sinfo, true) - } - }) + if sinfo != nil { + sinfo.doFunc(func() { + // Update the session + if !sinfo.update(ping) { /*panic("Should not happen in testing")*/ + return + } + if !ping.IsPong { + ss.sendPingPong(sinfo, true) + } + }) + } } // Get the MTU of the session. From 46c5df1c239187db35965037c59fba1582750422 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Tue, 13 Aug 2019 18:49:49 -0500 Subject: [PATCH 0216/1109] when we abandon a link because we already have a connection to that peer, only wait for the connection to close if it's an *outgoing* link, otherwise incomming connection attempts can cause us to leak links --- src/yggdrasil/link.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index de90fd94..eca96ebd 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -179,7 +179,10 @@ func (intf *linkInterface) handler() error { // That lets them do things like close connections on its own, avoid printing a connection message in the first place, etc. intf.link.core.log.Debugln("DEBUG: found existing interface for", intf.name) intf.msgIO.close() - <-oldIntf.closed + if !intf.incoming { + // Block outgoing connection attempts until the existing connection closes + <-oldIntf.closed + } return nil } else { intf.closed = make(chan struct{}) From 4702da2bcbb7b50d1fb1bb1535045a0df6908a44 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 14 Aug 2019 19:32:40 +0100 Subject: [PATCH 0217/1109] Use new netlink library (fixes #493) --- go.mod | 2 ++ go.sum | 4 ++++ src/tuntap/tun_linux.go | 30 ++++++------------------------ 3 files changed, 12 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index c7773cc4..be3c3025 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,8 @@ require ( github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/mitchellh/mapstructure v1.1.2 github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 + github.com/vishvananda/netlink v1.0.0 + github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 diff --git a/go.sum b/go.sum index c786f5ea..2fde9549 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,10 @@ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQz github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 h1:1zN6ImoqhSJhN8hGXFaJlSC8msLmIbX8bFqOfWLKw0w= github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091/go.mod h1:N20Z5Y8oye9a7HmytmZ+tr8Q2vlP0tAHP13kTHzwvQY= +github.com/vishvananda/netlink v1.0.0 h1:bqNY2lgheFIu1meHUFSH3d7vG93AFyqg3oGbJCOJgSM= +github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= +github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f h1:nBX3nTcmxEtHSERBJaIo1Qa26VwRaopnZmfDQUXsF4I= +github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae h1:MYCANF1kehCG6x6G+/9txLfq6n3lS5Vp0Mxn1hdiBAc= github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= github.com/yggdrasil-network/water v0.0.0-20190719211521-a76871ea954b/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= diff --git a/src/tuntap/tun_linux.go b/src/tuntap/tun_linux.go index c9c03c09..764e56b9 100644 --- a/src/tuntap/tun_linux.go +++ b/src/tuntap/tun_linux.go @@ -5,11 +5,7 @@ package tuntap // The linux platform specific tun parts import ( - "errors" - "fmt" - "net" - - "github.com/docker/libcontainer/netlink" + "github.com/vishvananda/netlink" water "github.com/yggdrasil-network/water" ) @@ -51,35 +47,21 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int // to exist on the system, but this will fail if Netlink is not present in the // kernel (it nearly always is). func (tun *TunAdapter) setupAddress(addr string) error { - // Set address - var netIF *net.Interface - ifces, err := net.Interfaces() + nladdr, err := netlink.ParseAddr(addr) if err != nil { return err } - for _, ifce := range ifces { - if ifce.Name == tun.iface.Name() { - var newIF = ifce - netIF = &newIF // Don't point inside ifces, it's apparently unsafe?... - } - } - if netIF == nil { - return errors.New(fmt.Sprintf("Failed to find interface: %s", tun.iface.Name())) - } - ip, ipNet, err := net.ParseCIDR(addr) + nlintf, err := netlink.LinkByName(tun.iface.Name()) if err != nil { return err } - err = netlink.NetworkLinkAddIp(netIF, ip, ipNet) - if err != nil { + if err := netlink.AddrAdd(nlintf, nladdr); err != nil { return err } - err = netlink.NetworkSetMTU(netIF, tun.mtu) - if err != nil { + if err := netlink.LinkSetMTU(nlintf, tun.mtu); err != nil { return err } - netlink.NetworkLinkUp(netIF) - if err != nil { + if err := netlink.LinkSetUp(nlintf); err != nil { return err } return nil From 02bfe283991b1eed20dbebca52e9582a3b11a378 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 14 Aug 2019 20:09:02 +0100 Subject: [PATCH 0218/1109] Minor tweaks --- cmd/yggdrasil/main.go | 4 ++-- cmd/yggdrasilctl/main.go | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 36494c40..15d7d226 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -120,7 +120,7 @@ func main() { normaliseconf := flag.Bool("normaliseconf", false, "use in combination with either -useconf or -useconffile, outputs your configuration normalised") confjson := flag.Bool("json", false, "print configuration from -genconf or -normaliseconf as JSON instead of HJSON") autoconf := flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)") - ver := flag.Bool("version", false, "prints the version of this build") + ver := flag.Bool("version", false, "prints the version of this build") logging := flag.String("logging", "info,warn,error", "comma-separated list of logging levels to enable") logto := flag.String("logto", "stdout", "file path to log to, \"syslog\" or \"stdout\"") flag.Parse() @@ -131,7 +131,7 @@ func main() { case *ver: fmt.Println("Build name:", version.BuildName()) fmt.Println("Build version:", version.BuildVersion()) - os.Exit(0) + return case *autoconf: // Use an autoconf-generated config, this will give us random keys and // port numbers, and will use an automatically selected TUN/TAP interface. diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index 4cc9745d..94d90842 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -59,10 +59,10 @@ func main() { args := flag.Args() if *ver { - fmt.Println(os.Args[0], "build name:", version.BuildName()) - fmt.Println(os.Args[0], "version:", version.BuildVersion()) - fmt.Println("\nFor get yggdrasil version use\n - ", os.Args[0], "getSelf") - os.Exit(0) + fmt.Println("Build name:", version.BuildName()) + fmt.Println("Build version:", version.BuildVersion()) + fmt.Println("To get the version number of the running Yggdrasil node, run", os.Args[0], "getSelf") + return } if len(args) == 0 { From 2abb71682fdcf956e5cfd39628b384530da37508 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 14 Aug 2019 22:21:30 +0100 Subject: [PATCH 0219/1109] Update changelog, readme, go.mod/go.sum --- CHANGELOG.md | 20 +++++++++++++++++++- README.md | 2 +- go.mod | 6 +++--- go.sum | 3 +++ 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee50dcc4..c2032806 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> +## [0.3.7] - 2019-08-14 +### Fixed +- A number of significant performance regressions introduced in version 0.3.6 + have been fixed, resulting in better performance +- Flow labels are now used to prioritise traffic flows again correctly +- The `Listen` statement, when configured as a string rather than an array, + will now be parsed correctly +- The admin socket now returns `coords` as a correct array of unsigned 64-bit + integers, rather than the internal representation +- The admin socket now returns `box_pub_key` in string format again +- Sessions no longer leak/block when no listener (e.g. TUN/TAP) is configured +- Incoming session connections no longer block when a session already exists, + which reduces in less leaked goroutines +- Flooded sessions will no longer block other sessions +- Searches are now cleaned up properly and a couple of edge-cases with duplicate + searches have been fixed +- A number of minor allocation and pointer fixes + ## [0.3.6] - 2019-08-03 ### Added - Yggdrasil now has a public API with interfaces such as `yggdrasil.ConnDialer`, `yggdrasil.ConnListener` and `yggdrasil.Conn` for using Yggdrasil as a transport directly within applications @@ -53,7 +71,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Session MTUs are now always calculated correctly, in some cases they were incorrectly defaulting to 1280 before - Multiple searches now don't take place for a single connection - Concurrency bugs fixed -- Fixed a number of bugs in the ICMPv6 neighbor solicitation in the TUN/TAP code +- Fixed a number of bugs in the ICMPv6 neighbor solicitation in the TUN/TAP code - A case where peers weren't always added correctly if one or more peers were unreachable has been fixed - Searches which include the local node are now handled correctly - Lots of small bug tweaks and clean-ups throughout the codebase diff --git a/README.md b/README.md index 3f07a2b2..ec7fe74c 100644 --- a/README.md +++ b/README.md @@ -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 packages: -1. Install [Go](https://golang.org) (requires Go 1.11 or later) +1. Install [Go](https://golang.org) (requires Go 1.12 or later) 2. Clone this repository 2. Run `./build` diff --git a/go.mod b/go.mod index be3c3025..5654dff8 100644 --- a/go.mod +++ b/go.mod @@ -12,8 +12,8 @@ require ( github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 - golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 - golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e + golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 + golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a golang.org/x/text v0.3.2 - golang.org/x/tools v0.0.0-20190809145639-6d4652c779c4 // indirect + golang.org/x/tools v0.0.0-20190814171936-5b18234b3ae0 // indirect ) diff --git a/go.sum b/go.sum index 2fde9549..5c5e6de4 100644 --- a/go.sum +++ b/go.sum @@ -40,6 +40,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e h1:njOxP/wVblhCLIUhjHXf6X+dzTt5OQ3vMQo9mkOIKIo= golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -52,6 +53,7 @@ golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdO golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e h1:TsjK5I7fXk8f2FQrgu6NS7i5Qih3knl2FL1htyguLRE= golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= @@ -62,4 +64,5 @@ golang.org/x/tools v0.0.0-20190724185037-8aa4eac1a7c1/go.mod h1:jcCCGcm9btYwXyDq golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190802003818-e9bb7d36c060/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190809145639-6d4652c779c4/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190814171936-5b18234b3ae0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 1a2b7a8b60985ed82681f5faefd51932ededde0b Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 14 Aug 2019 17:57:36 -0500 Subject: [PATCH 0220/1109] test a change to how switch hops are selected when multiple links are idle --- src/yggdrasil/switch.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index b53229cc..adc49abe 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -676,7 +676,8 @@ func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) boo update = true case cinfo.dist > bestDist: //nothing - case thisTime.Before(bestTime): + case thisTime.After(bestTime): + // Pick the one that was used most recently -- at least this should pick the same link consistently in low-traffic scenarios update = true default: //nothing From 382c2e65462e7c7ad0660239b4645e3827453bfa Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 14 Aug 2019 18:14:24 -0500 Subject: [PATCH 0221/1109] even more go.sum --- go.sum | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.sum b/go.sum index 5c5e6de4..62a38033 100644 --- a/go.sum +++ b/go.sum @@ -40,6 +40,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e h1:njOxP/wVblhCLIUhjHXf6X+dzTt5OQ3vMQo9mkOIKIo= @@ -53,6 +54,7 @@ golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdO golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e h1:TsjK5I7fXk8f2FQrgu6NS7i5Qih3knl2FL1htyguLRE= golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 5b054766a2d893ce0ca01673b528970753e90b2e Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 15 Aug 2019 10:54:04 +0100 Subject: [PATCH 0222/1109] Update comments in handleIn, add switch_getFlowLabelFromCoords helper (in case it is useful if we try to consider flowlabels in multi-link scenarios) --- src/yggdrasil/switch.go | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index adc49abe..d092625b 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -630,6 +630,16 @@ func switch_getPacketStreamID(packet []byte) string { return string(switch_getPacketCoords(packet)) } +// Returns the flowlabel from a given set of coords +func switch_getFlowLabelFromCoords(in []byte) []byte { + for i, v := range in { + if v == 0 { + return in[i+1:] + } + } + return []byte{} +} + // Find the best port for a given set of coords func (t *switchTable) bestPortForCoords(coords []byte) switchPort { table := t.getTable() @@ -667,20 +677,28 @@ func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) boo var update bool switch { case to == nil: - //nothing + // no port was found, ignore it case !isIdle: - //nothing + // the port is busy, ignore it case best == nil: + // this is the first idle port we've found, so select it until we find a + // better candidate port to use instead update = true case cinfo.dist < bestDist: + // the port takes a shorter path/is more direct than our current + // candidate, so select that instead update = true case cinfo.dist > bestDist: - //nothing + // the port takes a longer path/is less direct than our current candidate, + // ignore it case thisTime.After(bestTime): - // Pick the one that was used most recently -- at least this should pick the same link consistently in low-traffic scenarios + // all else equal, this port was used more recently than our current + // candidate, so choose that instead. this should mean that, in low + // traffic scenarios, we consistently pick the same link which helps with + // packet ordering update = true default: - //nothing + // the search for a port has finished } if update { best = to @@ -693,10 +711,9 @@ func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) boo delete(idle, best.port) best.sendPacket(packet) return true - } else { - // Didn't find anyone idle to send it to - return false } + // Didn't find anyone idle to send it to + return false } // Info about a buffered packet From ae0fe93de5c7ba994fdbab4b1a30a0f3973dd131 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 15 Aug 2019 12:54:04 +0100 Subject: [PATCH 0223/1109] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2032806..a3d7903c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - A number of significant performance regressions introduced in version 0.3.6 have been fixed, resulting in better performance - Flow labels are now used to prioritise traffic flows again correctly +- In low-traffic scenarios where there are multiple peerings between a pair of + nodes, Yggdrasil now prefers the most active peering instead of the least + active, helping to reduce packet reordering - The `Listen` statement, when configured as a string rather than an array, will now be parsed correctly - The admin socket now returns `coords` as a correct array of unsigned 64-bit From fdac8932a867a33c56242c34e67b9bd55bccdb2b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 15 Aug 2019 13:11:54 +0100 Subject: [PATCH 0224/1109] Update changelog --- CHANGELOG.md | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3d7903c..0057a005 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,23 +27,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [0.3.7] - 2019-08-14 ### Fixed -- A number of significant performance regressions introduced in version 0.3.6 - have been fixed, resulting in better performance +- A number of significant performance regressions introduced in version 0.3.6 have been fixed, resulting in better performance - Flow labels are now used to prioritise traffic flows again correctly -- In low-traffic scenarios where there are multiple peerings between a pair of - nodes, Yggdrasil now prefers the most active peering instead of the least - active, helping to reduce packet reordering -- The `Listen` statement, when configured as a string rather than an array, - will now be parsed correctly -- The admin socket now returns `coords` as a correct array of unsigned 64-bit - integers, rather than the internal representation +- In low-traffic scenarios where there are multiple peerings between a pair of nodes, Yggdrasil now prefers the most active peering instead of the least active, helping to reduce packet reordering +- The `Listen` statement, when configured as a string rather than an array, will now be parsed correctly +- The admin socket now returns `coords` as a correct array of unsigned 64-bit integers, rather than the internal representation - The admin socket now returns `box_pub_key` in string format again - Sessions no longer leak/block when no listener (e.g. TUN/TAP) is configured -- Incoming session connections no longer block when a session already exists, - which reduces in less leaked goroutines +- Incoming session connections no longer block when a session already exists, which reduces in less leaked goroutines - Flooded sessions will no longer block other sessions -- Searches are now cleaned up properly and a couple of edge-cases with duplicate - searches have been fixed +- Searches are now cleaned up properly and a couple of edge-cases with duplicate searches have been fixed - A number of minor allocation and pointer fixes ## [0.3.6] - 2019-08-03 From 03b8af9f1a1c178ec46d7cf7693371a322a0acae Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 16 Aug 2019 18:37:16 -0500 Subject: [PATCH 0225/1109] keep track of recent nonces with a heap and a map instead of a fixed-size bitmask --- src/yggdrasil/session.go | 131 +++++++++++++++++++++++++-------------- 1 file changed, 83 insertions(+), 48 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 179273e6..b675b469 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -6,6 +6,7 @@ package yggdrasil import ( "bytes" + "container/heap" "errors" "sync" "time" @@ -15,42 +16,62 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/util" ) +// Duration that we keep track of old nonces per session, to allow some out-of-order packet delivery +const nonceWindow = time.Second + +// A heap of nonces, used with a map[nonce]time to allow out-of-order packets a little time to arrive without rejecting them +// Less is backwards so the oldest node is the highest priority for Pop +type nonceHeap []crypto.BoxNonce + +func (h nonceHeap) Len() int { return len(h) } +func (h nonceHeap) Less(i, j int) bool { return h[i].Minus(&h[j]) > 0 } +func (h nonceHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } +func (h *nonceHeap) Push(x interface{}) { *h = append(*h, x.(crypto.BoxNonce)) } +func (h *nonceHeap) Pop() interface{} { + l := len(*h) + var n crypto.BoxNonce + n, *h = (*h)[l-1], (*h)[:l-1] + return n +} +func (h nonceHeap) peek() *crypto.BoxNonce { return &h[len(h)-1] } + // All the information we know about an active session. // This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API. type sessionInfo struct { - mutex sync.Mutex // Protects all of the below, use it any time you read/chance the contents of a session - core *Core // - reconfigure chan chan error // - theirAddr address.Address // - theirSubnet address.Subnet // - theirPermPub crypto.BoxPubKey // - theirSesPub crypto.BoxPubKey // - mySesPub crypto.BoxPubKey // - mySesPriv crypto.BoxPrivKey // - sharedSesKey crypto.BoxSharedKey // derived from session keys - theirHandle crypto.Handle // - myHandle crypto.Handle // - theirNonce crypto.BoxNonce // - theirNonceMask uint64 // - myNonce crypto.BoxNonce // - theirMTU uint16 // - myMTU uint16 // - wasMTUFixed bool // Was the MTU fixed by a receive error? - timeOpened time.Time // Time the sessino was opened - time time.Time // Time we last received a packet - mtuTime time.Time // time myMTU was last changed - pingTime time.Time // time the first ping was sent since the last received packet - pingSend time.Time // time the last ping was sent - coords []byte // coords of destination - reset bool // reset if coords change - tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation - bytesSent uint64 // Bytes of real traffic sent in this session - bytesRecvd uint64 // Bytes of real traffic received in this session - init chan struct{} // Closed when the first session pong arrives, used to signal that the session is ready for initial use - cancel util.Cancellation // Used to terminate workers - fromRouter chan wire_trafficPacket // Received packets go here, to be decrypted by the session - recv chan []byte // Decrypted packets go here, picked up by the associated Conn - send chan FlowKeyMessage // Packets with optional flow key go here, to be encrypted and sent + mutex sync.Mutex // Protects all of the below, use it any time you read/chance the contents of a session + core *Core // + reconfigure chan chan error // + theirAddr address.Address // + theirSubnet address.Subnet // + theirPermPub crypto.BoxPubKey // + theirSesPub crypto.BoxPubKey // + mySesPub crypto.BoxPubKey // + mySesPriv crypto.BoxPrivKey // + sharedSesKey crypto.BoxSharedKey // derived from session keys + theirHandle crypto.Handle // + myHandle crypto.Handle // + theirNonce crypto.BoxNonce // + theirNonceHeap nonceHeap // priority queue to keep track of the lowest nonce we recently accepted + theirNonceMap map[crypto.BoxNonce]time.Time // time we added each nonce to the heap + myNonce crypto.BoxNonce // + theirMTU uint16 // + myMTU uint16 // + wasMTUFixed bool // Was the MTU fixed by a receive error? + timeOpened time.Time // Time the sessino was opened + time time.Time // Time we last received a packet + mtuTime time.Time // time myMTU was last changed + pingTime time.Time // time the first ping was sent since the last received packet + pingSend time.Time // time the last ping was sent + coords []byte // coords of destination + reset bool // reset if coords change + tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation + bytesSent uint64 // Bytes of real traffic sent in this session + bytesRecvd uint64 // Bytes of real traffic received in this session + init chan struct{} // Closed when the first session pong arrives, used to signal that the session is ready for initial use + cancel util.Cancellation // Used to terminate workers + fromRouter chan wire_trafficPacket // Received packets go here, to be decrypted by the session + recv chan []byte // Decrypted packets go here, picked up by the associated Conn + send chan FlowKeyMessage // Packets with optional flow key go here, to be encrypted and sent } func (sinfo *sessionInfo) doFunc(f func()) { @@ -87,7 +108,8 @@ func (s *sessionInfo) update(p *sessionPing) bool { s.theirHandle = p.Handle s.sharedSesKey = *crypto.GetSharedKey(&s.mySesPriv, &s.theirSesPub) s.theirNonce = crypto.BoxNonce{} - s.theirNonceMask = 0 + s.theirNonceHeap = nil + s.theirNonceMap = make(map[crypto.BoxNonce]time.Time) } if p.MTU >= 1280 || p.MTU == 0 { s.theirMTU = p.MTU @@ -400,27 +422,40 @@ func (sinfo *sessionInfo) getMTU() uint16 { // Checks if a packet's nonce is recent enough to fall within the window of allowed packets, and not already received. func (sinfo *sessionInfo) nonceIsOK(theirNonce *crypto.BoxNonce) bool { // The bitmask is to allow for some non-duplicate out-of-order packets - diff := theirNonce.Minus(&sinfo.theirNonce) - if diff > 0 { + if theirNonce.Minus(&sinfo.theirNonce) > 0 { + // This is newer than the newest nonce we've seen return true } - return ^sinfo.theirNonceMask&(0x01< 0 { + if theirNonce.Minus(sinfo.theirNonceHeap.peek()) > 0 { + if _, isIn := sinfo.theirNonceMap[*theirNonce]; !isIn { + // This nonce is recent enough that we keep track of older nonces, but it's not one we've seen yet + return true + } + } + } + return false } // Updates the nonce mask by (possibly) shifting the bitmask and setting the bit corresponding to this nonce to 1, and then updating the most recent nonce func (sinfo *sessionInfo) updateNonce(theirNonce *crypto.BoxNonce) { - // Shift nonce mask if needed - // Set bit - diff := theirNonce.Minus(&sinfo.theirNonce) - if diff > 0 { - // This nonce is newer, so shift the window before setting the bit, and update theirNonce in the session info. - sinfo.theirNonceMask <<= uint64(diff) - sinfo.theirNonceMask &= 0x01 - sinfo.theirNonce = *theirNonce - } else { - // This nonce is older, so set the bit but do not shift the window. - sinfo.theirNonceMask &= 0x01 << uint64(-diff) + // Start with some cleanup + for len(sinfo.theirNonceHeap) > 64 { + if time.Since(sinfo.theirNonceMap[*sinfo.theirNonceHeap.peek()]) < nonceWindow { + // This nonce is still fairly new, so keep it around + break + } + // TODO? reallocate the map in some cases, to free unused map space? + delete(sinfo.theirNonceMap, *sinfo.theirNonceHeap.peek()) + heap.Pop(&sinfo.theirNonceHeap) } + if theirNonce.Minus(&sinfo.theirNonce) > 0 { + // This nonce is the newest we've seen, so make a note of that + sinfo.theirNonce = *theirNonce + } + // Add it to the heap/map so we know not to allow it again + heap.Push(&sinfo.theirNonceHeap, *theirNonce) + sinfo.theirNonceMap[*theirNonce] = time.Now() } // Resets all sessions to an uninitialized state. From fd5f3ca7649c9167290c76c3562c314f42121c98 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 16 Aug 2019 23:07:40 -0500 Subject: [PATCH 0226/1109] fix heap pop order --- src/yggdrasil/session.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index b675b469..57522209 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -20,11 +20,10 @@ import ( const nonceWindow = time.Second // A heap of nonces, used with a map[nonce]time to allow out-of-order packets a little time to arrive without rejecting them -// Less is backwards so the oldest node is the highest priority for Pop type nonceHeap []crypto.BoxNonce func (h nonceHeap) Len() int { return len(h) } -func (h nonceHeap) Less(i, j int) bool { return h[i].Minus(&h[j]) > 0 } +func (h nonceHeap) Less(i, j int) bool { return h[i].Minus(&h[j]) < 0 } func (h nonceHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } func (h *nonceHeap) Push(x interface{}) { *h = append(*h, x.(crypto.BoxNonce)) } func (h *nonceHeap) Pop() interface{} { From 57e7acdda806399484f480a8d818efaa993a560b Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 17 Aug 2019 12:42:17 -0500 Subject: [PATCH 0227/1109] Update CHANGELOG.md --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0057a005..9cea99c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. --> ## [0.3.7] - 2019-08-14 +### Changed +- The switch should now forward packets along a single path more consistently in cases where congestion is low and multiple equal-length paths exist, which should improve stability and result in fewer out-of-order packets +- Sessions should now be more tolerant of out-of-order packets, by replacing a bitmask with a variable sized heap+map structure to track recently received nonces, which should reduce the number of packets dropped due to reordering when multiple paths are used or multiple independent flows are transmitted through the same session + ### Fixed - A number of significant performance regressions introduced in version 0.3.6 have been fixed, resulting in better performance - Flow labels are now used to prioritise traffic flows again correctly @@ -34,7 +38,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - The admin socket now returns `coords` as a correct array of unsigned 64-bit integers, rather than the internal representation - The admin socket now returns `box_pub_key` in string format again - Sessions no longer leak/block when no listener (e.g. TUN/TAP) is configured -- Incoming session connections no longer block when a session already exists, which reduces in less leaked goroutines +- Incoming session connections no longer block when a session already exists, which results in less leaked goroutines - Flooded sessions will no longer block other sessions - Searches are now cleaned up properly and a couple of edge-cases with duplicate searches have been fixed - A number of minor allocation and pointer fixes From 039dd98f0de08170856295a3152c5ad93aab59e2 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 17 Aug 2019 12:46:34 -0500 Subject: [PATCH 0228/1109] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cea99c1..844409f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed - The switch should now forward packets along a single path more consistently in cases where congestion is low and multiple equal-length paths exist, which should improve stability and result in fewer out-of-order packets - Sessions should now be more tolerant of out-of-order packets, by replacing a bitmask with a variable sized heap+map structure to track recently received nonces, which should reduce the number of packets dropped due to reordering when multiple paths are used or multiple independent flows are transmitted through the same session +- The admin socket can no longer return a dotfile representation of the known parts of the network, this could be rebuilt by clients using information from `getSwitchPeers`,`getDHT` and `getSessions` ### Fixed - A number of significant performance regressions introduced in version 0.3.6 have been fixed, resulting in better performance From 62337bcd6481883694c7818a904911fc95adc17b Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 18 Aug 2019 12:17:54 -0500 Subject: [PATCH 0229/1109] allow links to send multiple packets at once, currently we still only bother to send 1 at a time from the switch level --- src/yggdrasil/link.go | 30 ++++++++++++++++-------------- src/yggdrasil/peer.go | 12 ++++++++---- src/yggdrasil/router.go | 18 ++++++++++-------- src/yggdrasil/stream.go | 32 +++++++++++--------------------- src/yggdrasil/switch.go | 6 +++--- 5 files changed, 48 insertions(+), 50 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index eca96ebd..4ce374b4 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -37,7 +37,7 @@ type linkInfo struct { type linkInterfaceMsgIO interface { readMsg() ([]byte, error) - writeMsg([]byte) (int, error) + writeMsgs([][]byte) (int, error) close() error // These are temporary workarounds to stream semantics _sendMetaBytes([]byte) error @@ -207,11 +207,11 @@ func (intf *linkInterface) handler() error { intf.link.core.peers.removePeer(intf.peer.port) }() // Finish setting up the peer struct - out := make(chan []byte, 1) + out := make(chan [][]byte, 1) defer close(out) - intf.peer.out = func(msg []byte) { + intf.peer.out = func(msgs [][]byte) { defer func() { recover() }() - out <- msg + out <- msgs } intf.peer.linkOut = make(chan []byte, 1) themAddr := address.AddrForNodeID(crypto.GetNodeID(&intf.info.box)) @@ -234,12 +234,12 @@ func (intf *linkInterface) handler() error { interval := 4 * time.Second tcpTimer := time.NewTimer(interval) // used for backwards compat with old tcp defer util.TimerStop(tcpTimer) - send := func(bs []byte) { + send := func(bss [][]byte) { sendBlocked.Reset(time.Second) - intf.msgIO.writeMsg(bs) + size, _ := intf.msgIO.writeMsgs(bss) util.TimerStop(sendBlocked) select { - case signalSent <- len(bs) > 0: + case signalSent <- size > 0: default: } } @@ -247,7 +247,7 @@ func (intf *linkInterface) handler() error { // First try to send any link protocol traffic select { case msg := <-intf.peer.linkOut: - send(msg) + send([][]byte{msg}) continue default: } @@ -259,19 +259,21 @@ func (intf *linkInterface) handler() error { case <-tcpTimer.C: intf.link.core.log.Tracef("Sending (legacy) keep-alive to %s: %s, source %s", strings.ToUpper(intf.info.linkType), themString, intf.info.local) - send(nil) + send([][]byte{nil}) case <-sendAck: intf.link.core.log.Tracef("Sending ack to %s: %s, source %s", strings.ToUpper(intf.info.linkType), themString, intf.info.local) - send(nil) + send([][]byte{nil}) case msg := <-intf.peer.linkOut: - send(msg) - case msg, ok := <-out: + send([][]byte{msg}) + case msgs, ok := <-out: if !ok { return } - send(msg) - util.PutBytes(msg) + send(msgs) + for _, msg := range msgs { + util.PutBytes(msg) + } select { case signalReady <- struct{}{}: default: diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 06201f9b..379ca85b 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -109,7 +109,7 @@ type peer struct { linkOut (chan []byte) // used for protocol traffic (to bypass queues) doSend (chan struct{}) // tell the linkLoop to send a switchMsg dinfo (chan *dhtInfo) // used to keep the DHT working - out func([]byte) // Set up by whatever created the peers struct, used to send packets to other nodes + out func([][]byte) // Set up by whatever created the peers struct, used to send packets to other nodes close func() // Called when a peer is removed, to close the underlying connection, or via admin api } @@ -250,11 +250,15 @@ func (p *peer) handleTraffic(packet []byte, pTypeLen int) { } // This just calls p.out(packet) for now. -func (p *peer) sendPacket(packet []byte) { +func (p *peer) sendPackets(packets [][]byte) { // Is there ever a case where something more complicated is needed? // What if p.out blocks? - atomic.AddUint64(&p.bytesSent, uint64(len(packet))) - p.out(packet) + var size int + for _, packet := range packets { + size += len(packet) + } + atomic.AddUint64(&p.bytesSent, uint64(size)) + p.out(packets) } // This wraps the packet in the inner (ephemeral) and outer (permanent) crypto layers. diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 7e2a325a..bdead848 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -39,10 +39,10 @@ type router struct { reconfigure chan chan error addr address.Address subnet address.Subnet - in <-chan []byte // packets we received from the network, link to peer's "out" - out func([]byte) // packets we're sending to the network, link to peer's "in" - reset chan struct{} // signal that coords changed (re-init sessions/dht) - admin chan func() // pass a lambda for the admin socket to query stuff + in <-chan [][]byte // packets we received from the network, link to peer's "out" + out func([]byte) // packets we're sending to the network, link to peer's "in" + reset chan struct{} // signal that coords changed (re-init sessions/dht) + admin chan func() // pass a lambda for the admin socket to query stuff nodeinfo nodeinfo } @@ -52,7 +52,7 @@ func (r *router) init(core *Core) { r.reconfigure = make(chan chan error, 1) r.addr = *address.AddrForNodeID(&r.core.dht.nodeID) r.subnet = *address.SubnetForNodeID(&r.core.dht.nodeID) - in := make(chan []byte, 1) // TODO something better than this... + in := make(chan [][]byte, 1) // TODO something better than this... self := linkInterface{ name: "(self)", info: linkInfo{ @@ -62,7 +62,7 @@ func (r *router) init(core *Core) { }, } p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &crypto.BoxSharedKey{}, &self, nil) - p.out = func(packet []byte) { in <- packet } + p.out = func(packets [][]byte) { in <- packets } r.in = in out := make(chan []byte, 32) go func() { @@ -114,8 +114,10 @@ func (r *router) mainLoop() { defer ticker.Stop() for { select { - case p := <-r.in: - r.handleIn(p) + case ps := <-r.in: + for _, p := range ps { + r.handleIn(p) + } case info := <-r.core.dht.peers: r.core.dht.insertPeer(info) case <-r.reset: diff --git a/src/yggdrasil/stream.go b/src/yggdrasil/stream.go index 56d4754a..4ab37c29 100644 --- a/src/yggdrasil/stream.go +++ b/src/yggdrasil/stream.go @@ -35,29 +35,19 @@ func (s *stream) init(rwc io.ReadWriteCloser) { } // writeMsg writes a message with stream padding, and is *not* thread safe. -func (s *stream) writeMsg(bs []byte) (int, error) { +func (s *stream) writeMsgs(bss [][]byte) (int, error) { buf := s.outputBuffer[:0] - buf = append(buf, streamMsg[:]) - l := wire_put_uint64(uint64(len(bs)), util.GetBytes()) - defer util.PutBytes(l) - buf = append(buf, l) - padLen := len(buf[0]) + len(buf[1]) - buf = append(buf, bs) - totalLen := padLen + len(bs) - s.outputBuffer = buf[:0] // So we can reuse the same underlying array later - var bn int - for bn < totalLen { - n, err := buf.WriteTo(s.rwc) - bn += int(n) - if err != nil { - l := bn - padLen - if l < 0 { - l = 0 - } - return l, err - } + var written int + for _, bs := range bss { + buf = append(buf, streamMsg[:]) + buf = append(buf, wire_encode_uint64(uint64(len(bs)))) + buf = append(buf, bs) + written += len(bs) } - return len(bs), nil + s.outputBuffer = buf[:0] // So we can reuse the same underlying array later + _, err := buf.WriteTo(s.rwc) + // TODO only include number of bytes from bs *successfully* written? + return written, err } // readMsg reads a message from the stream, accounting for stream padding, and is *not* thread safe. diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index d092625b..9be85542 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -709,7 +709,7 @@ func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) boo if best != nil { // Send to the best idle next hop delete(idle, best.port) - best.sendPacket(packet) + best.sendPackets([][]byte{packet}) return true } // Didn't find anyone idle to send it to @@ -812,7 +812,7 @@ func (t *switchTable) handleIdle(port switchPort) bool { // Need to update the map, since buf was retrieved by value t.queues.bufs[best] = buf } - to.sendPacket(packet.bytes) + to.sendPackets([][]byte{packet.bytes}) return true } else { return false @@ -826,7 +826,7 @@ func (t *switchTable) doWorker() { // Keep sending packets to the router self := t.core.peers.getPorts()[0] for bs := range sendingToRouter { - self.sendPacket(bs) + self.sendPackets([][]byte{bs}) } }() go func() { From 8af1a7086c3aabcd490711a5609d019afa3606ec Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 18 Aug 2019 12:29:07 -0500 Subject: [PATCH 0230/1109] when a link becomes idle and packet are buffered that the link could send, send at least 65535 bytes worth instead of 1 packet, this reduces syscall overhead when small packets are sent through the network --- src/yggdrasil/debug.go | 6 ++-- src/yggdrasil/switch.go | 64 ++++++++++++++++++++++++----------------- 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index ff3bbe70..9f7707e2 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -556,8 +556,10 @@ func DEBUG_simLinkPeers(p, q *peer) { goWorkers := func(source, dest *peer) { source.linkOut = make(chan []byte, 1) send := make(chan []byte, 1) - source.out = func(bs []byte) { - send <- bs + source.out = func(bss [][]byte) { + for _, bs := range bss { + send <- bs + } } go source.linkLoop() go func() { diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 9be85542..cc316d16 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -784,39 +784,49 @@ func (t *switchTable) handleIdle(port switchPort) bool { if to == nil { return true } - var best string - var bestPriority float64 + var packets [][]byte + var psize int t.queues.cleanup(t) now := time.Now() - for streamID, buf := range t.queues.bufs { - // Filter over the streams that this node is closer to - // Keep the one with the smallest queue - packet := buf.packets[0] - coords := switch_getPacketCoords(packet.bytes) - priority := float64(now.Sub(packet.time)) / float64(buf.size) - if priority > bestPriority && t.portIsCloser(coords, port) { - best = streamID - bestPriority = priority + for psize < 65535 { + var best string + var bestPriority float64 + for streamID, buf := range t.queues.bufs { + // Filter over the streams that this node is closer to + // Keep the one with the smallest queue + packet := buf.packets[0] + coords := switch_getPacketCoords(packet.bytes) + priority := float64(now.Sub(packet.time)) / float64(buf.size) + if priority > bestPriority && t.portIsCloser(coords, port) { + best = streamID + bestPriority = priority + } } - } - if bestPriority != 0 { - buf := t.queues.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.size -= uint64(len(packet.bytes)) - t.queues.size -= uint64(len(packet.bytes)) - if len(buf.packets) == 0 { - delete(t.queues.bufs, best) + if bestPriority != 0 { + buf := t.queues.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.size -= uint64(len(packet.bytes)) + t.queues.size -= uint64(len(packet.bytes)) + if len(buf.packets) == 0 { + delete(t.queues.bufs, best) + } else { + // Need to update the map, since buf was retrieved by value + t.queues.bufs[best] = buf + } + packets = append(packets, packet.bytes) + psize += len(packet.bytes) } else { - // Need to update the map, since buf was retrieved by value - t.queues.bufs[best] = buf + // Finished finding packets + break } - to.sendPackets([][]byte{packet.bytes}) - return true - } else { - return false } + if len(packets) > 0 { + to.sendPackets(packets) + return true + } + return false } // The switch worker does routing lookups and sends packets to where they need to be From 2a629880fd0ef26ca8696431c3b120f5e27837f3 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 19 Aug 2019 10:28:30 +0100 Subject: [PATCH 0231/1109] Rename crypto-key config options, improve control flow --- cmd/yggdrasil/main.go | 18 ++++++++ src/config/config.go | 10 ++-- src/tuntap/admin.go | 24 +++++----- src/tuntap/ckr.go | 74 ++++++++++++++--------------- src/tuntap/iface.go | 105 +++++++++++++++++++++++++++--------------- 5 files changed, 140 insertions(+), 91 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 15d7d226..2fa10720 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -80,6 +80,24 @@ func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *config if listen, ok := dat["Listen"].(string); ok { dat["Listen"] = []string{listen} } + if tunnelrouting, ok := dat["TunnelRouting"].(map[string]interface{}); ok { + if c, ok := tunnelrouting["IPv4Sources"]; ok { + delete(tunnelrouting, "IPv4Sources") + tunnelrouting["IPv4LocalSubnets"] = c + } + if c, ok := tunnelrouting["IPv6Sources"]; ok { + delete(tunnelrouting, "IPv6Sources") + tunnelrouting["IPv6LocalSubnets"] = c + } + if c, ok := tunnelrouting["IPv4Destinations"]; ok { + delete(tunnelrouting, "IPv4Destinations") + tunnelrouting["IPv4RemoteSubnets"] = c + } + if c, ok := tunnelrouting["IPv6Destinations"]; ok { + delete(tunnelrouting, "IPv6Destinations") + tunnelrouting["IPv6RemoteSubnets"] = c + } + } // Sanitise the config confJson, err := json.Marshal(dat) if err != nil { diff --git a/src/config/config.go b/src/config/config.go index a7bbbacf..6c127552 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -74,11 +74,11 @@ type SessionFirewall struct { // TunnelRouting contains the crypto-key routing tables for tunneling type TunnelRouting struct { - Enable bool `comment:"Enable or disable tunnel routing."` - IPv6Destinations map[string]string `comment:"IPv6 CIDR subnets, mapped to the EncryptionPublicKey to which they\nshould be routed, e.g. { \"aaaa:bbbb:cccc::/e\": \"boxpubkey\", ... }"` - IPv6Sources []string `comment:"Optional IPv6 source subnets which are allowed to be tunnelled in\naddition to this node's Yggdrasil address/subnet. If not\nspecified, only traffic originating from this node's Yggdrasil\naddress or subnet will be tunnelled."` - IPv4Destinations map[string]string `comment:"IPv4 CIDR subnets, mapped to the EncryptionPublicKey to which they\nshould be routed, e.g. { \"a.b.c.d/e\": \"boxpubkey\", ... }"` - IPv4Sources []string `comment:"IPv4 source subnets which are allowed to be tunnelled. Unlike for\nIPv6, this option is required for bridging IPv4 traffic. Only\ntraffic with a source matching these subnets will be tunnelled."` + Enable bool `comment:"Enable or disable tunnel routing."` + IPv6RemoteSubnets map[string]string `comment:"IPv6 subnets belonging to remote nodes, mapped to the node's public\nkey, e.g. { \"aaaa:bbbb:cccc::/e\": \"boxpubkey\", ... }"` + IPv6LocalSubnets []string `comment:"IPv6 subnets belonging to this node's end of the tunnels. Only traffic\nfrom these ranges (or the Yggdrasil node's IPv6 address/subnet)\nwill be tunnelled."` + IPv4RemoteSubnets map[string]string `comment:"IPv4 subnets belonging to remote nodes, mapped to the node's public\nkey, e.g. { \"a.b.c.d/e\": \"boxpubkey\", ... }"` + IPv4LocalSubnets []string `comment:"IPv4 subnets belonging to this node's end of the tunnels. Only traffic\nfrom these ranges will be tunnelled."` } // SwitchOptions contains tuning options for the switch diff --git a/src/tuntap/admin.go b/src/tuntap/admin.go index 21c7048d..778c03ae 100644 --- a/src/tuntap/admin.go +++ b/src/tuntap/admin.go @@ -66,15 +66,15 @@ func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { t.ckr.setEnabled(enabled) return admin.Info{"enabled": enabled}, nil }) - a.AddHandler("addSourceSubnet", []string{"subnet"}, func(in admin.Info) (admin.Info, error) { - if err := t.ckr.addSourceSubnet(in["subnet"].(string)); err == nil { + a.AddHandler("addLocalSubnet", []string{"subnet"}, func(in admin.Info) (admin.Info, error) { + if err := t.ckr.addLocalSubnet(in["subnet"].(string)); err == nil { return admin.Info{"added": []string{in["subnet"].(string)}}, nil } else { return admin.Info{"not_added": []string{in["subnet"].(string)}}, errors.New("Failed to add source subnet") } }) - a.AddHandler("addRoute", []string{"subnet", "box_pub_key"}, func(in admin.Info) (admin.Info, error) { - if err := t.ckr.addRoute(in["subnet"].(string), in["box_pub_key"].(string)); err == nil { + a.AddHandler("addRemoteSubnet", []string{"subnet", "box_pub_key"}, func(in admin.Info) (admin.Info, error) { + if err := t.ckr.addRemoteSubnet(in["subnet"].(string), in["box_pub_key"].(string)); err == nil { return admin.Info{"added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil } else { return admin.Info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to add route") @@ -87,8 +87,8 @@ func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { subnets = append(subnets, subnet.String()) } } - getSourceSubnets(t.ckr.ipv4sources) - getSourceSubnets(t.ckr.ipv6sources) + getSourceSubnets(t.ckr.ipv4locals) + getSourceSubnets(t.ckr.ipv6locals) return admin.Info{"source_subnets": subnets}, nil }) a.AddHandler("getRoutes", []string{}, func(in admin.Info) (admin.Info, error) { @@ -98,19 +98,19 @@ func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { routes[ckr.subnet.String()] = hex.EncodeToString(ckr.destination[:]) } } - getRoutes(t.ckr.ipv4routes) - getRoutes(t.ckr.ipv6routes) + getRoutes(t.ckr.ipv4remotes) + getRoutes(t.ckr.ipv6remotes) return admin.Info{"routes": routes}, nil }) - a.AddHandler("removeSourceSubnet", []string{"subnet"}, func(in admin.Info) (admin.Info, error) { - if err := t.ckr.removeSourceSubnet(in["subnet"].(string)); err == nil { + a.AddHandler("removeLocalSubnet", []string{"subnet"}, func(in admin.Info) (admin.Info, error) { + if err := t.ckr.removeLocalSubnet(in["subnet"].(string)); err == nil { return admin.Info{"removed": []string{in["subnet"].(string)}}, nil } else { return admin.Info{"not_removed": []string{in["subnet"].(string)}}, errors.New("Failed to remove source subnet") } }) - a.AddHandler("removeRoute", []string{"subnet", "box_pub_key"}, func(in admin.Info) (admin.Info, error) { - if err := t.ckr.removeRoute(in["subnet"].(string), in["box_pub_key"].(string)); err == nil { + a.AddHandler("removeRemoteSubnet", []string{"subnet", "box_pub_key"}, func(in admin.Info) (admin.Info, error) { + if err := t.ckr.removeRemoteSubnet(in["subnet"].(string), in["box_pub_key"].(string)); err == nil { return admin.Info{"removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil } else { return admin.Info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route") diff --git a/src/tuntap/ckr.go b/src/tuntap/ckr.go index 80e3e42f..f93cb7cf 100644 --- a/src/tuntap/ckr.go +++ b/src/tuntap/ckr.go @@ -21,12 +21,12 @@ type cryptokey struct { tun *TunAdapter enabled atomic.Value // bool reconfigure chan chan error - ipv4routes []cryptokey_route - ipv6routes []cryptokey_route + ipv4remotes []cryptokey_route + ipv6remotes []cryptokey_route ipv4cache map[address.Address]cryptokey_route ipv6cache map[address.Address]cryptokey_route - ipv4sources []net.IPNet - ipv6sources []net.IPNet + ipv4locals []net.IPNet + ipv6locals []net.IPNet mutexroutes sync.RWMutex mutexcaches sync.RWMutex mutexsources sync.RWMutex @@ -66,42 +66,42 @@ func (c *cryptokey) configure() error { // Clear out existing routes c.mutexroutes.Lock() - c.ipv6routes = make([]cryptokey_route, 0) - c.ipv4routes = make([]cryptokey_route, 0) + c.ipv6remotes = make([]cryptokey_route, 0) + c.ipv4remotes = make([]cryptokey_route, 0) c.mutexroutes.Unlock() // Add IPv6 routes - for ipv6, pubkey := range current.TunnelRouting.IPv6Destinations { - if err := c.addRoute(ipv6, pubkey); err != nil { + for ipv6, pubkey := range current.TunnelRouting.IPv6RemoteSubnets { + if err := c.addRemoteSubnet(ipv6, pubkey); err != nil { return err } } // Add IPv4 routes - for ipv4, pubkey := range current.TunnelRouting.IPv4Destinations { - if err := c.addRoute(ipv4, pubkey); err != nil { + for ipv4, pubkey := range current.TunnelRouting.IPv4RemoteSubnets { + if err := c.addRemoteSubnet(ipv4, pubkey); err != nil { return err } } // Clear out existing sources c.mutexsources.Lock() - c.ipv6sources = make([]net.IPNet, 0) - c.ipv4sources = make([]net.IPNet, 0) + c.ipv6locals = make([]net.IPNet, 0) + c.ipv4locals = make([]net.IPNet, 0) c.mutexsources.Unlock() // Add IPv6 sources - c.ipv6sources = make([]net.IPNet, 0) - for _, source := range current.TunnelRouting.IPv6Sources { - if err := c.addSourceSubnet(source); err != nil { + c.ipv6locals = make([]net.IPNet, 0) + for _, source := range current.TunnelRouting.IPv6LocalSubnets { + if err := c.addLocalSubnet(source); err != nil { return err } } // Add IPv4 sources - c.ipv4sources = make([]net.IPNet, 0) - for _, source := range current.TunnelRouting.IPv4Sources { - if err := c.addSourceSubnet(source); err != nil { + c.ipv4locals = make([]net.IPNet, 0) + for _, source := range current.TunnelRouting.IPv4LocalSubnets { + if err := c.addLocalSubnet(source); err != nil { return err } } @@ -128,8 +128,8 @@ func (c *cryptokey) isEnabled() bool { // Check whether the given address (with the address length specified in bytes) // matches either the current node's address, the node's routed subnet or the -// list of subnets specified in IPv4Sources/IPv6Sources. -func (c *cryptokey) isValidSource(addr address.Address, addrlen int) bool { +// list of subnets specified in ipv4locals/ipv6locals. +func (c *cryptokey) isValidLocalAddress(addr address.Address, addrlen int) bool { c.mutexsources.RLock() defer c.mutexsources.RUnlock() @@ -154,9 +154,9 @@ func (c *cryptokey) isValidSource(addr address.Address, addrlen int) bool { // Check if the prefix is IPv4 or IPv6 if addrlen == net.IPv6len { - routingsources = &c.ipv6sources + routingsources = &c.ipv6locals } else if addrlen == net.IPv4len { - routingsources = &c.ipv4sources + routingsources = &c.ipv4locals } else { return false } @@ -174,7 +174,7 @@ func (c *cryptokey) isValidSource(addr address.Address, addrlen int) bool { // Adds a source subnet, which allows traffic with these source addresses to // be tunnelled using crypto-key routing. -func (c *cryptokey) addSourceSubnet(cidr string) error { +func (c *cryptokey) addLocalSubnet(cidr string) error { c.mutexsources.Lock() defer c.mutexsources.Unlock() @@ -192,9 +192,9 @@ func (c *cryptokey) addSourceSubnet(cidr string) error { // Check if the prefix is IPv4 or IPv6 if prefixsize == net.IPv6len*8 { - routingsources = &c.ipv6sources + routingsources = &c.ipv6locals } else if prefixsize == net.IPv4len*8 { - routingsources = &c.ipv4sources + routingsources = &c.ipv4locals } else { return errors.New("Unexpected prefix size") } @@ -214,7 +214,7 @@ func (c *cryptokey) addSourceSubnet(cidr string) error { // Adds a destination route for the given CIDR to be tunnelled to the node // with the given BoxPubKey. -func (c *cryptokey) addRoute(cidr string, dest string) error { +func (c *cryptokey) addRemoteSubnet(cidr string, dest string) error { c.mutexroutes.Lock() c.mutexcaches.Lock() defer c.mutexroutes.Unlock() @@ -235,10 +235,10 @@ func (c *cryptokey) addRoute(cidr string, dest string) error { // Check if the prefix is IPv4 or IPv6 if prefixsize == net.IPv6len*8 { - routingtable = &c.ipv6routes + routingtable = &c.ipv6remotes routingcache = &c.ipv6cache } else if prefixsize == net.IPv4len*8 { - routingtable = &c.ipv4routes + routingtable = &c.ipv4remotes routingcache = &c.ipv4cache } else { return errors.New("Unexpected prefix size") @@ -328,9 +328,9 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c // Check if the prefix is IPv4 or IPv6 if addrlen == net.IPv6len { - routingtable = &c.ipv6routes + routingtable = &c.ipv6remotes } else if addrlen == net.IPv4len { - routingtable = &c.ipv4routes + routingtable = &c.ipv4remotes } else { return crypto.BoxPubKey{}, errors.New("Unexpected prefix size") } @@ -339,7 +339,7 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c ip := make(net.IP, addrlen) copy(ip[:addrlen], addr[:]) - // Check if we have a route. At this point c.ipv6routes should be + // Check if we have a route. At this point c.ipv6remotes should be // pre-sorted so that the most specific routes are first for _, route := range *routingtable { // Does this subnet match the given IP? @@ -371,7 +371,7 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c // Removes a source subnet, which allows traffic with these source addresses to // be tunnelled using crypto-key routing. -func (c *cryptokey) removeSourceSubnet(cidr string) error { +func (c *cryptokey) removeLocalSubnet(cidr string) error { c.mutexsources.Lock() defer c.mutexsources.Unlock() @@ -389,9 +389,9 @@ func (c *cryptokey) removeSourceSubnet(cidr string) error { // Check if the prefix is IPv4 or IPv6 if prefixsize == net.IPv6len*8 { - routingsources = &c.ipv6sources + routingsources = &c.ipv6locals } else if prefixsize == net.IPv4len*8 { - routingsources = &c.ipv4sources + routingsources = &c.ipv4locals } else { return errors.New("Unexpected prefix size") } @@ -409,7 +409,7 @@ func (c *cryptokey) removeSourceSubnet(cidr string) error { // Removes a destination route for the given CIDR to be tunnelled to the node // with the given BoxPubKey. -func (c *cryptokey) removeRoute(cidr string, dest string) error { +func (c *cryptokey) removeRemoteSubnet(cidr string, dest string) error { c.mutexroutes.Lock() c.mutexcaches.Lock() defer c.mutexroutes.Unlock() @@ -430,10 +430,10 @@ func (c *cryptokey) removeRoute(cidr string, dest string) error { // Check if the prefix is IPv4 or IPv6 if prefixsize == net.IPv6len*8 { - routingtable = &c.ipv6routes + routingtable = &c.ipv6remotes routingcache = &c.ipv6cache } else if prefixsize == net.IPv4len*8 { - routingtable = &c.ipv4routes + routingtable = &c.ipv4remotes routingcache = &c.ipv4cache } else { return errors.New("Unexpected prefix size") diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 670f7829..55ddd1b4 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -21,25 +21,50 @@ func (tun *TunAdapter) writer() error { if n == 0 { continue } - if tun.iface.IsTAP() { - var dstAddr address.Address - if b[0]&0xf0 == 0x60 { - if len(b) < 40 { - //panic("Tried to send a packet shorter than an IPv6 header...") - util.PutBytes(b) - continue - } - copy(dstAddr[:16], b[24:]) - } else if b[0]&0xf0 == 0x40 { - if len(b) < 20 { - //panic("Tried to send a packet shorter than an IPv4 header...") - util.PutBytes(b) - continue - } - copy(dstAddr[:4], b[16:]) - } else { - return errors.New("Invalid address family") + var dstAddr address.Address + var addrlen int + // Check whether the packet is IPv4, IPv6 or neither + if b[0]&0xf0 == 0x60 { + // IPv6 packet found + if len(b) < 40 { + // Packet was too short + util.PutBytes(b) + continue } + // Extract the destination IPv6 address + copy(dstAddr[:16], b[24:]) + addrlen = 16 + } else if b[0]&0xf0 == 0x40 { + // IPv4 packet found + if len(b) < 20 { + // Packet was too short + util.PutBytes(b) + continue + } + // Extract the destination IPv4 address + copy(dstAddr[:4], b[16:]) + addrlen = 4 + } else { + // Neither IPv4 nor IPv6 + return errors.New("Invalid address family") + } + // Check the crypto-key routing rules next + if tun.ckr.isEnabled() { + if !tun.ckr.isValidLocalAddress(dstAddr, addrlen) { + util.PutBytes(b) + continue + } + } else { + if addrlen != 16 { + util.PutBytes(b) + continue + } + if !bytes.Equal(tun.addr[:16], dstAddr[:16]) && !bytes.Equal(tun.subnet[:8], dstAddr[:8]) { + util.PutBytes(b) + continue + } + } + if tun.iface.IsTAP() { sendndp := func(dstAddr address.Address) { neigh, known := tun.icmpv6.getNeighbor(dstAddr) known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30) @@ -69,7 +94,6 @@ func (tun *TunAdapter) writer() error { } else { // Nothing has been discovered, try to discover the destination sendndp(tun.addr) - } if peerknown { var proto ethernet.Ethertype @@ -187,26 +211,33 @@ func (tun *TunAdapter) readerPacketHandler(ch chan []byte) { tun.log.Traceln("Unknown packet type, dropping") continue } - if tun.ckr.isEnabled() && !tun.ckr.isValidSource(srcAddr, addrlen) { - // The packet had a source address that doesn't belong to us or our - // configured crypto-key routing source subnets - continue - } - if !dstAddr.IsValid() && !dstSnet.IsValid() { - if key, err := tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { - // A public key was found, get the node ID for the search - dstNodeID = crypto.GetNodeID(&key) - // Do a quick check to ensure that the node ID refers to a vaild - // Yggdrasil address or subnet - this might be superfluous - addr := *address.AddrForNodeID(dstNodeID) - copy(dstAddr[:], addr[:]) - copy(dstSnet[:], addr[:]) - // Are we certain we looked up a valid node? - if !dstAddr.IsValid() && !dstSnet.IsValid() { + if tun.ckr.isEnabled() { + if !tun.ckr.isValidLocalAddress(srcAddr, addrlen) { + continue + } + if !dstAddr.IsValid() && !dstSnet.IsValid() { + if key, err := tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { + // A public key was found, get the node ID for the search + dstNodeID = crypto.GetNodeID(&key) + // Do a quick check to ensure that the node ID refers to a vaild + // Yggdrasil address or subnet - this might be superfluous + addr := *address.AddrForNodeID(dstNodeID) + copy(dstAddr[:], addr[:]) + copy(dstSnet[:], addr[:]) + // Are we certain we looked up a valid node? + if !dstAddr.IsValid() && !dstSnet.IsValid() { + continue + } + } else { + // No public key was found in the CKR table so we've exhausted our options continue } - } else { - // No public key was found in the CKR table so we've exhausted our options + } + } else { + if addrlen != 16 { + continue + } + if !dstAddr.IsValid() && !dstSnet.IsValid() { continue } } From 834a6a6f1ae5c3fd8cd10a8903a774245962b690 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 19 Aug 2019 18:06:05 -0500 Subject: [PATCH 0232/1109] don't allocate a new child cancellation in Conn read/write calls if no deadline is set --- src/yggdrasil/conn.go | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 134f3cd2..25330aae 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -137,20 +137,23 @@ func (c *Conn) doSearch() { go func() { c.core.router.admin <- routerWork }() } -func (c *Conn) getDeadlineCancellation(value *atomic.Value) util.Cancellation { +func (c *Conn) getDeadlineCancellation(value *atomic.Value) (util.Cancellation, bool) { if deadline, ok := value.Load().(time.Time); ok { // A deadline is set, so return a Cancellation that uses it - return util.CancellationWithDeadline(c.session.cancel, deadline) + c := util.CancellationWithDeadline(c.session.cancel, deadline) + return c, true } else { - // No cancellation was set, so return a child cancellation with no timeout - return util.CancellationChild(c.session.cancel) + // No deadline was set, so just return the existinc cancellation and a dummy value + return c.session.cancel, false } } // Used internally by Read, the caller is responsible for util.PutBytes when they're done. func (c *Conn) ReadNoCopy() ([]byte, error) { - cancel := c.getDeadlineCancellation(&c.readDeadline) - defer cancel.Cancel(nil) + cancel, doCancel := c.getDeadlineCancellation(&c.readDeadline) + if doCancel { + defer cancel.Cancel(nil) + } // Wait for some traffic to come through from the session select { case <-cancel.Finished(): @@ -207,8 +210,10 @@ func (c *Conn) WriteNoCopy(msg FlowKeyMessage) error { } c.session.doFunc(sessionFunc) if err == nil { - cancel := c.getDeadlineCancellation(&c.writeDeadline) - defer cancel.Cancel(nil) + cancel, doCancel := c.getDeadlineCancellation(&c.writeDeadline) + if doCancel { + defer cancel.Cancel(nil) + } select { case <-cancel.Finished(): if cancel.Error() == util.CancellationTimeoutError { From 2b6462c8a9c73376f0849d81d050fae3736453d4 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 20 Aug 2019 09:38:27 +0100 Subject: [PATCH 0233/1109] Strict checking of Yggdrasil source/destination addresses --- src/tuntap/conn.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index 61cdb2b4..d1dc60dc 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -1,6 +1,7 @@ package tuntap import ( + "bytes" "errors" "time" @@ -70,6 +71,17 @@ func (s *tunConn) reader() (err error) { return e } } else if len(bs) > 0 { + if bs[0]&0xf0 == 0x60 { + switch { + case bs[8] == 0x02 && !bytes.Equal(s.addr[:16], bs[8:24]): // source + case bs[8] == 0x03 && !bytes.Equal(s.snet[:8], bs[8:16]): // source + case bs[24] == 0x02 && !bytes.Equal(s.tun.addr[:16], bs[24:40]): // destination + case bs[24] == 0x03 && !bytes.Equal(s.tun.subnet[:8], bs[24:32]): // destination + util.PutBytes(bs) + continue + default: + } + } s.tun.send <- bs s.stillAlive() } else { @@ -96,6 +108,16 @@ func (s *tunConn) writer() error { if !ok { return errors.New("send closed") } + if bs[0]&0xf0 == 0x60 { + switch { + case bs[8] == 0x02 && !bytes.Equal(s.tun.addr[:16], bs[8:24]): // source + case bs[8] == 0x03 && !bytes.Equal(s.tun.subnet[:8], bs[8:16]): // source + case bs[24] == 0x02 && !bytes.Equal(s.addr[:16], bs[24:40]): // destination + case bs[24] == 0x03 && !bytes.Equal(s.snet[:8], bs[24:32]): // destination + continue + default: + } + } msg := yggdrasil.FlowKeyMessage{ FlowKey: util.GetFlowKey(bs), Message: bs, From b6e67bc0ba9b56d1cde8609617f4b0b23a57bc00 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 20 Aug 2019 09:38:46 +0100 Subject: [PATCH 0234/1109] Check CKR remotes when receiving traffic --- src/tuntap/ckr.go | 38 +++++++++++++++++++------------------- src/tuntap/iface.go | 21 +++++++++++++++++---- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/tuntap/ckr.go b/src/tuntap/ckr.go index f93cb7cf..9ce0120b 100644 --- a/src/tuntap/ckr.go +++ b/src/tuntap/ckr.go @@ -27,9 +27,9 @@ type cryptokey struct { ipv6cache map[address.Address]cryptokey_route ipv4locals []net.IPNet ipv6locals []net.IPNet - mutexroutes sync.RWMutex + mutexremotes sync.RWMutex mutexcaches sync.RWMutex - mutexsources sync.RWMutex + mutexlocals sync.RWMutex } type cryptokey_route struct { @@ -65,10 +65,10 @@ func (c *cryptokey) configure() error { c.setEnabled(current.TunnelRouting.Enable) // Clear out existing routes - c.mutexroutes.Lock() + c.mutexremotes.Lock() c.ipv6remotes = make([]cryptokey_route, 0) c.ipv4remotes = make([]cryptokey_route, 0) - c.mutexroutes.Unlock() + c.mutexremotes.Unlock() // Add IPv6 routes for ipv6, pubkey := range current.TunnelRouting.IPv6RemoteSubnets { @@ -85,10 +85,10 @@ func (c *cryptokey) configure() error { } // Clear out existing sources - c.mutexsources.Lock() + c.mutexlocals.Lock() c.ipv6locals = make([]net.IPNet, 0) c.ipv4locals = make([]net.IPNet, 0) - c.mutexsources.Unlock() + c.mutexlocals.Unlock() // Add IPv6 sources c.ipv6locals = make([]net.IPNet, 0) @@ -130,8 +130,8 @@ func (c *cryptokey) isEnabled() bool { // matches either the current node's address, the node's routed subnet or the // list of subnets specified in ipv4locals/ipv6locals. func (c *cryptokey) isValidLocalAddress(addr address.Address, addrlen int) bool { - c.mutexsources.RLock() - defer c.mutexsources.RUnlock() + c.mutexlocals.RLock() + defer c.mutexlocals.RUnlock() ip := net.IP(addr[:addrlen]) @@ -175,8 +175,8 @@ func (c *cryptokey) isValidLocalAddress(addr address.Address, addrlen int) bool // Adds a source subnet, which allows traffic with these source addresses to // be tunnelled using crypto-key routing. func (c *cryptokey) addLocalSubnet(cidr string) error { - c.mutexsources.Lock() - defer c.mutexsources.Unlock() + c.mutexlocals.Lock() + defer c.mutexlocals.Unlock() // Is the CIDR we've been given valid? _, ipnet, err := net.ParseCIDR(cidr) @@ -215,9 +215,9 @@ func (c *cryptokey) addLocalSubnet(cidr string) error { // Adds a destination route for the given CIDR to be tunnelled to the node // with the given BoxPubKey. func (c *cryptokey) addRemoteSubnet(cidr string, dest string) error { - c.mutexroutes.Lock() + c.mutexremotes.Lock() c.mutexcaches.Lock() - defer c.mutexroutes.Unlock() + defer c.mutexremotes.Unlock() defer c.mutexcaches.Unlock() // Is the CIDR we've been given valid? @@ -323,8 +323,8 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c c.mutexcaches.RUnlock() - c.mutexroutes.RLock() - defer c.mutexroutes.RUnlock() + c.mutexremotes.RLock() + defer c.mutexremotes.RUnlock() // Check if the prefix is IPv4 or IPv6 if addrlen == net.IPv6len { @@ -366,14 +366,14 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c } // No route was found if we got to this point - return crypto.BoxPubKey{}, errors.New(fmt.Sprintf("No route to %s", ip.String())) + return crypto.BoxPubKey{}, fmt.Errorf("no route to %s", ip.String()) } // Removes a source subnet, which allows traffic with these source addresses to // be tunnelled using crypto-key routing. func (c *cryptokey) removeLocalSubnet(cidr string) error { - c.mutexsources.Lock() - defer c.mutexsources.Unlock() + c.mutexlocals.Lock() + defer c.mutexlocals.Unlock() // Is the CIDR we've been given valid? _, ipnet, err := net.ParseCIDR(cidr) @@ -410,9 +410,9 @@ func (c *cryptokey) removeLocalSubnet(cidr string) error { // Removes a destination route for the given CIDR to be tunnelled to the node // with the given BoxPubKey. func (c *cryptokey) removeRemoteSubnet(cidr string, dest string) error { - c.mutexroutes.Lock() + c.mutexremotes.Lock() c.mutexcaches.Lock() - defer c.mutexroutes.Unlock() + defer c.mutexremotes.Unlock() defer c.mutexcaches.Unlock() // Is the CIDR we've been given valid? diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 55ddd1b4..f5b88133 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -21,6 +21,7 @@ func (tun *TunAdapter) writer() error { if n == 0 { continue } + var srcAddr address.Address var dstAddr address.Address var addrlen int // Check whether the packet is IPv4, IPv6 or neither @@ -31,8 +32,9 @@ func (tun *TunAdapter) writer() error { util.PutBytes(b) continue } - // Extract the destination IPv6 address - copy(dstAddr[:16], b[24:]) + // Extract the IPv6 addresses + copy(srcAddr[:16], b[8:24]) + copy(dstAddr[:16], b[24:40]) addrlen = 16 } else if b[0]&0xf0 == 0x40 { // IPv4 packet found @@ -41,8 +43,9 @@ func (tun *TunAdapter) writer() error { util.PutBytes(b) continue } - // Extract the destination IPv4 address - copy(dstAddr[:4], b[16:]) + // Extract the IPv4 addresses + copy(srcAddr[:4], b[12:16]) + copy(dstAddr[:4], b[16:20]) addrlen = 4 } else { // Neither IPv4 nor IPv6 @@ -54,6 +57,16 @@ func (tun *TunAdapter) writer() error { util.PutBytes(b) continue } + if srcAddr[0] != 0x02 && srcAddr[0] != 0x03 { + // TODO: is this check useful? this doesn't actually guarantee that the + // packet came from the configured public key for that remote, just that + // it came from *a* configured remote. at this stage we have no ability + // to know which Conn or public key was involved + if _, err := tun.ckr.getPublicKeyForAddress(srcAddr, addrlen); err != nil { + util.PutBytes(b) + continue + } + } } else { if addrlen != 16 { util.PutBytes(b) From 4156aa30034f50f848725627dcf42cbdb6ef53bd Mon Sep 17 00:00:00 2001 From: Arceliar Date: Tue, 20 Aug 2019 18:10:08 -0500 Subject: [PATCH 0235/1109] move ckr checks into the tunConn code --- src/tuntap/ckr.go | 16 +----- src/tuntap/conn.go | 128 ++++++++++++++++++++++++++++++++++++++------ src/tuntap/iface.go | 97 ++++----------------------------- 3 files changed, 123 insertions(+), 118 deletions(-) diff --git a/src/tuntap/ckr.go b/src/tuntap/ckr.go index 9ce0120b..ad8c89d4 100644 --- a/src/tuntap/ckr.go +++ b/src/tuntap/ckr.go @@ -132,23 +132,9 @@ func (c *cryptokey) isEnabled() bool { func (c *cryptokey) isValidLocalAddress(addr address.Address, addrlen int) bool { c.mutexlocals.RLock() defer c.mutexlocals.RUnlock() - - ip := net.IP(addr[:addrlen]) - - if addrlen == net.IPv6len { - // Does this match our node's address? - if bytes.Equal(addr[:16], c.tun.addr[:16]) { - return true - } - - // Does this match our node's subnet? - if bytes.Equal(addr[:8], c.tun.subnet[:8]) { - return true - } - } - // Does it match a configured CKR source? if c.isEnabled() { + ip := net.IP(addr[:addrlen]) // Build our references to the routing sources var routingsources *[]net.IPNet diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index d1dc60dc..3fb7a544 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -6,6 +6,7 @@ import ( "time" "github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" "golang.org/x/net/icmp" @@ -71,16 +72,62 @@ func (s *tunConn) reader() (err error) { return e } } else if len(bs) > 0 { - if bs[0]&0xf0 == 0x60 { - switch { - case bs[8] == 0x02 && !bytes.Equal(s.addr[:16], bs[8:24]): // source - case bs[8] == 0x03 && !bytes.Equal(s.snet[:8], bs[8:16]): // source - case bs[24] == 0x02 && !bytes.Equal(s.tun.addr[:16], bs[24:40]): // destination - case bs[24] == 0x03 && !bytes.Equal(s.tun.subnet[:8], bs[24:32]): // destination - util.PutBytes(bs) - continue - default: + ipv4 := len(bs) > 20 && bs[0]&0xf0 == 0x40 + ipv6 := len(bs) > 40 && bs[0]&0xf0 == 0x60 + isCGA := true + // Check source addresses + switch { + case ipv6 && bs[8] == 0x02 && bytes.Equal(s.addr[:16], bs[8:24]): // source + case ipv6 && bs[8] == 0x03 && bytes.Equal(s.snet[:8], bs[8:16]): // source + default: + isCGA = false + } + // Check destiantion addresses + switch { + case ipv6 && bs[24] == 0x02 && bytes.Equal(s.tun.addr[:16], bs[24:40]): // destination + case ipv6 && bs[24] == 0x03 && bytes.Equal(s.tun.subnet[:8], bs[24:32]): // destination + default: + isCGA = false + } + // Decide how to handle the packet + var skip bool + switch { + case isCGA: // Allowed + case s.tun.ckr.isEnabled() && (ipv4 || ipv6): + var srcAddr address.Address + var dstAddr address.Address + var addrlen int + if ipv4 { + copy(srcAddr[:], bs[12:16]) + copy(dstAddr[:], bs[16:20]) + addrlen = 4 } + if ipv6 { + copy(srcAddr[:], bs[8:24]) + copy(dstAddr[:], bs[24:40]) + addrlen = 16 + } + if !s.tun.ckr.isValidLocalAddress(dstAddr, addrlen) { + // The destination address isn't in our CKR allowed range + skip = true + } else if key, err := s.tun.ckr.getPublicKeyForAddress(srcAddr, addrlen); err == nil { + srcNodeID := crypto.GetNodeID(&key) + if s.conn.RemoteAddr() == *srcNodeID { + // This is the one allowed CKR case, where source and destination addresses are both good + } else { + // The CKR key associated with this address doesn't match the sender's NodeID + skip = true + } + } else { + // We have no CKR route for this source address + skip = true + } + default: + skip = true + } + if skip { + util.PutBytes(bs) + continue } s.tun.send <- bs s.stillAlive() @@ -108,15 +155,62 @@ func (s *tunConn) writer() error { if !ok { return errors.New("send closed") } - if bs[0]&0xf0 == 0x60 { - switch { - case bs[8] == 0x02 && !bytes.Equal(s.tun.addr[:16], bs[8:24]): // source - case bs[8] == 0x03 && !bytes.Equal(s.tun.subnet[:8], bs[8:16]): // source - case bs[24] == 0x02 && !bytes.Equal(s.addr[:16], bs[24:40]): // destination - case bs[24] == 0x03 && !bytes.Equal(s.snet[:8], bs[24:32]): // destination - continue - default: + v4 := len(bs) > 20 && bs[0]&0xf0 == 0x40 + v6 := len(bs) > 40 && bs[0]&0xf0 == 0x60 + isCGA := true + // Check source addresses + switch { + case v6 && bs[8] == 0x02 && bytes.Equal(s.tun.addr[:16], bs[8:24]): // source + case v6 && bs[8] == 0x03 && bytes.Equal(s.tun.subnet[:8], bs[8:16]): // source + default: + isCGA = false + } + // Check destiantion addresses + switch { + case v6 && bs[24] == 0x02 && bytes.Equal(s.addr[:16], bs[24:40]): // destination + case v6 && bs[24] == 0x03 && bytes.Equal(s.snet[:8], bs[24:32]): // destination + default: + isCGA = false + } + // Decide how to handle the packet + var skip bool + switch { + case isCGA: // Allowed + case s.tun.ckr.isEnabled() && (v4 || v6): + var srcAddr address.Address + var dstAddr address.Address + var addrlen int + if v4 { + copy(srcAddr[:], bs[12:16]) + copy(dstAddr[:], bs[16:20]) + addrlen = 4 } + if v6 { + copy(srcAddr[:], bs[8:24]) + copy(dstAddr[:], bs[24:40]) + addrlen = 16 + } + if !s.tun.ckr.isValidLocalAddress(srcAddr, addrlen) { + // The source address isn't in our CKR allowed range + skip = true + } else if key, err := s.tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { + dstNodeID := crypto.GetNodeID(&key) + if s.conn.RemoteAddr() == *dstNodeID { + // This is the one allowed CKR case, where source and destination addresses are both good + } else { + // The CKR key associated with this address doesn't match the sender's NodeID + skip = true + } + } else { + // We have no CKR route for this destination address... why do we have the packet in the first place? + skip = true + } + default: + skip = true + } + if skip { + util.PutBytes(bs) + continue } msg := yggdrasil.FlowKeyMessage{ FlowKey: util.GetFlowKey(bs), diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index f5b88133..35657a9f 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -2,7 +2,6 @@ package tuntap import ( "bytes" - "errors" "net" "time" @@ -21,62 +20,6 @@ func (tun *TunAdapter) writer() error { if n == 0 { continue } - var srcAddr address.Address - var dstAddr address.Address - var addrlen int - // Check whether the packet is IPv4, IPv6 or neither - if b[0]&0xf0 == 0x60 { - // IPv6 packet found - if len(b) < 40 { - // Packet was too short - util.PutBytes(b) - continue - } - // Extract the IPv6 addresses - copy(srcAddr[:16], b[8:24]) - copy(dstAddr[:16], b[24:40]) - addrlen = 16 - } else if b[0]&0xf0 == 0x40 { - // IPv4 packet found - if len(b) < 20 { - // Packet was too short - util.PutBytes(b) - continue - } - // Extract the IPv4 addresses - copy(srcAddr[:4], b[12:16]) - copy(dstAddr[:4], b[16:20]) - addrlen = 4 - } else { - // Neither IPv4 nor IPv6 - return errors.New("Invalid address family") - } - // Check the crypto-key routing rules next - if tun.ckr.isEnabled() { - if !tun.ckr.isValidLocalAddress(dstAddr, addrlen) { - util.PutBytes(b) - continue - } - if srcAddr[0] != 0x02 && srcAddr[0] != 0x03 { - // TODO: is this check useful? this doesn't actually guarantee that the - // packet came from the configured public key for that remote, just that - // it came from *a* configured remote. at this stage we have no ability - // to know which Conn or public key was involved - if _, err := tun.ckr.getPublicKeyForAddress(srcAddr, addrlen); err != nil { - util.PutBytes(b) - continue - } - } - } else { - if addrlen != 16 { - util.PutBytes(b) - continue - } - if !bytes.Equal(tun.addr[:16], dstAddr[:16]) && !bytes.Equal(tun.subnet[:8], dstAddr[:8]) { - util.PutBytes(b) - continue - } - } if tun.iface.IsTAP() { sendndp := func(dstAddr address.Address) { neigh, known := tun.icmpv6.getNeighbor(dstAddr) @@ -86,6 +29,7 @@ func (tun *TunAdapter) writer() error { } } peermac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + var dstAddr address.Address var peerknown bool if b[0]&0xf0 == 0x40 { dstAddr = tun.addr @@ -183,10 +127,7 @@ func (tun *TunAdapter) readerPacketHandler(ch chan []byte) { // From the IP header, work out what our source and destination addresses // and node IDs are. We will need these in order to work out where to send // the packet - var srcAddr address.Address var dstAddr address.Address - var dstNodeID *crypto.NodeID - var dstNodeIDMask *crypto.NodeID var dstSnet address.Subnet var addrlen int n := len(bs) @@ -203,7 +144,6 @@ func (tun *TunAdapter) readerPacketHandler(ch chan []byte) { } // IPv6 address addrlen = 16 - copy(srcAddr[:addrlen], bs[8:]) copy(dstAddr[:addrlen], bs[24:]) copy(dstSnet[:addrlen/2], bs[24:]) } else if bs[0]&0xf0 == 0x40 { @@ -217,7 +157,6 @@ func (tun *TunAdapter) readerPacketHandler(ch chan []byte) { } // IPv4 address addrlen = 4 - copy(srcAddr[:addrlen], bs[12:]) copy(dstAddr[:addrlen], bs[16:]) } else { // Unknown address length or protocol, so drop the packet and ignore it @@ -225,36 +164,22 @@ func (tun *TunAdapter) readerPacketHandler(ch chan []byte) { continue } if tun.ckr.isEnabled() { - if !tun.ckr.isValidLocalAddress(srcAddr, addrlen) { - continue - } - if !dstAddr.IsValid() && !dstSnet.IsValid() { + if addrlen != 16 || (!dstAddr.IsValid() && !dstSnet.IsValid()) { if key, err := tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { // A public key was found, get the node ID for the search - dstNodeID = crypto.GetNodeID(&key) - // Do a quick check to ensure that the node ID refers to a vaild - // Yggdrasil address or subnet - this might be superfluous - addr := *address.AddrForNodeID(dstNodeID) - copy(dstAddr[:], addr[:]) - copy(dstSnet[:], addr[:]) - // Are we certain we looked up a valid node? - if !dstAddr.IsValid() && !dstSnet.IsValid() { - continue - } - } else { - // No public key was found in the CKR table so we've exhausted our options - continue + dstNodeID := crypto.GetNodeID(&key) + dstAddr = *address.AddrForNodeID(dstNodeID) + dstSnet = *address.SubnetForNodeID(dstNodeID) + addrlen = 16 } } - } else { - if addrlen != 16 { - continue - } - if !dstAddr.IsValid() && !dstSnet.IsValid() { - continue - } + } + if addrlen != 16 || (!dstAddr.IsValid() && !dstSnet.IsValid()) { + // Couldn't find this node's ygg IP + continue } // Do we have an active connection for this node address? + var dstNodeID, dstNodeIDMask *crypto.NodeID tun.mutex.RLock() session, isIn := tun.addrToConn[dstAddr] if !isIn || session == nil { From 226dd6170d25574e31dbde47b1ea3bfa83e8d68c Mon Sep 17 00:00:00 2001 From: Arceliar Date: Tue, 20 Aug 2019 18:49:53 -0500 Subject: [PATCH 0236/1109] hopefully prevent a deadlock --- src/yggdrasil/dialer.go | 1 - src/yggdrasil/session.go | 14 +++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index db5d5a4d..6b24cfb4 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -69,7 +69,6 @@ func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (*Conn, er defer t.Stop() select { case <-conn.session.init: - conn.session.startWorkers() return conn, nil case <-t.C: conn.Close() diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 57522209..7cd92db9 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -259,6 +259,7 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { <-sinfo.cancel.Finished() sinfo.core.router.doAdmin(sinfo.close) }() + go sinfo.startWorkers() return &sinfo } @@ -386,7 +387,6 @@ func (ss *sessions) handlePing(ping *sessionPing) { for i := range conn.nodeMask { conn.nodeMask[i] = 0xFF } - conn.session.startWorkers() c := ss.listener.conn go func() { c <- conn }() } @@ -568,6 +568,12 @@ func (sinfo *sessionInfo) recvWorker() { } } }() + select { + case <-sinfo.cancel.Finished(): + return + case <-sinfo.init: + // Wait until the session has finished initializing before processing any packets + } for { for len(callbacks) > 0 { select { @@ -634,6 +640,12 @@ func (sinfo *sessionInfo) sendWorker() { util.WorkerGo(poolFunc) callbacks = append(callbacks, ch) } + select { + case <-sinfo.cancel.Finished(): + return + case <-sinfo.init: + // Wait until the session has finished initializing before processing any packets + } for { for len(callbacks) > 0 { select { From 0d5dd9c45523fcb4581d6d0359b7ff91363f9cbf Mon Sep 17 00:00:00 2001 From: Arceliar Date: Tue, 20 Aug 2019 23:44:20 -0500 Subject: [PATCH 0237/1109] update crypto dependency and run go mod tidy --- go.mod | 5 ++--- go.sum | 44 +++----------------------------------------- 2 files changed, 5 insertions(+), 44 deletions(-) diff --git a/go.mod b/go.mod index 5654dff8..5569496c 100644 --- a/go.mod +++ b/go.mod @@ -1,19 +1,18 @@ module github.com/yggdrasil-network/yggdrasil-go require ( - github.com/docker/libcontainer v2.2.1+incompatible github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 github.com/hashicorp/go-syslog v1.0.0 github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/mitchellh/mapstructure v1.1.2 github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 + github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b // indirect github.com/vishvananda/netlink v1.0.0 github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8 - golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 + golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a golang.org/x/text v0.3.2 - golang.org/x/tools v0.0.0-20190814171936-5b18234b3ae0 // indirect ) diff --git a/go.sum b/go.sum index 62a38033..756ed5af 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/docker/libcontainer v2.2.1+incompatible h1:++SbbkCw+X8vAd4j2gOCzZ2Nn7s2xFALTf7LZKmM1/0= -github.com/docker/libcontainer v2.2.1+incompatible/go.mod h1:osvj61pYsqhNCMLGX31xr7klUBhHb/ZBuXS0o1Fvwbw= github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 h1:WD8iJ37bRNwvETMfVTusVSAi0WdXTpfNVGY2aHycNKY= github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= @@ -12,48 +10,19 @@ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQz github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 h1:1zN6ImoqhSJhN8hGXFaJlSC8msLmIbX8bFqOfWLKw0w= github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091/go.mod h1:N20Z5Y8oye9a7HmytmZ+tr8Q2vlP0tAHP13kTHzwvQY= +github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b h1:+y4hCMc/WKsDbAPsOQZgBSaSZ26uh2afyaWeVg/3s/c= +github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= github.com/vishvananda/netlink v1.0.0 h1:bqNY2lgheFIu1meHUFSH3d7vG93AFyqg3oGbJCOJgSM= github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f h1:nBX3nTcmxEtHSERBJaIo1Qa26VwRaopnZmfDQUXsF4I= github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= -github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae h1:MYCANF1kehCG6x6G+/9txLfq6n3lS5Vp0Mxn1hdiBAc= -github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= -github.com/yggdrasil-network/water v0.0.0-20190719211521-a76871ea954b/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= -github.com/yggdrasil-network/water v0.0.0-20190719213007-b160316e362e/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= -github.com/yggdrasil-network/water v0.0.0-20190720101301-5db94379a5eb/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= -github.com/yggdrasil-network/water v0.0.0-20190720145626-28ccb9101d55/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= -github.com/yggdrasil-network/water v0.0.0-20190725073841-250edb919f8a h1:mQ0mPD+dyB/vaDPyVkCBiXUQu9Or7/cRSTjPlV8tXvw= -github.com/yggdrasil-network/water v0.0.0-20190725073841-250edb919f8a/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= -github.com/yggdrasil-network/water v0.0.0-20190725123504-a16161896c34 h1:Qh5FE+Q5iGqpmR/FPMYHuoZLN921au/nxAlmKe+Hdbo= -github.com/yggdrasil-network/water v0.0.0-20190725123504-a16161896c34/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= 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= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/net v0.0.0-20181207154023-610586996380 h1:zPQexyRtNYBc7bcHmehl1dH6TB3qn8zytv8cBGLDNY0= -golang.org/x/net v0.0.0-20181207154023-610586996380/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e h1:njOxP/wVblhCLIUhjHXf6X+dzTt5OQ3vMQo9mkOIKIo= -golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI= -golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e h1:TsjK5I7fXk8f2FQrgu6NS7i5Qih3knl2FL1htyguLRE= -golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= @@ -61,10 +30,3 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= -golang.org/x/tools v0.0.0-20190724185037-8aa4eac1a7c1/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= -golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= -golang.org/x/tools v0.0.0-20190802003818-e9bb7d36c060/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= -golang.org/x/tools v0.0.0-20190809145639-6d4652c779c4/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190814171936-5b18234b3ae0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 0cb99d522f379b6f8b71d3f1e3e463e27008dfe0 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 21 Aug 2019 18:18:46 +0100 Subject: [PATCH 0238/1109] Update changelog --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 844409f5..b8133a29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,25 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> +## [0.3.8] - 2019-08-21 +### Changed +- Yggdrasil can now send multiple packets from the switch at once, which results in improved throughput with smaller packets or lower MTUs +- Performance has been slightly improved by not allocating cancellations where not necessary +- Crypto-key routing options have been renamed for clarity + - `IPv4Sources` is now named `IPv4LocalSubnets` + - `IPv6Sources` is now named `IPv6LocalSubnets` + - `IPv4Destinations` is now named `IPv4RemoteSubnets` + - `IPv6Destinations` is now named `IPv6RemoteSubnets` + - The old option names will continue to be accepted by the configuration parser for now but may not be indefinitely +- When presented with multiple paths between two nodes, the switch now prefers the most recently used port when possible instead of the least recently used, helping to reduce packet reordering +- New nonce tracking should help to reduce the number of packets dropped as a result of multiple/aggregate paths or congestion control in the switch + +### Fixed +- **Security vulnerability**: Address verification was not strict enough, which could result in a malicious session sending traffic with unexpected or spoofed source or destination addresses which Yggdrasil could fail to reject + - Versions `0.3.6` and `0.3.7` are vulnerable - users of these versions should upgrade as soon as possible + - Versions `0.3.5` and earlier are not affected +- A deadlock was fixed in the session code which could result in Yggdrasil failing to pass traffic after some time + ## [0.3.7] - 2019-08-14 ### Changed - The switch should now forward packets along a single path more consistently in cases where congestion is low and multiple equal-length paths exist, which should improve stability and result in fewer out-of-order packets From 9d7e7288c68093f8e857ab83bc44203102a72ca0 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 23 Aug 2019 18:47:15 -0500 Subject: [PATCH 0239/1109] start migrating the router to an actor --- go.mod | 1 + go.sum | 2 + src/yggdrasil/conn.go | 2 +- src/yggdrasil/dht.go | 2 - src/yggdrasil/peer.go | 2 +- src/yggdrasil/router.go | 84 +++++++++++++++++++++-------------------- src/yggdrasil/search.go | 2 +- src/yggdrasil/switch.go | 10 +---- 8 files changed, 51 insertions(+), 54 deletions(-) diff --git a/go.mod b/go.mod index 5569496c..3f5cae88 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,7 @@ module github.com/yggdrasil-network/yggdrasil-go require ( + github.com/Arceliar/phony v0.0.0-20190821233739-c7f353f14438 github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 github.com/hashicorp/go-syslog v1.0.0 github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 diff --git a/go.sum b/go.sum index 756ed5af..22276c2c 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/Arceliar/phony v0.0.0-20190821233739-c7f353f14438 h1:t4tRgrItIq2ap4O31yOuWm17lUiyzf8gf/P+bEfgmrw= +github.com/Arceliar/phony v0.0.0-20190821233739-c7f353f14438/go.mod h1:2Q9yJvg2PlMrnOEa3RTEy9hElWAICo/D8HTUDqAHUAo= github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 h1:WD8iJ37bRNwvETMfVTusVSAi0WdXTpfNVGY2aHycNKY= github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 25330aae..c77e19d6 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -134,7 +134,7 @@ func (c *Conn) doSearch() { sinfo.continueSearch() } } - go func() { c.core.router.admin <- routerWork }() + go c.core.router.doAdmin(routerWork) } func (c *Conn) getDeadlineCancellation(value *atomic.Value) (util.Cancellation, bool) { diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index b53e29c9..d35d3aaf 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -68,7 +68,6 @@ type dht struct { core *Core reconfigure chan chan error nodeID crypto.NodeID - peers chan *dhtInfo // other goroutines put incoming dht updates here reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests callbacks map[dhtReqKey][]dht_callbackInfo // Search and admin lookup callbacks // These next two could be replaced by a single linked list or similar... @@ -87,7 +86,6 @@ func (t *dht) init(c *Core) { } }() t.nodeID = *t.core.NodeID() - t.peers = make(chan *dhtInfo, 1024) t.callbacks = make(map[dhtReqKey][]dht_callbackInfo) t.reset() } diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 379ca85b..fcd9364c 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -210,7 +210,7 @@ func (p *peer) linkLoop() { case dinfo = <-p.dinfo: case _ = <-tick.C: if dinfo != nil { - p.core.dht.peers <- dinfo + p.core.router.insertPeer(&p.core.router, dinfo) } } } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index bdead848..4ce6b7ed 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -30,19 +30,19 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/util" + + "github.com/Arceliar/phony" ) // The router struct has channels to/from the adapter device and a self peer (0), which is how messages are passed between this node and the peers/switch layer. // The router's mainLoop goroutine is responsible for managing all information related to the dht, searches, and crypto sessions. type router struct { + phony.Actor core *Core reconfigure chan chan error addr address.Address subnet address.Subnet - in <-chan [][]byte // packets we received from the network, link to peer's "out" - out func([]byte) // packets we're sending to the network, link to peer's "in" - reset chan struct{} // signal that coords changed (re-init sessions/dht) - admin chan func() // pass a lambda for the admin socket to query stuff + out func([]byte) // packets we're sending to the network, link to peer's "in" nodeinfo nodeinfo } @@ -52,7 +52,6 @@ func (r *router) init(core *Core) { r.reconfigure = make(chan chan error, 1) r.addr = *address.AddrForNodeID(&r.core.dht.nodeID) r.subnet = *address.SubnetForNodeID(&r.core.dht.nodeID) - in := make(chan [][]byte, 1) // TODO something better than this... self := linkInterface{ name: "(self)", info: linkInfo{ @@ -62,8 +61,10 @@ func (r *router) init(core *Core) { }, } p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &crypto.BoxSharedKey{}, &self, nil) - p.out = func(packets [][]byte) { in <- packets } - r.in = in + p.out = func(packets [][]byte) { + // TODO make peers and/or the switch into actors, have them pass themselves as the from field + r.handlePackets(r, packets) + } out := make(chan []byte, 32) go func() { for packet := range out { @@ -90,8 +91,6 @@ func (r *router) init(core *Core) { } }() r.out = func(packet []byte) { out2 <- packet } - r.reset = make(chan struct{}, 1) - r.admin = make(chan func(), 32) r.nodeinfo.init(r.core) r.core.config.Mutex.RLock() r.nodeinfo.setNodeInfo(r.core.config.Current.NodeInfo, r.core.config.Current.NodeInfoPrivacy) @@ -105,42 +104,55 @@ func (r *router) start() error { return nil } -// Takes traffic from the adapter and passes it to router.send, or from r.in and handles incoming traffic. -// Also adds new peer info to the DHT. -// Also resets the DHT and sesssions in the event of a coord change. -// Also does periodic maintenance stuff. +// In practice, the switch will call this with 1 packet +func (r *router) handlePackets(from phony.IActor, packets [][]byte) { + r.EnqueueFrom(from, func() { + for _, packet := range packets { + r.handlePacket(packet) + } + }) +} + +// Insert a peer info into the dht, TODO? make the dht a separate actor +func (r *router) insertPeer(from phony.IActor, info *dhtInfo) { + r.EnqueueFrom(from, func() { + r.core.dht.insertPeer(info) + }) +} + +// Reset sessions and DHT after the switch sees our coords change +func (r *router) reset(from phony.IActor) { + r.EnqueueFrom(from, func() { + r.core.sessions.reset() + r.core.dht.reset() + }) +} + +// TODO remove reconfigure so this is just a ticker loop +// and then find something better than a ticker loop to schedule things... func (r *router) mainLoop() { ticker := time.NewTicker(time.Second) defer ticker.Stop() for { select { - case ps := <-r.in: - for _, p := range ps { - r.handleIn(p) - } - case info := <-r.core.dht.peers: - r.core.dht.insertPeer(info) - case <-r.reset: - r.core.sessions.reset() - r.core.dht.reset() case <-ticker.C: - { + r.SyncExec(func() { // Any periodic maintenance stuff goes here r.core.switchTable.doMaintenance() r.core.dht.doMaintenance() r.core.sessions.cleanup() - } - case f := <-r.admin: - f() + }) case e := <-r.reconfigure: - current := r.core.config.GetCurrent() - e <- r.nodeinfo.setNodeInfo(current.NodeInfo, current.NodeInfoPrivacy) + r.SyncExec(func() { + current := r.core.config.GetCurrent() + e <- r.nodeinfo.setNodeInfo(current.NodeInfo, current.NodeInfoPrivacy) + }) } } } // Checks incoming traffic type and passes it to the appropriate handler. -func (r *router) handleIn(packet []byte) { +func (r *router) handlePacket(packet []byte) { pType, pTypeLen := wire_decode_uint64(packet) if pTypeLen == 0 { return @@ -263,17 +275,7 @@ func (r *router) handleNodeInfo(bs []byte, fromKey *crypto.BoxPubKey) { r.nodeinfo.handleNodeInfo(&req) } -// Passed a function to call. -// This will send the function to r.admin and block until it finishes. -// It's used by the admin socket to ask the router mainLoop goroutine about information in the session or dht structs, which cannot be read safely from outside that goroutine. +// TODO remove this, have things either be actors that send message or else call SyncExec directly func (r *router) doAdmin(f func()) { - // Pass this a function that needs to be run by the router's main goroutine - // It will pass the function to the router and wait for the router to finish - done := make(chan struct{}) - newF := func() { - f() - close(done) - } - r.admin <- newF - <-done + r.SyncExec(f) } diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index b970fe55..c035e72f 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -165,7 +165,7 @@ func (sinfo *searchInfo) continueSearch() { } go func() { time.Sleep(search_RETRY_TIME) - sinfo.core.router.admin <- retryLater + sinfo.core.router.doAdmin(retryLater) }() } diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index cc316d16..86ae102b 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -245,10 +245,7 @@ func (t *switchTable) cleanRoot() { if t.data.locator.root != t.key { t.data.seq++ t.updater.Store(&sync.Once{}) - select { - case t.core.router.reset <- struct{}{}: - default: - } + t.core.router.reset(&t.core.router) } t.data.locator = switchLocator{root: t.key, tstamp: now.Unix()} t.core.peers.sendSwitchMsgs() @@ -511,10 +508,7 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep if !equiv(&sender.locator, &t.data.locator) { doUpdate = true t.data.seq++ - select { - case t.core.router.reset <- struct{}{}: - default: - } + t.core.router.reset(&t.core.router) } if t.data.locator.tstamp != sender.locator.tstamp { t.time = now From 232e6d3cb3e6f63100e4af1aa9fa05f21c7cb78d Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 23 Aug 2019 18:55:41 -0500 Subject: [PATCH 0240/1109] more router migration --- src/yggdrasil/router.go | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 4ce6b7ed..34dcb913 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -65,32 +65,7 @@ func (r *router) init(core *Core) { // TODO make peers and/or the switch into actors, have them pass themselves as the from field r.handlePackets(r, packets) } - out := make(chan []byte, 32) - go func() { - for packet := range out { - p.handlePacket(packet) - } - }() - out2 := make(chan []byte, 32) - go func() { - // This worker makes sure r.out never blocks - // It will buffer traffic long enough for the switch worker to take it - // If (somehow) you can send faster than the switch can receive, then this would use unbounded memory - // But crypto slows sends enough that the switch should always be able to take the packets... - var buf [][]byte - for { - buf = append(buf, <-out2) - for len(buf) > 0 { - select { - case bs := <-out2: - buf = append(buf, bs) - case out <- buf[0]: - buf = buf[1:] - } - } - } - }() - r.out = func(packet []byte) { out2 <- packet } + r.out = p.handlePacket // TODO if the peer becomes its own actor, then send a message here r.nodeinfo.init(r.core) r.core.config.Mutex.RLock() r.nodeinfo.setNodeInfo(r.core.config.Current.NodeInfo, r.core.config.Current.NodeInfoPrivacy) From 8e89816099b842f73fa7c25cd441d0e54400231e Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 23 Aug 2019 18:59:34 -0500 Subject: [PATCH 0241/1109] more router migration: rename functions that should only be called internally by the actor --- src/yggdrasil/router.go | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 34dcb913..b24400da 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -75,7 +75,7 @@ func (r *router) init(core *Core) { // Starts the mainLoop goroutine. func (r *router) start() error { r.core.log.Infoln("Starting router") - go r.mainLoop() + go r._mainLoop() return nil } @@ -83,7 +83,7 @@ func (r *router) start() error { func (r *router) handlePackets(from phony.IActor, packets [][]byte) { r.EnqueueFrom(from, func() { for _, packet := range packets { - r.handlePacket(packet) + r._handlePacket(packet) } }) } @@ -105,7 +105,7 @@ func (r *router) reset(from phony.IActor) { // TODO remove reconfigure so this is just a ticker loop // and then find something better than a ticker loop to schedule things... -func (r *router) mainLoop() { +func (r *router) _mainLoop() { ticker := time.NewTicker(time.Second) defer ticker.Stop() for { @@ -127,23 +127,23 @@ func (r *router) mainLoop() { } // Checks incoming traffic type and passes it to the appropriate handler. -func (r *router) handlePacket(packet []byte) { +func (r *router) _handlePacket(packet []byte) { pType, pTypeLen := wire_decode_uint64(packet) if pTypeLen == 0 { return } switch pType { case wire_Traffic: - r.handleTraffic(packet) + r._handleTraffic(packet) case wire_ProtocolTraffic: - r.handleProto(packet) + r._handleProto(packet) default: } } // Handles incoming traffic, i.e. encapuslated ordinary IPv6 packets. // Passes them to the crypto session worker to be decrypted and sent to the adapter. -func (r *router) handleTraffic(packet []byte) { +func (r *router) _handleTraffic(packet []byte) { defer util.PutBytes(packet) p := wire_trafficPacket{} if !p.decode(packet) { @@ -162,7 +162,7 @@ func (r *router) handleTraffic(packet []byte) { } // Handles protocol traffic by decrypting it, checking its type, and passing it to the appropriate handler for that traffic type. -func (r *router) handleProto(packet []byte) { +func (r *router) _handleProto(packet []byte) { // First parse the packet p := wire_protoTrafficPacket{} if !p.decode(packet) { @@ -189,24 +189,24 @@ func (r *router) handleProto(packet []byte) { } switch bsType { case wire_SessionPing: - r.handlePing(bs, &p.FromKey) + r._handlePing(bs, &p.FromKey) case wire_SessionPong: - r.handlePong(bs, &p.FromKey) + r._handlePong(bs, &p.FromKey) case wire_NodeInfoRequest: fallthrough case wire_NodeInfoResponse: - r.handleNodeInfo(bs, &p.FromKey) + r._handleNodeInfo(bs, &p.FromKey) case wire_DHTLookupRequest: - r.handleDHTReq(bs, &p.FromKey) + r._handleDHTReq(bs, &p.FromKey) case wire_DHTLookupResponse: - r.handleDHTRes(bs, &p.FromKey) + r._handleDHTRes(bs, &p.FromKey) default: util.PutBytes(packet) } } // Decodes session pings from wire format and passes them to sessions.handlePing where they either create or update a session. -func (r *router) handlePing(bs []byte, fromKey *crypto.BoxPubKey) { +func (r *router) _handlePing(bs []byte, fromKey *crypto.BoxPubKey) { ping := sessionPing{} if !ping.decode(bs) { return @@ -216,12 +216,12 @@ func (r *router) handlePing(bs []byte, fromKey *crypto.BoxPubKey) { } // Handles session pongs (which are really pings with an extra flag to prevent acknowledgement). -func (r *router) handlePong(bs []byte, fromKey *crypto.BoxPubKey) { - r.handlePing(bs, fromKey) +func (r *router) _handlePong(bs []byte, fromKey *crypto.BoxPubKey) { + r._handlePing(bs, fromKey) } // Decodes dht requests and passes them to dht.handleReq to trigger a lookup/response. -func (r *router) handleDHTReq(bs []byte, fromKey *crypto.BoxPubKey) { +func (r *router) _handleDHTReq(bs []byte, fromKey *crypto.BoxPubKey) { req := dhtReq{} if !req.decode(bs) { return @@ -231,7 +231,7 @@ func (r *router) handleDHTReq(bs []byte, fromKey *crypto.BoxPubKey) { } // Decodes dht responses and passes them to dht.handleRes to update the DHT table and further pass them to the search code (if applicable). -func (r *router) handleDHTRes(bs []byte, fromKey *crypto.BoxPubKey) { +func (r *router) _handleDHTRes(bs []byte, fromKey *crypto.BoxPubKey) { res := dhtRes{} if !res.decode(bs) { return @@ -241,7 +241,7 @@ func (r *router) handleDHTRes(bs []byte, fromKey *crypto.BoxPubKey) { } // Decodes nodeinfo request -func (r *router) handleNodeInfo(bs []byte, fromKey *crypto.BoxPubKey) { +func (r *router) _handleNodeInfo(bs []byte, fromKey *crypto.BoxPubKey) { req := nodeinfoReqRes{} if !req.decode(bs) { return From bbcbbaf3b14d5b8ceb6f06ca33fc3f7969fc950b Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 23 Aug 2019 20:05:18 -0500 Subject: [PATCH 0242/1109] start migrating sessionInfo to be an actor --- src/yggdrasil/api.go | 2 +- src/yggdrasil/conn.go | 8 ++--- src/yggdrasil/router.go | 8 ++--- src/yggdrasil/search.go | 2 +- src/yggdrasil/session.go | 71 ++++++++++++++++++++++------------------ 5 files changed, 49 insertions(+), 42 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 57ee5c65..785a5f72 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -213,7 +213,7 @@ func (c *Core) GetSessions() []Session { workerFunc := func() { session = Session{ Coords: append([]uint64{}, wire_coordsBytestoUint64s(sinfo.coords)...), - MTU: sinfo.getMTU(), + MTU: sinfo._getMTU(), BytesSent: sinfo.bytesSent, BytesRecvd: sinfo.bytesRecvd, Uptime: time.Now().Sub(sinfo.timeOpened), diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index c77e19d6..51dd9509 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -190,8 +190,8 @@ func (c *Conn) WriteNoCopy(msg FlowKeyMessage) error { var err error sessionFunc := func() { // Does the packet exceed the permitted size for the session? - if uint16(len(msg.Message)) > c.session.getMTU() { - err = ConnError{errors.New("packet too big"), true, false, false, int(c.session.getMTU())} + if uint16(len(msg.Message)) > c.session._getMTU() { + err = ConnError{errors.New("packet too big"), true, false, false, int(c.session._getMTU())} return } // The rest of this work is session keep-alive traffic @@ -201,10 +201,10 @@ func (c *Conn) WriteNoCopy(msg FlowKeyMessage) error { // TODO double check that the above condition is correct c.doSearch() } else { - c.core.sessions.ping(c.session) + c.session.ping(c.session) // TODO send from self if this becomes an actor } case c.session.reset && c.session.pingTime.Before(c.session.time): - c.core.sessions.ping(c.session) + c.session.ping(c.session) // TODO send from self if this becomes an actor default: // Don't do anything, to keep traffic throttled } } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index b24400da..95521841 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -98,7 +98,7 @@ func (r *router) insertPeer(from phony.IActor, info *dhtInfo) { // Reset sessions and DHT after the switch sees our coords change func (r *router) reset(from phony.IActor) { r.EnqueueFrom(from, func() { - r.core.sessions.reset() + r.core.sessions.reset(r) r.core.dht.reset() }) } @@ -111,14 +111,14 @@ func (r *router) _mainLoop() { for { select { case <-ticker.C: - r.SyncExec(func() { + <-r.SyncExec(func() { // Any periodic maintenance stuff goes here r.core.switchTable.doMaintenance() r.core.dht.doMaintenance() r.core.sessions.cleanup() }) case e := <-r.reconfigure: - r.SyncExec(func() { + <-r.SyncExec(func() { current := r.core.config.GetCurrent() e <- r.nodeinfo.setNodeInfo(current.NodeInfo, current.NodeInfoPrivacy) }) @@ -252,5 +252,5 @@ func (r *router) _handleNodeInfo(bs []byte, fromKey *crypto.BoxPubKey) { // TODO remove this, have things either be actors that send message or else call SyncExec directly func (r *router) doAdmin(f func()) { - r.SyncExec(f) + <-r.SyncExec(f) } diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index c035e72f..56adda80 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -213,7 +213,7 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { } // FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)? sess.coords = res.Coords - sinfo.core.sessions.ping(sess) + sess.ping(&sinfo.core.router) sinfo.callback(sess, nil) // Cleanup delete(sinfo.core.searches.searches, res.Dest) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 7cd92db9..f28e014a 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -14,6 +14,8 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/util" + + "github.com/Arceliar/phony" ) // Duration that we keep track of old nonces per session, to allow some out-of-order packet delivery @@ -37,7 +39,7 @@ func (h nonceHeap) peek() *crypto.BoxNonce { return &h[len(h)-1] } // All the information we know about an active session. // This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API. type sessionInfo struct { - mutex sync.Mutex // Protects all of the below, use it any time you read/chance the contents of a session + phony.Actor // Protects all of the below, use it any time you read/change the contents of a session core *Core // reconfigure chan chan error // theirAddr address.Address // @@ -46,6 +48,7 @@ type sessionInfo struct { theirSesPub crypto.BoxPubKey // mySesPub crypto.BoxPubKey // mySesPriv crypto.BoxPrivKey // + sharedPermKey crypto.BoxSharedKey // used for session pings sharedSesKey crypto.BoxSharedKey // derived from session keys theirHandle crypto.Handle // myHandle crypto.Handle // @@ -73,10 +76,9 @@ type sessionInfo struct { send chan FlowKeyMessage // Packets with optional flow key go here, to be encrypted and sent } +// TODO remove this, call SyncExec directly func (sinfo *sessionInfo) doFunc(f func()) { - sinfo.mutex.Lock() - defer sinfo.mutex.Unlock() - f() + <-sinfo.SyncExec(f) } // Represents a session ping/pong packet, andincludes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU. @@ -92,7 +94,7 @@ type sessionPing struct { // Updates session info in response to a ping, after checking that the ping is OK. // Returns true if the session was updated, or false otherwise. -func (s *sessionInfo) update(p *sessionPing) bool { +func (s *sessionInfo) _update(p *sessionPing) bool { if !(p.Tstamp > s.tstamp) { // To protect against replay attacks return false @@ -214,6 +216,7 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.core = ss.core sinfo.reconfigure = make(chan chan error, 1) sinfo.theirPermPub = *theirPermKey + sinfo.sharedPermKey = *ss.getSharedKey(&ss.core.boxPriv, &sinfo.theirPermPub) pub, priv := crypto.NewBoxKeys() sinfo.mySesPub = *pub sinfo.mySesPriv = *priv @@ -257,7 +260,9 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { go func() { // Run cleanup when the session is canceled <-sinfo.cancel.Finished() - sinfo.core.router.doAdmin(sinfo.close) + sinfo.core.router.doAdmin(func() { + sinfo.core.sessions.removeSession(&sinfo) + }) }() go sinfo.startWorkers() return &sinfo @@ -292,7 +297,7 @@ func (ss *sessions) cleanup() { } // Closes a session, removing it from sessions maps. -func (sinfo *sessionInfo) close() { +func (ss *sessions) removeSession(sinfo *sessionInfo) { if s := sinfo.core.sessions.sinfos[sinfo.myHandle]; s == sinfo { delete(sinfo.core.sessions.sinfos, sinfo.myHandle) delete(sinfo.core.sessions.byTheirPerm, sinfo.theirPermPub) @@ -300,11 +305,11 @@ func (sinfo *sessionInfo) close() { } // Returns a session ping appropriate for the given session info. -func (ss *sessions) getPing(sinfo *sessionInfo) sessionPing { - loc := ss.core.switchTable.getLocator() +func (sinfo *sessionInfo) _getPing() sessionPing { + loc := sinfo.core.switchTable.getLocator() coords := loc.getCoords() - ref := sessionPing{ - SendPermPub: ss.core.boxPub, + ping := sessionPing{ + SendPermPub: sinfo.core.boxPub, Handle: sinfo.myHandle, SendSesPub: sinfo.mySesPub, Tstamp: time.Now().Unix(), @@ -312,7 +317,7 @@ func (ss *sessions) getPing(sinfo *sessionInfo) sessionPing { MTU: sinfo.myMTU, } sinfo.myNonce.Increment() - return ref + return ping } // Gets the shared key for a pair of box keys. @@ -339,27 +344,29 @@ func (ss *sessions) getSharedKey(myPriv *crypto.BoxPrivKey, } // Sends a session ping by calling sendPingPong in ping mode. -func (ss *sessions) ping(sinfo *sessionInfo) { - ss.sendPingPong(sinfo, false) +func (sinfo *sessionInfo) ping(from phony.IActor) { + sinfo.EnqueueFrom(from, func() { + sinfo._sendPingPong(false) + }) } // Calls getPing, sets the appropriate ping/pong flag, encodes to wire format, and send it. // Updates the time the last ping was sent in the session info. -func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) { - ping := ss.getPing(sinfo) +func (sinfo *sessionInfo) _sendPingPong(isPong bool) { + ping := sinfo._getPing() ping.IsPong = isPong bs := ping.encode() - shared := ss.getSharedKey(&ss.core.boxPriv, &sinfo.theirPermPub) - payload, nonce := crypto.BoxSeal(shared, bs, nil) + payload, nonce := crypto.BoxSeal(&sinfo.sharedPermKey, bs, nil) p := wire_protoTrafficPacket{ Coords: sinfo.coords, ToKey: sinfo.theirPermPub, - FromKey: ss.core.boxPub, + FromKey: sinfo.core.boxPub, Nonce: *nonce, Payload: payload, } packet := p.encode() - ss.core.router.out(packet) + // TODO rewrite the below if/when the peer struct becomes an actor, to not go through the router first + sinfo.core.router.EnqueueFrom(sinfo, func() { sinfo.core.router.out(packet) }) if sinfo.pingTime.Before(sinfo.time) { sinfo.pingTime = time.Now() } @@ -371,9 +378,9 @@ func (ss *sessions) handlePing(ping *sessionPing) { // Get the corresponding session (or create a new session) sinfo, isIn := ss.getByTheirPerm(&ping.SendPermPub) switch { + case ping.IsPong: // This is a response, not an initial ping, so ignore it. case isIn: // Session already exists case !ss.isSessionAllowed(&ping.SendPermPub, false): // Session is not allowed - case ping.IsPong: // This is a response, not an initial ping, so ignore it. default: ss.listenerMutex.Lock() if ss.listener != nil { @@ -393,13 +400,13 @@ func (ss *sessions) handlePing(ping *sessionPing) { ss.listenerMutex.Unlock() } if sinfo != nil { - sinfo.doFunc(func() { + sinfo.EnqueueFrom(&ss.core.router, func() { // Update the session - if !sinfo.update(ping) { /*panic("Should not happen in testing")*/ + if !sinfo._update(ping) { /*panic("Should not happen in testing")*/ return } if !ping.IsPong { - ss.sendPingPong(sinfo, true) + sinfo._sendPingPong(true) } }) } @@ -408,7 +415,7 @@ func (ss *sessions) handlePing(ping *sessionPing) { // Get the MTU of the session. // Will be equal to the smaller of this node's MTU or the remote node's MTU. // If sending over links with a maximum message size (this was a thing with the old UDP code), it could be further lowered, to a minimum of 1280. -func (sinfo *sessionInfo) getMTU() uint16 { +func (sinfo *sessionInfo) _getMTU() uint16 { if sinfo.theirMTU == 0 || sinfo.myMTU == 0 { return 0 } @@ -419,7 +426,7 @@ func (sinfo *sessionInfo) getMTU() uint16 { } // Checks if a packet's nonce is recent enough to fall within the window of allowed packets, and not already received. -func (sinfo *sessionInfo) nonceIsOK(theirNonce *crypto.BoxNonce) bool { +func (sinfo *sessionInfo) _nonceIsOK(theirNonce *crypto.BoxNonce) bool { // The bitmask is to allow for some non-duplicate out-of-order packets if theirNonce.Minus(&sinfo.theirNonce) > 0 { // This is newer than the newest nonce we've seen @@ -437,7 +444,7 @@ func (sinfo *sessionInfo) nonceIsOK(theirNonce *crypto.BoxNonce) bool { } // Updates the nonce mask by (possibly) shifting the bitmask and setting the bit corresponding to this nonce to 1, and then updating the most recent nonce -func (sinfo *sessionInfo) updateNonce(theirNonce *crypto.BoxNonce) { +func (sinfo *sessionInfo) _updateNonce(theirNonce *crypto.BoxNonce) { // Start with some cleanup for len(sinfo.theirNonceHeap) > 64 { if time.Since(sinfo.theirNonceMap[*sinfo.theirNonceHeap.peek()]) < nonceWindow { @@ -459,9 +466,9 @@ func (sinfo *sessionInfo) updateNonce(theirNonce *crypto.BoxNonce) { // Resets all sessions to an uninitialized state. // Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change. -func (ss *sessions) reset() { +func (ss *sessions) reset(from phony.IActor) { for _, sinfo := range ss.sinfos { - sinfo.doFunc(func() { + sinfo.EnqueueFrom(from, func() { sinfo.reset = true }) } @@ -492,7 +499,7 @@ func (sinfo *sessionInfo) recvWorker() { var err error var k crypto.BoxSharedKey sessionFunc := func() { - if !sinfo.nonceIsOK(&p.Nonce) { + if !sinfo._nonceIsOK(&p.Nonce) { err = ConnError{errors.New("packet dropped due to invalid nonce"), false, true, false, 0} return } @@ -514,12 +521,12 @@ func (sinfo *sessionInfo) recvWorker() { return } sessionFunc = func() { - if k != sinfo.sharedSesKey || !sinfo.nonceIsOK(&p.Nonce) { + if k != sinfo.sharedSesKey || !sinfo._nonceIsOK(&p.Nonce) { // The session updated in the mean time, so return an error err = ConnError{errors.New("session updated during crypto operation"), false, true, false, 0} return } - sinfo.updateNonce(&p.Nonce) + sinfo._updateNonce(&p.Nonce) sinfo.time = time.Now() sinfo.bytesRecvd += uint64(len(bs)) } From 9835c638180dc7a493c10ecb0a5e4772bacc145c Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 23 Aug 2019 20:26:15 -0500 Subject: [PATCH 0243/1109] refactor things the router owns (dht, sessions, searches) into that struct, to make the ownership more explicit --- src/yggdrasil/api.go | 26 +++++++++++++------------- src/yggdrasil/conn.go | 8 ++++---- src/yggdrasil/core.go | 15 ++++++--------- src/yggdrasil/dht.go | 4 ++-- src/yggdrasil/listener.go | 4 ++-- src/yggdrasil/nodeinfo.go | 2 +- src/yggdrasil/router.go | 27 +++++++++++++++------------ src/yggdrasil/search.go | 20 ++++++++++---------- src/yggdrasil/session.go | 12 ++++++------ 9 files changed, 59 insertions(+), 59 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 785a5f72..f50c8ceb 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -156,11 +156,11 @@ func (c *Core) GetDHT() []DHTEntry { getDHT := func() { now := time.Now() var dhtentry []*dhtInfo - for _, v := range c.dht.table { + for _, v := range c.router.dht.table { dhtentry = append(dhtentry, v) } sort.SliceStable(dhtentry, func(i, j int) bool { - return dht_ordered(&c.dht.nodeID, dhtentry[i].getNodeID(), dhtentry[j].getNodeID()) + return dht_ordered(&c.router.dht.nodeID, dhtentry[i].getNodeID(), dhtentry[j].getNodeID()) }) for _, v := range dhtentry { info := DHTEntry{ @@ -208,7 +208,7 @@ func (c *Core) GetSwitchQueues() SwitchQueues { func (c *Core) GetSessions() []Session { var sessions []Session getSessions := func() { - for _, sinfo := range c.sessions.sinfos { + for _, sinfo := range c.router.sessions.sinfos { var session Session workerFunc := func() { session = Session{ @@ -243,17 +243,17 @@ func (c *Core) GetSessions() []Session { // ConnListen returns a listener for Yggdrasil session connections. func (c *Core) ConnListen() (*Listener, error) { - c.sessions.listenerMutex.Lock() - defer c.sessions.listenerMutex.Unlock() - if c.sessions.listener != nil { + c.router.sessions.listenerMutex.Lock() + defer c.router.sessions.listenerMutex.Unlock() + if c.router.sessions.listener != nil { return nil, errors.New("a listener already exists") } - c.sessions.listener = &Listener{ + c.router.sessions.listener = &Listener{ core: c, conn: make(chan *Conn), close: make(chan interface{}), } - return c.sessions.listener, nil + return c.router.sessions.listener, nil } // ConnDialer returns a dialer for Yggdrasil session connections. @@ -356,10 +356,10 @@ func (c *Core) GetNodeInfo(key crypto.BoxPubKey, coords []uint64, nocache bool) // received an incoming session request. The function should return true to // allow the session or false to reject it. func (c *Core) SetSessionGatekeeper(f func(pubkey *crypto.BoxPubKey, initiator bool) bool) { - c.sessions.isAllowedMutex.Lock() - defer c.sessions.isAllowedMutex.Unlock() + c.router.sessions.isAllowedMutex.Lock() + defer c.router.sessions.isAllowedMutex.Unlock() - c.sessions.isAllowedHandler = f + c.router.sessions.isAllowedHandler = f } // SetLogger sets the output logger of the Yggdrasil node after startup. This @@ -445,10 +445,10 @@ func (c *Core) DHTPing(key crypto.BoxPubKey, coords []uint64, target *crypto.Nod } rq := dhtReqKey{info.key, *target} sendPing := func() { - c.dht.addCallback(&rq, func(res *dhtRes) { + c.router.dht.addCallback(&rq, func(res *dhtRes) { resCh <- res }) - c.dht.ping(&info, &rq.dest) + c.router.dht.ping(&info, &rq.dest) } c.router.doAdmin(sendPing) // TODO: do something better than the below... diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 51dd9509..b4ce71f9 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -84,7 +84,7 @@ func (c *Conn) String() string { func (c *Conn) search() error { var sinfo *searchInfo var isIn bool - c.core.router.doAdmin(func() { sinfo, isIn = c.core.searches.searches[*c.nodeID] }) + c.core.router.doAdmin(func() { sinfo, isIn = c.core.router.searches.searches[*c.nodeID] }) if !isIn { done := make(chan struct{}, 1) var sess *sessionInfo @@ -99,7 +99,7 @@ func (c *Conn) search() error { } } c.core.router.doAdmin(func() { - sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) + sinfo = c.core.router.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) sinfo.continueSearch() }) <-done @@ -124,11 +124,11 @@ func (c *Conn) search() error { func (c *Conn) doSearch() { routerWork := func() { // Check to see if there is a search already matching the destination - sinfo, isIn := c.core.searches.searches[*c.nodeID] + sinfo, isIn := c.core.router.searches.searches[*c.nodeID] if !isIn { // Nothing was found, so create a new search searchCompleted := func(sinfo *sessionInfo, e error) {} - sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) + sinfo = c.core.router.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo) // Start the search sinfo.continueSearch() diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 0921ab9f..870597a9 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -26,10 +26,7 @@ type Core struct { sigPriv crypto.SigPrivKey switchTable switchTable peers peers - sessions sessions router router - dht dht - searches searches link link log *log.Logger } @@ -76,9 +73,9 @@ func (c *Core) init() error { c.log.Warnln("SigningPublicKey in config is incorrect, should be", sp) } - c.searches.init(c) - c.dht.init(c) - c.sessions.init(c) + c.router.searches.init(c) + c.router.dht.init(c) + c.router.sessions.init(c) c.peers.init(c) c.router.init(c) c.switchTable.init(c) // TODO move before peers? before router? @@ -124,9 +121,9 @@ func (c *Core) UpdateConfig(config *config.NodeConfig) { errors := 0 components := []chan chan error{ - c.searches.reconfigure, - c.dht.reconfigure, - c.sessions.reconfigure, + c.router.searches.reconfigure, + c.router.dht.reconfigure, + c.router.sessions.reconfigure, c.peers.reconfigure, c.router.reconfigure, c.switchTable.reconfigure, diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index d35d3aaf..d0d98558 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -221,7 +221,7 @@ func (t *dht) handleReq(req *dhtReq) { func (t *dht) sendRes(res *dhtRes, req *dhtReq) { // Send a reply for a dhtReq bs := res.encode() - shared := t.core.sessions.getSharedKey(&t.core.boxPriv, &req.Key) + shared := t.core.router.sessions.getSharedKey(&t.core.boxPriv, &req.Key) payload, nonce := crypto.BoxSeal(shared, bs, nil) p := wire_protoTrafficPacket{ Coords: req.Coords, @@ -285,7 +285,7 @@ func (t *dht) handleRes(res *dhtRes) { func (t *dht) sendReq(req *dhtReq, dest *dhtInfo) { // Send a dhtReq to the node in dhtInfo bs := req.encode() - shared := t.core.sessions.getSharedKey(&t.core.boxPriv, &dest.key) + shared := t.core.router.sessions.getSharedKey(&t.core.boxPriv, &dest.key) payload, nonce := crypto.BoxSeal(shared, bs, nil) p := wire_protoTrafficPacket{ Coords: dest.coords, diff --git a/src/yggdrasil/listener.go b/src/yggdrasil/listener.go index 62225412..fec543f4 100644 --- a/src/yggdrasil/listener.go +++ b/src/yggdrasil/listener.go @@ -31,8 +31,8 @@ func (l *Listener) Close() (err error) { recover() err = errors.New("already closed") }() - if l.core.sessions.listener == l { - l.core.sessions.listener = nil + if l.core.router.sessions.listener == l { + l.core.router.sessions.listener = nil } close(l.close) close(l.conn) diff --git a/src/yggdrasil/nodeinfo.go b/src/yggdrasil/nodeinfo.go index 73d4e115..50f5bf9c 100644 --- a/src/yggdrasil/nodeinfo.go +++ b/src/yggdrasil/nodeinfo.go @@ -172,7 +172,7 @@ func (m *nodeinfo) sendNodeInfo(key crypto.BoxPubKey, coords []byte, isResponse NodeInfo: m.getNodeInfo(), } bs := nodeinfo.encode() - shared := m.core.sessions.getSharedKey(&m.core.boxPriv, &key) + shared := m.core.router.sessions.getSharedKey(&m.core.boxPriv, &key) payload, nonce := crypto.BoxSeal(shared, bs, nil) p := wire_protoTrafficPacket{ Coords: coords, diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 95521841..ed919983 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -43,15 +43,18 @@ type router struct { addr address.Address subnet address.Subnet out func([]byte) // packets we're sending to the network, link to peer's "in" + dht dht nodeinfo nodeinfo + searches searches + sessions sessions } // Initializes the router struct, which includes setting up channels to/from the adapter. func (r *router) init(core *Core) { r.core = core r.reconfigure = make(chan chan error, 1) - r.addr = *address.AddrForNodeID(&r.core.dht.nodeID) - r.subnet = *address.SubnetForNodeID(&r.core.dht.nodeID) + r.addr = *address.AddrForNodeID(&r.dht.nodeID) + r.subnet = *address.SubnetForNodeID(&r.dht.nodeID) self := linkInterface{ name: "(self)", info: linkInfo{ @@ -91,15 +94,15 @@ func (r *router) handlePackets(from phony.IActor, packets [][]byte) { // Insert a peer info into the dht, TODO? make the dht a separate actor func (r *router) insertPeer(from phony.IActor, info *dhtInfo) { r.EnqueueFrom(from, func() { - r.core.dht.insertPeer(info) + r.dht.insertPeer(info) }) } // Reset sessions and DHT after the switch sees our coords change func (r *router) reset(from phony.IActor) { r.EnqueueFrom(from, func() { - r.core.sessions.reset(r) - r.core.dht.reset() + r.sessions.reset() + r.dht.reset() }) } @@ -114,8 +117,8 @@ func (r *router) _mainLoop() { <-r.SyncExec(func() { // Any periodic maintenance stuff goes here r.core.switchTable.doMaintenance() - r.core.dht.doMaintenance() - r.core.sessions.cleanup() + r.dht.doMaintenance() + r.sessions.cleanup() }) case e := <-r.reconfigure: <-r.SyncExec(func() { @@ -149,7 +152,7 @@ func (r *router) _handleTraffic(packet []byte) { if !p.decode(packet) { return } - sinfo, isIn := r.core.sessions.getSessionForHandle(&p.Handle) + sinfo, isIn := r.sessions.getSessionForHandle(&p.Handle) if !isIn { util.PutBytes(p.Payload) return @@ -172,7 +175,7 @@ func (r *router) _handleProto(packet []byte) { var sharedKey *crypto.BoxSharedKey if p.ToKey == r.core.boxPub { // Try to open using our permanent key - sharedKey = r.core.sessions.getSharedKey(&r.core.boxPriv, &p.FromKey) + sharedKey = r.sessions.getSharedKey(&r.core.boxPriv, &p.FromKey) } else { return } @@ -212,7 +215,7 @@ func (r *router) _handlePing(bs []byte, fromKey *crypto.BoxPubKey) { return } ping.SendPermPub = *fromKey - r.core.sessions.handlePing(&ping) + r.sessions.handlePing(&ping) } // Handles session pongs (which are really pings with an extra flag to prevent acknowledgement). @@ -227,7 +230,7 @@ func (r *router) _handleDHTReq(bs []byte, fromKey *crypto.BoxPubKey) { return } req.Key = *fromKey - r.core.dht.handleReq(&req) + r.dht.handleReq(&req) } // Decodes dht responses and passes them to dht.handleRes to update the DHT table and further pass them to the search code (if applicable). @@ -237,7 +240,7 @@ func (r *router) _handleDHTRes(bs []byte, fromKey *crypto.BoxPubKey) { return } res.Key = *fromKey - r.core.dht.handleRes(&res) + r.dht.handleRes(&res) } // Decodes nodeinfo request diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 56adda80..5751043f 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -100,7 +100,7 @@ func (sinfo *searchInfo) addToSearch(res *dhtRes) { from := dhtInfo{key: res.Key, coords: res.Coords} sinfo.visited[*from.getNodeID()] = true for _, info := range res.Infos { - if *info.getNodeID() == sinfo.core.dht.nodeID || sinfo.visited[*info.getNodeID()] { + if *info.getNodeID() == sinfo.core.router.dht.nodeID || sinfo.visited[*info.getNodeID()] { continue } if dht_ordered(&sinfo.dest, info.getNodeID(), from.getNodeID()) { @@ -134,7 +134,7 @@ func (sinfo *searchInfo) doSearchStep() { if len(sinfo.toVisit) == 0 { if time.Since(sinfo.time) > search_RETRY_TIME { // Dead end and no response in too long, do cleanup - delete(sinfo.core.searches.searches, sinfo.dest) + delete(sinfo.core.router.searches.searches, sinfo.dest) sinfo.callback(nil, errors.New("search reached dead end")) } return @@ -143,8 +143,8 @@ func (sinfo *searchInfo) doSearchStep() { var next *dhtInfo next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:] rq := dhtReqKey{next.key, sinfo.dest} - sinfo.core.dht.addCallback(&rq, sinfo.handleDHTRes) - sinfo.core.dht.ping(next, &sinfo.dest) + sinfo.core.router.dht.addCallback(&rq, sinfo.handleDHTRes) + sinfo.core.router.dht.ping(next, &sinfo.dest) sinfo.time = time.Now() } @@ -157,7 +157,7 @@ func (sinfo *searchInfo) continueSearch() { // Any that die aren't restarted, but a new one will start later retryLater := func() { // FIXME this keeps the search alive forever if not for the searches map, fix that - newSearchInfo := sinfo.core.searches.searches[sinfo.dest] + newSearchInfo := sinfo.core.router.searches.searches[sinfo.dest] if newSearchInfo != sinfo { return } @@ -196,17 +196,17 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { return false } // They match, so create a session and send a sessionRequest - sess, isIn := sinfo.core.sessions.getByTheirPerm(&res.Key) + sess, isIn := sinfo.core.router.sessions.getByTheirPerm(&res.Key) if !isIn { - sess = sinfo.core.sessions.createSession(&res.Key) + sess = sinfo.core.router.sessions.createSession(&res.Key) if sess == nil { // nil if the DHT search finished but the session wasn't allowed sinfo.callback(nil, errors.New("session not allowed")) // Cleanup - delete(sinfo.core.searches.searches, res.Dest) + delete(sinfo.core.router.searches.searches, res.Dest) return true } - _, isIn := sinfo.core.sessions.getByTheirPerm(&res.Key) + _, isIn := sinfo.core.router.sessions.getByTheirPerm(&res.Key) if !isIn { panic("This should never happen") } @@ -216,6 +216,6 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { sess.ping(&sinfo.core.router) sinfo.callback(sess, nil) // Cleanup - delete(sinfo.core.searches.searches, res.Dest) + delete(sinfo.core.router.searches.searches, res.Dest) return true } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index f28e014a..02d4dc82 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -261,7 +261,7 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { // Run cleanup when the session is canceled <-sinfo.cancel.Finished() sinfo.core.router.doAdmin(func() { - sinfo.core.sessions.removeSession(&sinfo) + sinfo.core.router.sessions.removeSession(&sinfo) }) }() go sinfo.startWorkers() @@ -298,9 +298,9 @@ func (ss *sessions) cleanup() { // Closes a session, removing it from sessions maps. func (ss *sessions) removeSession(sinfo *sessionInfo) { - if s := sinfo.core.sessions.sinfos[sinfo.myHandle]; s == sinfo { - delete(sinfo.core.sessions.sinfos, sinfo.myHandle) - delete(sinfo.core.sessions.byTheirPerm, sinfo.theirPermPub) + if s := sinfo.core.router.sessions.sinfos[sinfo.myHandle]; s == sinfo { + delete(sinfo.core.router.sessions.sinfos, sinfo.myHandle) + delete(sinfo.core.router.sessions.byTheirPerm, sinfo.theirPermPub) } } @@ -466,9 +466,9 @@ func (sinfo *sessionInfo) _updateNonce(theirNonce *crypto.BoxNonce) { // Resets all sessions to an uninitialized state. // Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change. -func (ss *sessions) reset(from phony.IActor) { +func (ss *sessions) reset() { for _, sinfo := range ss.sinfos { - sinfo.EnqueueFrom(from, func() { + sinfo.EnqueueFrom(&ss.core.router, func() { sinfo.reset = true }) } From ebd806f27a9396d5184345425e4cf7d409f66582 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 23 Aug 2019 20:29:16 -0500 Subject: [PATCH 0244/1109] move router member initialization into router.init --- src/yggdrasil/core.go | 3 --- src/yggdrasil/router.go | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 870597a9..fb2142c0 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -73,9 +73,6 @@ func (c *Core) init() error { c.log.Warnln("SigningPublicKey in config is incorrect, should be", sp) } - c.router.searches.init(c) - c.router.dht.init(c) - c.router.sessions.init(c) c.peers.init(c) c.router.init(c) c.switchTable.init(c) // TODO move before peers? before router? diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index ed919983..29bdf1d4 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -73,6 +73,9 @@ func (r *router) init(core *Core) { r.core.config.Mutex.RLock() r.nodeinfo.setNodeInfo(r.core.config.Current.NodeInfo, r.core.config.Current.NodeInfoPrivacy) r.core.config.Mutex.RUnlock() + r.dht.init(r.core) + r.searches.init(r.core) + r.sessions.init(r.core) } // Starts the mainLoop goroutine. From e7024a00e712c7097ed8c194a937546566d80f10 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 23 Aug 2019 20:35:54 -0500 Subject: [PATCH 0245/1109] have dht store a pointer to router instead of core --- src/yggdrasil/dht.go | 34 +++++++++++++++++----------------- src/yggdrasil/router.go | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index d0d98558..adfc40e7 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -65,7 +65,7 @@ type dhtReqKey struct { // The main DHT struct. type dht struct { - core *Core + router *router reconfigure chan chan error nodeID crypto.NodeID reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests @@ -76,8 +76,8 @@ type dht struct { } // Initializes the DHT. -func (t *dht) init(c *Core) { - t.core = c +func (t *dht) init(r *router) { + t.router = r t.reconfigure = make(chan chan error, 1) go func() { for { @@ -85,7 +85,7 @@ func (t *dht) init(c *Core) { e <- nil } }() - t.nodeID = *t.core.NodeID() + t.nodeID = *t.router.core.NodeID() t.callbacks = make(map[dhtReqKey][]dht_callbackInfo) t.reset() } @@ -190,10 +190,10 @@ func dht_ordered(first, second, third *crypto.NodeID) bool { // Update info about the node that sent the request. func (t *dht) handleReq(req *dhtReq) { // Send them what they asked for - loc := t.core.switchTable.getLocator() + loc := t.router.core.switchTable.getLocator() coords := loc.getCoords() res := dhtRes{ - Key: t.core.boxPub, + Key: t.router.core.boxPub, Coords: coords, Dest: req.Dest, Infos: t.lookup(&req.Dest, false), @@ -221,17 +221,17 @@ func (t *dht) handleReq(req *dhtReq) { func (t *dht) sendRes(res *dhtRes, req *dhtReq) { // Send a reply for a dhtReq bs := res.encode() - shared := t.core.router.sessions.getSharedKey(&t.core.boxPriv, &req.Key) + shared := t.router.sessions.getSharedKey(&t.router.core.boxPriv, &req.Key) payload, nonce := crypto.BoxSeal(shared, bs, nil) p := wire_protoTrafficPacket{ Coords: req.Coords, ToKey: req.Key, - FromKey: t.core.boxPub, + FromKey: t.router.core.boxPub, Nonce: *nonce, Payload: payload, } packet := p.encode() - t.core.router.out(packet) + t.router.out(packet) } type dht_callbackInfo struct { @@ -285,17 +285,17 @@ func (t *dht) handleRes(res *dhtRes) { func (t *dht) sendReq(req *dhtReq, dest *dhtInfo) { // Send a dhtReq to the node in dhtInfo bs := req.encode() - shared := t.core.router.sessions.getSharedKey(&t.core.boxPriv, &dest.key) + shared := t.router.sessions.getSharedKey(&t.router.core.boxPriv, &dest.key) payload, nonce := crypto.BoxSeal(shared, bs, nil) p := wire_protoTrafficPacket{ Coords: dest.coords, ToKey: dest.key, - FromKey: t.core.boxPub, + FromKey: t.router.core.boxPub, Nonce: *nonce, Payload: payload, } packet := p.encode() - t.core.router.out(packet) + t.router.out(packet) rq := dhtReqKey{dest.key, req.Dest} t.reqs[rq] = time.Now() } @@ -306,10 +306,10 @@ func (t *dht) ping(info *dhtInfo, target *crypto.NodeID) { if target == nil { target = &t.nodeID } - loc := t.core.switchTable.getLocator() + loc := t.router.core.switchTable.getLocator() coords := loc.getCoords() req := dhtReq{ - Key: t.core.boxPub, + Key: t.router.core.boxPub, Coords: coords, Dest: *target, } @@ -384,7 +384,7 @@ func (t *dht) getImportant() []*dhtInfo { }) // Keep the ones that are no further than the closest seen so far minDist := ^uint64(0) - loc := t.core.switchTable.getLocator() + loc := t.router.core.switchTable.getLocator() important := infos[:0] for _, info := range infos { dist := uint64(loc.dist(info.coords)) @@ -413,12 +413,12 @@ func (t *dht) getImportant() []*dhtInfo { // Returns true if this is a node we need to keep track of for the DHT to work. func (t *dht) isImportant(ninfo *dhtInfo) bool { - if ninfo.key == t.core.boxPub { + if ninfo.key == t.router.core.boxPub { return false } important := t.getImportant() // Check if ninfo is of equal or greater importance to what we already know - loc := t.core.switchTable.getLocator() + loc := t.router.core.switchTable.getLocator() ndist := uint64(loc.dist(ninfo.coords)) minDist := ^uint64(0) for _, info := range important { diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 29bdf1d4..bd23768b 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -73,7 +73,7 @@ func (r *router) init(core *Core) { r.core.config.Mutex.RLock() r.nodeinfo.setNodeInfo(r.core.config.Current.NodeInfo, r.core.config.Current.NodeInfoPrivacy) r.core.config.Mutex.RUnlock() - r.dht.init(r.core) + r.dht.init(r) r.searches.init(r.core) r.sessions.init(r.core) } From 5bb85cf07b7bc7d75f6a1afd4648e3d88b10d676 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 23 Aug 2019 20:42:38 -0500 Subject: [PATCH 0246/1109] refactor searches to store a pointer to router instead of core --- src/yggdrasil/router.go | 2 +- src/yggdrasil/search.go | 38 +++++++++++++++++++------------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index bd23768b..ad9bc15a 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -74,7 +74,7 @@ func (r *router) init(core *Core) { r.nodeinfo.setNodeInfo(r.core.config.Current.NodeInfo, r.core.config.Current.NodeInfoPrivacy) r.core.config.Mutex.RUnlock() r.dht.init(r) - r.searches.init(r.core) + r.searches.init(r) r.sessions.init(r.core) } diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 5751043f..397c28a9 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -33,7 +33,7 @@ const search_RETRY_TIME = time.Second // Information about an ongoing search. // Includes the target NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited. type searchInfo struct { - core *Core + searches *searches dest crypto.NodeID mask crypto.NodeID time time.Time @@ -45,14 +45,14 @@ type searchInfo struct { // This stores a map of active searches. type searches struct { - core *Core + router *router reconfigure chan chan error searches map[crypto.NodeID]*searchInfo } // Initializes the searches struct. -func (s *searches) init(core *Core) { - s.core = core +func (s *searches) init(r *router) { + s.router = r s.reconfigure = make(chan chan error, 1) go func() { for { @@ -66,7 +66,7 @@ func (s *searches) init(core *Core) { // Creates a new search info, adds it to the searches struct, and returns a pointer to the info. func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo { info := searchInfo{ - core: s.core, + searches: s, dest: *dest, mask: *mask, time: time.Now(), @@ -100,7 +100,7 @@ func (sinfo *searchInfo) addToSearch(res *dhtRes) { from := dhtInfo{key: res.Key, coords: res.Coords} sinfo.visited[*from.getNodeID()] = true for _, info := range res.Infos { - if *info.getNodeID() == sinfo.core.router.dht.nodeID || sinfo.visited[*info.getNodeID()] { + if *info.getNodeID() == sinfo.searches.router.dht.nodeID || sinfo.visited[*info.getNodeID()] { continue } if dht_ordered(&sinfo.dest, info.getNodeID(), from.getNodeID()) { @@ -134,7 +134,7 @@ func (sinfo *searchInfo) doSearchStep() { if len(sinfo.toVisit) == 0 { if time.Since(sinfo.time) > search_RETRY_TIME { // Dead end and no response in too long, do cleanup - delete(sinfo.core.router.searches.searches, sinfo.dest) + delete(sinfo.searches.searches, sinfo.dest) sinfo.callback(nil, errors.New("search reached dead end")) } return @@ -143,8 +143,8 @@ func (sinfo *searchInfo) doSearchStep() { var next *dhtInfo next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:] rq := dhtReqKey{next.key, sinfo.dest} - sinfo.core.router.dht.addCallback(&rq, sinfo.handleDHTRes) - sinfo.core.router.dht.ping(next, &sinfo.dest) + sinfo.searches.router.dht.addCallback(&rq, sinfo.handleDHTRes) + sinfo.searches.router.dht.ping(next, &sinfo.dest) sinfo.time = time.Now() } @@ -157,7 +157,7 @@ func (sinfo *searchInfo) continueSearch() { // Any that die aren't restarted, but a new one will start later retryLater := func() { // FIXME this keeps the search alive forever if not for the searches map, fix that - newSearchInfo := sinfo.core.router.searches.searches[sinfo.dest] + newSearchInfo := sinfo.searches.searches[sinfo.dest] if newSearchInfo != sinfo { return } @@ -165,7 +165,7 @@ func (sinfo *searchInfo) continueSearch() { } go func() { time.Sleep(search_RETRY_TIME) - sinfo.core.router.doAdmin(retryLater) + sinfo.searches.router.doAdmin(retryLater) }() } @@ -173,9 +173,9 @@ func (sinfo *searchInfo) continueSearch() { func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo { sinfo := s.createSearch(dest, mask, callback) sinfo.visited = make(map[crypto.NodeID]bool) - loc := s.core.switchTable.getLocator() + loc := s.router.core.switchTable.getLocator() sinfo.toVisit = append(sinfo.toVisit, &dhtInfo{ - key: s.core.boxPub, + key: s.router.core.boxPub, coords: loc.getCoords(), }) // Start the search by asking ourself, useful if we're the destination return sinfo @@ -196,26 +196,26 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { return false } // They match, so create a session and send a sessionRequest - sess, isIn := sinfo.core.router.sessions.getByTheirPerm(&res.Key) + sess, isIn := sinfo.searches.router.sessions.getByTheirPerm(&res.Key) if !isIn { - sess = sinfo.core.router.sessions.createSession(&res.Key) + sess = sinfo.searches.router.sessions.createSession(&res.Key) if sess == nil { // nil if the DHT search finished but the session wasn't allowed sinfo.callback(nil, errors.New("session not allowed")) // Cleanup - delete(sinfo.core.router.searches.searches, res.Dest) + delete(sinfo.searches.searches, res.Dest) return true } - _, isIn := sinfo.core.router.sessions.getByTheirPerm(&res.Key) + _, isIn := sinfo.searches.router.sessions.getByTheirPerm(&res.Key) if !isIn { panic("This should never happen") } } // FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)? sess.coords = res.Coords - sess.ping(&sinfo.core.router) + sess.ping(sinfo.searches.router) sinfo.callback(sess, nil) // Cleanup - delete(sinfo.core.router.searches.searches, res.Dest) + delete(sinfo.searches.searches, res.Dest) return true } From 436c84ca3311cf9c57eb8eec6901a0edd408645f Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 23 Aug 2019 20:53:00 -0500 Subject: [PATCH 0247/1109] refactor sessions to store a pointer to router instead of core --- src/yggdrasil/conn.go | 2 +- src/yggdrasil/router.go | 2 +- src/yggdrasil/session.go | 53 +++++++++++++++++++++------------------- 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index b4ce71f9..4ba0b2aa 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -252,7 +252,7 @@ func (c *Conn) Close() (err error) { } func (c *Conn) LocalAddr() crypto.NodeID { - return *crypto.GetNodeID(&c.session.core.boxPub) + return *crypto.GetNodeID(&c.core.boxPub) } func (c *Conn) RemoteAddr() crypto.NodeID { diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index ad9bc15a..25f8e800 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -75,7 +75,7 @@ func (r *router) init(core *Core) { r.core.config.Mutex.RUnlock() r.dht.init(r) r.searches.init(r) - r.sessions.init(r.core) + r.sessions.init(r) } // Starts the mainLoop goroutine. diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 02d4dc82..13f64424 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -40,7 +40,7 @@ func (h nonceHeap) peek() *crypto.BoxNonce { return &h[len(h)-1] } // This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API. type sessionInfo struct { phony.Actor // Protects all of the below, use it any time you read/change the contents of a session - core *Core // + sessions *sessions // reconfigure chan chan error // theirAddr address.Address // theirSubnet address.Subnet // @@ -136,7 +136,7 @@ func (s *sessionInfo) _update(p *sessionPing) bool { // Sessions are indexed by handle. // Additionally, stores maps of address/subnet onto keys, and keys onto handles. type sessions struct { - core *Core + router *router listener *Listener listenerMutex sync.Mutex reconfigure chan chan error @@ -149,8 +149,8 @@ type sessions struct { } // Initializes the session struct. -func (ss *sessions) init(core *Core) { - ss.core = core +func (ss *sessions) init(r *router) { + ss.router = r ss.reconfigure = make(chan chan error, 1) go func() { for { @@ -213,18 +213,18 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { return nil } sinfo := sessionInfo{} - sinfo.core = ss.core + sinfo.sessions = ss sinfo.reconfigure = make(chan chan error, 1) sinfo.theirPermPub = *theirPermKey - sinfo.sharedPermKey = *ss.getSharedKey(&ss.core.boxPriv, &sinfo.theirPermPub) + sinfo.sharedPermKey = *ss.getSharedKey(&ss.router.core.boxPriv, &sinfo.theirPermPub) pub, priv := crypto.NewBoxKeys() sinfo.mySesPub = *pub sinfo.mySesPriv = *priv sinfo.myNonce = *crypto.NewBoxNonce() sinfo.theirMTU = 1280 - ss.core.config.Mutex.RLock() - sinfo.myMTU = uint16(ss.core.config.Current.IfMTU) - ss.core.config.Mutex.RUnlock() + ss.router.core.config.Mutex.RLock() + sinfo.myMTU = uint16(ss.router.core.config.Current.IfMTU) + ss.router.core.config.Mutex.RUnlock() now := time.Now() sinfo.timeOpened = now sinfo.time = now @@ -234,11 +234,11 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.init = make(chan struct{}) sinfo.cancel = util.NewCancellation() higher := false - for idx := range ss.core.boxPub { - if ss.core.boxPub[idx] > sinfo.theirPermPub[idx] { + for idx := range ss.router.core.boxPub { + if ss.router.core.boxPub[idx] > sinfo.theirPermPub[idx] { higher = true break - } else if ss.core.boxPub[idx] < sinfo.theirPermPub[idx] { + } else if ss.router.core.boxPub[idx] < sinfo.theirPermPub[idx] { break } } @@ -260,8 +260,8 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { go func() { // Run cleanup when the session is canceled <-sinfo.cancel.Finished() - sinfo.core.router.doAdmin(func() { - sinfo.core.router.sessions.removeSession(&sinfo) + sinfo.sessions.router.doAdmin(func() { + sinfo.sessions.removeSession(&sinfo) }) }() go sinfo.startWorkers() @@ -298,18 +298,18 @@ func (ss *sessions) cleanup() { // Closes a session, removing it from sessions maps. func (ss *sessions) removeSession(sinfo *sessionInfo) { - if s := sinfo.core.router.sessions.sinfos[sinfo.myHandle]; s == sinfo { - delete(sinfo.core.router.sessions.sinfos, sinfo.myHandle) - delete(sinfo.core.router.sessions.byTheirPerm, sinfo.theirPermPub) + if s := sinfo.sessions.sinfos[sinfo.myHandle]; s == sinfo { + delete(sinfo.sessions.sinfos, sinfo.myHandle) + delete(sinfo.sessions.byTheirPerm, sinfo.theirPermPub) } } // Returns a session ping appropriate for the given session info. func (sinfo *sessionInfo) _getPing() sessionPing { - loc := sinfo.core.switchTable.getLocator() + loc := sinfo.sessions.router.core.switchTable.getLocator() coords := loc.getCoords() ping := sessionPing{ - SendPermPub: sinfo.core.boxPub, + SendPermPub: sinfo.sessions.router.core.boxPub, Handle: sinfo.myHandle, SendSesPub: sinfo.mySesPub, Tstamp: time.Now().Unix(), @@ -360,13 +360,13 @@ func (sinfo *sessionInfo) _sendPingPong(isPong bool) { p := wire_protoTrafficPacket{ Coords: sinfo.coords, ToKey: sinfo.theirPermPub, - FromKey: sinfo.core.boxPub, + FromKey: sinfo.sessions.router.core.boxPub, Nonce: *nonce, Payload: payload, } packet := p.encode() // TODO rewrite the below if/when the peer struct becomes an actor, to not go through the router first - sinfo.core.router.EnqueueFrom(sinfo, func() { sinfo.core.router.out(packet) }) + sinfo.sessions.router.EnqueueFrom(sinfo, func() { sinfo.sessions.router.out(packet) }) if sinfo.pingTime.Before(sinfo.time) { sinfo.pingTime = time.Now() } @@ -390,7 +390,7 @@ func (ss *sessions) handlePing(ping *sessionPing) { if s, _ := ss.getByTheirPerm(&ping.SendPermPub); s != sinfo { panic("This should not happen") } - conn := newConn(ss.core, crypto.GetNodeID(&sinfo.theirPermPub), &crypto.NodeID{}, sinfo) + conn := newConn(ss.router.core, crypto.GetNodeID(&sinfo.theirPermPub), &crypto.NodeID{}, sinfo) for i := range conn.nodeMask { conn.nodeMask[i] = 0xFF } @@ -400,7 +400,7 @@ func (ss *sessions) handlePing(ping *sessionPing) { ss.listenerMutex.Unlock() } if sinfo != nil { - sinfo.EnqueueFrom(&ss.core.router, func() { + sinfo.EnqueueFrom(ss.router, func() { // Update the session if !sinfo._update(ping) { /*panic("Should not happen in testing")*/ return @@ -468,7 +468,7 @@ func (sinfo *sessionInfo) _updateNonce(theirNonce *crypto.BoxNonce) { // Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change. func (ss *sessions) reset() { for _, sinfo := range ss.sinfos { - sinfo.EnqueueFrom(&ss.core.router, func() { + sinfo.EnqueueFrom(ss.router, func() { sinfo.reset = true }) } @@ -639,7 +639,10 @@ func (sinfo *sessionInfo) sendWorker() { util.PutBytes(msg.Message) util.PutBytes(p.Payload) // Send the packet - sinfo.core.router.out(packet) + // TODO replace this with a send to the peer struct if that becomes an actor + sinfo.sessions.router.EnqueueFrom(sinfo, func() { + sinfo.sessions.router.out(packet) + }) } ch <- callback } From 533da351f9b0f0b1ffa6a2d459aeece7c05d2b2c Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 23 Aug 2019 22:23:01 -0500 Subject: [PATCH 0248/1109] fix actor EnqueueFrom stack overflow (use nil now to send from self) and replace session send/recv workers with actor functions --- go.mod | 2 +- go.sum | 4 +- src/yggdrasil/conn.go | 4 +- src/yggdrasil/peer.go | 2 +- src/yggdrasil/router.go | 4 +- src/yggdrasil/session.go | 129 ++++++++++++++++++++++++++++++++++++--- src/yggdrasil/switch.go | 4 +- 7 files changed, 132 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index 3f5cae88..fff4ae6f 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/yggdrasil-network/yggdrasil-go require ( - github.com/Arceliar/phony v0.0.0-20190821233739-c7f353f14438 + github.com/Arceliar/phony v0.0.0-20190824031448-b53e115f69b5 github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 github.com/hashicorp/go-syslog v1.0.0 github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 diff --git a/go.sum b/go.sum index 22276c2c..29854cbf 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/phony v0.0.0-20190821233739-c7f353f14438 h1:t4tRgrItIq2ap4O31yOuWm17lUiyzf8gf/P+bEfgmrw= -github.com/Arceliar/phony v0.0.0-20190821233739-c7f353f14438/go.mod h1:2Q9yJvg2PlMrnOEa3RTEy9hElWAICo/D8HTUDqAHUAo= +github.com/Arceliar/phony v0.0.0-20190824031448-b53e115f69b5 h1:D2Djqo/q7mftrtHLCpW4Rpplm8jj+Edc9jNz8Ll6E0A= +github.com/Arceliar/phony v0.0.0-20190824031448-b53e115f69b5/go.mod h1:2Q9yJvg2PlMrnOEa3RTEy9hElWAICo/D8HTUDqAHUAo= github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 h1:WD8iJ37bRNwvETMfVTusVSAi0WdXTpfNVGY2aHycNKY= github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 4ba0b2aa..1828b556 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -162,7 +162,7 @@ func (c *Conn) ReadNoCopy() ([]byte, error) { } else { return nil, ConnError{errors.New("session closed"), false, false, true, 0} } - case bs := <-c.session.recv: + case bs := <-c.session.toConn: return bs, nil } } @@ -221,7 +221,7 @@ func (c *Conn) WriteNoCopy(msg FlowKeyMessage) error { } else { err = ConnError{errors.New("session closed"), false, false, true, 0} } - case c.session.send <- msg: + case <-c.session.SyncExec(func() { c.session._send(msg) }): } } return err diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index fcd9364c..8869bd2a 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -210,7 +210,7 @@ func (p *peer) linkLoop() { case dinfo = <-p.dinfo: case _ = <-tick.C: if dinfo != nil { - p.core.router.insertPeer(&p.core.router, dinfo) + p.core.router.insertPeer(nil, dinfo) } } } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 25f8e800..b5df107e 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -66,7 +66,7 @@ func (r *router) init(core *Core) { p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &crypto.BoxSharedKey{}, &self, nil) p.out = func(packets [][]byte) { // TODO make peers and/or the switch into actors, have them pass themselves as the from field - r.handlePackets(r, packets) + r.handlePackets(nil, packets) } r.out = p.handlePacket // TODO if the peer becomes its own actor, then send a message here r.nodeinfo.init(r.core) @@ -160,6 +160,8 @@ func (r *router) _handleTraffic(packet []byte) { util.PutBytes(p.Payload) return } + sinfo.recv(r, &p) + return select { case sinfo.fromRouter <- p: case <-sinfo.cancel.Finished(): diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 13f64424..84ea92af 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -72,8 +72,9 @@ type sessionInfo struct { init chan struct{} // Closed when the first session pong arrives, used to signal that the session is ready for initial use cancel util.Cancellation // Used to terminate workers fromRouter chan wire_trafficPacket // Received packets go here, to be decrypted by the session - recv chan []byte // Decrypted packets go here, picked up by the associated Conn - send chan FlowKeyMessage // Packets with optional flow key go here, to be encrypted and sent + toConn chan []byte // Decrypted packets go here, picked up by the associated Conn + fromConn chan FlowKeyMessage // Packets with optional flow key go here, to be encrypted and sent + callbacks []chan func() // Finished work from crypto workers } // TODO remove this, call SyncExec directly @@ -253,8 +254,8 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.theirAddr = *address.AddrForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) sinfo.fromRouter = make(chan wire_trafficPacket, 1) - sinfo.recv = make(chan []byte, 32) - sinfo.send = make(chan FlowKeyMessage, 32) + sinfo.toConn = make(chan []byte, 32) + sinfo.fromConn = make(chan FlowKeyMessage, 32) ss.sinfos[sinfo.myHandle] = &sinfo ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle go func() { @@ -264,7 +265,7 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.sessions.removeSession(&sinfo) }) }() - go sinfo.startWorkers() + //go sinfo.startWorkers() return &sinfo } @@ -539,7 +540,7 @@ func (sinfo *sessionInfo) recvWorker() { select { case <-sinfo.cancel.Finished(): util.PutBytes(bs) - case sinfo.recv <- bs: + case sinfo.toConn <- bs: } } } @@ -664,15 +665,127 @@ func (sinfo *sessionInfo) sendWorker() { f() case <-sinfo.cancel.Finished(): return - case msg := <-sinfo.send: + case msg := <-sinfo.fromConn: doSend(msg) } } select { case <-sinfo.cancel.Finished(): return - case bs := <-sinfo.send: + case bs := <-sinfo.fromConn: doSend(bs) } } } + +//////////////////////////////////////////////////////////////////////////////// + +func (sinfo *sessionInfo) recv(from phony.IActor, packet *wire_trafficPacket) { + sinfo.EnqueueFrom(from, func() { + sinfo._recvPacket(packet) + }) +} + +func (sinfo *sessionInfo) _recvPacket(p *wire_trafficPacket) { + select { + case <-sinfo.init: + default: + // TODO find a better way to drop things until initialized + util.PutBytes(p.Payload) + return + } + switch { + case sinfo._nonceIsOK(&p.Nonce): + case len(sinfo.toConn) < cap(sinfo.toConn): + default: + // We're either full or don't like this nonce + util.PutBytes(p.Payload) + return + } + + k := sinfo.sharedSesKey + var isOK bool + var bs []byte + ch := make(chan func(), 1) + poolFunc := func() { + bs, isOK = crypto.BoxOpen(&k, p.Payload, &p.Nonce) + callback := func() { + util.PutBytes(p.Payload) + if !isOK || k != sinfo.sharedSesKey || !sinfo._nonceIsOK(&p.Nonce) { + // Either we failed to decrypt, or the session was updated, or we received this packet in the mean time + util.PutBytes(bs) + return + } + sinfo._updateNonce(&p.Nonce) + sinfo.time = time.Now() + sinfo.bytesRecvd += uint64(len(bs)) + select { + case sinfo.toConn <- bs: + default: + // We seem to have filled up the buffer in the mean time, so drop it + util.PutBytes(bs) + } + } + ch <- callback + sinfo.checkCallbacks() + } + sinfo.callbacks = append(sinfo.callbacks, ch) + util.WorkerGo(poolFunc) +} + +func (sinfo *sessionInfo) _send(msg FlowKeyMessage) { + select { + case <-sinfo.init: + default: + // TODO find a better way to drop things until initialized + util.PutBytes(msg.Message) + return + } + sinfo.bytesSent += uint64(len(msg.Message)) + coords := append([]byte(nil), sinfo.coords...) + if msg.FlowKey != 0 { + coords = append(coords, 0) + coords = append(coords, wire_encode_uint64(msg.FlowKey)...) + } + p := wire_trafficPacket{ + Coords: coords, + Handle: sinfo.theirHandle, + Nonce: sinfo.myNonce, + } + sinfo.myNonce.Increment() + k := sinfo.sharedSesKey + ch := make(chan func(), 1) + poolFunc := func() { + p.Payload, _ = crypto.BoxSeal(&k, msg.Message, &p.Nonce) + callback := func() { + // Encoding may block on a util.GetBytes(), so kept out of the worker pool + packet := p.encode() + // Cleanup + util.PutBytes(msg.Message) + util.PutBytes(p.Payload) + // Send the packet + // TODO replace this with a send to the peer struct if that becomes an actor + sinfo.sessions.router.EnqueueFrom(sinfo, func() { + sinfo.sessions.router.out(packet) + }) + } + ch <- callback + sinfo.checkCallbacks() + } + sinfo.callbacks = append(sinfo.callbacks, ch) + util.WorkerGo(poolFunc) +} + +func (sinfo *sessionInfo) checkCallbacks() { + sinfo.EnqueueFrom(nil, func() { + if len(sinfo.callbacks) > 0 { + select { + case callback := <-sinfo.callbacks[0]: + sinfo.callbacks = sinfo.callbacks[1:] + callback() + sinfo.checkCallbacks() + default: + } + } + }) +} diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 86ae102b..1fa75a6c 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -245,7 +245,7 @@ func (t *switchTable) cleanRoot() { if t.data.locator.root != t.key { t.data.seq++ t.updater.Store(&sync.Once{}) - t.core.router.reset(&t.core.router) + t.core.router.reset(nil) } t.data.locator = switchLocator{root: t.key, tstamp: now.Unix()} t.core.peers.sendSwitchMsgs() @@ -508,7 +508,7 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep if !equiv(&sender.locator, &t.data.locator) { doUpdate = true t.data.seq++ - t.core.router.reset(&t.core.router) + t.core.router.reset(nil) } if t.data.locator.tstamp != sender.locator.tstamp { t.time = now From e3603c0462fba858e330d7bc69d1c41d1016a98d Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 23 Aug 2019 22:25:40 -0500 Subject: [PATCH 0249/1109] clean up unused session code --- src/yggdrasil/router.go | 6 -- src/yggdrasil/session.go | 201 --------------------------------------- 2 files changed, 207 deletions(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index b5df107e..464a4778 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -161,12 +161,6 @@ func (r *router) _handleTraffic(packet []byte) { return } sinfo.recv(r, &p) - return - select { - case sinfo.fromRouter <- p: - case <-sinfo.cancel.Finished(): - util.PutBytes(p.Payload) - } } // Handles protocol traffic by decrypting it, checking its type, and passing it to the appropriate handler for that traffic type. diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 84ea92af..d17b119f 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -7,7 +7,6 @@ package yggdrasil import ( "bytes" "container/heap" - "errors" "sync" "time" @@ -71,9 +70,7 @@ type sessionInfo struct { bytesRecvd uint64 // Bytes of real traffic received in this session init chan struct{} // Closed when the first session pong arrives, used to signal that the session is ready for initial use cancel util.Cancellation // Used to terminate workers - fromRouter chan wire_trafficPacket // Received packets go here, to be decrypted by the session toConn chan []byte // Decrypted packets go here, picked up by the associated Conn - fromConn chan FlowKeyMessage // Packets with optional flow key go here, to be encrypted and sent callbacks []chan func() // Finished work from crypto workers } @@ -253,9 +250,7 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.myHandle = *crypto.NewHandle() sinfo.theirAddr = *address.AddrForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) - sinfo.fromRouter = make(chan wire_trafficPacket, 1) sinfo.toConn = make(chan []byte, 32) - sinfo.fromConn = make(chan FlowKeyMessage, 32) ss.sinfos[sinfo.myHandle] = &sinfo ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle go func() { @@ -479,207 +474,11 @@ func (ss *sessions) reset() { //////////////////////////// Worker Functions Below //////////////////////////// //////////////////////////////////////////////////////////////////////////////// -func (sinfo *sessionInfo) startWorkers() { - go sinfo.recvWorker() - go sinfo.sendWorker() -} - type FlowKeyMessage struct { FlowKey uint64 Message []byte } -func (sinfo *sessionInfo) recvWorker() { - // TODO move theirNonce etc into a struct that gets stored here, passed in over a channel - // Since there's no reason for anywhere else in the session code to need to *read* it... - // Only needs to be updated from the outside if a ping resets it... - // That would get rid of the need to take a mutex for the sessionFunc - var callbacks []chan func() - doRecv := func(p wire_trafficPacket) { - var bs []byte - var err error - var k crypto.BoxSharedKey - sessionFunc := func() { - if !sinfo._nonceIsOK(&p.Nonce) { - err = ConnError{errors.New("packet dropped due to invalid nonce"), false, true, false, 0} - return - } - k = sinfo.sharedSesKey - } - sinfo.doFunc(sessionFunc) - if err != nil { - util.PutBytes(p.Payload) - return - } - var isOK bool - ch := make(chan func(), 1) - poolFunc := func() { - bs, isOK = crypto.BoxOpen(&k, p.Payload, &p.Nonce) - callback := func() { - util.PutBytes(p.Payload) - if !isOK { - util.PutBytes(bs) - return - } - sessionFunc = func() { - if k != sinfo.sharedSesKey || !sinfo._nonceIsOK(&p.Nonce) { - // The session updated in the mean time, so return an error - err = ConnError{errors.New("session updated during crypto operation"), false, true, false, 0} - return - } - sinfo._updateNonce(&p.Nonce) - sinfo.time = time.Now() - sinfo.bytesRecvd += uint64(len(bs)) - } - sinfo.doFunc(sessionFunc) - if err != nil { - // Not sure what else to do with this packet, I guess just drop it - util.PutBytes(bs) - } else { - // Pass the packet to the buffer for Conn.Read - select { - case <-sinfo.cancel.Finished(): - util.PutBytes(bs) - case sinfo.toConn <- bs: - } - } - } - ch <- callback - } - // Send to the worker and wait for it to finish - util.WorkerGo(poolFunc) - callbacks = append(callbacks, ch) - } - fromHelper := make(chan wire_trafficPacket, 1) - go func() { - var buf []wire_trafficPacket - for { - for len(buf) > 0 { - select { - case <-sinfo.cancel.Finished(): - return - case p := <-sinfo.fromRouter: - buf = append(buf, p) - for len(buf) > 64 { // Based on nonce window size - util.PutBytes(buf[0].Payload) - buf = buf[1:] - } - case fromHelper <- buf[0]: - buf = buf[1:] - } - } - select { - case <-sinfo.cancel.Finished(): - return - case p := <-sinfo.fromRouter: - buf = append(buf, p) - } - } - }() - select { - case <-sinfo.cancel.Finished(): - return - case <-sinfo.init: - // Wait until the session has finished initializing before processing any packets - } - for { - for len(callbacks) > 0 { - select { - case f := <-callbacks[0]: - callbacks = callbacks[1:] - f() - case <-sinfo.cancel.Finished(): - return - case p := <-fromHelper: - doRecv(p) - } - } - select { - case <-sinfo.cancel.Finished(): - return - case p := <-fromHelper: - doRecv(p) - } - } -} - -func (sinfo *sessionInfo) sendWorker() { - // TODO move info that this worker needs here, send updates via a channel - // Otherwise we need to take a mutex to avoid races with update() - var callbacks []chan func() - doSend := func(msg FlowKeyMessage) { - var p wire_trafficPacket - var k crypto.BoxSharedKey - sessionFunc := func() { - sinfo.bytesSent += uint64(len(msg.Message)) - p = wire_trafficPacket{ - Coords: append([]byte(nil), sinfo.coords...), - Handle: sinfo.theirHandle, - Nonce: sinfo.myNonce, - } - if msg.FlowKey != 0 { - // Helps ensure that traffic from this flow ends up in a separate queue from other flows - // The zero padding relies on the fact that the self-peer is always on port 0 - p.Coords = append(p.Coords, 0) - p.Coords = wire_put_uint64(msg.FlowKey, p.Coords) - } - sinfo.myNonce.Increment() - k = sinfo.sharedSesKey - } - // Get the mutex-protected info needed to encrypt the packet - sinfo.doFunc(sessionFunc) - ch := make(chan func(), 1) - poolFunc := func() { - // Encrypt the packet - p.Payload, _ = crypto.BoxSeal(&k, msg.Message, &p.Nonce) - // The callback will send the packet - callback := func() { - // Encoding may block on a util.GetBytes(), so kept out of the worker pool - packet := p.encode() - // Cleanup - util.PutBytes(msg.Message) - util.PutBytes(p.Payload) - // Send the packet - // TODO replace this with a send to the peer struct if that becomes an actor - sinfo.sessions.router.EnqueueFrom(sinfo, func() { - sinfo.sessions.router.out(packet) - }) - } - ch <- callback - } - // Send to the worker and wait for it to finish - util.WorkerGo(poolFunc) - callbacks = append(callbacks, ch) - } - select { - case <-sinfo.cancel.Finished(): - return - case <-sinfo.init: - // Wait until the session has finished initializing before processing any packets - } - for { - for len(callbacks) > 0 { - select { - case f := <-callbacks[0]: - callbacks = callbacks[1:] - f() - case <-sinfo.cancel.Finished(): - return - case msg := <-sinfo.fromConn: - doSend(msg) - } - } - select { - case <-sinfo.cancel.Finished(): - return - case bs := <-sinfo.fromConn: - doSend(bs) - } - } -} - -//////////////////////////////////////////////////////////////////////////////// - func (sinfo *sessionInfo) recv(from phony.IActor, packet *wire_trafficPacket) { sinfo.EnqueueFrom(from, func() { sinfo._recvPacket(packet) From cf9880464bdf100ea6c88aebd58dc02afef35e07 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 23 Aug 2019 22:36:59 -0500 Subject: [PATCH 0250/1109] explicitly consider the session finished case, and make a note that we could fix the packet drop situation by making the Conn into an actor too --- src/yggdrasil/session.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index d17b119f..3f5e913e 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -520,8 +520,12 @@ func (sinfo *sessionInfo) _recvPacket(p *wire_trafficPacket) { sinfo.bytesRecvd += uint64(len(bs)) select { case sinfo.toConn <- bs: + case <-sinfo.cancel.Finished(): + util.PutBytes(bs) default: - // We seem to have filled up the buffer in the mean time, so drop it + // We seem to have filled up the buffer in the mean time + // Since we need to not block, but the conn isn't an actor, we need to drop this packet + // TODO find some nicer way to interact with the Conn... util.PutBytes(bs) } } From cac3444d9ae0a2e45aabe142430461843f91948b Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 23 Aug 2019 22:40:13 -0500 Subject: [PATCH 0251/1109] fix debug builds --- src/yggdrasil/debug.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index 9f7707e2..d90bf1f4 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -258,7 +258,7 @@ func DEBUG_wire_encode_coords(coords []byte) []byte { func (c *Core) DEBUG_getDHTSize() int { var total int c.router.doAdmin(func() { - total = len(c.dht.table) + total = len(c.router.dht.table) }) return total } @@ -320,7 +320,7 @@ func (c *Core) DEBUG_startLoopbackUDPInterface() { //////////////////////////////////////////////////////////////////////////////// func (c *Core) DEBUG_getAddr() *address.Address { - return address.AddrForNodeID(&c.dht.nodeID) + return address.AddrForNodeID(&c.router.dht.nodeID) } /* From 6ecbc439f0c0755597ab56fe1fe1b364fcea9be4 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 23 Aug 2019 23:36:00 -0500 Subject: [PATCH 0252/1109] start migrating Conn to be an actor --- src/yggdrasil/conn.go | 54 +++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 1828b556..fa05fff0 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -3,12 +3,12 @@ package yggdrasil import ( "errors" "fmt" - "sync" - "sync/atomic" "time" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/util" + + "github.com/Arceliar/phony" ) // ConnError implements the net.Error interface @@ -54,10 +54,10 @@ func (e *ConnError) Closed() bool { } type Conn struct { + phony.Actor core *Core - readDeadline atomic.Value // time.Time // TODO timer - writeDeadline atomic.Value // time.Time // TODO timer - mutex sync.RWMutex // protects the below + readDeadline *time.Time + writeDeadline *time.Time nodeID *crypto.NodeID nodeMask *crypto.NodeID session *sessionInfo @@ -75,9 +75,9 @@ func newConn(core *Core, nodeID *crypto.NodeID, nodeMask *crypto.NodeID, session } func (c *Conn) String() string { - c.mutex.RLock() - defer c.mutex.RUnlock() - return fmt.Sprintf("conn=%p", c) + var s string + <-c.SyncExec(func() { s = fmt.Sprintf("conn=%p", c) }) + return s } // This should never be called from the router goroutine, used in the dial functions @@ -137,10 +137,10 @@ func (c *Conn) doSearch() { go c.core.router.doAdmin(routerWork) } -func (c *Conn) getDeadlineCancellation(value *atomic.Value) (util.Cancellation, bool) { - if deadline, ok := value.Load().(time.Time); ok { +func (c *Conn) _getDeadlineCancellation(t *time.Time) (util.Cancellation, bool) { + if t != nil { // A deadline is set, so return a Cancellation that uses it - c := util.CancellationWithDeadline(c.session.cancel, deadline) + c := util.CancellationWithDeadline(c.session.cancel, *t) return c, true } else { // No deadline was set, so just return the existinc cancellation and a dummy value @@ -150,7 +150,9 @@ func (c *Conn) getDeadlineCancellation(value *atomic.Value) (util.Cancellation, // Used internally by Read, the caller is responsible for util.PutBytes when they're done. func (c *Conn) ReadNoCopy() ([]byte, error) { - cancel, doCancel := c.getDeadlineCancellation(&c.readDeadline) + var cancel util.Cancellation + var doCancel bool + <-c.SyncExec(func() { cancel, doCancel = c._getDeadlineCancellation(c.readDeadline) }) if doCancel { defer cancel.Cancel(nil) } @@ -210,7 +212,9 @@ func (c *Conn) WriteNoCopy(msg FlowKeyMessage) error { } c.session.doFunc(sessionFunc) if err == nil { - cancel, doCancel := c.getDeadlineCancellation(&c.writeDeadline) + var cancel util.Cancellation + var doCancel bool + <-c.SyncExec(func() { cancel, doCancel = c._getDeadlineCancellation(c.writeDeadline) }) if doCancel { defer cancel.Cancel(nil) } @@ -240,14 +244,14 @@ func (c *Conn) Write(b []byte) (int, error) { } func (c *Conn) Close() (err error) { - c.mutex.Lock() - defer c.mutex.Unlock() - if c.session != nil { - // Close the session, if it hasn't been closed already - if e := c.session.cancel.Cancel(errors.New("connection closed")); e != nil { - err = ConnError{errors.New("close failed, session already closed"), false, false, true, 0} + <-c.SyncExec(func() { + if c.session != nil { + // Close the session, if it hasn't been closed already + if e := c.session.cancel.Cancel(errors.New("connection closed")); e != nil { + err = ConnError{errors.New("close failed, session already closed"), false, false, true, 0} + } } - } + }) return } @@ -256,9 +260,9 @@ func (c *Conn) LocalAddr() crypto.NodeID { } func (c *Conn) RemoteAddr() crypto.NodeID { - c.mutex.RLock() - defer c.mutex.RUnlock() - return *c.nodeID + var n crypto.NodeID + <-c.SyncExec(func() { n = *c.nodeID }) + return n } func (c *Conn) SetDeadline(t time.Time) error { @@ -268,11 +272,11 @@ func (c *Conn) SetDeadline(t time.Time) error { } func (c *Conn) SetReadDeadline(t time.Time) error { - c.readDeadline.Store(t) + <-c.SyncExec(func() { c.readDeadline = &t }) return nil } func (c *Conn) SetWriteDeadline(t time.Time) error { - c.writeDeadline.Store(t) + <-c.SyncExec(func() { c.writeDeadline = &t }) return nil } From da9f7151e3da50547b7320a6bbe9e9bbede23694 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 24 Aug 2019 00:17:37 -0500 Subject: [PATCH 0253/1109] more conn migration --- src/yggdrasil/conn.go | 63 ++++++++++++++++++++++------------------ src/yggdrasil/dialer.go | 1 + src/yggdrasil/session.go | 12 ++++++++ 3 files changed, 47 insertions(+), 29 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index fa05fff0..7121fc15 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -61,6 +61,7 @@ type Conn struct { nodeID *crypto.NodeID nodeMask *crypto.NodeID session *sessionInfo + mtu uint16 } // TODO func NewConn() that initializes additional fields as needed @@ -80,6 +81,10 @@ func (c *Conn) String() string { return s } +func (c *Conn) setMTU(from phony.IActor, mtu uint16) { + c.EnqueueFrom(from, func() { c.mtu = mtu }) +} + // This should never be called from the router goroutine, used in the dial functions func (c *Conn) search() error { var sinfo *searchInfo @@ -112,6 +117,7 @@ func (c *Conn) search() error { for i := range c.nodeMask { c.nodeMask[i] = 0xFF } + c.session.conn = c } return err } else { @@ -120,7 +126,7 @@ func (c *Conn) search() error { return nil } -// Used in session keep-alive traffic in Conn.Write +// Used in session keep-alive traffic func (c *Conn) doSearch() { routerWork := func() { // Check to see if there is a search already matching the destination @@ -134,7 +140,7 @@ func (c *Conn) doSearch() { sinfo.continueSearch() } } - go c.core.router.doAdmin(routerWork) + c.core.router.EnqueueFrom(c.session, routerWork) } func (c *Conn) _getDeadlineCancellation(t *time.Time) (util.Cancellation, bool) { @@ -187,16 +193,14 @@ func (c *Conn) Read(b []byte) (int, error) { return n, err } -// Used internally by Write, the caller must not reuse the argument bytes when no error occurs -func (c *Conn) WriteNoCopy(msg FlowKeyMessage) error { - var err error - sessionFunc := func() { - // Does the packet exceed the permitted size for the session? - if uint16(len(msg.Message)) > c.session._getMTU() { - err = ConnError{errors.New("packet too big"), true, false, false, int(c.session._getMTU())} - return - } - // The rest of this work is session keep-alive traffic +func (c *Conn) _write(msg FlowKeyMessage) error { + if len(msg.Message) > int(c.mtu) { + return ConnError{errors.New("packet too big"), true, false, false, int(c.mtu)} + } + c.session.EnqueueFrom(c, func() { + // Send the packet + c.session._send(msg) + // Session keep-alive, while we wait for the crypto workers from send switch { case time.Since(c.session.time) > 6*time.Second: if c.session.time.Before(c.session.pingTime) && time.Since(c.session.pingTime) > 6*time.Second { @@ -209,24 +213,25 @@ func (c *Conn) WriteNoCopy(msg FlowKeyMessage) error { c.session.ping(c.session) // TODO send from self if this becomes an actor default: // Don't do anything, to keep traffic throttled } - } - c.session.doFunc(sessionFunc) - if err == nil { - var cancel util.Cancellation - var doCancel bool - <-c.SyncExec(func() { cancel, doCancel = c._getDeadlineCancellation(c.writeDeadline) }) - if doCancel { - defer cancel.Cancel(nil) - } - select { - case <-cancel.Finished(): - if cancel.Error() == util.CancellationTimeoutError { - err = ConnError{errors.New("write timeout"), true, false, false, 0} - } else { - err = ConnError{errors.New("session closed"), false, false, true, 0} - } - case <-c.session.SyncExec(func() { c.session._send(msg) }): + }) + return nil +} + +// Used internally by Write, the caller must not reuse the argument bytes when no error occurs +func (c *Conn) WriteNoCopy(msg FlowKeyMessage) error { + var cancel util.Cancellation + var doCancel bool + <-c.SyncExec(func() { cancel, doCancel = c._getDeadlineCancellation(c.writeDeadline) }) + var err error + select { + case <-cancel.Finished(): + if cancel.Error() == util.CancellationTimeoutError { + err = ConnError{errors.New("write timeout"), true, false, false, 0} + } else { + err = ConnError{errors.New("session closed"), false, false, true, 0} } + default: + <-c.SyncExec(func() { err = c._write(msg) }) } return err } diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index 6b24cfb4..04410855 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -65,6 +65,7 @@ func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (*Conn, er conn.Close() return nil, err } + conn.session.setConn(nil, conn) t := time.NewTimer(6 * time.Second) // TODO use a context instead defer t.Stop() select { diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 3f5e913e..854eb24d 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -71,6 +71,7 @@ type sessionInfo struct { init chan struct{} // Closed when the first session pong arrives, used to signal that the session is ready for initial use cancel util.Cancellation // Used to terminate workers toConn chan []byte // Decrypted packets go here, picked up by the associated Conn + conn *Conn // The associated Conn object callbacks []chan func() // Finished work from crypto workers } @@ -112,6 +113,9 @@ func (s *sessionInfo) _update(p *sessionPing) bool { } if p.MTU >= 1280 || p.MTU == 0 { s.theirMTU = p.MTU + if s.conn != nil { + s.conn.setMTU(s, s._getMTU()) + } } if !bytes.Equal(s.coords, p.Coords) { // allocate enough space for additional coords @@ -368,6 +372,13 @@ func (sinfo *sessionInfo) _sendPingPong(isPong bool) { } } +func (sinfo *sessionInfo) setConn(from phony.IActor, conn *Conn) { + sinfo.EnqueueFrom(from, func() { + sinfo.conn = conn + sinfo.conn.setMTU(sinfo, sinfo._getMTU()) + }) +} + // Handles a session ping, creating a session if needed and calling update, then possibly responding with a pong if the ping was in ping mode and the update was successful. // If the session has a packet cached (common when first setting up a session), it will be sent. func (ss *sessions) handlePing(ping *sessionPing) { @@ -390,6 +401,7 @@ func (ss *sessions) handlePing(ping *sessionPing) { for i := range conn.nodeMask { conn.nodeMask[i] = 0xFF } + sinfo.setConn(ss.router, conn) c := ss.listener.conn go func() { c <- conn }() } From 9948e3d6591e905dd6095b10eae5f1232d37bd0f Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 24 Aug 2019 00:44:02 -0500 Subject: [PATCH 0254/1109] add Conn.WriteFrom to allow actor-based sending --- src/yggdrasil/conn.go | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 7121fc15..9e237436 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -217,7 +217,17 @@ func (c *Conn) _write(msg FlowKeyMessage) error { return nil } -// Used internally by Write, the caller must not reuse the argument bytes when no error occurs +// WriteFrom should be called by a phony.IActor, and tells the Conn to send a message. +// This is used internaly by WriteNoCopy and Write. +// If the callback is called with a non-nil value, then it is safe to reuse the argument FlowKeyMessage. +func (c *Conn) WriteFrom(from phony.IActor, msg FlowKeyMessage, callback func(error)) { + c.EnqueueFrom(from, func() { + callback(c._write(msg)) + }) +} + +// WriteNoCopy is used internally by Write and makes use of WriteFrom under the hood. +// The caller must not reuse the argument FlowKeyMessage when a nil error is returned. func (c *Conn) WriteNoCopy(msg FlowKeyMessage) error { var cancel util.Cancellation var doCancel bool @@ -231,12 +241,15 @@ func (c *Conn) WriteNoCopy(msg FlowKeyMessage) error { err = ConnError{errors.New("session closed"), false, false, true, 0} } default: - <-c.SyncExec(func() { err = c._write(msg) }) + done := make(chan struct{}) + callback := func(e error) { err = e; close(done) } + c.WriteFrom(nil, msg, callback) + <-done } return err } -// Implements net.Conn.Write +// Write implement the Write function of a net.Conn, and makes use of WriteNoCopy under the hood. func (c *Conn) Write(b []byte) (int, error) { written := len(b) msg := FlowKeyMessage{Message: append(util.GetBytes(), b...)} From 1e346aaad052a23ada257f07e34355b622dafc90 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 24 Aug 2019 01:52:21 -0500 Subject: [PATCH 0255/1109] have the conn actor receive messages from the session actor and either pass them to a callback or buffer them in a channel for Read to use if no callback was set --- src/yggdrasil/conn.go | 55 ++++++++++++++++++++++++++++++++++++---- src/yggdrasil/session.go | 20 ++------------- 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 9e237436..18c0b880 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -62,15 +62,18 @@ type Conn struct { nodeMask *crypto.NodeID session *sessionInfo mtu uint16 + readCallback func([]byte) + readBuffer chan []byte } // TODO func NewConn() that initializes additional fields as needed func newConn(core *Core, nodeID *crypto.NodeID, nodeMask *crypto.NodeID, session *sessionInfo) *Conn { conn := Conn{ - core: core, - nodeID: nodeID, - nodeMask: nodeMask, - session: session, + core: core, + nodeID: nodeID, + nodeMask: nodeMask, + session: session, + readBuffer: make(chan []byte, 1024), } return &conn } @@ -154,6 +157,45 @@ func (c *Conn) _getDeadlineCancellation(t *time.Time) (util.Cancellation, bool) } } +// SetReadCallback sets a callback which will be called whenever a packet is received. +// Note that calls to Read will fail if the callback has been set to a non-nil value. +func (c *Conn) SetReadCallback(callback func([]byte)) { + c.EnqueueFrom(nil, func() { + c._setReadCallback(callback) + }) +} + +func (c *Conn) _setReadCallback(callback func([]byte)) { + c.readCallback = callback + c._drainReadBuffer() +} + +func (c *Conn) _drainReadBuffer() { + if c.readCallback == nil { + return + } + select { + case bs := <-c.readBuffer: + c.readCallback(bs) + c.EnqueueFrom(nil, c._drainReadBuffer) // In case there's more + default: + } +} + +// Called by the session to pass a new message to the Conn +func (c *Conn) recvMsg(from phony.IActor, msg []byte) { + c.EnqueueFrom(from, func() { + if c.readCallback != nil { + c.readCallback(msg) + } else { + select { + case c.readBuffer <- msg: + default: + } + } + }) +} + // Used internally by Read, the caller is responsible for util.PutBytes when they're done. func (c *Conn) ReadNoCopy() ([]byte, error) { var cancel util.Cancellation @@ -170,7 +212,7 @@ func (c *Conn) ReadNoCopy() ([]byte, error) { } else { return nil, ConnError{errors.New("session closed"), false, false, true, 0} } - case bs := <-c.session.toConn: + case bs := <-c.readBuffer: return bs, nil } } @@ -278,6 +320,7 @@ func (c *Conn) LocalAddr() 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... var n crypto.NodeID <-c.SyncExec(func() { n = *c.nodeID }) return n @@ -290,11 +333,13 @@ func (c *Conn) SetDeadline(t time.Time) error { } func (c *Conn) SetReadDeadline(t time.Time) error { + // TODO warn that this can block while waiting for the Conn actor to run, so don't call it from other actors... <-c.SyncExec(func() { c.readDeadline = &t }) return nil } func (c *Conn) SetWriteDeadline(t time.Time) error { + // TODO warn that this can block while waiting for the Conn actor to run, so don't call it from other actors... <-c.SyncExec(func() { c.writeDeadline = &t }) return nil } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 854eb24d..a483a059 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -70,7 +70,6 @@ type sessionInfo struct { bytesRecvd uint64 // Bytes of real traffic received in this session init chan struct{} // Closed when the first session pong arrives, used to signal that the session is ready for initial use cancel util.Cancellation // Used to terminate workers - toConn chan []byte // Decrypted packets go here, picked up by the associated Conn conn *Conn // The associated Conn object callbacks []chan func() // Finished work from crypto workers } @@ -254,7 +253,6 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.myHandle = *crypto.NewHandle() sinfo.theirAddr = *address.AddrForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) - sinfo.toConn = make(chan []byte, 32) ss.sinfos[sinfo.myHandle] = &sinfo ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle go func() { @@ -505,15 +503,10 @@ func (sinfo *sessionInfo) _recvPacket(p *wire_trafficPacket) { util.PutBytes(p.Payload) return } - switch { - case sinfo._nonceIsOK(&p.Nonce): - case len(sinfo.toConn) < cap(sinfo.toConn): - default: - // We're either full or don't like this nonce + if !sinfo._nonceIsOK(&p.Nonce) { util.PutBytes(p.Payload) return } - k := sinfo.sharedSesKey var isOK bool var bs []byte @@ -530,16 +523,7 @@ func (sinfo *sessionInfo) _recvPacket(p *wire_trafficPacket) { sinfo._updateNonce(&p.Nonce) sinfo.time = time.Now() sinfo.bytesRecvd += uint64(len(bs)) - select { - case sinfo.toConn <- bs: - case <-sinfo.cancel.Finished(): - util.PutBytes(bs) - default: - // We seem to have filled up the buffer in the mean time - // Since we need to not block, but the conn isn't an actor, we need to drop this packet - // TODO find some nicer way to interact with the Conn... - util.PutBytes(bs) - } + sinfo.conn.recvMsg(sinfo, bs) } ch <- callback sinfo.checkCallbacks() From b582c444f876cf50866fefdd70cf88357fd87f3a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 24 Aug 2019 01:57:08 -0500 Subject: [PATCH 0256/1109] minor cleanup --- src/yggdrasil/conn.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 18c0b880..2f69139e 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -158,18 +158,13 @@ func (c *Conn) _getDeadlineCancellation(t *time.Time) (util.Cancellation, bool) } // SetReadCallback sets a callback which will be called whenever a packet is received. -// Note that calls to Read will fail if the callback has been set to a non-nil value. func (c *Conn) SetReadCallback(callback func([]byte)) { c.EnqueueFrom(nil, func() { - c._setReadCallback(callback) + c.readCallback = callback + c._drainReadBuffer() }) } -func (c *Conn) _setReadCallback(callback func([]byte)) { - c.readCallback = callback - c._drainReadBuffer() -} - func (c *Conn) _drainReadBuffer() { if c.readCallback == nil { return From 4893a07696c25aaed69f71cb4304f7f3312764b6 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 24 Aug 2019 11:38:47 -0500 Subject: [PATCH 0257/1109] start migrating tunConn to the actor model --- src/tuntap/conn.go | 183 ++++++++++++++++++++++++++++++++++++++++++++ src/tuntap/iface.go | 4 +- src/tuntap/tun.go | 9 ++- 3 files changed, 192 insertions(+), 4 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index 3fb7a544..32998e99 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -5,6 +5,7 @@ import ( "errors" "time" + "github.com/Arceliar/phony" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/util" @@ -16,6 +17,7 @@ import ( const tunConnTimeout = 2 * time.Minute type tunConn struct { + phony.Actor tun *TunAdapter conn *yggdrasil.Conn addr address.Address @@ -45,6 +47,83 @@ func (s *tunConn) _close_nomutex() { }() } +func (s *tunConn) _read(bs []byte) (err error) { + select { + case <-s.stop: + err = errors.New("session was already closed") + util.PutBytes(bs) + return + default: + } + if len(bs) == 0 { + err = errors.New("read packet with 0 size") + util.PutBytes(bs) + return + } + ipv4 := len(bs) > 20 && bs[0]&0xf0 == 0x40 + ipv6 := len(bs) > 40 && bs[0]&0xf0 == 0x60 + isCGA := true + // Check source addresses + switch { + case ipv6 && bs[8] == 0x02 && bytes.Equal(s.addr[:16], bs[8:24]): // source + case ipv6 && bs[8] == 0x03 && bytes.Equal(s.snet[:8], bs[8:16]): // source + default: + isCGA = false + } + // Check destiantion addresses + switch { + case ipv6 && bs[24] == 0x02 && bytes.Equal(s.tun.addr[:16], bs[24:40]): // destination + case ipv6 && bs[24] == 0x03 && bytes.Equal(s.tun.subnet[:8], bs[24:32]): // destination + default: + isCGA = false + } + // Decide how to handle the packet + var skip bool + switch { + case isCGA: // Allowed + case s.tun.ckr.isEnabled() && (ipv4 || ipv6): + var srcAddr address.Address + var dstAddr address.Address + var addrlen int + if ipv4 { + copy(srcAddr[:], bs[12:16]) + copy(dstAddr[:], bs[16:20]) + addrlen = 4 + } + if ipv6 { + copy(srcAddr[:], bs[8:24]) + copy(dstAddr[:], bs[24:40]) + addrlen = 16 + } + if !s.tun.ckr.isValidLocalAddress(dstAddr, addrlen) { + // The destination address isn't in our CKR allowed range + skip = true + } else if key, err := s.tun.ckr.getPublicKeyForAddress(srcAddr, addrlen); err == nil { + srcNodeID := crypto.GetNodeID(&key) + if s.conn.RemoteAddr() == *srcNodeID { + // This is the one allowed CKR case, where source and destination addresses are both good + } else { + // The CKR key associated with this address doesn't match the sender's NodeID + skip = true + } + } else { + // We have no CKR route for this source address + skip = true + } + default: + skip = true + } + if skip { + err = errors.New("address not allowed") + util.PutBytes(bs) + return + } + // FIXME this send can block if the tuntap isn't running, which isn't really safe... + s.tun.send <- bs + s.stillAlive() + return +} + func (s *tunConn) reader() (err error) { select { case _, ok := <-s.stop: @@ -137,6 +216,110 @@ func (s *tunConn) reader() (err error) { } } +func (s *tunConn) _write(bs []byte) (err error) { + select { + case <-s.stop: + err = errors.New("session was already closed") + util.PutBytes(bs) + return + default: + } + v4 := len(bs) > 20 && bs[0]&0xf0 == 0x40 + v6 := len(bs) > 40 && bs[0]&0xf0 == 0x60 + isCGA := true + // Check source addresses + switch { + case v6 && bs[8] == 0x02 && bytes.Equal(s.tun.addr[:16], bs[8:24]): // source + case v6 && bs[8] == 0x03 && bytes.Equal(s.tun.subnet[:8], bs[8:16]): // source + default: + isCGA = false + } + // Check destiantion addresses + switch { + case v6 && bs[24] == 0x02 && bytes.Equal(s.addr[:16], bs[24:40]): // destination + case v6 && bs[24] == 0x03 && bytes.Equal(s.snet[:8], bs[24:32]): // destination + default: + isCGA = false + } + // Decide how to handle the packet + var skip bool + switch { + case isCGA: // Allowed + case s.tun.ckr.isEnabled() && (v4 || v6): + var srcAddr address.Address + var dstAddr address.Address + var addrlen int + if v4 { + copy(srcAddr[:], bs[12:16]) + copy(dstAddr[:], bs[16:20]) + addrlen = 4 + } + if v6 { + copy(srcAddr[:], bs[8:24]) + copy(dstAddr[:], bs[24:40]) + addrlen = 16 + } + if !s.tun.ckr.isValidLocalAddress(srcAddr, addrlen) { + // The source address isn't in our CKR allowed range + skip = true + } else if key, err := s.tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { + dstNodeID := crypto.GetNodeID(&key) + if s.conn.RemoteAddr() == *dstNodeID { + // This is the one allowed CKR case, where source and destination addresses are both good + } else { + // The CKR key associated with this address doesn't match the sender's NodeID + skip = true + } + } else { + // We have no CKR route for this destination address... why do we have the packet in the first place? + skip = true + } + default: + skip = true + } + if skip { + err = errors.New("address not allowed") + util.PutBytes(bs) + return + } + msg := yggdrasil.FlowKeyMessage{ + FlowKey: util.GetFlowKey(bs), + Message: bs, + } + s.conn.WriteFrom(s, msg, func(err error) { + if err == nil { + // No point in wasting resources to send back an error if there was none + return + } + s.EnqueueFrom(s.conn, func() { + if e, eok := err.(yggdrasil.ConnError); !eok { + if e.Closed() { + s.tun.log.Debugln(s.conn.String(), "TUN/TAP generic write debug:", err) + } else { + s.tun.log.Errorln(s.conn.String(), "TUN/TAP generic write error:", err) + } + } else if e.PacketTooBig() { + // TODO: This currently isn't aware of IPv4 for CKR + ptb := &icmp.PacketTooBig{ + MTU: int(e.PacketMaximumSize()), + Data: bs[:900], + } + if packet, err := CreateICMPv6(bs[8:24], bs[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb); err == nil { + s.tun.send <- packet + } + } else { + if e.Closed() { + s.tun.log.Debugln(s.conn.String(), "TUN/TAP conn write debug:", err) + } else { + s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn write error:", err) + } + } + }) + }) + s.stillAlive() + return +} + func (s *tunConn) writer() error { select { case _, ok := <-s.stop: diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 35657a9f..e0693e56 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -232,7 +232,7 @@ func (tun *TunAdapter) readerPacketHandler(ch chan []byte) { if tc != nil { for _, packet := range packets { p := packet // Possibly required because of how range - tc.send <- p + <-tc.SyncExec(func() { tc._write(p) }) } } }() @@ -242,7 +242,7 @@ func (tun *TunAdapter) readerPacketHandler(ch chan []byte) { } // If we have a connection now, try writing to it if isIn && session != nil { - session.send <- bs + <-session.SyncExec(func() { session._write(bs) }) } } } diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index eef05b87..dfc9ac3a 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -257,8 +257,13 @@ func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { tun.addrToConn[s.addr] = &s tun.subnetToConn[s.snet] = &s // Start the connection goroutines - go s.reader() - go s.writer() + conn.SetReadCallback(func(bs []byte) { + s.EnqueueFrom(conn, func() { + s._read(bs) + }) + }) + //go s.reader() + //go s.writer() go s.checkForTimeouts() // Return return c, err From ef15a6bd794145e49abdab53ca7247004046af3b Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 24 Aug 2019 11:44:21 -0500 Subject: [PATCH 0258/1109] tunConn cleanup --- src/tuntap/conn.go | 202 --------------------------------------------- src/tuntap/tun.go | 5 +- 2 files changed, 1 insertion(+), 206 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index 32998e99..744ca9fc 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -22,7 +22,6 @@ type tunConn struct { conn *yggdrasil.Conn addr address.Address snet address.Subnet - send chan []byte stop chan struct{} alive chan struct{} } @@ -124,98 +123,6 @@ func (s *tunConn) _read(bs []byte) (err error) { return } -func (s *tunConn) reader() (err error) { - select { - case _, ok := <-s.stop: - if !ok { - return errors.New("session was already closed") - } - default: - } - s.tun.log.Debugln("Starting conn reader for", s.conn.String()) - defer s.tun.log.Debugln("Stopping conn reader for", s.conn.String()) - for { - select { - case <-s.stop: - return nil - default: - } - var bs []byte - if bs, err = s.conn.ReadNoCopy(); err != nil { - if e, eok := err.(yggdrasil.ConnError); eok && !e.Temporary() { - if e.Closed() { - s.tun.log.Debugln(s.conn.String(), "TUN/TAP conn read debug:", err) - } else { - s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn read error:", err) - } - return e - } - } else if len(bs) > 0 { - ipv4 := len(bs) > 20 && bs[0]&0xf0 == 0x40 - ipv6 := len(bs) > 40 && bs[0]&0xf0 == 0x60 - isCGA := true - // Check source addresses - switch { - case ipv6 && bs[8] == 0x02 && bytes.Equal(s.addr[:16], bs[8:24]): // source - case ipv6 && bs[8] == 0x03 && bytes.Equal(s.snet[:8], bs[8:16]): // source - default: - isCGA = false - } - // Check destiantion addresses - switch { - case ipv6 && bs[24] == 0x02 && bytes.Equal(s.tun.addr[:16], bs[24:40]): // destination - case ipv6 && bs[24] == 0x03 && bytes.Equal(s.tun.subnet[:8], bs[24:32]): // destination - default: - isCGA = false - } - // Decide how to handle the packet - var skip bool - switch { - case isCGA: // Allowed - case s.tun.ckr.isEnabled() && (ipv4 || ipv6): - var srcAddr address.Address - var dstAddr address.Address - var addrlen int - if ipv4 { - copy(srcAddr[:], bs[12:16]) - copy(dstAddr[:], bs[16:20]) - addrlen = 4 - } - if ipv6 { - copy(srcAddr[:], bs[8:24]) - copy(dstAddr[:], bs[24:40]) - addrlen = 16 - } - if !s.tun.ckr.isValidLocalAddress(dstAddr, addrlen) { - // The destination address isn't in our CKR allowed range - skip = true - } else if key, err := s.tun.ckr.getPublicKeyForAddress(srcAddr, addrlen); err == nil { - srcNodeID := crypto.GetNodeID(&key) - if s.conn.RemoteAddr() == *srcNodeID { - // This is the one allowed CKR case, where source and destination addresses are both good - } else { - // The CKR key associated with this address doesn't match the sender's NodeID - skip = true - } - } else { - // We have no CKR route for this source address - skip = true - } - default: - skip = true - } - if skip { - util.PutBytes(bs) - continue - } - s.tun.send <- bs - s.stillAlive() - } else { - util.PutBytes(bs) - } - } -} - func (s *tunConn) _write(bs []byte) (err error) { select { case <-s.stop: @@ -320,115 +227,6 @@ func (s *tunConn) _write(bs []byte) (err error) { return } -func (s *tunConn) writer() error { - select { - case _, ok := <-s.stop: - if !ok { - return errors.New("session was already closed") - } - default: - } - s.tun.log.Debugln("Starting conn writer for", s.conn.String()) - defer s.tun.log.Debugln("Stopping conn writer for", s.conn.String()) - for { - select { - case <-s.stop: - return nil - case bs, ok := <-s.send: - if !ok { - return errors.New("send closed") - } - v4 := len(bs) > 20 && bs[0]&0xf0 == 0x40 - v6 := len(bs) > 40 && bs[0]&0xf0 == 0x60 - isCGA := true - // Check source addresses - switch { - case v6 && bs[8] == 0x02 && bytes.Equal(s.tun.addr[:16], bs[8:24]): // source - case v6 && bs[8] == 0x03 && bytes.Equal(s.tun.subnet[:8], bs[8:16]): // source - default: - isCGA = false - } - // Check destiantion addresses - switch { - case v6 && bs[24] == 0x02 && bytes.Equal(s.addr[:16], bs[24:40]): // destination - case v6 && bs[24] == 0x03 && bytes.Equal(s.snet[:8], bs[24:32]): // destination - default: - isCGA = false - } - // Decide how to handle the packet - var skip bool - switch { - case isCGA: // Allowed - case s.tun.ckr.isEnabled() && (v4 || v6): - var srcAddr address.Address - var dstAddr address.Address - var addrlen int - if v4 { - copy(srcAddr[:], bs[12:16]) - copy(dstAddr[:], bs[16:20]) - addrlen = 4 - } - if v6 { - copy(srcAddr[:], bs[8:24]) - copy(dstAddr[:], bs[24:40]) - addrlen = 16 - } - if !s.tun.ckr.isValidLocalAddress(srcAddr, addrlen) { - // The source address isn't in our CKR allowed range - skip = true - } else if key, err := s.tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { - dstNodeID := crypto.GetNodeID(&key) - if s.conn.RemoteAddr() == *dstNodeID { - // This is the one allowed CKR case, where source and destination addresses are both good - } else { - // The CKR key associated with this address doesn't match the sender's NodeID - skip = true - } - } else { - // We have no CKR route for this destination address... why do we have the packet in the first place? - skip = true - } - default: - skip = true - } - if skip { - util.PutBytes(bs) - continue - } - msg := yggdrasil.FlowKeyMessage{ - FlowKey: util.GetFlowKey(bs), - Message: bs, - } - if err := s.conn.WriteNoCopy(msg); err != nil { - if e, eok := err.(yggdrasil.ConnError); !eok { - if e.Closed() { - s.tun.log.Debugln(s.conn.String(), "TUN/TAP generic write debug:", err) - } else { - s.tun.log.Errorln(s.conn.String(), "TUN/TAP generic write error:", err) - } - } else if e.PacketTooBig() { - // TODO: This currently isn't aware of IPv4 for CKR - ptb := &icmp.PacketTooBig{ - MTU: int(e.PacketMaximumSize()), - Data: bs[:900], - } - if packet, err := CreateICMPv6(bs[8:24], bs[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb); err == nil { - s.tun.send <- packet - } - } else { - if e.Closed() { - s.tun.log.Debugln(s.conn.String(), "TUN/TAP conn write debug:", err) - } else { - s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn write error:", err) - } - } - } else { - s.stillAlive() - } - } - } -} - func (s *tunConn) stillAlive() { defer func() { recover() }() select { diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index dfc9ac3a..895f893b 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -229,7 +229,6 @@ func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { s := tunConn{ tun: tun, conn: conn, - send: make(chan []byte, 32), // TODO: is this a sensible value? stop: make(chan struct{}), alive: make(chan struct{}, 1), } @@ -256,14 +255,12 @@ func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { // we receive a packet through the interface for this address tun.addrToConn[s.addr] = &s tun.subnetToConn[s.snet] = &s - // Start the connection goroutines + // Set the read callback and start the timeout goroutine conn.SetReadCallback(func(bs []byte) { s.EnqueueFrom(conn, func() { s._read(bs) }) }) - //go s.reader() - //go s.writer() go s.checkForTimeouts() // Return return c, err From 775fb535dce6ad3d1639dff10fa50d518a9f041b Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 24 Aug 2019 12:46:24 -0500 Subject: [PATCH 0259/1109] start converting the peer struct into an actor --- src/yggdrasil/peer.go | 34 +++++++++++++++++++++------------- src/yggdrasil/switch.go | 8 +++++--- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 8869bd2a..851a376a 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -12,6 +12,8 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/util" + + "github.com/Arceliar/phony" ) // The peers struct represents peers with an active connection. @@ -97,6 +99,7 @@ type peer struct { bytesSent uint64 // To track bandwidth usage for getPeers bytesRecvd uint64 // To track bandwidth usage for getPeers // BUG: sync/atomic, 32 bit platforms need the above to be the first element + phony.Actor core *Core intf *linkInterface port switchPort @@ -206,7 +209,7 @@ func (p *peer) linkLoop() { if !ok { return } - p.sendSwitchMsg() + <-p.SyncExec(p._sendSwitchMsg) case dinfo = <-p.dinfo: case _ = <-tick.C: if dinfo != nil { @@ -227,20 +230,19 @@ func (p *peer) handlePacket(packet []byte) { } switch pType { case wire_Traffic: - p.handleTraffic(packet, pTypeLen) + p._handleTraffic(packet, pTypeLen) case wire_ProtocolTraffic: - p.handleTraffic(packet, pTypeLen) + p._handleTraffic(packet, pTypeLen) case wire_LinkProtocolTraffic: - p.handleLinkTraffic(packet) + p._handleLinkTraffic(packet) default: util.PutBytes(packet) } - return } // Called to handle traffic or protocolTraffic packets. // In either case, this reads from the coords of the packet header, does a switch lookup, and forwards to the next node. -func (p *peer) handleTraffic(packet []byte, pTypeLen int) { +func (p *peer) _handleTraffic(packet []byte, pTypeLen int) { table := p.core.switchTable.getTable() if _, isIn := table.elems[p.port]; !isIn && p.port != 0 { // Drop traffic if the peer isn't in the switch @@ -249,8 +251,14 @@ func (p *peer) handleTraffic(packet []byte, pTypeLen int) { p.core.switchTable.packetIn <- packet } +func (p *peer) sendPacketsFrom(from phony.IActor, packets [][]byte) { + p.EnqueueFrom(from, func() { + p._sendPackets(packets) + }) +} + // This just calls p.out(packet) for now. -func (p *peer) sendPackets(packets [][]byte) { +func (p *peer) _sendPackets(packets [][]byte) { // Is there ever a case where something more complicated is needed? // What if p.out blocks? var size int @@ -263,7 +271,7 @@ func (p *peer) sendPackets(packets [][]byte) { // This wraps the packet in the inner (ephemeral) and outer (permanent) crypto layers. // It sends it to p.linkOut, which bypasses the usual packet queues. -func (p *peer) sendLinkPacket(packet []byte) { +func (p *peer) _sendLinkPacket(packet []byte) { innerPayload, innerNonce := crypto.BoxSeal(&p.linkShared, packet, nil) innerLinkPacket := wire_linkProtoTrafficPacket{ Nonce: *innerNonce, @@ -281,7 +289,7 @@ func (p *peer) sendLinkPacket(packet []byte) { // Decrypts the outer (permanent) and inner (ephemeral) crypto layers on link traffic. // Identifies the link traffic type and calls the appropriate handler. -func (p *peer) handleLinkTraffic(bs []byte) { +func (p *peer) _handleLinkTraffic(bs []byte) { packet := wire_linkProtoTrafficPacket{} if !packet.decode(bs) { return @@ -304,14 +312,14 @@ func (p *peer) handleLinkTraffic(bs []byte) { } switch pType { case wire_SwitchMsg: - p.handleSwitchMsg(payload) + p._handleSwitchMsg(payload) default: util.PutBytes(bs) } } // Gets a switchMsg from the switch, adds signed next-hop info for this peer, and sends it to them. -func (p *peer) sendSwitchMsg() { +func (p *peer) _sendSwitchMsg() { msg := p.core.switchTable.getMsg() if msg == nil { return @@ -323,12 +331,12 @@ func (p *peer) sendSwitchMsg() { Sig: *crypto.Sign(&p.core.sigPriv, bs), }) packet := msg.encode() - p.sendLinkPacket(packet) + p._sendLinkPacket(packet) } // Handles a switchMsg from the peer, checking signatures and passing good messages to the switch. // Also creates a dhtInfo struct and arranges for it to be added to the dht (this is how dht bootstrapping begins). -func (p *peer) handleSwitchMsg(packet []byte) { +func (p *peer) _handleSwitchMsg(packet []byte) { var msg switchMsg if !msg.decode(packet) { return diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 1fa75a6c..d87b7880 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -703,7 +703,7 @@ func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) boo if best != nil { // Send to the best idle next hop delete(idle, best.port) - best.sendPackets([][]byte{packet}) + best.sendPacketsFrom(nil, [][]byte{packet}) return true } // Didn't find anyone idle to send it to @@ -817,7 +817,8 @@ func (t *switchTable) handleIdle(port switchPort) bool { } } if len(packets) > 0 { - to.sendPackets(packets) + // TODO rewrite if/when the switch becomes an actor + to.sendPacketsFrom(nil, packets) return true } return false @@ -830,7 +831,8 @@ func (t *switchTable) doWorker() { // Keep sending packets to the router self := t.core.peers.getPorts()[0] for bs := range sendingToRouter { - self.sendPackets([][]byte{bs}) + // TODO remove this ugly mess of goroutines if/when the switch becomes an actor + <-self.SyncExec(func() { self._sendPackets([][]byte{bs}) }) } }() go func() { From 88161009e94740511fb25b73dbb5b19c68f00af1 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 24 Aug 2019 12:55:49 -0500 Subject: [PATCH 0260/1109] more peer migration --- src/yggdrasil/link.go | 3 ++- src/yggdrasil/peer.go | 8 +++++++- src/yggdrasil/router.go | 7 ++----- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 4ce374b4..824afd34 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -387,7 +387,8 @@ func (intf *linkInterface) handler() error { for { msg, err := intf.msgIO.readMsg() if len(msg) > 0 { - intf.peer.handlePacket(msg) + // TODO rewrite this if the link becomes an actor + <-intf.peer.SyncExec(func() { intf.peer._handlePacket(msg) }) } if err != nil { if err != io.EOF { diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 851a376a..22db88d1 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -219,9 +219,15 @@ func (p *peer) linkLoop() { } } +func (p *peer) handlePacketFrom(from phony.IActor, packet []byte) { + p.EnqueueFrom(from, func() { + p._handlePacket(packet) + }) +} + // Called to handle incoming packets. // Passes the packet to a handler for that packet type. -func (p *peer) handlePacket(packet []byte) { +func (p *peer) _handlePacket(packet []byte) { // FIXME this is off by stream padding and msg length overhead, should be done in tcp.go atomic.AddUint64(&p.bytesRecvd, uint64(len(packet))) pType, pTypeLen := wire_decode_uint64(packet) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 464a4778..adf1b1d4 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -64,11 +64,8 @@ func (r *router) init(core *Core) { }, } p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &crypto.BoxSharedKey{}, &self, nil) - p.out = func(packets [][]byte) { - // TODO make peers and/or the switch into actors, have them pass themselves as the from field - r.handlePackets(nil, packets) - } - r.out = p.handlePacket // TODO if the peer becomes its own actor, then send a message here + p.out = func(packets [][]byte) { r.handlePackets(p, packets) } + r.out = func(bs []byte) { p.handlePacketFrom(r, bs) } r.nodeinfo.init(r.core) r.core.config.Mutex.RLock() r.nodeinfo.setNodeInfo(r.core.config.Current.NodeInfo, r.core.config.Current.NodeInfoPrivacy) From ecd23ce9fc1e46c016697c4ac945162b9fb159e5 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 24 Aug 2019 12:59:20 -0500 Subject: [PATCH 0261/1109] safer linkloop --- src/yggdrasil/peer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 22db88d1..8c38c06a 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -213,7 +213,7 @@ func (p *peer) linkLoop() { case dinfo = <-p.dinfo: case _ = <-tick.C: if dinfo != nil { - p.core.router.insertPeer(nil, dinfo) + <-p.SyncExec(func() { p.core.router.insertPeer(p, dinfo) }) } } } From 034fece33f4fbabbf2bdf8c4fb643b91791438a7 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 24 Aug 2019 13:15:29 -0500 Subject: [PATCH 0262/1109] more peer migration --- src/yggdrasil/peer.go | 50 ++++++++++++++++------------------------- src/yggdrasil/switch.go | 4 ++-- 2 files changed, 21 insertions(+), 33 deletions(-) diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 8c38c06a..c94cf266 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -110,9 +110,9 @@ type peer struct { endpoint string firstSeen time.Time // To track uptime for getPeers linkOut (chan []byte) // used for protocol traffic (to bypass queues) - doSend (chan struct{}) // tell the linkLoop to send a switchMsg - dinfo (chan *dhtInfo) // used to keep the DHT working + dinfo *dhtInfo // used to keep the DHT working out func([][]byte) // Set up by whatever created the peers struct, used to send packets to other nodes + done (chan struct{}) // closed to exit the linkLoop close func() // Called when a peer is removed, to close the underlying connection, or via admin api } @@ -124,8 +124,7 @@ func (ps *peers) newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShare shared: *crypto.GetSharedKey(&ps.core.boxPriv, box), linkShared: *linkShared, firstSeen: now, - doSend: make(chan struct{}, 1), - dinfo: make(chan *dhtInfo, 1), + done: make(chan struct{}), close: closer, core: ps.core, intf: intf, @@ -170,29 +169,19 @@ func (ps *peers) removePeer(port switchPort) { if p.close != nil { p.close() } - close(p.doSend) + close(p.done) } } // If called, sends a notification to each peer that they should send a new switch message. // Mainly called by the switch after an update. -func (ps *peers) sendSwitchMsgs() { +func (ps *peers) sendSwitchMsgs(from phony.IActor) { ports := ps.getPorts() for _, p := range ports { if p.port == 0 { continue } - p.doSendSwitchMsgs() - } -} - -// If called, sends a notification to the peer's linkLoop to trigger a switchMsg send. -// Mainly called by sendSwitchMsgs or during linkLoop startup. -func (p *peer) doSendSwitchMsgs() { - defer func() { recover() }() // In case there's a race with close(p.doSend) - select { - case p.doSend <- struct{}{}: - default: + p.EnqueueFrom(from, p._sendSwitchMsg) } } @@ -201,24 +190,23 @@ func (p *peer) doSendSwitchMsgs() { func (p *peer) linkLoop() { tick := time.NewTicker(time.Second) defer tick.Stop() - p.doSendSwitchMsgs() - var dinfo *dhtInfo + <-p.SyncExec(p._sendSwitchMsg) // Startup message for { select { - case _, ok := <-p.doSend: - if !ok { - return - } - <-p.SyncExec(p._sendSwitchMsg) - case dinfo = <-p.dinfo: + case <-p.done: + return case _ = <-tick.C: - if dinfo != nil { - <-p.SyncExec(func() { p.core.router.insertPeer(p, dinfo) }) - } + <-p.SyncExec(p._updateDHT) } } } +func (p *peer) _updateDHT() { + if p.dinfo != nil { + p.core.router.insertPeer(p, p.dinfo) + } +} + func (p *peer) handlePacketFrom(from phony.IActor, packet []byte) { p.EnqueueFrom(from, func() { p._handlePacket(packet) @@ -366,16 +354,16 @@ func (p *peer) _handleSwitchMsg(packet []byte) { p.core.switchTable.handleMsg(&msg, p.port) if !p.core.switchTable.checkRoot(&msg) { // Bad switch message - p.dinfo <- nil + p.dinfo = nil return } // Pass a mesage to the dht informing it that this peer (still) exists loc.coords = loc.coords[:len(loc.coords)-1] - dinfo := dhtInfo{ + p.dinfo = &dhtInfo{ key: p.box, coords: loc.getCoords(), } - p.dinfo <- &dinfo + p._updateDHT() } // This generates the bytes that we sign or check the signature of for a switchMsg. diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index d87b7880..0be01240 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -248,7 +248,7 @@ func (t *switchTable) cleanRoot() { t.core.router.reset(nil) } t.data.locator = switchLocator{root: t.key, tstamp: now.Unix()} - t.core.peers.sendSwitchMsgs() + t.core.peers.sendSwitchMsgs(nil) // TODO update if/when the switch becomes an actor } } @@ -515,7 +515,7 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep } t.data.locator = sender.locator t.parent = sender.port - t.core.peers.sendSwitchMsgs() + t.core.peers.sendSwitchMsgs(nil) // TODO update if/when the switch becomes an actor } if doUpdate { t.updater.Store(&sync.Once{}) From 0539dee900342a9fad784e83bea51f32459cba9f Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 24 Aug 2019 13:25:38 -0500 Subject: [PATCH 0263/1109] warning about possible deadlock in legacy channel send, need to migrate the link code to fix it --- src/yggdrasil/peer.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index c94cf266..f3af140f 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -278,6 +278,8 @@ func (p *peer) _sendLinkPacket(packet []byte) { Payload: bs, } packet = linkPacket.encode() + // TODO replace this with a message send if/when the link becomes an actor + // FIXME not 100% sure the channel send version is deadlock-free... p.linkOut <- packet } From b337228aa45436736640492b54cd64059f21a199 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 24 Aug 2019 14:24:42 -0500 Subject: [PATCH 0264/1109] minor fixes to peer stuff --- src/yggdrasil/api.go | 43 +++++++++++++++++++++++------------------- src/yggdrasil/debug.go | 2 +- src/yggdrasil/peer.go | 20 +++++++++++++------- 3 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index f50c8ceb..cbf232aa 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -6,7 +6,6 @@ import ( "fmt" "net" "sort" - "sync/atomic" "time" "github.com/gologme/log" @@ -106,15 +105,18 @@ func (c *Core) GetPeers() []Peer { sort.Slice(ps, func(i, j int) bool { return ps[i] < ps[j] }) for _, port := range ps { p := ports[port] - info := Peer{ - Endpoint: p.intf.name, - BytesSent: atomic.LoadUint64(&p.bytesSent), - BytesRecvd: atomic.LoadUint64(&p.bytesRecvd), - Protocol: p.intf.info.linkType, - Port: uint64(port), - Uptime: time.Since(p.firstSeen), - } - copy(info.PublicKey[:], p.box[:]) + var info Peer + <-p.SyncExec(func() { + info = Peer{ + Endpoint: p.intf.name, + BytesSent: p.bytesSent, + BytesRecvd: p.bytesRecvd, + Protocol: p.intf.info.linkType, + Port: uint64(port), + Uptime: time.Since(p.firstSeen), + } + copy(info.PublicKey[:], p.box[:]) + }) peers = append(peers, info) } return peers @@ -135,15 +137,18 @@ func (c *Core) GetSwitchPeers() []SwitchPeer { continue } coords := elem.locator.getCoords() - info := SwitchPeer{ - Coords: append([]uint64{}, wire_coordsBytestoUint64s(coords)...), - BytesSent: atomic.LoadUint64(&peer.bytesSent), - BytesRecvd: atomic.LoadUint64(&peer.bytesRecvd), - Port: uint64(elem.port), - Protocol: peer.intf.info.linkType, - Endpoint: peer.intf.info.remote, - } - copy(info.PublicKey[:], peer.box[:]) + var info SwitchPeer + <-peer.SyncExec(func() { + info = SwitchPeer{ + Coords: append([]uint64{}, wire_coordsBytestoUint64s(coords)...), + BytesSent: peer.bytesSent, + BytesRecvd: peer.bytesRecvd, + Port: uint64(elem.port), + Protocol: peer.intf.info.linkType, + Endpoint: peer.intf.info.remote, + } + copy(info.PublicKey[:], peer.box[:]) + }) switchpeers = append(switchpeers, info) } return switchpeers diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index d90bf1f4..30eb874d 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -576,7 +576,7 @@ func DEBUG_simLinkPeers(p, q *peer) { default: } if len(packets) > 0 { - dest.handlePacket(packets[0]) + <-dest.SyncExec(func() { dest._handlePacket(packets[0]) }) packets = packets[1:] continue } diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index f3af140f..4bebc17e 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -96,9 +96,6 @@ func (ps *peers) putPorts(ports map[switchPort]*peer) { // Information known about a peer, including thier box/sig keys, precomputed shared keys (static and ephemeral) and a handler for their outgoing traffic type peer struct { - bytesSent uint64 // To track bandwidth usage for getPeers - bytesRecvd uint64 // To track bandwidth usage for getPeers - // BUG: sync/atomic, 32 bit platforms need the above to be the first element phony.Actor core *Core intf *linkInterface @@ -114,6 +111,9 @@ type peer struct { out func([][]byte) // Set up by whatever created the peers struct, used to send packets to other nodes done (chan struct{}) // closed to exit the linkLoop close func() // Called when a peer is removed, to close the underlying connection, or via admin api + // The below aren't actually useful internally, they're just gathered for getPeers statistics + bytesSent uint64 + bytesRecvd uint64 } // Creates a new peer with the specified box, sig, and linkShared keys, using the lowest unoccupied port number. @@ -217,7 +217,7 @@ func (p *peer) handlePacketFrom(from phony.IActor, packet []byte) { // Passes the packet to a handler for that packet type. func (p *peer) _handlePacket(packet []byte) { // FIXME this is off by stream padding and msg length overhead, should be done in tcp.go - atomic.AddUint64(&p.bytesRecvd, uint64(len(packet))) + p.bytesRecvd += uint64(len(packet)) pType, pTypeLen := wire_decode_uint64(packet) if pTypeLen == 0 { return @@ -259,10 +259,12 @@ func (p *peer) _sendPackets(packets [][]byte) { for _, packet := range packets { size += len(packet) } - atomic.AddUint64(&p.bytesSent, uint64(size)) + p.bytesSent += uint64(size) p.out(packets) } +var peerLinkOutHelper phony.Actor + // This wraps the packet in the inner (ephemeral) and outer (permanent) crypto layers. // It sends it to p.linkOut, which bypasses the usual packet queues. func (p *peer) _sendLinkPacket(packet []byte) { @@ -279,8 +281,12 @@ func (p *peer) _sendLinkPacket(packet []byte) { } packet = linkPacket.encode() // TODO replace this with a message send if/when the link becomes an actor - // FIXME not 100% sure the channel send version is deadlock-free... - p.linkOut <- packet + peerLinkOutHelper.EnqueueFrom(nil, func() { + select { + case p.linkOut <- packet: + case <-p.done: + } + }) } // Decrypts the outer (permanent) and inner (ephemeral) crypto layers on link traffic. From 498bc395e2ed8adc55813f8aeda24f2827c3d4d3 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 24 Aug 2019 14:56:33 -0500 Subject: [PATCH 0265/1109] start migrating switch to the actor model --- src/yggdrasil/switch.go | 117 ++++++++++++++++------------------------ 1 file changed, 47 insertions(+), 70 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 0be01240..7f3c7a78 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -19,6 +19,8 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/util" + + "github.com/Arceliar/phony" ) const ( @@ -172,12 +174,13 @@ type switchTable struct { data switchData // updater atomic.Value // *sync.Once table atomic.Value // lookupTable + phony.Actor // Owns the below packetIn chan []byte // Incoming packets for the worker to handle idleIn chan switchPort // Incoming idle notifications from peer links admin chan func() // Pass a lambda for the admin socket to query stuff queues switch_buffers // Queues - not atomic so ONLY use through admin chan queueTotalMaxSize uint64 // Maximum combined size of queues - toRouter chan []byte // Packets to be sent to the router + idle map[switchPort]time.Time // idle peers } // Minimum allowed total size of switch queues. @@ -199,7 +202,7 @@ func (t *switchTable) init(core *Core) { t.idleIn = make(chan switchPort, 1024) t.admin = make(chan func()) t.queueTotalMaxSize = SwitchQueueTotalMinSize - t.toRouter = make(chan []byte, 1) + t.idle = make(map[switchPort]time.Time) } // Safely gets a copy of this node's locator. @@ -653,12 +656,13 @@ func (t *switchTable) bestPortForCoords(coords []byte) switchPort { // Handle an incoming packet // Either send it to ourself, or to the first idle peer that's free // Returns true if the packet has been handled somehow, false if it should be queued -func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) bool { +func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]time.Time) bool { coords := switch_getPacketCoords(packet) closer := t.getCloser(coords) if len(closer) == 0 { // TODO? call the router directly, and remove the whole concept of a self peer? - t.toRouter <- packet + self := t.core.peers.getPorts()[0] + self.sendPacketsFrom(t, [][]byte{packet}) return true } var best *peer @@ -731,7 +735,7 @@ type switch_buffers struct { closer []closerInfo // Scratch space } -func (b *switch_buffers) cleanup(t *switchTable) { +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] @@ -773,14 +777,14 @@ func (b *switch_buffers) cleanup(t *switchTable) { // 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) bool { +func (t *switchTable) _handleIdle(port switchPort) bool { to := t.core.peers.getPorts()[port] if to == nil { return true } var packets [][]byte var psize int - t.queues.cleanup(t) + t.queues._cleanup(t) now := time.Now() for psize < 65535 { var best string @@ -824,79 +828,52 @@ func (t *switchTable) handleIdle(port switchPort) bool { return false } -// The switch worker does routing lookups and sends packets to where they need to be -func (t *switchTable) doWorker() { - sendingToRouter := make(chan []byte, 1) - go func() { - // Keep sending packets to the router - self := t.core.peers.getPorts()[0] - for bs := range sendingToRouter { - // TODO remove this ugly mess of goroutines if/when the switch becomes an actor - <-self.SyncExec(func() { self._sendPackets([][]byte{bs}) }) +func (t *switchTable) _packetIn(bytes []byte) { + // Try to send it somewhere (or drop it if it's corrupt or at a dead end) + if !t._handleIn(bytes, t.idle) { + // There's nobody free to take it right now, so queue it for later + packet := switch_packetInfo{bytes, time.Now()} + streamID := switch_getPacketStreamID(packet.bytes) + buf, bufExists := t.queues.bufs[streamID] + buf.packets = append(buf.packets, packet) + buf.size += uint64(len(packet.bytes)) + t.queues.size += uint64(len(packet.bytes)) + // Keep a track of the max total queue size + if t.queues.size > t.queues.maxsize { + t.queues.maxsize = t.queues.size } - }() - go func() { - // Keep taking packets from the idle worker and sending them to the above whenever it's idle, keeping anything extra in a (fifo, head-drop) buffer - var buf [][]byte - var size int - for { - bs := <-t.toRouter - size += len(bs) - buf = append(buf, bs) - for len(buf) > 0 { - select { - case bs := <-t.toRouter: - size += len(bs) - buf = append(buf, bs) - for size > int(t.queueTotalMaxSize) { - size -= len(buf[0]) - util.PutBytes(buf[0]) - buf = buf[1:] - } - case sendingToRouter <- buf[0]: - size -= len(buf[0]) - buf = buf[1:] - } + t.queues.bufs[streamID] = buf + if !bufExists { + // Keep a track of the max total queue count. Only recalculate this + // when the queue is new because otherwise repeating len(dict) might + // cause unnecessary processing overhead + if len(t.queues.bufs) > t.queues.maxbufs { + t.queues.maxbufs = len(t.queues.bufs) } } - }() + t.queues._cleanup(t) + } +} + +func (t *switchTable) _idleIn(port switchPort) { + // Try to find something to send to this peer + if !t._handleIdle(port) { + // Didn't find anything ready to send yet, so stay idle + t.idle[port] = time.Now() + } +} + +// The switch worker does routing lookups and sends packets to where they need to be +func (t *switchTable) doWorker() { t.queues.switchTable = t t.queues.bufs = make(map[string]switch_buffer) // Packets per PacketStreamID (string) - idle := make(map[switchPort]time.Time) // this is to deduplicate things for { //t.core.log.Debugf("Switch state: idle = %d, buffers = %d", len(idle), len(t.queues.bufs)) select { case bytes := <-t.packetIn: - // Try to send it somewhere (or drop it if it's corrupt or at a dead end) - if !t.handleIn(bytes, idle) { - // There's nobody free to take it right now, so queue it for later - packet := switch_packetInfo{bytes, time.Now()} - streamID := switch_getPacketStreamID(packet.bytes) - buf, bufExists := t.queues.bufs[streamID] - buf.packets = append(buf.packets, packet) - buf.size += uint64(len(packet.bytes)) - t.queues.size += uint64(len(packet.bytes)) - // Keep a track of the max total queue size - if t.queues.size > t.queues.maxsize { - t.queues.maxsize = t.queues.size - } - t.queues.bufs[streamID] = buf - if !bufExists { - // Keep a track of the max total queue count. Only recalculate this - // when the queue is new because otherwise repeating len(dict) might - // cause unnecessary processing overhead - if len(t.queues.bufs) > t.queues.maxbufs { - t.queues.maxbufs = len(t.queues.bufs) - } - } - t.queues.cleanup(t) - } + <-t.SyncExec(func() { t._packetIn(bytes) }) case port := <-t.idleIn: - // Try to find something to send to this peer - if !t.handleIdle(port) { - // Didn't find anything ready to send yet, so stay idle - idle[port] = time.Now() - } + <-t.SyncExec(func() { t._idleIn(port) }) case f := <-t.admin: f() case e := <-t.reconfigure: From 555b4c18d4938dd2ad70941e8ffaf95490057059 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 24 Aug 2019 15:05:18 -0500 Subject: [PATCH 0266/1109] a little switch cleanup --- src/yggdrasil/switch.go | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 7f3c7a78..3f4c41fe 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -177,7 +177,6 @@ type switchTable struct { phony.Actor // Owns the below packetIn chan []byte // Incoming packets for the worker to handle idleIn chan switchPort // Incoming idle notifications from peer links - admin chan func() // Pass a lambda for the admin socket to query stuff queues switch_buffers // Queues - not atomic so ONLY use through admin chan queueTotalMaxSize uint64 // Maximum combined size of queues idle map[switchPort]time.Time // idle peers @@ -200,9 +199,10 @@ func (t *switchTable) init(core *Core) { t.drop = make(map[crypto.SigPubKey]int64) t.packetIn = make(chan []byte, 1024) t.idleIn = make(chan switchPort, 1024) - t.admin = make(chan func()) t.queueTotalMaxSize = SwitchQueueTotalMinSize t.idle = make(map[switchPort]time.Time) + t.queues.switchTable = t + t.queues.bufs = make(map[string]switch_buffer) // Packets per PacketStreamID (string) } // Safely gets a copy of this node's locator. @@ -865,8 +865,6 @@ func (t *switchTable) _idleIn(port switchPort) { // The switch worker does routing lookups and sends packets to where they need to be func (t *switchTable) doWorker() { - t.queues.switchTable = t - t.queues.bufs = make(map[string]switch_buffer) // Packets per PacketStreamID (string) for { //t.core.log.Debugf("Switch state: idle = %d, buffers = %d", len(idle), len(t.queues.bufs)) select { @@ -874,8 +872,6 @@ func (t *switchTable) doWorker() { <-t.SyncExec(func() { t._packetIn(bytes) }) case port := <-t.idleIn: <-t.SyncExec(func() { t._idleIn(port) }) - case f := <-t.admin: - f() case e := <-t.reconfigure: e <- nil } @@ -885,13 +881,5 @@ func (t *switchTable) doWorker() { // Passed a function to call. // This will send the function to t.admin and block until it finishes. func (t *switchTable) doAdmin(f func()) { - // Pass this a function that needs to be run by the router's main goroutine - // It will pass the function to the router and wait for the router to finish - done := make(chan struct{}) - newF := func() { - f() - close(done) - } - t.admin <- newF - <-done + <-t.SyncExec(f) } From 998c76fd8cd3c97131a4ba31a24b53e0475b8f10 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 24 Aug 2019 15:22:46 -0500 Subject: [PATCH 0267/1109] more switch migration --- src/yggdrasil/api.go | 2 +- src/yggdrasil/core.go | 4 ++- src/yggdrasil/link.go | 8 ++++-- src/yggdrasil/peer.go | 2 +- src/yggdrasil/switch.go | 64 ++++++++++++++++++++--------------------- 5 files changed, 42 insertions(+), 38 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index cbf232aa..2bc5c816 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -191,7 +191,7 @@ func (c *Core) GetSwitchQueues() SwitchQueues { Size: switchTable.queues.size, HighestCount: uint64(switchTable.queues.maxbufs), HighestSize: switchTable.queues.maxsize, - MaximumSize: switchTable.queueTotalMaxSize, + MaximumSize: switchTable.queues.totalMaxSize, } for k, v := range switchTable.queues.bufs { nexthop := switchTable.bestPortForCoords([]byte(k)) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index fb2142c0..40982cdd 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -174,7 +174,9 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, c.config.Mutex.RLock() if c.config.Current.SwitchOptions.MaxTotalQueueSize >= SwitchQueueTotalMinSize { - c.switchTable.queueTotalMaxSize = c.config.Current.SwitchOptions.MaxTotalQueueSize + c.switchTable.doAdmin(func() { + c.switchTable.queues.totalMaxSize = c.config.Current.SwitchOptions.MaxTotalQueueSize + }) } c.config.Mutex.RUnlock() diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 824afd34..1389f41e 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -318,7 +318,9 @@ func (intf *linkInterface) handler() error { isAlive = true if !isReady { // (Re-)enable in the switch - intf.link.core.switchTable.idleIn <- intf.peer.port + intf.link.core.switchTable.EnqueueFrom(nil, func() { + intf.link.core.switchTable._idleIn(intf.peer.port) + }) isReady = true } if gotMsg && !sendTimerRunning { @@ -355,7 +357,9 @@ func (intf *linkInterface) handler() error { isReady = false } else { // Keep enabled in the switch - intf.link.core.switchTable.idleIn <- intf.peer.port + intf.link.core.switchTable.EnqueueFrom(nil, func() { + intf.link.core.switchTable._idleIn(intf.peer.port) + }) isReady = true } case <-sendBlocked.C: diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 4bebc17e..9a6f75f1 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -242,7 +242,7 @@ func (p *peer) _handleTraffic(packet []byte, pTypeLen int) { // Drop traffic if the peer isn't in the switch return } - p.core.switchTable.packetIn <- packet + p.core.switchTable.packetInFrom(p, packet) } func (p *peer) sendPacketsFrom(from phony.IActor, packets [][]byte) { diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 3f4c41fe..20552ae0 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -164,22 +164,19 @@ type switchData struct { // All the information stored by the switch. type switchTable struct { - core *Core - reconfigure chan chan error - key crypto.SigPubKey // Our own key - time time.Time // Time when locator.tstamp was last updated - drop map[crypto.SigPubKey]int64 // Tstamp associated with a dropped root - mutex sync.RWMutex // Lock for reads/writes of switchData - parent switchPort // Port of whatever peer is our parent, or self if we're root - data switchData // - updater atomic.Value // *sync.Once - table atomic.Value // lookupTable - phony.Actor // Owns the below - packetIn chan []byte // Incoming packets for the worker to handle - idleIn chan switchPort // Incoming idle notifications from peer links - queues switch_buffers // Queues - not atomic so ONLY use through admin chan - queueTotalMaxSize uint64 // Maximum combined size of queues - idle map[switchPort]time.Time // idle peers + core *Core + reconfigure chan chan error + key crypto.SigPubKey // Our own key + time time.Time // Time when locator.tstamp was last updated + drop map[crypto.SigPubKey]int64 // Tstamp associated with a dropped root + mutex sync.RWMutex // Lock for reads/writes of switchData + parent switchPort // Port of whatever peer is our parent, or self if we're root + data switchData // + updater atomic.Value // *sync.Once + table atomic.Value // lookupTable + phony.Actor // Owns the below + queues switch_buffers // Queues - not atomic so ONLY use through the actor + idle map[switchPort]time.Time // idle peers - not atomic so ONLY use through the actor } // Minimum allowed total size of switch queues. @@ -197,12 +194,11 @@ func (t *switchTable) init(core *Core) { t.updater.Store(&sync.Once{}) t.table.Store(lookupTable{}) t.drop = make(map[crypto.SigPubKey]int64) - t.packetIn = make(chan []byte, 1024) - t.idleIn = make(chan switchPort, 1024) - t.queueTotalMaxSize = SwitchQueueTotalMinSize - t.idle = make(map[switchPort]time.Time) - t.queues.switchTable = t - t.queues.bufs = make(map[string]switch_buffer) // Packets per PacketStreamID (string) + <-t.SyncExec(func() { + t.queues.totalMaxSize = SwitchQueueTotalMinSize + t.queues.bufs = make(map[string]switch_buffer) + t.idle = make(map[switchPort]time.Time) + }) } // Safely gets a copy of this node's locator. @@ -727,12 +723,12 @@ type switch_buffer struct { } type switch_buffers struct { - switchTable *switchTable - bufs map[string]switch_buffer // Buffers indexed by StreamID - size uint64 // Total size of all buffers, in bytes - maxbufs int - maxsize uint64 - closer []closerInfo // Scratch space + totalMaxSize uint64 + bufs map[string]switch_buffer // Buffers indexed by StreamID + size uint64 // Total size of all buffers, in bytes + maxbufs int + maxsize uint64 + closer []closerInfo // Scratch space } func (b *switch_buffers) _cleanup(t *switchTable) { @@ -749,7 +745,7 @@ func (b *switch_buffers) _cleanup(t *switchTable) { } } - for b.size > b.switchTable.queueTotalMaxSize { + for b.size > b.totalMaxSize { // Drop a random queue target := rand.Uint64() % b.size var size uint64 // running total @@ -828,6 +824,12 @@ func (t *switchTable) _handleIdle(port switchPort) bool { return false } +func (t *switchTable) packetInFrom(from phony.IActor, bytes []byte) { + t.EnqueueFrom(from, func() { + t._packetIn(bytes) + }) +} + func (t *switchTable) _packetIn(bytes []byte) { // Try to send it somewhere (or drop it if it's corrupt or at a dead end) if !t._handleIn(bytes, t.idle) { @@ -868,10 +870,6 @@ func (t *switchTable) doWorker() { for { //t.core.log.Debugf("Switch state: idle = %d, buffers = %d", len(idle), len(t.queues.bufs)) select { - case bytes := <-t.packetIn: - <-t.SyncExec(func() { t._packetIn(bytes) }) - case port := <-t.idleIn: - <-t.SyncExec(func() { t._idleIn(port) }) case e := <-t.reconfigure: e <- nil } From c573170886a85e9a88fe3d8e770949ccfd78c4ea Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 24 Aug 2019 15:27:56 -0500 Subject: [PATCH 0268/1109] remove switch doworker loop, start a dummy loop to respond to (unused) reconfiguration instead --- src/yggdrasil/switch.go | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 20552ae0..905f2a79 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -566,7 +566,12 @@ func (t *switchTable) getTable() lookupTable { // Starts the switch worker func (t *switchTable) start() error { t.core.log.Infoln("Starting switch") - go t.doWorker() + go func() { + // TODO find a better way to handle reconfiguration... and have the switch do something with the new configuration + for ch := range t.reconfigure { + ch <- nil + } + }() return nil } @@ -865,17 +870,6 @@ func (t *switchTable) _idleIn(port switchPort) { } } -// The switch worker does routing lookups and sends packets to where they need to be -func (t *switchTable) doWorker() { - for { - //t.core.log.Debugf("Switch state: idle = %d, buffers = %d", len(idle), len(t.queues.bufs)) - select { - case e := <-t.reconfigure: - e <- nil - } - } -} - // Passed a function to call. // This will send the function to t.admin and block until it finishes. func (t *switchTable) doAdmin(f func()) { From 8c7e9ec7c0266fce7ca074a45857233e5b583d4a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 24 Aug 2019 15:32:19 -0500 Subject: [PATCH 0269/1109] fix debug builds --- src/yggdrasil/debug.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index 30eb874d..f6ec8716 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -571,7 +571,9 @@ func DEBUG_simLinkPeers(p, q *peer) { continue case packet := <-send: packets = append(packets, packet) - source.core.switchTable.idleIn <- source.port + <-source.core.switchTable.SyncExec(func() { + source.core.switchTable._idleIn(source.port) + }) continue default: } @@ -585,15 +587,21 @@ func DEBUG_simLinkPeers(p, q *peer) { packets = append(packets, packet) case packet := <-send: packets = append(packets, packet) - source.core.switchTable.idleIn <- source.port + <-source.core.switchTable.SyncExec(func() { + source.core.switchTable._idleIn(source.port) + }) } } }() } goWorkers(p, q) goWorkers(q, p) - p.core.switchTable.idleIn <- p.port - q.core.switchTable.idleIn <- q.port + <-p.core.switchTable.SyncExec(func() { + p.core.switchTable._idleIn(p.port) + }) + <-q.core.switchTable.SyncExec(func() { + q.core.switchTable._idleIn(q.port) + }) } /* From 209d2ffea55a6ce3f1f3d22a2f06bda4ec25aae3 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 24 Aug 2019 16:04:05 -0500 Subject: [PATCH 0270/1109] correctly call peer.sendPacketsFrom in the switch --- src/yggdrasil/peer.go | 6 +++--- src/yggdrasil/switch.go | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 9a6f75f1..7fe33ecd 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -224,9 +224,9 @@ func (p *peer) _handlePacket(packet []byte) { } switch pType { case wire_Traffic: - p._handleTraffic(packet, pTypeLen) + p._handleTraffic(packet) case wire_ProtocolTraffic: - p._handleTraffic(packet, pTypeLen) + p._handleTraffic(packet) case wire_LinkProtocolTraffic: p._handleLinkTraffic(packet) default: @@ -236,7 +236,7 @@ func (p *peer) _handlePacket(packet []byte) { // Called to handle traffic or protocolTraffic packets. // In either case, this reads from the coords of the packet header, does a switch lookup, and forwards to the next node. -func (p *peer) _handleTraffic(packet []byte, pTypeLen int) { +func (p *peer) _handleTraffic(packet []byte) { table := p.core.switchTable.getTable() if _, isIn := table.elems[p.port]; !isIn && p.port != 0 { // Drop traffic if the peer isn't in the switch diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 905f2a79..b6fc26f8 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -708,7 +708,7 @@ func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]time.Time) bo if best != nil { // Send to the best idle next hop delete(idle, best.port) - best.sendPacketsFrom(nil, [][]byte{packet}) + best.sendPacketsFrom(t, [][]byte{packet}) return true } // Didn't find anyone idle to send it to @@ -822,8 +822,7 @@ func (t *switchTable) _handleIdle(port switchPort) bool { } } if len(packets) > 0 { - // TODO rewrite if/when the switch becomes an actor - to.sendPacketsFrom(nil, packets) + to.sendPacketsFrom(t, packets) return true } return false From 99be6b037d5cccccf5f7103eaf582bec002d4eee Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 24 Aug 2019 16:13:34 -0500 Subject: [PATCH 0271/1109] stop synchronizing message reads for now, not 100% safe but I don't have any better ideas --- src/yggdrasil/link.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 1389f41e..4b7f766d 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -392,7 +392,10 @@ func (intf *linkInterface) handler() error { msg, err := intf.msgIO.readMsg() if len(msg) > 0 { // TODO rewrite this if the link becomes an actor - <-intf.peer.SyncExec(func() { intf.peer._handlePacket(msg) }) + // FIXME this could theoretically send traffic faster than the peer can handle + // The alternative is to SyncExec, but that causes traffic to block while *other* links work + // Need to figure out why and find a workaround + intf.peer.handlePacketFrom(nil, msg) } if err != nil { if err != io.EOF { From 48bbdac9b3596c513e2530a5bc20dad6bfc4faea Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 24 Aug 2019 16:27:12 -0500 Subject: [PATCH 0272/1109] add a helper actor to the link reader to make it play nicer with backpressure --- src/yggdrasil/link.go | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 4b7f766d..98f44bc7 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -16,6 +16,8 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/util" + + "github.com/Arceliar/phony" ) type link struct { @@ -388,14 +390,17 @@ func (intf *linkInterface) handler() error { } }() // Run reader loop - for { + var helper phony.Actor + done := make(chan struct{}) + var helperFunc func() + helperFunc = func() { + // The helper reads in a loop and sends to the peer + // It loops by sending itself a message, which can be delayed by backpressure + // So if the peer is busy, backpressure will pause reading until the peer catches up msg, err := intf.msgIO.readMsg() if len(msg) > 0 { // TODO rewrite this if the link becomes an actor - // FIXME this could theoretically send traffic faster than the peer can handle - // The alternative is to SyncExec, but that causes traffic to block while *other* links work - // Need to figure out why and find a workaround - intf.peer.handlePacketFrom(nil, msg) + intf.peer.handlePacketFrom(&helper, msg) } if err != nil { if err != io.EOF { @@ -404,13 +409,19 @@ func (intf *linkInterface) handler() error { default: } } - break + close(done) + return } select { case signalAlive <- len(msg) > 0: default: } + // Now try to read again + helper.EnqueueFrom(nil, helperFunc) } + // Start the read loop + helper.EnqueueFrom(nil, helperFunc) + <-done // Wait for the helper to exit //////////////////////////////////////////////////////////////////////////////// // Remember to set `err` to something useful before returning select { From 68c380ff452c4bc5fcbe70786f39b8db05b041a6 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 24 Aug 2019 17:03:19 -0500 Subject: [PATCH 0273/1109] update phony dependency --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fff4ae6f..db307d33 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/yggdrasil-network/yggdrasil-go require ( - github.com/Arceliar/phony v0.0.0-20190824031448-b53e115f69b5 + github.com/Arceliar/phony v0.0.0-20190824220211-2c0698579651 github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 github.com/hashicorp/go-syslog v1.0.0 github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 diff --git a/go.sum b/go.sum index 29854cbf..3839b51a 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/phony v0.0.0-20190824031448-b53e115f69b5 h1:D2Djqo/q7mftrtHLCpW4Rpplm8jj+Edc9jNz8Ll6E0A= -github.com/Arceliar/phony v0.0.0-20190824031448-b53e115f69b5/go.mod h1:2Q9yJvg2PlMrnOEa3RTEy9hElWAICo/D8HTUDqAHUAo= +github.com/Arceliar/phony v0.0.0-20190824220211-2c0698579651 h1:FDO8ctwDOOs8mI7EVE/hR3lpGjWoY2y21tAlblIYvs8= +github.com/Arceliar/phony v0.0.0-20190824220211-2c0698579651/go.mod h1:+/sVcxsqK1Sjm3Vd+yCfMAohJOfTRyNh24apkxhqU3Q= github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 h1:WD8iJ37bRNwvETMfVTusVSAi0WdXTpfNVGY2aHycNKY= github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= From f62bc842ae0a01b89b13779ce6320286fef2e10d Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 24 Aug 2019 18:23:54 -0500 Subject: [PATCH 0274/1109] fix memory leak in session nonce map --- src/yggdrasil/session.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 7cd92db9..6f6a96a8 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -32,7 +32,7 @@ func (h *nonceHeap) Pop() interface{} { n, *h = (*h)[l-1], (*h)[:l-1] return n } -func (h nonceHeap) peek() *crypto.BoxNonce { return &h[len(h)-1] } +func (h nonceHeap) peek() *crypto.BoxNonce { return &h[0] } // All the information we know about an active session. // This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API. From cff1366146456e6421ab6eb4199879794938b784 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 24 Aug 2019 22:28:20 -0500 Subject: [PATCH 0275/1109] update phony dependency --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index db307d33..a71eb4bc 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/yggdrasil-network/yggdrasil-go require ( - github.com/Arceliar/phony v0.0.0-20190824220211-2c0698579651 + github.com/Arceliar/phony v0.0.0-20190825032731-f8ba56f9093e github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 github.com/hashicorp/go-syslog v1.0.0 github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 diff --git a/go.sum b/go.sum index 3839b51a..0aba11ef 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/phony v0.0.0-20190824220211-2c0698579651 h1:FDO8ctwDOOs8mI7EVE/hR3lpGjWoY2y21tAlblIYvs8= -github.com/Arceliar/phony v0.0.0-20190824220211-2c0698579651/go.mod h1:+/sVcxsqK1Sjm3Vd+yCfMAohJOfTRyNh24apkxhqU3Q= +github.com/Arceliar/phony v0.0.0-20190825032731-f8ba56f9093e h1:qsNZzfxSvlSE4JZ3OpmDmAeqCRpOO3RI9eJ7U6z23Gk= +github.com/Arceliar/phony v0.0.0-20190825032731-f8ba56f9093e/go.mod h1:+/sVcxsqK1Sjm3Vd+yCfMAohJOfTRyNh24apkxhqU3Q= github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 h1:WD8iJ37bRNwvETMfVTusVSAi0WdXTpfNVGY2aHycNKY= github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= From aa30c6cc98071cb4c953049baf3ff8e19e213044 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 25 Aug 2019 10:36:09 -0500 Subject: [PATCH 0276/1109] upgrade phony dependency and switch to its new interface --- go.mod | 2 +- go.sum | 4 ++-- src/tuntap/conn.go | 4 ++-- src/tuntap/tun.go | 2 +- src/yggdrasil/conn.go | 24 ++++++++++++------------ src/yggdrasil/link.go | 10 +++++----- src/yggdrasil/peer.go | 18 +++++++++--------- src/yggdrasil/router.go | 14 +++++++------- src/yggdrasil/session.go | 24 ++++++++++++------------ src/yggdrasil/switch.go | 6 +++--- 10 files changed, 54 insertions(+), 54 deletions(-) diff --git a/go.mod b/go.mod index a71eb4bc..f3d8417f 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/yggdrasil-network/yggdrasil-go require ( - github.com/Arceliar/phony v0.0.0-20190825032731-f8ba56f9093e + github.com/Arceliar/phony v0.0.0-20190825152505-180ac75690fe github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 github.com/hashicorp/go-syslog v1.0.0 github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 diff --git a/go.sum b/go.sum index 0aba11ef..29e0dfec 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/phony v0.0.0-20190825032731-f8ba56f9093e h1:qsNZzfxSvlSE4JZ3OpmDmAeqCRpOO3RI9eJ7U6z23Gk= -github.com/Arceliar/phony v0.0.0-20190825032731-f8ba56f9093e/go.mod h1:+/sVcxsqK1Sjm3Vd+yCfMAohJOfTRyNh24apkxhqU3Q= +github.com/Arceliar/phony v0.0.0-20190825152505-180ac75690fe h1:U5bediuXjZ1y6bByIXXraoE319yFp9kx1z8K6el7Ftc= +github.com/Arceliar/phony v0.0.0-20190825152505-180ac75690fe/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/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index 744ca9fc..0e0dd461 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -17,7 +17,7 @@ import ( const tunConnTimeout = 2 * time.Minute type tunConn struct { - phony.Actor + phony.Inbox tun *TunAdapter conn *yggdrasil.Conn addr address.Address @@ -198,7 +198,7 @@ func (s *tunConn) _write(bs []byte) (err error) { // No point in wasting resources to send back an error if there was none return } - s.EnqueueFrom(s.conn, func() { + s.RecvFrom(s.conn, func() { if e, eok := err.(yggdrasil.ConnError); !eok { if e.Closed() { s.tun.log.Debugln(s.conn.String(), "TUN/TAP generic write debug:", err) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 895f893b..dfc343df 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -257,7 +257,7 @@ func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { tun.subnetToConn[s.snet] = &s // Set the read callback and start the timeout goroutine conn.SetReadCallback(func(bs []byte) { - s.EnqueueFrom(conn, func() { + s.RecvFrom(conn, func() { s._read(bs) }) }) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 2f69139e..b0c26867 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -54,7 +54,7 @@ func (e *ConnError) Closed() bool { } type Conn struct { - phony.Actor + phony.Inbox core *Core readDeadline *time.Time writeDeadline *time.Time @@ -84,8 +84,8 @@ func (c *Conn) String() string { return s } -func (c *Conn) setMTU(from phony.IActor, mtu uint16) { - c.EnqueueFrom(from, func() { c.mtu = mtu }) +func (c *Conn) setMTU(from phony.Actor, mtu uint16) { + c.RecvFrom(from, func() { c.mtu = mtu }) } // This should never be called from the router goroutine, used in the dial functions @@ -143,7 +143,7 @@ func (c *Conn) doSearch() { sinfo.continueSearch() } } - c.core.router.EnqueueFrom(c.session, routerWork) + c.core.router.RecvFrom(c.session, routerWork) } func (c *Conn) _getDeadlineCancellation(t *time.Time) (util.Cancellation, bool) { @@ -159,7 +159,7 @@ func (c *Conn) _getDeadlineCancellation(t *time.Time) (util.Cancellation, bool) // SetReadCallback sets a callback which will be called whenever a packet is received. func (c *Conn) SetReadCallback(callback func([]byte)) { - c.EnqueueFrom(nil, func() { + c.RecvFrom(nil, func() { c.readCallback = callback c._drainReadBuffer() }) @@ -172,14 +172,14 @@ func (c *Conn) _drainReadBuffer() { select { case bs := <-c.readBuffer: c.readCallback(bs) - c.EnqueueFrom(nil, c._drainReadBuffer) // In case there's more + c.RecvFrom(nil, c._drainReadBuffer) // In case there's more default: } } // Called by the session to pass a new message to the Conn -func (c *Conn) recvMsg(from phony.IActor, msg []byte) { - c.EnqueueFrom(from, func() { +func (c *Conn) recvMsg(from phony.Actor, msg []byte) { + c.RecvFrom(from, func() { if c.readCallback != nil { c.readCallback(msg) } else { @@ -234,7 +234,7 @@ func (c *Conn) _write(msg FlowKeyMessage) error { if len(msg.Message) > int(c.mtu) { return ConnError{errors.New("packet too big"), true, false, false, int(c.mtu)} } - c.session.EnqueueFrom(c, func() { + c.session.RecvFrom(c, func() { // Send the packet c.session._send(msg) // Session keep-alive, while we wait for the crypto workers from send @@ -254,11 +254,11 @@ func (c *Conn) _write(msg FlowKeyMessage) error { return nil } -// WriteFrom should be called by a phony.IActor, and tells the Conn to send a message. +// WriteFrom should be called by a phony.Actor, and tells the Conn to send a message. // This is used internaly by WriteNoCopy and Write. // If the callback is called with a non-nil value, then it is safe to reuse the argument FlowKeyMessage. -func (c *Conn) WriteFrom(from phony.IActor, msg FlowKeyMessage, callback func(error)) { - c.EnqueueFrom(from, func() { +func (c *Conn) WriteFrom(from phony.Actor, msg FlowKeyMessage, callback func(error)) { + c.RecvFrom(from, func() { callback(c._write(msg)) }) } diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 98f44bc7..d4779ead 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -320,7 +320,7 @@ func (intf *linkInterface) handler() error { isAlive = true if !isReady { // (Re-)enable in the switch - intf.link.core.switchTable.EnqueueFrom(nil, func() { + intf.link.core.switchTable.RecvFrom(nil, func() { intf.link.core.switchTable._idleIn(intf.peer.port) }) isReady = true @@ -359,7 +359,7 @@ func (intf *linkInterface) handler() error { isReady = false } else { // Keep enabled in the switch - intf.link.core.switchTable.EnqueueFrom(nil, func() { + intf.link.core.switchTable.RecvFrom(nil, func() { intf.link.core.switchTable._idleIn(intf.peer.port) }) isReady = true @@ -390,7 +390,7 @@ func (intf *linkInterface) handler() error { } }() // Run reader loop - var helper phony.Actor + var helper phony.Inbox done := make(chan struct{}) var helperFunc func() helperFunc = func() { @@ -417,10 +417,10 @@ func (intf *linkInterface) handler() error { default: } // Now try to read again - helper.EnqueueFrom(nil, helperFunc) + helper.RecvFrom(nil, helperFunc) } // Start the read loop - helper.EnqueueFrom(nil, helperFunc) + helper.RecvFrom(nil, helperFunc) <-done // Wait for the helper to exit //////////////////////////////////////////////////////////////////////////////// // Remember to set `err` to something useful before returning diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 7fe33ecd..aa31bb1b 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -96,7 +96,7 @@ func (ps *peers) putPorts(ports map[switchPort]*peer) { // Information known about a peer, including thier box/sig keys, precomputed shared keys (static and ephemeral) and a handler for their outgoing traffic type peer struct { - phony.Actor + phony.Inbox core *Core intf *linkInterface port switchPort @@ -175,13 +175,13 @@ func (ps *peers) removePeer(port switchPort) { // If called, sends a notification to each peer that they should send a new switch message. // Mainly called by the switch after an update. -func (ps *peers) sendSwitchMsgs(from phony.IActor) { +func (ps *peers) sendSwitchMsgs(from phony.Actor) { ports := ps.getPorts() for _, p := range ports { if p.port == 0 { continue } - p.EnqueueFrom(from, p._sendSwitchMsg) + p.RecvFrom(from, p._sendSwitchMsg) } } @@ -207,8 +207,8 @@ func (p *peer) _updateDHT() { } } -func (p *peer) handlePacketFrom(from phony.IActor, packet []byte) { - p.EnqueueFrom(from, func() { +func (p *peer) handlePacketFrom(from phony.Actor, packet []byte) { + p.RecvFrom(from, func() { p._handlePacket(packet) }) } @@ -245,8 +245,8 @@ func (p *peer) _handleTraffic(packet []byte) { p.core.switchTable.packetInFrom(p, packet) } -func (p *peer) sendPacketsFrom(from phony.IActor, packets [][]byte) { - p.EnqueueFrom(from, func() { +func (p *peer) sendPacketsFrom(from phony.Actor, packets [][]byte) { + p.RecvFrom(from, func() { p._sendPackets(packets) }) } @@ -263,7 +263,7 @@ func (p *peer) _sendPackets(packets [][]byte) { p.out(packets) } -var peerLinkOutHelper phony.Actor +var peerLinkOutHelper phony.Inbox // This wraps the packet in the inner (ephemeral) and outer (permanent) crypto layers. // It sends it to p.linkOut, which bypasses the usual packet queues. @@ -281,7 +281,7 @@ func (p *peer) _sendLinkPacket(packet []byte) { } packet = linkPacket.encode() // TODO replace this with a message send if/when the link becomes an actor - peerLinkOutHelper.EnqueueFrom(nil, func() { + peerLinkOutHelper.RecvFrom(nil, func() { select { case p.linkOut <- packet: case <-p.done: diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index adf1b1d4..7b6a9b80 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -37,7 +37,7 @@ import ( // The router struct has channels to/from the adapter device and a self peer (0), which is how messages are passed between this node and the peers/switch layer. // The router's mainLoop goroutine is responsible for managing all information related to the dht, searches, and crypto sessions. type router struct { - phony.Actor + phony.Inbox core *Core reconfigure chan chan error addr address.Address @@ -83,8 +83,8 @@ func (r *router) start() error { } // In practice, the switch will call this with 1 packet -func (r *router) handlePackets(from phony.IActor, packets [][]byte) { - r.EnqueueFrom(from, func() { +func (r *router) handlePackets(from phony.Actor, packets [][]byte) { + r.RecvFrom(from, func() { for _, packet := range packets { r._handlePacket(packet) } @@ -92,15 +92,15 @@ func (r *router) handlePackets(from phony.IActor, packets [][]byte) { } // Insert a peer info into the dht, TODO? make the dht a separate actor -func (r *router) insertPeer(from phony.IActor, info *dhtInfo) { - r.EnqueueFrom(from, func() { +func (r *router) insertPeer(from phony.Actor, info *dhtInfo) { + r.RecvFrom(from, func() { r.dht.insertPeer(info) }) } // Reset sessions and DHT after the switch sees our coords change -func (r *router) reset(from phony.IActor) { - r.EnqueueFrom(from, func() { +func (r *router) reset(from phony.Actor) { + r.RecvFrom(from, func() { r.sessions.reset() r.dht.reset() }) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 5243fb0f..f1263379 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -38,7 +38,7 @@ func (h nonceHeap) peek() *crypto.BoxNonce { return &h[0] } // All the information we know about an active session. // This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API. type sessionInfo struct { - phony.Actor // Protects all of the below, use it any time you read/change the contents of a session + phony.Inbox // Protects all of the below, use it any time you read/change the contents of a session sessions *sessions // reconfigure chan chan error // theirAddr address.Address // @@ -342,8 +342,8 @@ func (ss *sessions) getSharedKey(myPriv *crypto.BoxPrivKey, } // Sends a session ping by calling sendPingPong in ping mode. -func (sinfo *sessionInfo) ping(from phony.IActor) { - sinfo.EnqueueFrom(from, func() { +func (sinfo *sessionInfo) ping(from phony.Actor) { + sinfo.RecvFrom(from, func() { sinfo._sendPingPong(false) }) } @@ -364,14 +364,14 @@ func (sinfo *sessionInfo) _sendPingPong(isPong bool) { } packet := p.encode() // TODO rewrite the below if/when the peer struct becomes an actor, to not go through the router first - sinfo.sessions.router.EnqueueFrom(sinfo, func() { sinfo.sessions.router.out(packet) }) + sinfo.sessions.router.RecvFrom(sinfo, func() { sinfo.sessions.router.out(packet) }) if sinfo.pingTime.Before(sinfo.time) { sinfo.pingTime = time.Now() } } -func (sinfo *sessionInfo) setConn(from phony.IActor, conn *Conn) { - sinfo.EnqueueFrom(from, func() { +func (sinfo *sessionInfo) setConn(from phony.Actor, conn *Conn) { + sinfo.RecvFrom(from, func() { sinfo.conn = conn sinfo.conn.setMTU(sinfo, sinfo._getMTU()) }) @@ -406,7 +406,7 @@ func (ss *sessions) handlePing(ping *sessionPing) { ss.listenerMutex.Unlock() } if sinfo != nil { - sinfo.EnqueueFrom(ss.router, func() { + sinfo.RecvFrom(ss.router, func() { // Update the session if !sinfo._update(ping) { /*panic("Should not happen in testing")*/ return @@ -474,7 +474,7 @@ func (sinfo *sessionInfo) _updateNonce(theirNonce *crypto.BoxNonce) { // Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change. func (ss *sessions) reset() { for _, sinfo := range ss.sinfos { - sinfo.EnqueueFrom(ss.router, func() { + sinfo.RecvFrom(ss.router, func() { sinfo.reset = true }) } @@ -489,8 +489,8 @@ type FlowKeyMessage struct { Message []byte } -func (sinfo *sessionInfo) recv(from phony.IActor, packet *wire_trafficPacket) { - sinfo.EnqueueFrom(from, func() { +func (sinfo *sessionInfo) recv(from phony.Actor, packet *wire_trafficPacket) { + sinfo.RecvFrom(from, func() { sinfo._recvPacket(packet) }) } @@ -564,7 +564,7 @@ func (sinfo *sessionInfo) _send(msg FlowKeyMessage) { util.PutBytes(p.Payload) // Send the packet // TODO replace this with a send to the peer struct if that becomes an actor - sinfo.sessions.router.EnqueueFrom(sinfo, func() { + sinfo.sessions.router.RecvFrom(sinfo, func() { sinfo.sessions.router.out(packet) }) } @@ -576,7 +576,7 @@ func (sinfo *sessionInfo) _send(msg FlowKeyMessage) { } func (sinfo *sessionInfo) checkCallbacks() { - sinfo.EnqueueFrom(nil, func() { + sinfo.RecvFrom(nil, func() { if len(sinfo.callbacks) > 0 { select { case callback := <-sinfo.callbacks[0]: diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index b6fc26f8..db63a845 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -174,7 +174,7 @@ type switchTable struct { data switchData // updater atomic.Value // *sync.Once table atomic.Value // lookupTable - phony.Actor // Owns the below + phony.Inbox // Owns the below queues switch_buffers // Queues - not atomic so ONLY use through the actor idle map[switchPort]time.Time // idle peers - not atomic so ONLY use through the actor } @@ -828,8 +828,8 @@ func (t *switchTable) _handleIdle(port switchPort) bool { return false } -func (t *switchTable) packetInFrom(from phony.IActor, bytes []byte) { - t.EnqueueFrom(from, func() { +func (t *switchTable) packetInFrom(from phony.Actor, bytes []byte) { + t.RecvFrom(from, func() { t._packetIn(bytes) }) } From a3d4d8125b4acb438779d4d5263ea0faa48d0125 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 25 Aug 2019 12:10:59 -0500 Subject: [PATCH 0277/1109] make the main library reconfiguration more actor-friendly --- src/yggdrasil/core.go | 15 ++++---- src/yggdrasil/dht.go | 21 +++++------ src/yggdrasil/link.go | 32 +++++++---------- src/yggdrasil/peer.go | 19 +++++----- src/yggdrasil/router.go | 63 +++++++++++++++++--------------- src/yggdrasil/search.go | 17 ++++----- src/yggdrasil/session.go | 42 +++++++++++----------- src/yggdrasil/switch.go | 16 ++++----- src/yggdrasil/tcp.go | 78 ++++++++++++++++++---------------------- 9 files changed, 144 insertions(+), 159 deletions(-) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 40982cdd..ec530745 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -117,20 +117,21 @@ func (c *Core) UpdateConfig(config *config.NodeConfig) { errors := 0 - components := []chan chan error{ - c.router.searches.reconfigure, - c.router.dht.reconfigure, - c.router.sessions.reconfigure, + // Each reconfigure function should pass any errors to the channel, then close it + components := []func(chan error){ + c.link.reconfigure, c.peers.reconfigure, c.router.reconfigure, + c.router.dht.reconfigure, + c.router.searches.reconfigure, + c.router.sessions.reconfigure, c.switchTable.reconfigure, - c.link.reconfigure, } for _, component := range components { response := make(chan error) - component <- response - if err := <-response; err != nil { + go component(response) + for err := range response { c.log.Errorln(err) errors++ } diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index adfc40e7..4f380363 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -65,11 +65,10 @@ type dhtReqKey struct { // The main DHT struct. type dht struct { - router *router - reconfigure chan chan error - nodeID crypto.NodeID - reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests - callbacks map[dhtReqKey][]dht_callbackInfo // Search and admin lookup callbacks + router *router + nodeID crypto.NodeID + reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests + callbacks map[dhtReqKey][]dht_callbackInfo // Search and admin lookup callbacks // These next two could be replaced by a single linked list or similar... table map[crypto.NodeID]*dhtInfo imp []*dhtInfo @@ -78,18 +77,16 @@ type dht struct { // Initializes the DHT. func (t *dht) init(r *router) { t.router = r - t.reconfigure = make(chan chan error, 1) - go func() { - for { - e := <-t.reconfigure - e <- nil - } - }() t.nodeID = *t.router.core.NodeID() t.callbacks = make(map[dhtReqKey][]dht_callbackInfo) t.reset() } +func (t *dht) reconfigure(e chan error) { + defer close(e) + // This is where reconfiguration would go, if we had anything to do +} + // Resets the DHT in response to coord changes. // This empties all info from the DHT and drops outstanding requests. func (t *dht) reset() { diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index d4779ead..bfbcc99b 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -21,11 +21,10 @@ import ( ) type link struct { - core *Core - reconfigure chan chan error - mutex sync.RWMutex // protects interfaces below - interfaces map[linkInfo]*linkInterface - tcp tcp // TCP interface support + core *Core + mutex sync.RWMutex // protects interfaces below + interfaces map[linkInfo]*linkInterface + tcp tcp // TCP interface support // TODO timeout (to remove from switch), read from config.ReadTimeout } @@ -61,7 +60,6 @@ func (l *link) init(c *Core) error { l.core = c l.mutex.Lock() l.interfaces = make(map[linkInfo]*linkInterface) - l.reconfigure = make(chan chan error) l.mutex.Unlock() if err := l.tcp.init(l); err != nil { @@ -69,22 +67,18 @@ func (l *link) init(c *Core) error { return err } - go func() { - for { - e := <-l.reconfigure - tcpresponse := make(chan error) - l.tcp.reconfigure <- tcpresponse - if err := <-tcpresponse; err != nil { - e <- err - continue - } - e <- nil - } - }() - return nil } +func (l *link) reconfigure(e chan error) { + defer close(e) + tcpResponse := make(chan error) + go l.tcp.reconfigure(tcpResponse) + for err := range tcpResponse { + e <- err + } +} + func (l *link) call(uri string, sintf string) error { u, err := url.Parse(uri) if err != nil { diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index aa31bb1b..989d9ee1 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -21,10 +21,9 @@ import ( // In most cases, this involves passing the packet to the handler for outgoing traffic to another peer. // In other cases, it's link protocol traffic used to build the spanning tree, in which case this checks signatures and passes the message along to the switch. type peers struct { - core *Core - reconfigure chan chan error - mutex sync.Mutex // Synchronize writes to atomic - ports atomic.Value //map[switchPort]*peer, use CoW semantics + core *Core + mutex sync.Mutex // Synchronize writes to atomic + ports atomic.Value //map[switchPort]*peer, use CoW semantics } // Initializes the peers struct. @@ -33,13 +32,11 @@ func (ps *peers) init(c *Core) { defer ps.mutex.Unlock() ps.putPorts(make(map[switchPort]*peer)) ps.core = c - ps.reconfigure = make(chan chan error, 1) - go func() { - for { - e := <-ps.reconfigure - e <- nil - } - }() +} + +func (ps *peers) reconfigure(e chan error) { + defer close(e) + // This is where reconfiguration would go, if we had anything to do } // Returns true if an incoming peer connection to a key is allowed, either diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 7b6a9b80..002905bc 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -35,24 +35,22 @@ import ( ) // The router struct has channels to/from the adapter device and a self peer (0), which is how messages are passed between this node and the peers/switch layer. -// The router's mainLoop goroutine is responsible for managing all information related to the dht, searches, and crypto sessions. +// The router's phony.Inbox goroutine is responsible for managing all information related to the dht, searches, and crypto sessions. type router struct { phony.Inbox - core *Core - reconfigure chan chan error - addr address.Address - subnet address.Subnet - out func([]byte) // packets we're sending to the network, link to peer's "in" - dht dht - nodeinfo nodeinfo - searches searches - sessions sessions + core *Core + addr address.Address + subnet address.Subnet + out func([]byte) // packets we're sending to the network, link to peer's "in" + dht dht + nodeinfo nodeinfo + searches searches + sessions sessions } // Initializes the router struct, which includes setting up channels to/from the adapter. func (r *router) init(core *Core) { r.core = core - r.reconfigure = make(chan chan error, 1) r.addr = *address.AddrForNodeID(&r.dht.nodeID) r.subnet = *address.SubnetForNodeID(&r.dht.nodeID) self := linkInterface{ @@ -75,10 +73,26 @@ func (r *router) init(core *Core) { r.sessions.init(r) } -// Starts the mainLoop goroutine. +func (r *router) reconfigure(e chan error) { + defer close(e) + var errs []error + // Reconfigure the router + <-r.SyncExec(func() { + current := r.core.config.GetCurrent() + err := r.nodeinfo.setNodeInfo(current.NodeInfo, current.NodeInfoPrivacy) + if err != nil { + errs = append(errs, err) + } + }) + for _, err := range errs { + e <- err + } +} + +// Starts the tickerLoop goroutine. func (r *router) start() error { r.core.log.Infoln("Starting router") - go r._mainLoop() + go r.tickerLoop() return nil } @@ -108,24 +122,17 @@ func (r *router) reset(from phony.Actor) { // TODO remove reconfigure so this is just a ticker loop // and then find something better than a ticker loop to schedule things... -func (r *router) _mainLoop() { +func (r *router) tickerLoop() { ticker := time.NewTicker(time.Second) defer ticker.Stop() for { - select { - case <-ticker.C: - <-r.SyncExec(func() { - // Any periodic maintenance stuff goes here - r.core.switchTable.doMaintenance() - r.dht.doMaintenance() - r.sessions.cleanup() - }) - case e := <-r.reconfigure: - <-r.SyncExec(func() { - current := r.core.config.GetCurrent() - e <- r.nodeinfo.setNodeInfo(current.NodeInfo, current.NodeInfoPrivacy) - }) - } + <-ticker.C + <-r.SyncExec(func() { + // Any periodic maintenance stuff goes here + r.core.switchTable.doMaintenance() + r.dht.doMaintenance() + r.sessions.cleanup() + }) } } diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 397c28a9..5fb36584 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -45,24 +45,21 @@ type searchInfo struct { // This stores a map of active searches. type searches struct { - router *router - reconfigure chan chan error - searches map[crypto.NodeID]*searchInfo + router *router + searches map[crypto.NodeID]*searchInfo } // Initializes the searches struct. func (s *searches) init(r *router) { s.router = r - s.reconfigure = make(chan chan error, 1) - go func() { - for { - e := <-s.reconfigure - e <- nil - } - }() s.searches = make(map[crypto.NodeID]*searchInfo) } +func (s *searches) reconfigure(e chan error) { + defer close(e) + // This is where reconfiguration would go, if we had anything to do +} + // Creates a new search info, adds it to the searches struct, and returns a pointer to the info. func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo { info := searchInfo{ diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index f1263379..e448cf25 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -40,7 +40,6 @@ func (h nonceHeap) peek() *crypto.BoxNonce { return &h[0] } type sessionInfo struct { phony.Inbox // Protects all of the below, use it any time you read/change the contents of a session sessions *sessions // - reconfigure chan chan error // theirAddr address.Address // theirSubnet address.Subnet // theirPermPub crypto.BoxPubKey // @@ -74,6 +73,11 @@ type sessionInfo struct { callbacks []chan func() // Finished work from crypto workers } +func (sinfo *sessionInfo) reconfigure(e chan error) { + defer close(e) + // This is where reconfiguration would go, if we had anything to do +} + // TODO remove this, call SyncExec directly func (sinfo *sessionInfo) doFunc(f func()) { <-sinfo.SyncExec(f) @@ -140,7 +144,6 @@ type sessions struct { router *router listener *Listener listenerMutex sync.Mutex - reconfigure chan chan error lastCleanup time.Time isAllowedHandler func(pubkey *crypto.BoxPubKey, initiator bool) bool // Returns true or false if session setup is allowed isAllowedMutex sync.RWMutex // Protects the above @@ -152,30 +155,28 @@ type sessions struct { // Initializes the session struct. func (ss *sessions) init(r *router) { ss.router = r - ss.reconfigure = make(chan chan error, 1) - go func() { - for { - e := <-ss.reconfigure - responses := make(map[crypto.Handle]chan error) - for index, session := range ss.sinfos { - responses[index] = make(chan error) - session.reconfigure <- responses[index] - } - for _, response := range responses { - if err := <-response; err != nil { - e <- err - continue - } - } - e <- nil - } - }() ss.permShared = make(map[crypto.BoxPubKey]*crypto.BoxSharedKey) ss.sinfos = make(map[crypto.Handle]*sessionInfo) ss.byTheirPerm = make(map[crypto.BoxPubKey]*crypto.Handle) ss.lastCleanup = time.Now() } +func (ss *sessions) reconfigure(e chan error) { + defer close(e) + responses := make(map[crypto.Handle]chan error) + <-ss.router.SyncExec(func() { + for index, session := range ss.sinfos { + responses[index] = make(chan error) + go session.reconfigure(responses[index]) + } + }) + for _, response := range responses { + for err := range response { + e <- err + } + } +} + // Determines whether the session with a given publickey is allowed based on // session firewall rules. func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) bool { @@ -215,7 +216,6 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { } sinfo := sessionInfo{} sinfo.sessions = ss - sinfo.reconfigure = make(chan chan error, 1) sinfo.theirPermPub = *theirPermKey sinfo.sharedPermKey = *ss.getSharedKey(&ss.router.core.boxPriv, &sinfo.theirPermPub) pub, priv := crypto.NewBoxKeys() diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index db63a845..cb5cf1eb 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -165,7 +165,6 @@ type switchData struct { // All the information stored by the switch. type switchTable struct { core *Core - reconfigure chan chan error key crypto.SigPubKey // Our own key time time.Time // Time when locator.tstamp was last updated drop map[crypto.SigPubKey]int64 // Tstamp associated with a dropped root @@ -186,7 +185,6 @@ const SwitchQueueTotalMinSize = 4 * 1024 * 1024 func (t *switchTable) init(core *Core) { now := time.Now() t.core = core - t.reconfigure = make(chan chan error, 1) t.key = t.core.sigPub locator := switchLocator{root: t.key, tstamp: now.Unix()} peers := make(map[switchPort]peerInfo) @@ -201,6 +199,13 @@ func (t *switchTable) init(core *Core) { }) } +func (t *switchTable) reconfigure(e chan error) { + go func() { + defer close(e) + // This is where reconfiguration would go, if we had anything useful to do. + }() +} + // Safely gets a copy of this node's locator. func (t *switchTable) getLocator() switchLocator { t.mutex.RLock() @@ -566,12 +571,7 @@ func (t *switchTable) getTable() lookupTable { // Starts the switch worker func (t *switchTable) start() error { t.core.log.Infoln("Starting switch") - go func() { - // TODO find a better way to handle reconfiguration... and have the switch do something with the new configuration - for ch := range t.reconfigure { - ch <- nil - } - }() + // There's actually nothing to do to start it... return nil } diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index dfb41510..ccb488f9 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -33,12 +33,11 @@ const tcp_ping_interval = (default_timeout * 2 / 3) // The TCP listener and information about active TCP connections, to avoid duplication. type tcp struct { - link *link - reconfigure chan chan error - mutex sync.Mutex // Protecting the below - listeners map[string]*TcpListener - calls map[string]struct{} - conns map[linkInfo](chan struct{}) + link *link + mutex sync.Mutex // Protecting the below + listeners map[string]*TcpListener + calls map[string]struct{} + conns map[linkInfo](chan struct{}) } // TcpListener is a stoppable TCP listener interface. These are typically @@ -76,49 +75,12 @@ func (t *tcp) getAddr() *net.TCPAddr { // Initializes the struct. func (t *tcp) init(l *link) error { t.link = l - t.reconfigure = make(chan chan error, 1) t.mutex.Lock() t.calls = make(map[string]struct{}) t.conns = make(map[linkInfo](chan struct{})) t.listeners = make(map[string]*TcpListener) t.mutex.Unlock() - go func() { - for { - e := <-t.reconfigure - t.link.core.config.Mutex.RLock() - added := util.Difference(t.link.core.config.Current.Listen, t.link.core.config.Previous.Listen) - deleted := util.Difference(t.link.core.config.Previous.Listen, t.link.core.config.Current.Listen) - t.link.core.config.Mutex.RUnlock() - if len(added) > 0 || len(deleted) > 0 { - for _, a := range added { - if a[:6] != "tcp://" { - continue - } - if _, err := t.listen(a[6:]); err != nil { - e <- err - continue - } - } - for _, d := range deleted { - if d[:6] != "tcp://" { - continue - } - t.mutex.Lock() - if listener, ok := t.listeners[d[6:]]; ok { - t.mutex.Unlock() - listener.Stop <- true - } else { - t.mutex.Unlock() - } - } - e <- nil - } else { - e <- nil - } - } - }() - t.link.core.config.Mutex.RLock() defer t.link.core.config.Mutex.RUnlock() for _, listenaddr := range t.link.core.config.Current.Listen { @@ -133,6 +95,36 @@ func (t *tcp) init(l *link) error { return nil } +func (t *tcp) reconfigure(e chan error) { + defer close(e) + t.link.core.config.Mutex.RLock() + added := util.Difference(t.link.core.config.Current.Listen, t.link.core.config.Previous.Listen) + deleted := util.Difference(t.link.core.config.Previous.Listen, t.link.core.config.Current.Listen) + t.link.core.config.Mutex.RUnlock() + if len(added) > 0 || len(deleted) > 0 { + for _, a := range added { + if a[:6] != "tcp://" { + continue + } + if _, err := t.listen(a[6:]); err != nil { + e <- err + } + } + for _, d := range deleted { + if d[:6] != "tcp://" { + continue + } + t.mutex.Lock() + if listener, ok := t.listeners[d[6:]]; ok { + t.mutex.Unlock() + listener.Stop <- true + } else { + t.mutex.Unlock() + } + } + } +} + func (t *tcp) listen(listenaddr string) (*TcpListener, error) { var err error From 502f2937a9419fabc7985da9a55d5913a40499db Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 25 Aug 2019 17:00:02 -0500 Subject: [PATCH 0278/1109] a couple race fixes and use timer.AfterFunc instead of sleeping goroutines or ticker in a few places --- src/tuntap/conn.go | 31 ++++--------------------------- src/tuntap/tun.go | 11 +++++------ src/yggdrasil/api.go | 6 ++---- src/yggdrasil/debug.go | 4 ++-- src/yggdrasil/link.go | 2 +- src/yggdrasil/nodeinfo.go | 34 +++++++++++++++++----------------- src/yggdrasil/peer.go | 25 ++++++++++++++----------- src/yggdrasil/router.go | 22 +++++++++------------- src/yggdrasil/search.go | 22 ++++++++++------------ src/yggdrasil/session.go | 1 - src/yggdrasil/switch.go | 6 ++---- 11 files changed, 66 insertions(+), 98 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index 0e0dd461..31490916 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -23,7 +23,7 @@ type tunConn struct { addr address.Address snet address.Subnet stop chan struct{} - alive chan struct{} + alive *time.Timer // From calling time.AfterFunc } func (s *tunConn) close() { @@ -40,10 +40,6 @@ func (s *tunConn) _close_nomutex() { defer func() { recover() }() close(s.stop) // Closes reader/writer goroutines }() - func() { - defer func() { recover() }() - close(s.alive) // Closes timeout goroutine - }() } func (s *tunConn) _read(bs []byte) (err error) { @@ -228,27 +224,8 @@ func (s *tunConn) _write(bs []byte) (err error) { } func (s *tunConn) stillAlive() { - defer func() { recover() }() - select { - case s.alive <- struct{}{}: - default: - } -} - -func (s *tunConn) checkForTimeouts() error { - timer := time.NewTimer(tunConnTimeout) - defer util.TimerStop(timer) - defer s.close() - for { - select { - case _, ok := <-s.alive: - if !ok { - return errors.New("connection closed") - } - util.TimerStop(timer) - timer.Reset(tunConnTimeout) - case <-timer.C: - return errors.New("timed out") - } + if s.alive != nil { + s.alive.Stop() } + s.alive = time.AfterFunc(tunConnTimeout, s.close) } diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index dfc343df..d73384d4 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -227,10 +227,9 @@ func (tun *TunAdapter) handler() error { func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { // Prepare a session wrapper for the given connection s := tunConn{ - tun: tun, - conn: conn, - stop: make(chan struct{}), - alive: make(chan struct{}, 1), + tun: tun, + conn: conn, + stop: make(chan struct{}), } c = &s // Get the remote address and subnet of the other side @@ -255,13 +254,13 @@ func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { // we receive a packet through the interface for this address tun.addrToConn[s.addr] = &s tun.subnetToConn[s.snet] = &s - // Set the read callback and start the timeout goroutine + // Set the read callback and start the timeout conn.SetReadCallback(func(bs []byte) { s.RecvFrom(conn, func() { s._read(bs) }) }) - go s.checkForTimeouts() + s.RecvFrom(nil, s.stillAlive) // Return return c, err } diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 2bc5c816..c6c15e24 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -344,10 +344,8 @@ func (c *Core) GetNodeInfo(key crypto.BoxPubKey, coords []uint64, nocache bool) c.router.nodeinfo.sendNodeInfo(key, wire_coordsUint64stoBytes(coords), false) } c.router.doAdmin(sendNodeInfoRequest) - go func() { - time.Sleep(6 * time.Second) - close(response) - }() + timer := time.AfterFunc(6*time.Second, func() { close(response) }) + defer timer.Stop() for res := range response { return *res, nil } diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index f6ec8716..c6f98ca0 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -552,7 +552,7 @@ func (c *Core) DEBUG_addAllowedEncryptionPublicKey(boxStr string) { //////////////////////////////////////////////////////////////////////////////// func DEBUG_simLinkPeers(p, q *peer) { - // Sets q.out() to point to p and starts p.linkLoop() + // Sets q.out() to point to p and starts p.start() goWorkers := func(source, dest *peer) { source.linkOut = make(chan []byte, 1) send := make(chan []byte, 1) @@ -561,7 +561,7 @@ func DEBUG_simLinkPeers(p, q *peer) { send <- bs } } - go source.linkLoop() + go source.start() go func() { var packets [][]byte for { diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index bfbcc99b..1b48f391 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -216,7 +216,7 @@ func (intf *linkInterface) handler() error { intf.link.core.log.Infof("Connected %s: %s, source %s", strings.ToUpper(intf.info.linkType), themString, intf.info.local) // Start the link loop - go intf.peer.linkLoop() + go intf.peer.start() // Start the writer signalReady := make(chan struct{}, 1) signalSent := make(chan bool, 1) diff --git a/src/yggdrasil/nodeinfo.go b/src/yggdrasil/nodeinfo.go index 50f5bf9c..8a5d7872 100644 --- a/src/yggdrasil/nodeinfo.go +++ b/src/yggdrasil/nodeinfo.go @@ -47,25 +47,25 @@ func (m *nodeinfo) init(core *Core) { m.callbacks = make(map[crypto.BoxPubKey]nodeinfoCallback) m.cache = make(map[crypto.BoxPubKey]nodeinfoCached) - go func() { - for { - m.callbacksMutex.Lock() - for boxPubKey, callback := range m.callbacks { - if time.Since(callback.created) > time.Minute { - delete(m.callbacks, boxPubKey) - } + var f func() + f = func() { + m.callbacksMutex.Lock() + for boxPubKey, callback := range m.callbacks { + if time.Since(callback.created) > time.Minute { + delete(m.callbacks, boxPubKey) } - m.callbacksMutex.Unlock() - m.cacheMutex.Lock() - for boxPubKey, cache := range m.cache { - if time.Since(cache.created) > time.Hour { - delete(m.cache, boxPubKey) - } - } - m.cacheMutex.Unlock() - time.Sleep(time.Second * 30) } - }() + m.callbacksMutex.Unlock() + m.cacheMutex.Lock() + for boxPubKey, cache := range m.cache { + if time.Since(cache.created) > time.Hour { + delete(m.cache, boxPubKey) + } + } + m.cacheMutex.Unlock() + time.AfterFunc(time.Second*30, f) + } + go f() } // Add a callback for a nodeinfo lookup diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 989d9ee1..50fb03f8 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -184,18 +184,21 @@ func (ps *peers) sendSwitchMsgs(from phony.Actor) { // This must be launched in a separate goroutine by whatever sets up the peer struct. // It handles link protocol traffic. -func (p *peer) linkLoop() { - tick := time.NewTicker(time.Second) - defer tick.Stop() - <-p.SyncExec(p._sendSwitchMsg) // Startup message - for { - select { - case <-p.done: - return - case _ = <-tick.C: - <-p.SyncExec(p._updateDHT) - } +func (p *peer) start() { + var updateDHT func() + updateDHT = func() { + <-p.SyncExec(func() { + select { + case <-p.done: + default: + p._updateDHT() + time.AfterFunc(time.Second, updateDHT) + } + }) } + updateDHT() + // Just for good measure, immediately send a switch message to this peer when we start + <-p.SyncExec(p._sendSwitchMsg) } func (p *peer) _updateDHT() { diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 002905bc..fec55b7c 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -92,7 +92,7 @@ func (r *router) reconfigure(e chan error) { // Starts the tickerLoop goroutine. func (r *router) start() error { r.core.log.Infoln("Starting router") - go r.tickerLoop() + go r.doMaintenance() return nil } @@ -122,18 +122,14 @@ func (r *router) reset(from phony.Actor) { // TODO remove reconfigure so this is just a ticker loop // and then find something better than a ticker loop to schedule things... -func (r *router) tickerLoop() { - ticker := time.NewTicker(time.Second) - defer ticker.Stop() - for { - <-ticker.C - <-r.SyncExec(func() { - // Any periodic maintenance stuff goes here - r.core.switchTable.doMaintenance() - r.dht.doMaintenance() - r.sessions.cleanup() - }) - } +func (r *router) doMaintenance() { + <-r.SyncExec(func() { + // Any periodic maintenance stuff goes here + r.core.switchTable.doMaintenance() + r.dht.doMaintenance() + r.sessions.cleanup() + }) + time.AfterFunc(time.Second, r.doMaintenance) } // Checks incoming traffic type and passes it to the appropriate handler. diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 5fb36584..9cf5f06e 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -152,18 +152,16 @@ func (sinfo *searchInfo) continueSearch() { // In case the search dies, try to spawn another thread later // Note that this will spawn multiple parallel searches as time passes // Any that die aren't restarted, but a new one will start later - retryLater := func() { - // FIXME this keeps the search alive forever if not for the searches map, fix that - newSearchInfo := sinfo.searches.searches[sinfo.dest] - if newSearchInfo != sinfo { - return - } - sinfo.continueSearch() - } - go func() { - time.Sleep(search_RETRY_TIME) - sinfo.searches.router.doAdmin(retryLater) - }() + time.AfterFunc(search_RETRY_TIME, func() { + sinfo.searches.router.RecvFrom(nil, func() { + // FIXME this keeps the search alive forever if not for the searches map, fix that + newSearchInfo := sinfo.searches.searches[sinfo.dest] + if newSearchInfo != sinfo { + return + } + sinfo.continueSearch() + }) + }) } // Calls create search, and initializes the iterative search parts of the struct before returning it. diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index e448cf25..94ee41e6 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -262,7 +262,6 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.sessions.removeSession(&sinfo) }) }() - //go sinfo.startWorkers() return &sinfo } diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index cb5cf1eb..6d882252 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -200,10 +200,8 @@ func (t *switchTable) init(core *Core) { } func (t *switchTable) reconfigure(e chan error) { - go func() { - defer close(e) - // This is where reconfiguration would go, if we had anything useful to do. - }() + defer close(e) + // This is where reconfiguration would go, if we had anything useful to do. } // Safely gets a copy of this node's locator. From aaf34c63049ed8314fd222367cfbc1fa51813ca7 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 25 Aug 2019 18:08:43 -0500 Subject: [PATCH 0279/1109] start migrating the TunAdapter to the actor model --- src/tuntap/conn.go | 5 +- src/tuntap/iface.go | 501 +++++++++++++++++++++++--------------------- src/tuntap/tun.go | 11 +- 3 files changed, 267 insertions(+), 250 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index 31490916..cdb8c32e 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -113,8 +113,7 @@ func (s *tunConn) _read(bs []byte) (err error) { util.PutBytes(bs) return } - // FIXME this send can block if the tuntap isn't running, which isn't really safe... - s.tun.send <- bs + s.tun.writer.writeFrom(s, bs) s.stillAlive() return } @@ -208,7 +207,7 @@ func (s *tunConn) _write(bs []byte) (err error) { Data: bs[:900], } if packet, err := CreateICMPv6(bs[8:24], bs[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb); err == nil { - s.tun.send <- packet + s.tun.writer.writeFrom(s, packet) } } else { if e.Closed() { diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index e0693e56..3f923513 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -9,264 +9,279 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/util" + + "github.com/Arceliar/phony" ) -func (tun *TunAdapter) writer() error { - var w int +type tunWriter struct { + phony.Inbox + tun *TunAdapter +} + +func (w *tunWriter) writeFrom(from phony.Actor, b []byte) { + w.RecvFrom(from, func() { + w._write(b) + }) +} + +// write is pretty loose with the memory safety rules, e.g. it assumes it can read w.tun.iface.IsTap() safely +func (w *tunWriter) _write(b []byte) { + var written int var err error - for { - b := <-tun.send - n := len(b) - if n == 0 { - continue - } - if tun.iface.IsTAP() { - sendndp := func(dstAddr address.Address) { - neigh, known := tun.icmpv6.getNeighbor(dstAddr) - known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30) - if !known { - tun.icmpv6.Solicit(dstAddr) - } - } - peermac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00} - var dstAddr address.Address - var peerknown bool - if b[0]&0xf0 == 0x40 { - dstAddr = tun.addr - } else if b[0]&0xf0 == 0x60 { - if !bytes.Equal(tun.addr[:16], dstAddr[:16]) && !bytes.Equal(tun.subnet[:8], dstAddr[:8]) { - dstAddr = tun.addr - } - } - if neighbor, ok := tun.icmpv6.getNeighbor(dstAddr); ok && neighbor.learned { - // If we've learned the MAC of a 300::/7 address, for example, or a CKR - // address, use the MAC address of that - peermac = neighbor.mac - peerknown = true - } else if neighbor, ok := tun.icmpv6.getNeighbor(tun.addr); ok && neighbor.learned { - // Otherwise send directly to the MAC address of the host if that's - // known instead - peermac = neighbor.mac - peerknown = true - } else { - // Nothing has been discovered, try to discover the destination - sendndp(tun.addr) - } - if peerknown { - var proto ethernet.Ethertype - switch { - case b[0]&0xf0 == 0x60: - proto = ethernet.IPv6 - case b[0]&0xf0 == 0x40: - proto = ethernet.IPv4 - } - var frame ethernet.Frame - frame.Prepare( - peermac[:6], // Destination MAC address - tun.icmpv6.mymac[:6], // Source MAC address - ethernet.NotTagged, // VLAN tagging - proto, // Ethertype - len(b)) // Payload length - copy(frame[tun_ETHER_HEADER_LENGTH:], b[:n]) - n += tun_ETHER_HEADER_LENGTH - w, err = tun.iface.Write(frame[:n]) - } else { - tun.log.Errorln("TUN/TAP iface write error: no peer MAC known for", net.IP(dstAddr[:]).String(), "- dropping packet") - } - } else { - w, err = tun.iface.Write(b[:n]) - util.PutBytes(b) - } - if err != nil { - if !tun.isOpen { - return err - } - tun.log.Errorln("TUN/TAP iface write error:", err) - continue - } - if w != n { - tun.log.Errorln("TUN/TAP iface write mismatch:", w, "bytes written vs", n, "bytes given") - continue - } + n := len(b) + if n == 0 { + return } -} - -// Run in a separate goroutine by the reader -// Does all of the per-packet ICMP checks, passes packets to the right Conn worker -func (tun *TunAdapter) readerPacketHandler(ch chan []byte) { - for recvd := range ch { - // If it's a TAP adapter, update the buffer slice so that we no longer - // include the ethernet headers - offset := 0 - if tun.iface.IsTAP() { - // Set our offset to beyond the ethernet headers - offset = tun_ETHER_HEADER_LENGTH - // Check first of all that we can go beyond the ethernet headers - if len(recvd) <= offset { - continue + if w.tun.iface.IsTAP() { + sendndp := func(dstAddr address.Address) { + neigh, known := w.tun.icmpv6.getNeighbor(dstAddr) + known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30) + if !known { + w.tun.icmpv6.Solicit(dstAddr) } } - // Offset the buffer from now on so that we can ignore ethernet frames if - // they are present - bs := recvd[offset:] - // If we detect an ICMP packet then hand it to the ICMPv6 module - if bs[6] == 58 { - // Found an ICMPv6 packet - we need to make sure to give ICMPv6 the full - // Ethernet frame rather than just the IPv6 packet as this is needed for - // NDP to work correctly - if err := tun.icmpv6.ParsePacket(recvd); err == nil { - // We acted on the packet in the ICMPv6 module so don't forward or do - // anything else with it - continue - } - } - if offset != 0 { - // Shift forward to avoid leaking bytes off the front of the slice when we eventually store it - bs = append(recvd[:0], bs...) - } - // From the IP header, work out what our source and destination addresses - // and node IDs are. We will need these in order to work out where to send - // the packet + peermac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00} var dstAddr address.Address - var dstSnet address.Subnet - var addrlen int - n := len(bs) - // Check the IP protocol - if it doesn't match then we drop the packet and - // do nothing with it - if bs[0]&0xf0 == 0x60 { - // Check if we have a fully-sized IPv6 header - if len(bs) < 40 { - continue + var peerknown bool + if b[0]&0xf0 == 0x40 { + dstAddr = w.tun.addr + } else if b[0]&0xf0 == 0x60 { + if !bytes.Equal(w.tun.addr[:16], dstAddr[:16]) && !bytes.Equal(w.tun.subnet[:8], dstAddr[:8]) { + dstAddr = w.tun.addr } - // Check the packet size - if n-tun_IPv6_HEADER_LENGTH != 256*int(bs[4])+int(bs[5]) { - continue - } - // IPv6 address - addrlen = 16 - copy(dstAddr[:addrlen], bs[24:]) - copy(dstSnet[:addrlen/2], bs[24:]) - } else if bs[0]&0xf0 == 0x40 { - // Check if we have a fully-sized IPv4 header - if len(bs) < 20 { - continue - } - // Check the packet size - if n != 256*int(bs[2])+int(bs[3]) { - continue - } - // IPv4 address - addrlen = 4 - copy(dstAddr[:addrlen], bs[16:]) + } + if neighbor, ok := w.tun.icmpv6.getNeighbor(dstAddr); ok && neighbor.learned { + // If we've learned the MAC of a 300::/7 address, for example, or a CKR + // address, use the MAC address of that + peermac = neighbor.mac + peerknown = true + } else if neighbor, ok := w.tun.icmpv6.getNeighbor(w.tun.addr); ok && neighbor.learned { + // Otherwise send directly to the MAC address of the host if that's + // known instead + peermac = neighbor.mac + peerknown = true } else { - // Unknown address length or protocol, so drop the packet and ignore it - tun.log.Traceln("Unknown packet type, dropping") - continue + // Nothing has been discovered, try to discover the destination + sendndp(w.tun.addr) } - if tun.ckr.isEnabled() { - if addrlen != 16 || (!dstAddr.IsValid() && !dstSnet.IsValid()) { - if key, err := tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { - // A public key was found, get the node ID for the search - dstNodeID := crypto.GetNodeID(&key) - dstAddr = *address.AddrForNodeID(dstNodeID) - dstSnet = *address.SubnetForNodeID(dstNodeID) - addrlen = 16 - } + if peerknown { + var proto ethernet.Ethertype + switch { + case b[0]&0xf0 == 0x60: + proto = ethernet.IPv6 + case b[0]&0xf0 == 0x40: + proto = ethernet.IPv4 } + var frame ethernet.Frame + frame.Prepare( + peermac[:6], // Destination MAC address + w.tun.icmpv6.mymac[:6], // Source MAC address + ethernet.NotTagged, // VLAN tagging + proto, // Ethertype + len(b)) // Payload length + copy(frame[tun_ETHER_HEADER_LENGTH:], b[:n]) + n += tun_ETHER_HEADER_LENGTH + written, err = w.tun.iface.Write(frame[:n]) + } else { + w.tun.log.Errorln("TUN/TAP iface write error: no peer MAC known for", net.IP(dstAddr[:]).String(), "- dropping packet") } - if addrlen != 16 || (!dstAddr.IsValid() && !dstSnet.IsValid()) { - // Couldn't find this node's ygg IP - continue - } - // Do we have an active connection for this node address? - var dstNodeID, dstNodeIDMask *crypto.NodeID - tun.mutex.RLock() - session, isIn := tun.addrToConn[dstAddr] - if !isIn || session == nil { - session, isIn = tun.subnetToConn[dstSnet] - if !isIn || session == nil { - // Neither an address nor a subnet mapping matched, therefore populate - // the node ID and mask to commence a search - if dstAddr.IsValid() { - dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() - } else { - dstNodeID, dstNodeIDMask = dstSnet.GetNodeIDandMask() - } - } - } - tun.mutex.RUnlock() - // If we don't have a connection then we should open one - if !isIn || session == nil { - // Check we haven't been given empty node ID, really this shouldn't ever - // happen but just to be sure... - if dstNodeID == nil || dstNodeIDMask == nil { - panic("Given empty dstNodeID and dstNodeIDMask - this shouldn't happen") - } - // Dial to the remote node - go func() { - // FIXME just spitting out a goroutine to do this is kind of ugly and means we drop packets until the dial finishes - tun.mutex.Lock() - _, known := tun.dials[*dstNodeID] - tun.dials[*dstNodeID] = append(tun.dials[*dstNodeID], bs) - for len(tun.dials[*dstNodeID]) > 32 { - util.PutBytes(tun.dials[*dstNodeID][0]) - tun.dials[*dstNodeID] = tun.dials[*dstNodeID][1:] - } - tun.mutex.Unlock() - if known { - return - } - var tc *tunConn - if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { - // We've been given a connection so prepare the session wrapper - if tc, err = tun.wrap(conn); err != nil { - // Something went wrong when storing the connection, typically that - // something already exists for this address or subnet - tun.log.Debugln("TUN/TAP iface wrap:", err) - } - } - tun.mutex.Lock() - packets := tun.dials[*dstNodeID] - delete(tun.dials, *dstNodeID) - tun.mutex.Unlock() - if tc != nil { - for _, packet := range packets { - p := packet // Possibly required because of how range - <-tc.SyncExec(func() { tc._write(p) }) - } - } - }() - // While the dial is going on we can't do much else - // continuing this iteration - skip to the next one - continue - } - // If we have a connection now, try writing to it - if isIn && session != nil { - <-session.SyncExec(func() { session._write(bs) }) + } else { + written, err = w.tun.iface.Write(b[:n]) + util.PutBytes(b) + } + if err != nil { + w.tun.mutex.Lock() + open := w.tun.isOpen + w.tun.mutex.Unlock() + if !open { + return } + w.tun.log.Errorln("TUN/TAP iface write error:", err) + } + if written != n { + w.tun.log.Errorln("TUN/TAP iface write mismatch:", written, "bytes written vs", n, "bytes given") } } -func (tun *TunAdapter) reader() error { - toWorker := make(chan []byte, 32) - defer close(toWorker) - go tun.readerPacketHandler(toWorker) - for { - // Get a slice to store the packet in - recvd := util.ResizeBytes(util.GetBytes(), 65535+tun_ETHER_HEADER_LENGTH) - // Wait for a packet to be delivered to us through the TUN/TAP adapter - n, err := tun.iface.Read(recvd) - if err != nil { - if !tun.isOpen { - return err - } - panic(err) - } - if n == 0 { - util.PutBytes(recvd) - continue - } - // Send the packet to the worker - toWorker <- recvd[:n] +type tunReader struct { + phony.Inbox + tun *TunAdapter +} + +func (r *tunReader) _read() { + // Get a slice to store the packet in + recvd := util.ResizeBytes(util.GetBytes(), 65535+tun_ETHER_HEADER_LENGTH) + // Wait for a packet to be delivered to us through the TUN/TAP adapter + n, err := r.tun.iface.Read(recvd) + if n == 0 { + util.PutBytes(recvd) + } else { + r.tun.handlePacketFrom(r, recvd[:n], err) + } + if err == nil { + // Now read again + r.RecvFrom(nil, r._read) + } +} + +func (tun *TunAdapter) handlePacketFrom(from phony.Actor, packet []byte, err error) { + tun.RecvFrom(from, func() { + tun._handlePacket(packet, err) + }) +} + +// does the work of reading a packet and sending it to the correct tunConn +func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { + if err != nil { + tun.log.Errorln("TUN/TAP iface read error:", err) + return + } + // If it's a TAP adapter, update the buffer slice so that we no longer + // include the ethernet headers + offset := 0 + if tun.iface.IsTAP() { + // Set our offset to beyond the ethernet headers + offset = tun_ETHER_HEADER_LENGTH + // Check first of all that we can go beyond the ethernet headers + if len(recvd) <= offset { + return + } + } + // Offset the buffer from now on so that we can ignore ethernet frames if + // they are present + bs := recvd[offset:] + // If we detect an ICMP packet then hand it to the ICMPv6 module + if bs[6] == 58 { + // Found an ICMPv6 packet - we need to make sure to give ICMPv6 the full + // Ethernet frame rather than just the IPv6 packet as this is needed for + // NDP to work correctly + if err := tun.icmpv6.ParsePacket(recvd); err == nil { + // We acted on the packet in the ICMPv6 module so don't forward or do + // anything else with it + return + } + } + if offset != 0 { + // Shift forward to avoid leaking bytes off the front of the slice when we eventually store it + bs = append(recvd[:0], bs...) + } + // From the IP header, work out what our source and destination addresses + // and node IDs are. We will need these in order to work out where to send + // the packet + var dstAddr address.Address + var dstSnet address.Subnet + var addrlen int + n := len(bs) + // Check the IP protocol - if it doesn't match then we drop the packet and + // do nothing with it + if bs[0]&0xf0 == 0x60 { + // Check if we have a fully-sized IPv6 header + if len(bs) < 40 { + return + } + // Check the packet size + if n-tun_IPv6_HEADER_LENGTH != 256*int(bs[4])+int(bs[5]) { + return + } + // IPv6 address + addrlen = 16 + copy(dstAddr[:addrlen], bs[24:]) + copy(dstSnet[:addrlen/2], bs[24:]) + } else if bs[0]&0xf0 == 0x40 { + // Check if we have a fully-sized IPv4 header + if len(bs) < 20 { + return + } + // Check the packet size + if n != 256*int(bs[2])+int(bs[3]) { + return + } + // IPv4 address + addrlen = 4 + copy(dstAddr[:addrlen], bs[16:]) + } else { + // Unknown address length or protocol, so drop the packet and ignore it + tun.log.Traceln("Unknown packet type, dropping") + return + } + if tun.ckr.isEnabled() { + if addrlen != 16 || (!dstAddr.IsValid() && !dstSnet.IsValid()) { + if key, err := tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { + // A public key was found, get the node ID for the search + dstNodeID := crypto.GetNodeID(&key) + dstAddr = *address.AddrForNodeID(dstNodeID) + dstSnet = *address.SubnetForNodeID(dstNodeID) + addrlen = 16 + } + } + } + if addrlen != 16 || (!dstAddr.IsValid() && !dstSnet.IsValid()) { + // Couldn't find this node's ygg IP + return + } + // Do we have an active connection for this node address? + var dstNodeID, dstNodeIDMask *crypto.NodeID + tun.mutex.RLock() + session, isIn := tun.addrToConn[dstAddr] + if !isIn || session == nil { + session, isIn = tun.subnetToConn[dstSnet] + if !isIn || session == nil { + // Neither an address nor a subnet mapping matched, therefore populate + // the node ID and mask to commence a search + if dstAddr.IsValid() { + dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() + } else { + dstNodeID, dstNodeIDMask = dstSnet.GetNodeIDandMask() + } + } + } + tun.mutex.RUnlock() + // If we don't have a connection then we should open one + if !isIn || session == nil { + // Check we haven't been given empty node ID, really this shouldn't ever + // happen but just to be sure... + if dstNodeID == nil || dstNodeIDMask == nil { + panic("Given empty dstNodeID and dstNodeIDMask - this shouldn't happen") + } + // Dial to the remote node + go func() { + // FIXME just spitting out a goroutine to do this is kind of ugly and means we drop packets until the dial finishes + tun.mutex.Lock() + _, known := tun.dials[*dstNodeID] + tun.dials[*dstNodeID] = append(tun.dials[*dstNodeID], bs) + for len(tun.dials[*dstNodeID]) > 32 { + util.PutBytes(tun.dials[*dstNodeID][0]) + tun.dials[*dstNodeID] = tun.dials[*dstNodeID][1:] + } + tun.mutex.Unlock() + if known { + return + } + var tc *tunConn + if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { + // We've been given a connection so prepare the session wrapper + if tc, err = tun.wrap(conn); err != nil { + // Something went wrong when storing the connection, typically that + // something already exists for this address or subnet + tun.log.Debugln("TUN/TAP iface wrap:", err) + } + } + tun.mutex.Lock() + packets := tun.dials[*dstNodeID] + delete(tun.dials, *dstNodeID) + tun.mutex.Unlock() + if tc != nil { + for _, packet := range packets { + p := packet // Possibly required because of how range + <-tc.SyncExec(func() { tc._write(p) }) + } + } + }() + // While the dial is going on we can't do much else + return + } + // If we have a connection now, try writing to it + if isIn && session != nil { + session.RecvFrom(tun, func() { session._write(bs) }) } } diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index d73384d4..7bb3a59f 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -15,6 +15,7 @@ import ( "net" "sync" + "github.com/Arceliar/phony" "github.com/gologme/log" "github.com/yggdrasil-network/water" @@ -33,6 +34,8 @@ const tun_ETHER_HEADER_LENGTH = 14 // you should pass this object to the yggdrasil.SetRouterAdapter() function // before calling yggdrasil.Start(). type TunAdapter struct { + writer tunWriter + reader tunReader config *config.NodeState log *log.Logger reconfigure chan chan error @@ -44,7 +47,7 @@ type TunAdapter struct { icmpv6 ICMPv6 mtu int iface *water.Interface - send chan []byte + phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below mutex sync.RWMutex // Protects the below addrToConn map[address.Address]*tunConn subnetToConn map[address.Subnet]*tunConn @@ -114,6 +117,8 @@ func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener tun.addrToConn = make(map[address.Address]*tunConn) tun.subnetToConn = make(map[address.Subnet]*tunConn) tun.dials = make(map[crypto.NodeID][][]byte) + tun.writer.tun = tun + tun.reader.tun = tun } // Start the setup process for the TUN/TAP adapter. If successful, starts the @@ -147,7 +152,6 @@ func (tun *TunAdapter) Start() error { } tun.mutex.Lock() tun.isOpen = true - tun.send = make(chan []byte, 32) // TODO: is this a sensible value? tun.reconfigure = make(chan chan error) tun.mutex.Unlock() go func() { @@ -157,8 +161,7 @@ func (tun *TunAdapter) Start() error { } }() go tun.handler() - go tun.reader() - go tun.writer() + tun.reader.RecvFrom(nil, tun.reader._read) // Start the reader tun.icmpv6.Init(tun) if iftapmode { go tun.icmpv6.Solicit(tun.addr) From b2a2e251ad173686c752463f38efdf77e6814487 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 25 Aug 2019 18:53:11 -0500 Subject: [PATCH 0280/1109] more TunAdapter migration --- src/tuntap/conn.go | 12 ++++--- src/tuntap/iface.go | 78 +++++++++++++++++++-------------------------- src/tuntap/tun.go | 72 ++++++++++++++++++++++++----------------- 3 files changed, 84 insertions(+), 78 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index cdb8c32e..afb0f09e 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -27,12 +27,10 @@ type tunConn struct { } func (s *tunConn) close() { - s.tun.mutex.Lock() - defer s.tun.mutex.Unlock() - s._close_nomutex() + s.tun.RecvFrom(s, s._close_from_tun) } -func (s *tunConn) _close_nomutex() { +func (s *tunConn) _close_from_tun() { s.conn.Close() delete(s.tun.addrToConn, s.addr) delete(s.tun.subnetToConn, s.snet) @@ -118,6 +116,12 @@ func (s *tunConn) _read(bs []byte) (err error) { return } +func (s *tunConn) writeFrom(from phony.Actor, bs []byte) { + s.RecvFrom(from, func() { + s._write(bs) + }) +} + func (s *tunConn) _write(bs []byte) (err error) { select { case <-s.stop: diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 3f923513..16e8d25d 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -90,13 +90,11 @@ func (w *tunWriter) _write(b []byte) { util.PutBytes(b) } if err != nil { - w.tun.mutex.Lock() - open := w.tun.isOpen - w.tun.mutex.Unlock() - if !open { - return - } - w.tun.log.Errorln("TUN/TAP iface write error:", err) + w.tun.RecvFrom(w, func() { + if !w.tun.isOpen { + w.tun.log.Errorln("TUN/TAP iface write error:", err) + } + }) } if written != n { w.tun.log.Errorln("TUN/TAP iface write mismatch:", written, "bytes written vs", n, "bytes given") @@ -221,7 +219,6 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { } // Do we have an active connection for this node address? var dstNodeID, dstNodeIDMask *crypto.NodeID - tun.mutex.RLock() session, isIn := tun.addrToConn[dstAddr] if !isIn || session == nil { session, isIn = tun.subnetToConn[dstSnet] @@ -235,7 +232,6 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { } } } - tun.mutex.RUnlock() // If we don't have a connection then we should open one if !isIn || session == nil { // Check we haven't been given empty node ID, really this shouldn't ever @@ -243,45 +239,37 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { if dstNodeID == nil || dstNodeIDMask == nil { panic("Given empty dstNodeID and dstNodeIDMask - this shouldn't happen") } - // Dial to the remote node - go func() { - // FIXME just spitting out a goroutine to do this is kind of ugly and means we drop packets until the dial finishes - tun.mutex.Lock() - _, known := tun.dials[*dstNodeID] - tun.dials[*dstNodeID] = append(tun.dials[*dstNodeID], bs) - for len(tun.dials[*dstNodeID]) > 32 { - util.PutBytes(tun.dials[*dstNodeID][0]) - tun.dials[*dstNodeID] = tun.dials[*dstNodeID][1:] - } - tun.mutex.Unlock() - if known { - return - } - var tc *tunConn - if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { - // We've been given a connection so prepare the session wrapper - if tc, err = tun.wrap(conn); err != nil { - // Something went wrong when storing the connection, typically that - // something already exists for this address or subnet - tun.log.Debugln("TUN/TAP iface wrap:", err) + _, known := tun.dials[*dstNodeID] + tun.dials[*dstNodeID] = append(tun.dials[*dstNodeID], bs) + for len(tun.dials[*dstNodeID]) > 32 { + util.PutBytes(tun.dials[*dstNodeID][0]) + tun.dials[*dstNodeID] = tun.dials[*dstNodeID][1:] + } + if !known { + go func() { + if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { + tun.RecvFrom(nil, func() { + // We've been given a connection so prepare the session wrapper + packets := tun.dials[*dstNodeID] + delete(tun.dials, *dstNodeID) + var tc *tunConn + var err error + if tc, err = tun._wrap(conn); err != nil { + // Something went wrong when storing the connection, typically that + // something already exists for this address or subnet + tun.log.Debugln("TUN/TAP iface wrap:", err) + return + } + for _, packet := range packets { + tc.writeFrom(nil, packet) + } + }) } - } - tun.mutex.Lock() - packets := tun.dials[*dstNodeID] - delete(tun.dials, *dstNodeID) - tun.mutex.Unlock() - if tc != nil { - for _, packet := range packets { - p := packet // Possibly required because of how range - <-tc.SyncExec(func() { tc._write(p) }) - } - } - }() - // While the dial is going on we can't do much else - return + }() + } } // If we have a connection now, try writing to it if isIn && session != nil { - session.RecvFrom(tun, func() { session._write(bs) }) + session.writeFrom(tun, bs) } } diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 7bb3a59f..2b163e3b 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -13,7 +13,7 @@ import ( "errors" "fmt" "net" - "sync" + //"sync" "github.com/Arceliar/phony" "github.com/gologme/log" @@ -34,21 +34,21 @@ const tun_ETHER_HEADER_LENGTH = 14 // you should pass this object to the yggdrasil.SetRouterAdapter() function // before calling yggdrasil.Start(). type TunAdapter struct { - writer tunWriter - reader tunReader - config *config.NodeState - log *log.Logger - reconfigure chan chan error - listener *yggdrasil.Listener - dialer *yggdrasil.Dialer - addr address.Address - subnet address.Subnet - ckr cryptokey - icmpv6 ICMPv6 - mtu int - iface *water.Interface - phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below - mutex sync.RWMutex // Protects the below + writer tunWriter + reader tunReader + config *config.NodeState + log *log.Logger + reconfigure chan chan error + listener *yggdrasil.Listener + dialer *yggdrasil.Dialer + addr address.Address + subnet address.Subnet + ckr cryptokey + icmpv6 ICMPv6 + mtu int + iface *water.Interface + phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below + //mutex sync.RWMutex // Protects the below addrToConn map[address.Address]*tunConn subnetToConn map[address.Subnet]*tunConn dials map[crypto.NodeID][][]byte // Buffer of packets to send after dialing finishes @@ -122,8 +122,16 @@ func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener } // Start the setup process for the TUN/TAP adapter. If successful, starts the -// read/write goroutines to handle packets on that interface. +// reader actor to handle packets on that interface. func (tun *TunAdapter) Start() error { + var err error + <-tun.SyncExec(func() { + err = tun._start() + }) + return err +} + +func (tun *TunAdapter) _start() error { current := tun.config.GetCurrent() if tun.config == nil || tun.listener == nil || tun.dialer == nil { return errors.New("No configuration available to TUN/TAP") @@ -150,10 +158,8 @@ func (tun *TunAdapter) Start() error { tun.log.Debugln("Not starting TUN/TAP as ifname is none or dummy") return nil } - tun.mutex.Lock() tun.isOpen = true tun.reconfigure = make(chan chan error) - tun.mutex.Unlock() go func() { for { e := <-tun.reconfigure @@ -173,6 +179,14 @@ func (tun *TunAdapter) Start() error { // Start the setup process for the TUN/TAP adapter. If successful, starts the // read/write goroutines to handle packets on that interface. func (tun *TunAdapter) Stop() error { + var err error + <-tun.SyncExec(func() { + err = tun._stop() + }) + return err +} + +func (tun *TunAdapter) _stop() error { tun.isOpen = false // TODO: we have nothing that cleanly stops all the various goroutines opened // by TUN/TAP, e.g. readers/writers, sessions @@ -219,15 +233,17 @@ func (tun *TunAdapter) handler() error { tun.log.Errorln("TUN/TAP connection accept error:", err) return err } - if _, err := tun.wrap(conn); err != nil { - // Something went wrong when storing the connection, typically that - // something already exists for this address or subnet - tun.log.Debugln("TUN/TAP handler wrap:", err) - } + <-tun.SyncExec(func() { + if _, err := tun._wrap(conn); err != nil { + // Something went wrong when storing the connection, typically that + // something already exists for this address or subnet + tun.log.Debugln("TUN/TAP handler wrap:", err) + } + }) } } -func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { +func (tun *TunAdapter) _wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { // Prepare a session wrapper for the given connection s := tunConn{ tun: tun, @@ -240,17 +256,15 @@ func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { s.addr = *address.AddrForNodeID(&remoteNodeID) s.snet = *address.SubnetForNodeID(&remoteNodeID) // Work out if this is already a destination we already know about - tun.mutex.Lock() - defer tun.mutex.Unlock() atc, aok := tun.addrToConn[s.addr] stc, sok := tun.subnetToConn[s.snet] // If we know about a connection for this destination already then assume it // is no longer valid and close it if aok { - atc._close_nomutex() + atc._close_from_tun() err = errors.New("replaced connection for address") } else if sok { - stc._close_nomutex() + stc._close_from_tun() err = errors.New("replaced connection for subnet") } // Save the session wrapper so that we can look it up quickly next time From dffd70119da10ef92665a14edc224d9db690a036 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 25 Aug 2019 19:13:47 -0500 Subject: [PATCH 0281/1109] remove session shutdown goroutine, just send a message instead --- src/yggdrasil/conn.go | 2 ++ src/yggdrasil/session.go | 13 ++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index b0c26867..efc0c81e 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -304,6 +304,8 @@ func (c *Conn) Close() (err error) { // Close the session, if it hasn't been closed already if e := c.session.cancel.Cancel(errors.New("connection closed")); e != nil { err = ConnError{errors.New("close failed, session already closed"), false, false, true, 0} + } else { + c.session.doRemove() } } }) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 94ee41e6..0fc7ec80 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -255,13 +255,6 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) ss.sinfos[sinfo.myHandle] = &sinfo ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle - go func() { - // Run cleanup when the session is canceled - <-sinfo.cancel.Finished() - sinfo.sessions.router.doAdmin(func() { - sinfo.sessions.removeSession(&sinfo) - }) - }() return &sinfo } @@ -293,6 +286,12 @@ func (ss *sessions) cleanup() { ss.lastCleanup = time.Now() } +func (sinfo *sessionInfo) doRemove() { + sinfo.sessions.router.RecvFrom(nil, func() { + sinfo.sessions.removeSession(sinfo) + }) +} + // Closes a session, removing it from sessions maps. func (ss *sessions) removeSession(sinfo *sessionInfo) { if s := sinfo.sessions.sinfos[sinfo.myHandle]; s == sinfo { From b5b179904b89a668c03e735a430e9a534f5140b2 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 25 Aug 2019 22:19:20 -0500 Subject: [PATCH 0282/1109] ugly work-in-progress to migrate link to the actor model --- src/yggdrasil/link.go | 211 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 210 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 1b48f391..5489c015 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -54,6 +54,15 @@ type linkInterface struct { incoming bool force bool closed chan struct{} + reader linkReader // Reads packets, notifies this linkInterface, passes packets to switch + writer linkWriter // Writes packets, notifies this linkInterface + phony.Inbox // Protects the below + sendTimer *time.Timer // Fires to signal that sending is blocked + stallTimer *time.Timer // Fires to signal that no incoming traffic (including keep-alive) has been seen + recvTimer *time.Timer // Fires to send keep-alive traffic + closeTimer *time.Timer // Fires when the link has been idle so long we need to close it + inSwitch bool // True if the switch is tracking this link + stalled bool // True if we haven't been receiving any response traffic } func (l *link) init(c *Core) error { @@ -124,6 +133,9 @@ func (l *link) create(msgIO linkInterfaceMsgIO, name, linkType, local, remote st incoming: incoming, force: force, } + intf.writer.intf = &intf + intf.reader.intf = &intf + intf.reader.err = make(chan error) return &intf, nil } @@ -187,7 +199,7 @@ func (intf *linkInterface) handler() error { intf.link.mutex.Lock() delete(intf.link.interfaces, intf.info) intf.link.mutex.Unlock() - close(intf.closed) + //close(intf.closed) }() intf.link.core.log.Debugln("DEBUG: registered interface for", intf.name) } @@ -203,13 +215,24 @@ func (intf *linkInterface) handler() error { intf.link.core.peers.removePeer(intf.peer.port) }() // Finish setting up the peer struct + /* out := make(chan [][]byte, 1) defer close(out) intf.peer.out = func(msgs [][]byte) { defer func() { recover() }() out <- msgs } + */ + intf.peer.out = func(msgs [][]byte) { + intf.writer.sendFrom(intf.peer, msgs, false) + } intf.peer.linkOut = make(chan []byte, 1) + go func() { + // TODO fix this + for bs := range intf.peer.linkOut { + intf.writer.sendFrom(intf.peer, [][]byte{bs}, true) + } + }() themAddr := address.AddrForNodeID(crypto.GetNodeID(&intf.info.box)) themAddrString := net.IP(themAddr[:]).String() themString := fmt.Sprintf("%s@%s", themAddrString, intf.info.remote) @@ -218,6 +241,7 @@ func (intf *linkInterface) handler() error { // Start the link loop go intf.peer.start() // Start the writer + /* signalReady := make(chan struct{}, 1) signalSent := make(chan bool, 1) sendAck := make(chan struct{}, 1) @@ -413,6 +437,7 @@ func (intf *linkInterface) handler() error { // Now try to read again helper.RecvFrom(nil, helperFunc) } + // Start the read loop helper.RecvFrom(nil, helperFunc) <-done // Wait for the helper to exit @@ -427,5 +452,189 @@ func (intf *linkInterface) handler() error { intf.link.core.log.Infof("Disconnected %s: %s, source %s", strings.ToUpper(intf.info.linkType), themString, intf.info.local) } + */ + // Start the reader + intf.reader.RecvFrom(nil, intf.reader._read) + // Wait for the reader to finish + err = <- intf.reader.err + if err != nil { + intf.link.core.log.Infof("Disconnected %s: %s, source %s; error: %s", + strings.ToUpper(intf.info.linkType), themString, intf.info.local, err) + } else { + intf.link.core.log.Infof("Disconnected %s: %s, source %s", + strings.ToUpper(intf.info.linkType), themString, intf.info.local) + } return err } + +//////////////////////////////////////////////////////////////////////////////// + + +/* + phony.Inbox // Protects the below + sendTimer *time.Timer // Fires to signal that sending is blocked + stallTimer *time.Time // Fires to signal that no incoming traffic (including keep-alive) has been seen + recvTimer *time.Timer // Fires to send keep-alive traffic + closeTimer *time.Timer // Fires when the link has been idle so long we need to close it + inSwitch bool // True if the switch is tracking this link + stalled bool // True if we haven't been receiving any response traffic +*/ + +const ( + sendBlockedTime = time.Second // How long to wait before deciding a send is blocked + keepAliveTime = 2*time.Second // How long to wait before sending a keep-alive response if we have no real traffic to send + stallTime = 6*time.Second // How long to wait for response traffic before deciding the connection has stalled + closeTime = 2*switch_timeout // How long to wait before closing the link +) + +// notify the intf that we're currently sending +func (intf *linkInterface) notifySending(size int, isLinkTraffic bool) { + intf.RecvFrom(nil, func() { + if !isLinkTraffic && size > 0 { + intf.inSwitch = false + } + intf.sendTimer = time.AfterFunc(sendBlockedTime, intf.notifyBlockedSend) + intf._cancelRecvTimer() + }) +} + +// we just sent something, so cancel any pending timer to send keep-alive traffic +func (intf *linkInterface) _cancelRecvTimer() { + intf.RecvFrom(nil, func() { + if intf.recvTimer != nil { + intf.recvTimer.Stop() + intf.recvTimer = nil + } + }) +} + +// called by an AfterFunc if we appear to have timed out +func (intf *linkInterface) notifyBlockedSend() { + intf.RecvFrom(nil, func() { + if intf.sendTimer != nil { + //As far as we know, we're still trying to send, and the timer fired. + intf.link.core.switchTable.blockPeer(intf.peer.port) + } + }) +} + +// notify the intf that we've finished sending, returning the peer to the switch +func (intf *linkInterface) notifySent(size int, isLinkTraffic bool) { + intf.RecvFrom(nil, func() { + intf.sendTimer.Stop() + intf.sendTimer = nil + if !isLinkTraffic { + intf._notifySwitch() + } + if size > 0 && intf.stallTimer == nil { + intf.stallTimer = time.AfterFunc(stallTime, intf.notifyStalled) + } + }) +} + +// Notify the switch that we're ready for more traffic, assuming we're not in a stalled state +func (intf *linkInterface) _notifySwitch() { + if !intf.inSwitch && !intf.stalled { + intf.inSwitch = true + intf.link.core.switchTable.RecvFrom(intf, func() { + intf.link.core.switchTable._idleIn(intf.peer.port) + }) + } +} + +// Set the peer as stalled, to prevent them from returning to the switch until a read succeeds +func (intf *linkInterface) notifyStalled() { + intf.RecvFrom(nil, func() { + if intf.stallTimer != nil { + intf.stallTimer = nil + intf.stalled = true + intf.link.core.switchTable.blockPeer(intf.peer.port) + } + }) +} + + +// reset the close timer +func (intf *linkInterface) notifyReading(from phony.Actor) { + intf.RecvFrom(from, func() { + if intf.closeTimer != nil { + intf.closeTimer.Stop() + } + intf.closeTimer = time.AfterFunc(closeTime, func() { intf.msgIO.close() }) + }) +} + +// wake up the link if it was stalled, and (if size > 0) prepare to send keep-alive traffic +func (intf *linkInterface) notifyReadFrom(from phony.Actor, size int) { + intf.RecvFrom(from, func() { + intf.link.core.log.Printf("DEBUG notifyReadFrom: inSwitch %v, stalled %v\n", intf.inSwitch, intf.stalled) + if intf.stallTimer != nil { + intf.stallTimer.Stop() + intf.stallTimer = nil + } + intf.stalled = false + intf._notifySwitch() + if size > 0 && intf.recvTimer == nil { + intf.recvTimer = time.AfterFunc(keepAliveTime, intf.notifyDoKeepAlive) + } + }) +} + +// We need to send keep-alive traffic now +func (intf *linkInterface) notifyDoKeepAlive() { + intf.RecvFrom(nil, func() { + if intf.recvTimer != nil { + intf.recvTimer.Stop() + intf.recvTimer = nil + intf.writer.sendFrom(nil, [][]byte{nil}, true) // Empty keep-alive traffic + } + }) +} + +//////////////////////////////////////////////////////////////////////////////// + +type linkWriter struct { + phony.Inbox + intf *linkInterface +} + +func (w *linkWriter) sendFrom(from phony.Actor, bss [][]byte, isLinkTraffic bool) { + w.RecvFrom(from, func() { + var size int + for _, bs := range bss { + size += len(bs) + } + w.intf.notifySending(size, isLinkTraffic) + w.intf.msgIO.writeMsgs(bss) + w.intf.notifySent(size, isLinkTraffic) + w.intf.link.core.log.Println("DEBUG: wrote something, size:", size, "isLinkTraffic:", isLinkTraffic) + }) +} + +//////////////////////////////////////////////////////////////////////////////// + +type linkReader struct { + phony.Inbox + intf *linkInterface + err chan error +} + +func (r *linkReader) _read() { + r.intf.notifyReading(r) + msg, err := r.intf.msgIO.readMsg() + r.intf.link.core.log.Println("DEBUG read something") + r.intf.notifyReadFrom(r, len(msg)) + if len(msg) > 0 { + r.intf.peer.handlePacketFrom(r, msg) + } + if err != nil { + if err != io.EOF { + r.err<-err + } + close(r.err) + return + } + // Now try to read again + r.RecvFrom(nil, r._read) +} + From bd3eaefb72675473ca46a0c0b943ffc9ac2ffdee Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 25 Aug 2019 22:55:17 -0500 Subject: [PATCH 0283/1109] more link migration --- src/yggdrasil/debug.go | 7 +- src/yggdrasil/link.go | 497 ++++++++++------------------------------ src/yggdrasil/peer.go | 12 +- src/yggdrasil/switch.go | 4 +- 4 files changed, 135 insertions(+), 385 deletions(-) diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index c6f98ca0..1cc7eae4 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -554,7 +554,8 @@ func (c *Core) DEBUG_addAllowedEncryptionPublicKey(boxStr string) { func DEBUG_simLinkPeers(p, q *peer) { // Sets q.out() to point to p and starts p.start() goWorkers := func(source, dest *peer) { - source.linkOut = make(chan []byte, 1) + linkOut := make(chan []byte, 1) + source.linkOut = func(bs []byte) { linkOut <- bs } send := make(chan []byte, 1) source.out = func(bss [][]byte) { for _, bs := range bss { @@ -566,7 +567,7 @@ func DEBUG_simLinkPeers(p, q *peer) { var packets [][]byte for { select { - case packet := <-source.linkOut: + case packet := <-linkOut: packets = append(packets, packet) continue case packet := <-send: @@ -583,7 +584,7 @@ func DEBUG_simLinkPeers(p, q *peer) { continue } select { - case packet := <-source.linkOut: + case packet := <-linkOut: packets = append(packets, packet) case packet := <-send: packets = append(packets, packet) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 5489c015..b4ca38c4 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -46,23 +46,23 @@ type linkInterfaceMsgIO interface { } type linkInterface struct { - name string - link *link - peer *peer - msgIO linkInterfaceMsgIO - info linkInfo - incoming bool - force bool - closed chan struct{} - reader linkReader // Reads packets, notifies this linkInterface, passes packets to switch - writer linkWriter // Writes packets, notifies this linkInterface - phony.Inbox // Protects the below - sendTimer *time.Timer // Fires to signal that sending is blocked - stallTimer *time.Timer // Fires to signal that no incoming traffic (including keep-alive) has been seen - recvTimer *time.Timer // Fires to send keep-alive traffic - closeTimer *time.Timer // Fires when the link has been idle so long we need to close it - inSwitch bool // True if the switch is tracking this link - stalled bool // True if we haven't been receiving any response traffic + name string + link *link + peer *peer + msgIO linkInterfaceMsgIO + info linkInfo + incoming bool + force bool + closed chan struct{} + reader linkReader // Reads packets, notifies this linkInterface, passes packets to switch + writer linkWriter // Writes packets, notifies this linkInterface + phony.Inbox // Protects the below + sendTimer *time.Timer // Fires to signal that sending is blocked + stallTimer *time.Timer // Fires to signal that no incoming traffic (including keep-alive) has been seen + recvTimer *time.Timer // Fires to send keep-alive traffic + closeTimer *time.Timer // Fires when the link has been idle so long we need to close it + inSwitch bool // True if the switch is tracking this link + stalled bool // True if we haven't been receiving any response traffic } func (l *link) init(c *Core) error { @@ -133,9 +133,9 @@ func (l *link) create(msgIO linkInterfaceMsgIO, name, linkType, local, remote st incoming: incoming, force: force, } - intf.writer.intf = &intf - intf.reader.intf = &intf - intf.reader.err = make(chan error) + intf.writer.intf = &intf + intf.reader.intf = &intf + intf.reader.err = make(chan error) return &intf, nil } @@ -199,7 +199,7 @@ func (intf *linkInterface) handler() error { intf.link.mutex.Lock() delete(intf.link.interfaces, intf.info) intf.link.mutex.Unlock() - //close(intf.closed) + close(intf.closed) }() intf.link.core.log.Debugln("DEBUG: registered interface for", intf.name) } @@ -214,427 +214,184 @@ func (intf *linkInterface) handler() error { // More cleanup can go here intf.link.core.peers.removePeer(intf.peer.port) }() - // Finish setting up the peer struct - /* - out := make(chan [][]byte, 1) - defer close(out) intf.peer.out = func(msgs [][]byte) { - defer func() { recover() }() - out <- msgs + intf.writer.sendFrom(intf.peer, msgs, false) + } + intf.peer.linkOut = func(bs []byte) { + intf.writer.sendFrom(intf.peer, [][]byte{bs}, true) } - */ - intf.peer.out = func(msgs [][]byte) { - intf.writer.sendFrom(intf.peer, msgs, false) - } - intf.peer.linkOut = make(chan []byte, 1) - go func() { - // TODO fix this - for bs := range intf.peer.linkOut { - intf.writer.sendFrom(intf.peer, [][]byte{bs}, true) - } - }() themAddr := address.AddrForNodeID(crypto.GetNodeID(&intf.info.box)) themAddrString := net.IP(themAddr[:]).String() themString := fmt.Sprintf("%s@%s", themAddrString, intf.info.remote) intf.link.core.log.Infof("Connected %s: %s, source %s", strings.ToUpper(intf.info.linkType), themString, intf.info.local) - // Start the link loop + // Start things go intf.peer.start() - // Start the writer - /* - signalReady := make(chan struct{}, 1) - signalSent := make(chan bool, 1) - sendAck := make(chan struct{}, 1) - sendBlocked := time.NewTimer(time.Second) - defer util.TimerStop(sendBlocked) - util.TimerStop(sendBlocked) - go func() { - defer close(signalReady) - defer close(signalSent) - interval := 4 * time.Second - tcpTimer := time.NewTimer(interval) // used for backwards compat with old tcp - defer util.TimerStop(tcpTimer) - send := func(bss [][]byte) { - sendBlocked.Reset(time.Second) - size, _ := intf.msgIO.writeMsgs(bss) - util.TimerStop(sendBlocked) - select { - case signalSent <- size > 0: - default: - } - } - for { - // First try to send any link protocol traffic - select { - case msg := <-intf.peer.linkOut: - send([][]byte{msg}) - continue - default: - } - // No protocol traffic to send, so reset the timer - util.TimerStop(tcpTimer) - tcpTimer.Reset(interval) - // Now block until something is ready or the timer triggers keepalive traffic - select { - case <-tcpTimer.C: - intf.link.core.log.Tracef("Sending (legacy) keep-alive to %s: %s, source %s", - strings.ToUpper(intf.info.linkType), themString, intf.info.local) - send([][]byte{nil}) - case <-sendAck: - intf.link.core.log.Tracef("Sending ack to %s: %s, source %s", - strings.ToUpper(intf.info.linkType), themString, intf.info.local) - send([][]byte{nil}) - case msg := <-intf.peer.linkOut: - send([][]byte{msg}) - case msgs, ok := <-out: - if !ok { - return - } - send(msgs) - for _, msg := range msgs { - util.PutBytes(msg) - } - select { - case signalReady <- struct{}{}: - default: - } - //intf.link.core.log.Tracef("Sending packet to %s: %s, source %s", - // strings.ToUpper(intf.info.linkType), themString, intf.info.local) - } - } - }() - //intf.link.core.switchTable.idleIn <- intf.peer.port // notify switch that we're idle - // Used to enable/disable activity in the switch - signalAlive := make(chan bool, 1) // True = real packet, false = keep-alive - defer close(signalAlive) - ret := make(chan error, 1) // How we signal the return value when multiple goroutines are involved - go func() { - var isAlive bool - var isReady bool - var sendTimerRunning bool - var recvTimerRunning bool - recvTime := 6 * time.Second // TODO set to ReadTimeout from the config, reset if it gets changed - closeTime := 2 * switch_timeout // TODO or maybe this makes more sense for ReadTimeout?... - sendTime := time.Second - sendTimer := time.NewTimer(sendTime) - defer util.TimerStop(sendTimer) - recvTimer := time.NewTimer(recvTime) - defer util.TimerStop(recvTimer) - closeTimer := time.NewTimer(closeTime) - defer util.TimerStop(closeTimer) - for { - //intf.link.core.log.Debugf("State of %s: %s, source %s :: isAlive %t isReady %t sendTimerRunning %t recvTimerRunning %t", - // strings.ToUpper(intf.info.linkType), themString, intf.info.local, - // isAlive, isReady, sendTimerRunning, recvTimerRunning) - select { - case gotMsg, ok := <-signalAlive: - if !ok { - return - } - util.TimerStop(closeTimer) - closeTimer.Reset(closeTime) - util.TimerStop(recvTimer) - recvTimerRunning = false - isAlive = true - if !isReady { - // (Re-)enable in the switch - intf.link.core.switchTable.RecvFrom(nil, func() { - intf.link.core.switchTable._idleIn(intf.peer.port) - }) - isReady = true - } - if gotMsg && !sendTimerRunning { - // We got a message - // Start a timer, if it expires then send a 0-sized ack to let them know we're alive - util.TimerStop(sendTimer) - sendTimer.Reset(sendTime) - sendTimerRunning = true - } - if !gotMsg { - intf.link.core.log.Tracef("Received ack from %s: %s, source %s", - strings.ToUpper(intf.info.linkType), themString, intf.info.local) - } - case sentMsg, ok := <-signalSent: - // Stop any running ack timer - if !ok { - return - } - util.TimerStop(sendTimer) - sendTimerRunning = false - if sentMsg && !recvTimerRunning { - // We sent a message - // Start a timer, if it expires and we haven't gotten any return traffic (including a 0-sized ack), then assume there's a problem - util.TimerStop(recvTimer) - recvTimer.Reset(recvTime) - recvTimerRunning = true - } - case _, ok := <-signalReady: - if !ok { - return - } - if !isAlive { - // Disable in the switch - isReady = false - } else { - // Keep enabled in the switch - intf.link.core.switchTable.RecvFrom(nil, func() { - intf.link.core.switchTable._idleIn(intf.peer.port) - }) - isReady = true - } - case <-sendBlocked.C: - // We blocked while trying to send something - isReady = false - intf.link.core.switchTable.blockPeer(intf.peer.port) - case <-sendTimer.C: - // We haven't sent anything, so signal a send of a 0 packet to let them know we're alive - select { - case sendAck <- struct{}{}: - default: - } - case <-recvTimer.C: - // We haven't received anything, so assume there's a problem and don't return this node to the switch until they start responding - isAlive = false - intf.link.core.switchTable.blockPeer(intf.peer.port) - case <-closeTimer.C: - // We haven't received anything in a really long time, so things have died at the switch level and then some... - // Just close the connection at this point... - select { - case ret <- errors.New("timeout"): - default: - } - intf.msgIO.close() - } - } - }() - // Run reader loop - var helper phony.Inbox - done := make(chan struct{}) - var helperFunc func() - helperFunc = func() { - // The helper reads in a loop and sends to the peer - // It loops by sending itself a message, which can be delayed by backpressure - // So if the peer is busy, backpressure will pause reading until the peer catches up - msg, err := intf.msgIO.readMsg() - if len(msg) > 0 { - // TODO rewrite this if the link becomes an actor - intf.peer.handlePacketFrom(&helper, msg) - } - if err != nil { - if err != io.EOF { - select { - case ret <- err: - default: - } - } - close(done) - return - } - select { - case signalAlive <- len(msg) > 0: - default: - } - // Now try to read again - helper.RecvFrom(nil, helperFunc) - } - - // Start the read loop - helper.RecvFrom(nil, helperFunc) - <-done // Wait for the helper to exit - //////////////////////////////////////////////////////////////////////////////// - // Remember to set `err` to something useful before returning - select { - case err = <-ret: + intf.reader.RecvFrom(nil, intf.reader._read) + // Wait for the reader to finish + err = <-intf.reader.err + if err != nil { intf.link.core.log.Infof("Disconnected %s: %s, source %s; error: %s", strings.ToUpper(intf.info.linkType), themString, intf.info.local, err) - default: - err = nil + } else { intf.link.core.log.Infof("Disconnected %s: %s, source %s", strings.ToUpper(intf.info.linkType), themString, intf.info.local) } - */ - // Start the reader - intf.reader.RecvFrom(nil, intf.reader._read) - // Wait for the reader to finish - err = <- intf.reader.err - if err != nil { - intf.link.core.log.Infof("Disconnected %s: %s, source %s; error: %s", - strings.ToUpper(intf.info.linkType), themString, intf.info.local, err) - } else { - intf.link.core.log.Infof("Disconnected %s: %s, source %s", - strings.ToUpper(intf.info.linkType), themString, intf.info.local) - } return err } //////////////////////////////////////////////////////////////////////////////// - -/* - phony.Inbox // Protects the below - sendTimer *time.Timer // Fires to signal that sending is blocked - stallTimer *time.Time // Fires to signal that no incoming traffic (including keep-alive) has been seen - recvTimer *time.Timer // Fires to send keep-alive traffic - closeTimer *time.Timer // Fires when the link has been idle so long we need to close it - inSwitch bool // True if the switch is tracking this link - stalled bool // True if we haven't been receiving any response traffic -*/ - const ( - sendBlockedTime = time.Second // How long to wait before deciding a send is blocked - keepAliveTime = 2*time.Second // How long to wait before sending a keep-alive response if we have no real traffic to send - stallTime = 6*time.Second // How long to wait for response traffic before deciding the connection has stalled - closeTime = 2*switch_timeout // How long to wait before closing the link + sendBlockedTime = time.Second // How long to wait before deciding a send is blocked + keepAliveTime = 2 * time.Second // How long to wait before sending a keep-alive response if we have no real traffic to send + stallTime = 6 * time.Second // How long to wait for response traffic before deciding the connection has stalled + closeTime = 2 * switch_timeout // How long to wait before closing the link ) // notify the intf that we're currently sending func (intf *linkInterface) notifySending(size int, isLinkTraffic bool) { - intf.RecvFrom(nil, func() { - if !isLinkTraffic && size > 0 { - intf.inSwitch = false - } - intf.sendTimer = time.AfterFunc(sendBlockedTime, intf.notifyBlockedSend) - intf._cancelRecvTimer() - }) + intf.RecvFrom(nil, func() { + if !isLinkTraffic && size > 0 { + intf.inSwitch = false + } + intf.sendTimer = time.AfterFunc(sendBlockedTime, intf.notifyBlockedSend) + intf._cancelRecvTimer() + }) } // we just sent something, so cancel any pending timer to send keep-alive traffic func (intf *linkInterface) _cancelRecvTimer() { - intf.RecvFrom(nil, func() { - if intf.recvTimer != nil { - intf.recvTimer.Stop() - intf.recvTimer = nil - } - }) + intf.RecvFrom(nil, func() { + if intf.recvTimer != nil { + intf.recvTimer.Stop() + intf.recvTimer = nil + } + }) } // called by an AfterFunc if we appear to have timed out func (intf *linkInterface) notifyBlockedSend() { - intf.RecvFrom(nil, func() { - if intf.sendTimer != nil { - //As far as we know, we're still trying to send, and the timer fired. + intf.RecvFrom(nil, func() { + if intf.sendTimer != nil { + //As far as we know, we're still trying to send, and the timer fired. intf.link.core.switchTable.blockPeer(intf.peer.port) - } - }) + } + }) } // notify the intf that we've finished sending, returning the peer to the switch func (intf *linkInterface) notifySent(size int, isLinkTraffic bool) { - intf.RecvFrom(nil, func() { - intf.sendTimer.Stop() - intf.sendTimer = nil - if !isLinkTraffic { - intf._notifySwitch() - } - if size > 0 && intf.stallTimer == nil { - intf.stallTimer = time.AfterFunc(stallTime, intf.notifyStalled) - } - }) + intf.RecvFrom(nil, func() { + intf.sendTimer.Stop() + intf.sendTimer = nil + if !isLinkTraffic { + intf._notifySwitch() + } + if size > 0 && intf.stallTimer == nil { + intf.stallTimer = time.AfterFunc(stallTime, intf.notifyStalled) + } + }) } // Notify the switch that we're ready for more traffic, assuming we're not in a stalled state func (intf *linkInterface) _notifySwitch() { - if !intf.inSwitch && !intf.stalled { - intf.inSwitch = true - intf.link.core.switchTable.RecvFrom(intf, func() { - intf.link.core.switchTable._idleIn(intf.peer.port) - }) - } + if !intf.inSwitch && !intf.stalled { + intf.inSwitch = true + intf.link.core.switchTable.RecvFrom(intf, func() { + intf.link.core.switchTable._idleIn(intf.peer.port) + }) + } } // Set the peer as stalled, to prevent them from returning to the switch until a read succeeds func (intf *linkInterface) notifyStalled() { - intf.RecvFrom(nil, func() { - if intf.stallTimer != nil { - intf.stallTimer = nil - intf.stalled = true + intf.RecvFrom(nil, func() { + if intf.stallTimer != nil { + intf.stallTimer = nil + intf.stalled = true intf.link.core.switchTable.blockPeer(intf.peer.port) - } - }) + } + }) } - // reset the close timer func (intf *linkInterface) notifyReading(from phony.Actor) { - intf.RecvFrom(from, func() { - if intf.closeTimer != nil { - intf.closeTimer.Stop() - } - intf.closeTimer = time.AfterFunc(closeTime, func() { intf.msgIO.close() }) - }) + intf.RecvFrom(from, func() { + if intf.closeTimer != nil { + intf.closeTimer.Stop() + } + intf.closeTimer = time.AfterFunc(closeTime, func() { intf.msgIO.close() }) + }) } // wake up the link if it was stalled, and (if size > 0) prepare to send keep-alive traffic func (intf *linkInterface) notifyReadFrom(from phony.Actor, size int) { - intf.RecvFrom(from, func() { - intf.link.core.log.Printf("DEBUG notifyReadFrom: inSwitch %v, stalled %v\n", intf.inSwitch, intf.stalled) - if intf.stallTimer != nil { - intf.stallTimer.Stop() - intf.stallTimer = nil - } - intf.stalled = false - intf._notifySwitch() - if size > 0 && intf.recvTimer == nil { - intf.recvTimer = time.AfterFunc(keepAliveTime, intf.notifyDoKeepAlive) - } - }) + intf.RecvFrom(from, func() { + if intf.stallTimer != nil { + intf.stallTimer.Stop() + intf.stallTimer = nil + } + intf.stalled = false + intf._notifySwitch() + if size > 0 && intf.recvTimer == nil { + intf.recvTimer = time.AfterFunc(keepAliveTime, intf.notifyDoKeepAlive) + } + }) } // We need to send keep-alive traffic now func (intf *linkInterface) notifyDoKeepAlive() { - intf.RecvFrom(nil, func() { - if intf.recvTimer != nil { - intf.recvTimer.Stop() - intf.recvTimer = nil - intf.writer.sendFrom(nil, [][]byte{nil}, true) // Empty keep-alive traffic - } - }) + intf.RecvFrom(nil, func() { + if intf.recvTimer != nil { + intf.recvTimer.Stop() + intf.recvTimer = nil + intf.writer.sendFrom(nil, [][]byte{nil}, true) // Empty keep-alive traffic + } + }) } //////////////////////////////////////////////////////////////////////////////// type linkWriter struct { - phony.Inbox - intf *linkInterface + phony.Inbox + intf *linkInterface } func (w *linkWriter) sendFrom(from phony.Actor, bss [][]byte, isLinkTraffic bool) { - w.RecvFrom(from, func() { - var size int - for _, bs := range bss { - size += len(bs) - } - w.intf.notifySending(size, isLinkTraffic) - w.intf.msgIO.writeMsgs(bss) - w.intf.notifySent(size, isLinkTraffic) - w.intf.link.core.log.Println("DEBUG: wrote something, size:", size, "isLinkTraffic:", isLinkTraffic) - }) + w.RecvFrom(from, func() { + var size int + for _, bs := range bss { + size += len(bs) + } + w.intf.notifySending(size, isLinkTraffic) + w.intf.msgIO.writeMsgs(bss) + w.intf.notifySent(size, isLinkTraffic) + }) } //////////////////////////////////////////////////////////////////////////////// type linkReader struct { - phony.Inbox - intf *linkInterface - err chan error + phony.Inbox + intf *linkInterface + err chan error } func (r *linkReader) _read() { - r.intf.notifyReading(r) - msg, err := r.intf.msgIO.readMsg() - r.intf.link.core.log.Println("DEBUG read something") - r.intf.notifyReadFrom(r, len(msg)) - if len(msg) > 0 { - r.intf.peer.handlePacketFrom(r, msg) + r.intf.notifyReading(r) + msg, err := r.intf.msgIO.readMsg() + r.intf.notifyReadFrom(r, len(msg)) + if len(msg) > 0 { + r.intf.peer.handlePacketFrom(r, msg) + } + if err != nil { + if err != io.EOF { + r.err <- err } - if err != nil { - if err != io.EOF { - r.err<-err - } - close(r.err) - return - } - // Now try to read again - r.RecvFrom(nil, r._read) + close(r.err) + return + } + // Now try to read again + r.RecvFrom(nil, r._read) } - diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 50fb03f8..4cf0068b 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -103,7 +103,7 @@ type peer struct { linkShared crypto.BoxSharedKey endpoint string firstSeen time.Time // To track uptime for getPeers - linkOut (chan []byte) // used for protocol traffic (to bypass queues) + linkOut func([]byte) // used for protocol traffic (bypasses the switch) dinfo *dhtInfo // used to keep the DHT working out func([][]byte) // Set up by whatever created the peers struct, used to send packets to other nodes done (chan struct{}) // closed to exit the linkLoop @@ -263,8 +263,6 @@ func (p *peer) _sendPackets(packets [][]byte) { p.out(packets) } -var peerLinkOutHelper phony.Inbox - // This wraps the packet in the inner (ephemeral) and outer (permanent) crypto layers. // It sends it to p.linkOut, which bypasses the usual packet queues. func (p *peer) _sendLinkPacket(packet []byte) { @@ -280,13 +278,7 @@ func (p *peer) _sendLinkPacket(packet []byte) { Payload: bs, } packet = linkPacket.encode() - // TODO replace this with a message send if/when the link becomes an actor - peerLinkOutHelper.RecvFrom(nil, func() { - select { - case p.linkOut <- packet: - case <-p.done: - } - }) + p.linkOut(packet) } // Decrypts the outer (permanent) and inner (ephemeral) crypto layers on link traffic. diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 6d882252..b30e59d5 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -250,7 +250,7 @@ func (t *switchTable) cleanRoot() { t.core.router.reset(nil) } t.data.locator = switchLocator{root: t.key, tstamp: now.Unix()} - t.core.peers.sendSwitchMsgs(nil) // TODO update if/when the switch becomes an actor + t.core.peers.sendSwitchMsgs(t) } } @@ -517,7 +517,7 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep } t.data.locator = sender.locator t.parent = sender.port - t.core.peers.sendSwitchMsgs(nil) // TODO update if/when the switch becomes an actor + t.core.peers.sendSwitchMsgs(t) } if doUpdate { t.updater.Store(&sync.Once{}) From e5b88c0da3998cd7582d07b2385054f6005f402f Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 25 Aug 2019 23:07:56 -0500 Subject: [PATCH 0284/1109] update switch --- src/yggdrasil/switch.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 6d882252..b30e59d5 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -250,7 +250,7 @@ func (t *switchTable) cleanRoot() { t.core.router.reset(nil) } t.data.locator = switchLocator{root: t.key, tstamp: now.Unix()} - t.core.peers.sendSwitchMsgs(nil) // TODO update if/when the switch becomes an actor + t.core.peers.sendSwitchMsgs(t) } } @@ -517,7 +517,7 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep } t.data.locator = sender.locator t.parent = sender.port - t.core.peers.sendSwitchMsgs(nil) // TODO update if/when the switch becomes an actor + t.core.peers.sendSwitchMsgs(t) } if doUpdate { t.updater.Store(&sync.Once{}) From ab591295578e66aae15452b843194562c56ab490 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 25 Aug 2019 23:24:18 -0500 Subject: [PATCH 0285/1109] have the writer clean things up. note that their still seem to be bugs in the main linkInterface actor's state machine--links sometimes just die, presumably because they're dropped from the switch and never replaced --- src/yggdrasil/link.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index b4ca38c4..548f5341 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -367,6 +367,10 @@ func (w *linkWriter) sendFrom(from phony.Actor, bss [][]byte, isLinkTraffic bool w.intf.notifySending(size, isLinkTraffic) w.intf.msgIO.writeMsgs(bss) w.intf.notifySent(size, isLinkTraffic) + // Cleanup + for _, bs := range bss { + util.PutBytes(bs) + } }) } From c97dd4ad28c167b09e0b3ea9d26d41128085273e Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 26 Aug 2019 00:38:14 -0500 Subject: [PATCH 0286/1109] fix dial bug --- src/tuntap/iface.go | 38 ++++++++++++++++++++------------------ src/yggdrasil/link.go | 2 +- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 16e8d25d..da419369 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -247,24 +247,26 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { } if !known { go func() { - if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { - tun.RecvFrom(nil, func() { - // We've been given a connection so prepare the session wrapper - packets := tun.dials[*dstNodeID] - delete(tun.dials, *dstNodeID) - var tc *tunConn - var err error - if tc, err = tun._wrap(conn); err != nil { - // Something went wrong when storing the connection, typically that - // something already exists for this address or subnet - tun.log.Debugln("TUN/TAP iface wrap:", err) - return - } - for _, packet := range packets { - tc.writeFrom(nil, packet) - } - }) - } + conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask) + tun.RecvFrom(nil, func() { + packets := tun.dials[*dstNodeID] + delete(tun.dials, *dstNodeID) + if err != nil { + return + } + // We've been given a connection so prepare the session wrapper + var tc *tunConn + if tc, err = tun._wrap(conn); err != nil { + // Something went wrong when storing the connection, typically that + // something already exists for this address or subnet + tun.log.Debugln("TUN/TAP iface wrap:", err) + return + } + for _, packet := range packets { + tc.writeFrom(nil, packet) + } + }) + return }() } } diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 548f5341..8fbd0317 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -252,7 +252,7 @@ const ( // notify the intf that we're currently sending func (intf *linkInterface) notifySending(size int, isLinkTraffic bool) { intf.RecvFrom(nil, func() { - if !isLinkTraffic && size > 0 { + if !isLinkTraffic { intf.inSwitch = false } intf.sendTimer = time.AfterFunc(sendBlockedTime, intf.notifyBlockedSend) From 4d9c6342a7383a4481851be11c9357af770b6f18 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 26 Aug 2019 18:37:38 -0500 Subject: [PATCH 0287/1109] more link updates --- src/yggdrasil/link.go | 91 +++++++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 46 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 8fbd0317..6b7f446f 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -46,23 +46,23 @@ type linkInterfaceMsgIO interface { } type linkInterface struct { - name string - link *link - peer *peer - msgIO linkInterfaceMsgIO - info linkInfo - incoming bool - force bool - closed chan struct{} - reader linkReader // Reads packets, notifies this linkInterface, passes packets to switch - writer linkWriter // Writes packets, notifies this linkInterface - phony.Inbox // Protects the below - sendTimer *time.Timer // Fires to signal that sending is blocked - stallTimer *time.Timer // Fires to signal that no incoming traffic (including keep-alive) has been seen - recvTimer *time.Timer // Fires to send keep-alive traffic - closeTimer *time.Timer // Fires when the link has been idle so long we need to close it - inSwitch bool // True if the switch is tracking this link - stalled bool // True if we haven't been receiving any response traffic + name string + link *link + peer *peer + msgIO linkInterfaceMsgIO + info linkInfo + incoming bool + force bool + closed chan struct{} + reader linkReader // Reads packets, notifies this linkInterface, passes packets to switch + writer linkWriter // Writes packets, notifies this linkInterface + phony.Inbox // Protects the below + sendTimer *time.Timer // Fires to signal that sending is blocked + keepAliveTimer *time.Timer // Fires to send keep-alive traffic + stallTimer *time.Timer // Fires to signal that no incoming traffic (including keep-alive) has been seen + closeTimer *time.Timer // Fires when the link has been idle so long we need to close it + inSwitch bool // True if the switch is tracking this link + stalled bool // True if we haven't been receiving any response traffic } func (l *link) init(c *Core) error { @@ -243,36 +243,34 @@ func (intf *linkInterface) handler() error { //////////////////////////////////////////////////////////////////////////////// const ( - sendBlockedTime = time.Second // How long to wait before deciding a send is blocked - keepAliveTime = 2 * time.Second // How long to wait before sending a keep-alive response if we have no real traffic to send - stallTime = 6 * time.Second // How long to wait for response traffic before deciding the connection has stalled - closeTime = 2 * switch_timeout // How long to wait before closing the link + sendTime = 1 * time.Second // How long to wait before deciding a send is blocked + keepAliveTime = 2 * time.Second // How long to wait before sending a keep-alive response if we have no real traffic to send + stallTime = 6 * time.Second // How long to wait for response traffic before deciding the connection has stalled + closeTime = 2 * switch_timeout // How long to wait before closing the link ) // notify the intf that we're currently sending func (intf *linkInterface) notifySending(size int, isLinkTraffic bool) { - intf.RecvFrom(nil, func() { + intf.RecvFrom(&intf.writer, func() { if !isLinkTraffic { intf.inSwitch = false } - intf.sendTimer = time.AfterFunc(sendBlockedTime, intf.notifyBlockedSend) - intf._cancelRecvTimer() + intf.sendTimer = time.AfterFunc(sendTime, intf.notifyBlockedSend) + intf._cancelStallTimer() }) } // we just sent something, so cancel any pending timer to send keep-alive traffic -func (intf *linkInterface) _cancelRecvTimer() { - intf.RecvFrom(nil, func() { - if intf.recvTimer != nil { - intf.recvTimer.Stop() - intf.recvTimer = nil - } - }) +func (intf *linkInterface) _cancelStallTimer() { + if intf.stallTimer != nil { + intf.stallTimer.Stop() + intf.stallTimer = nil + } } // called by an AfterFunc if we appear to have timed out func (intf *linkInterface) notifyBlockedSend() { - intf.RecvFrom(nil, func() { + intf.RecvFrom(nil, func() { // Sent from a time.AfterFunc if intf.sendTimer != nil { //As far as we know, we're still trying to send, and the timer fired. intf.link.core.switchTable.blockPeer(intf.peer.port) @@ -282,7 +280,7 @@ func (intf *linkInterface) notifyBlockedSend() { // notify the intf that we've finished sending, returning the peer to the switch func (intf *linkInterface) notifySent(size int, isLinkTraffic bool) { - intf.RecvFrom(nil, func() { + intf.RecvFrom(&intf.writer, func() { intf.sendTimer.Stop() intf.sendTimer = nil if !isLinkTraffic { @@ -306,8 +304,9 @@ func (intf *linkInterface) _notifySwitch() { // Set the peer as stalled, to prevent them from returning to the switch until a read succeeds func (intf *linkInterface) notifyStalled() { - intf.RecvFrom(nil, func() { + intf.RecvFrom(nil, func() { // Sent from a time.AfterFunc if intf.stallTimer != nil { + intf.stallTimer.Stop() intf.stallTimer = nil intf.stalled = true intf.link.core.switchTable.blockPeer(intf.peer.port) @@ -316,8 +315,8 @@ func (intf *linkInterface) notifyStalled() { } // reset the close timer -func (intf *linkInterface) notifyReading(from phony.Actor) { - intf.RecvFrom(from, func() { +func (intf *linkInterface) notifyReading() { + intf.RecvFrom(&intf.reader, func() { if intf.closeTimer != nil { intf.closeTimer.Stop() } @@ -326,26 +325,26 @@ func (intf *linkInterface) notifyReading(from phony.Actor) { } // wake up the link if it was stalled, and (if size > 0) prepare to send keep-alive traffic -func (intf *linkInterface) notifyReadFrom(from phony.Actor, size int) { - intf.RecvFrom(from, func() { +func (intf *linkInterface) notifyRead(size int) { + intf.RecvFrom(&intf.reader, func() { if intf.stallTimer != nil { intf.stallTimer.Stop() intf.stallTimer = nil } intf.stalled = false intf._notifySwitch() - if size > 0 && intf.recvTimer == nil { - intf.recvTimer = time.AfterFunc(keepAliveTime, intf.notifyDoKeepAlive) + if size > 0 && intf.stallTimer == nil { + intf.stallTimer = time.AfterFunc(keepAliveTime, intf.notifyDoKeepAlive) } }) } // We need to send keep-alive traffic now func (intf *linkInterface) notifyDoKeepAlive() { - intf.RecvFrom(nil, func() { - if intf.recvTimer != nil { - intf.recvTimer.Stop() - intf.recvTimer = nil + intf.RecvFrom(nil, func() { // Sent from a time.AfterFunc + if intf.stallTimer != nil { + intf.stallTimer.Stop() + intf.stallTimer = nil intf.writer.sendFrom(nil, [][]byte{nil}, true) // Empty keep-alive traffic } }) @@ -383,9 +382,9 @@ type linkReader struct { } func (r *linkReader) _read() { - r.intf.notifyReading(r) + r.intf.notifyReading() msg, err := r.intf.msgIO.readMsg() - r.intf.notifyReadFrom(r, len(msg)) + r.intf.notifyRead(len(msg)) if len(msg) > 0 { r.intf.peer.handlePacketFrom(r, msg) } From 3845f81357dae0f29062e7caa60b1a723efddf05 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Tue, 27 Aug 2019 19:43:54 -0500 Subject: [PATCH 0288/1109] update to latest phony, adjust interface use accordingly --- go.mod | 2 +- go.sum | 4 ++-- src/tuntap/conn.go | 6 +++--- src/tuntap/iface.go | 10 +++++----- src/tuntap/tun.go | 12 ++++++------ src/yggdrasil/api.go | 6 ++++-- src/yggdrasil/conn.go | 28 ++++++++++++++-------------- src/yggdrasil/debug.go | 12 +++++++----- src/yggdrasil/link.go | 22 +++++++++++----------- src/yggdrasil/peer.go | 10 +++++----- src/yggdrasil/router.go | 14 +++++++------- src/yggdrasil/search.go | 2 +- src/yggdrasil/session.go | 22 +++++++++++----------- src/yggdrasil/switch.go | 6 +++--- 14 files changed, 80 insertions(+), 76 deletions(-) diff --git a/go.mod b/go.mod index f3d8417f..9ff6031a 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/yggdrasil-network/yggdrasil-go require ( - github.com/Arceliar/phony v0.0.0-20190825152505-180ac75690fe + github.com/Arceliar/phony v0.0.0-20190828002416-0337564e2c44 github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 github.com/hashicorp/go-syslog v1.0.0 github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 diff --git a/go.sum b/go.sum index 29e0dfec..60388964 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/phony v0.0.0-20190825152505-180ac75690fe h1:U5bediuXjZ1y6bByIXXraoE319yFp9kx1z8K6el7Ftc= -github.com/Arceliar/phony v0.0.0-20190825152505-180ac75690fe/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= +github.com/Arceliar/phony v0.0.0-20190828002416-0337564e2c44 h1:8EiuIp65v8aLkLc4LWxtn4NTH+P2LwDmrKKWdBzn9cI= +github.com/Arceliar/phony v0.0.0-20190828002416-0337564e2c44/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/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index afb0f09e..31875d94 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -27,7 +27,7 @@ type tunConn struct { } func (s *tunConn) close() { - s.tun.RecvFrom(s, s._close_from_tun) + s.tun.Act(s, s._close_from_tun) } func (s *tunConn) _close_from_tun() { @@ -117,7 +117,7 @@ func (s *tunConn) _read(bs []byte) (err error) { } func (s *tunConn) writeFrom(from phony.Actor, bs []byte) { - s.RecvFrom(from, func() { + s.Act(from, func() { s._write(bs) }) } @@ -197,7 +197,7 @@ func (s *tunConn) _write(bs []byte) (err error) { // No point in wasting resources to send back an error if there was none return } - s.RecvFrom(s.conn, func() { + s.Act(s.conn, func() { if e, eok := err.(yggdrasil.ConnError); !eok { if e.Closed() { s.tun.log.Debugln(s.conn.String(), "TUN/TAP generic write debug:", err) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index da419369..92ba36ab 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -19,7 +19,7 @@ type tunWriter struct { } func (w *tunWriter) writeFrom(from phony.Actor, b []byte) { - w.RecvFrom(from, func() { + w.Act(from, func() { w._write(b) }) } @@ -90,7 +90,7 @@ func (w *tunWriter) _write(b []byte) { util.PutBytes(b) } if err != nil { - w.tun.RecvFrom(w, func() { + w.tun.Act(w, func() { if !w.tun.isOpen { w.tun.log.Errorln("TUN/TAP iface write error:", err) } @@ -118,12 +118,12 @@ func (r *tunReader) _read() { } if err == nil { // Now read again - r.RecvFrom(nil, r._read) + r.Act(nil, r._read) } } func (tun *TunAdapter) handlePacketFrom(from phony.Actor, packet []byte, err error) { - tun.RecvFrom(from, func() { + tun.Act(from, func() { tun._handlePacket(packet, err) }) } @@ -248,7 +248,7 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { if !known { go func() { conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask) - tun.RecvFrom(nil, func() { + tun.Act(nil, func() { packets := tun.dials[*dstNodeID] delete(tun.dials, *dstNodeID) if err != nil { diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 2b163e3b..6317459c 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -125,7 +125,7 @@ func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener // reader actor to handle packets on that interface. func (tun *TunAdapter) Start() error { var err error - <-tun.SyncExec(func() { + phony.Block(tun, func() { err = tun._start() }) return err @@ -167,7 +167,7 @@ func (tun *TunAdapter) _start() error { } }() go tun.handler() - tun.reader.RecvFrom(nil, tun.reader._read) // Start the reader + tun.reader.Act(nil, tun.reader._read) // Start the reader tun.icmpv6.Init(tun) if iftapmode { go tun.icmpv6.Solicit(tun.addr) @@ -180,7 +180,7 @@ func (tun *TunAdapter) _start() error { // read/write goroutines to handle packets on that interface. func (tun *TunAdapter) Stop() error { var err error - <-tun.SyncExec(func() { + phony.Block(tun, func() { err = tun._stop() }) return err @@ -233,7 +233,7 @@ func (tun *TunAdapter) handler() error { tun.log.Errorln("TUN/TAP connection accept error:", err) return err } - <-tun.SyncExec(func() { + phony.Block(tun, func() { if _, err := tun._wrap(conn); err != nil { // Something went wrong when storing the connection, typically that // something already exists for this address or subnet @@ -273,11 +273,11 @@ func (tun *TunAdapter) _wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { tun.subnetToConn[s.snet] = &s // Set the read callback and start the timeout conn.SetReadCallback(func(bs []byte) { - s.RecvFrom(conn, func() { + s.Act(conn, func() { s._read(bs) }) }) - s.RecvFrom(nil, s.stillAlive) + s.Act(nil, s.stillAlive) // Return return c, err } diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index c6c15e24..44cbad1d 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -11,6 +11,8 @@ import ( "github.com/gologme/log" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + + "github.com/Arceliar/phony" ) // Peer represents a single peer object. This contains information from the @@ -106,7 +108,7 @@ func (c *Core) GetPeers() []Peer { for _, port := range ps { p := ports[port] var info Peer - <-p.SyncExec(func() { + phony.Block(p, func() { info = Peer{ Endpoint: p.intf.name, BytesSent: p.bytesSent, @@ -138,7 +140,7 @@ func (c *Core) GetSwitchPeers() []SwitchPeer { } coords := elem.locator.getCoords() var info SwitchPeer - <-peer.SyncExec(func() { + phony.Block(peer, func() { info = SwitchPeer{ Coords: append([]uint64{}, wire_coordsBytestoUint64s(coords)...), BytesSent: peer.bytesSent, diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index efc0c81e..c56c2a90 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -80,12 +80,12 @@ func newConn(core *Core, nodeID *crypto.NodeID, nodeMask *crypto.NodeID, session func (c *Conn) String() string { var s string - <-c.SyncExec(func() { s = fmt.Sprintf("conn=%p", c) }) + phony.Block(c, func() { s = fmt.Sprintf("conn=%p", c) }) return s } func (c *Conn) setMTU(from phony.Actor, mtu uint16) { - c.RecvFrom(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 @@ -143,7 +143,7 @@ func (c *Conn) doSearch() { sinfo.continueSearch() } } - c.core.router.RecvFrom(c.session, routerWork) + c.core.router.Act(c.session, routerWork) } func (c *Conn) _getDeadlineCancellation(t *time.Time) (util.Cancellation, bool) { @@ -159,7 +159,7 @@ func (c *Conn) _getDeadlineCancellation(t *time.Time) (util.Cancellation, bool) // SetReadCallback sets a callback which will be called whenever a packet is received. func (c *Conn) SetReadCallback(callback func([]byte)) { - c.RecvFrom(nil, func() { + c.Act(nil, func() { c.readCallback = callback c._drainReadBuffer() }) @@ -172,14 +172,14 @@ func (c *Conn) _drainReadBuffer() { select { case bs := <-c.readBuffer: c.readCallback(bs) - c.RecvFrom(nil, c._drainReadBuffer) // In case there's more + c.Act(nil, c._drainReadBuffer) // In case there's more default: } } // Called by the session to pass a new message to the Conn func (c *Conn) recvMsg(from phony.Actor, msg []byte) { - c.RecvFrom(from, func() { + c.Act(from, func() { if c.readCallback != nil { c.readCallback(msg) } else { @@ -195,7 +195,7 @@ func (c *Conn) recvMsg(from phony.Actor, msg []byte) { func (c *Conn) ReadNoCopy() ([]byte, error) { var cancel util.Cancellation var doCancel bool - <-c.SyncExec(func() { cancel, doCancel = c._getDeadlineCancellation(c.readDeadline) }) + phony.Block(c, func() { cancel, doCancel = c._getDeadlineCancellation(c.readDeadline) }) if doCancel { defer cancel.Cancel(nil) } @@ -234,7 +234,7 @@ func (c *Conn) _write(msg FlowKeyMessage) error { if len(msg.Message) > int(c.mtu) { return ConnError{errors.New("packet too big"), true, false, false, int(c.mtu)} } - c.session.RecvFrom(c, func() { + c.session.Act(c, func() { // Send the packet c.session._send(msg) // Session keep-alive, while we wait for the crypto workers from send @@ -258,7 +258,7 @@ func (c *Conn) _write(msg FlowKeyMessage) error { // This is used internaly by WriteNoCopy and Write. // If the callback is called with a non-nil value, then it is safe to reuse the argument FlowKeyMessage. func (c *Conn) WriteFrom(from phony.Actor, msg FlowKeyMessage, callback func(error)) { - c.RecvFrom(from, func() { + c.Act(from, func() { callback(c._write(msg)) }) } @@ -268,7 +268,7 @@ func (c *Conn) WriteFrom(from phony.Actor, msg FlowKeyMessage, callback func(err func (c *Conn) WriteNoCopy(msg FlowKeyMessage) error { var cancel util.Cancellation var doCancel bool - <-c.SyncExec(func() { cancel, doCancel = c._getDeadlineCancellation(c.writeDeadline) }) + phony.Block(c, func() { cancel, doCancel = c._getDeadlineCancellation(c.writeDeadline) }) var err error select { case <-cancel.Finished(): @@ -299,7 +299,7 @@ func (c *Conn) Write(b []byte) (int, error) { } func (c *Conn) Close() (err error) { - <-c.SyncExec(func() { + phony.Block(c, func() { if c.session != nil { // Close the session, if it hasn't been closed already if e := c.session.cancel.Cancel(errors.New("connection closed")); e != nil { @@ -319,7 +319,7 @@ func (c *Conn) LocalAddr() 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... var n crypto.NodeID - <-c.SyncExec(func() { n = *c.nodeID }) + phony.Block(c, func() { n = *c.nodeID }) return n } @@ -331,12 +331,12 @@ func (c *Conn) SetDeadline(t time.Time) error { func (c *Conn) SetReadDeadline(t time.Time) error { // TODO warn that this can block while waiting for the Conn actor to run, so don't call it from other actors... - <-c.SyncExec(func() { c.readDeadline = &t }) + phony.Block(c, func() { c.readDeadline = &t }) return nil } func (c *Conn) SetWriteDeadline(t time.Time) error { // TODO warn that this can block while waiting for the Conn actor to run, so don't call it from other actors... - <-c.SyncExec(func() { c.writeDeadline = &t }) + phony.Block(c, func() { c.writeDeadline = &t }) return nil } diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index 1cc7eae4..9d2199d0 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -29,6 +29,8 @@ import "github.com/yggdrasil-network/yggdrasil-go/src/config" import "github.com/yggdrasil-network/yggdrasil-go/src/crypto" import "github.com/yggdrasil-network/yggdrasil-go/src/defaults" +import "github.com/Arceliar/phony" + // Start the profiler in debug builds, if the required environment variable is set. func init() { envVarName := "PPROFLISTEN" @@ -572,14 +574,14 @@ func DEBUG_simLinkPeers(p, q *peer) { continue case packet := <-send: packets = append(packets, packet) - <-source.core.switchTable.SyncExec(func() { + phony.Block(&source.core.switchTable, func() { source.core.switchTable._idleIn(source.port) }) continue default: } if len(packets) > 0 { - <-dest.SyncExec(func() { dest._handlePacket(packets[0]) }) + phony.Block(dest, func() { dest._handlePacket(packets[0]) }) packets = packets[1:] continue } @@ -588,7 +590,7 @@ func DEBUG_simLinkPeers(p, q *peer) { packets = append(packets, packet) case packet := <-send: packets = append(packets, packet) - <-source.core.switchTable.SyncExec(func() { + phony.Block(&source.core.switchTable, func() { source.core.switchTable._idleIn(source.port) }) } @@ -597,10 +599,10 @@ func DEBUG_simLinkPeers(p, q *peer) { } goWorkers(p, q) goWorkers(q, p) - <-p.core.switchTable.SyncExec(func() { + phony.Block(&p.core.switchTable, func() { p.core.switchTable._idleIn(p.port) }) - <-q.core.switchTable.SyncExec(func() { + phony.Block(&q.core.switchTable, func() { q.core.switchTable._idleIn(q.port) }) } diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 6b7f446f..e65055bc 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -227,7 +227,7 @@ func (intf *linkInterface) handler() error { strings.ToUpper(intf.info.linkType), themString, intf.info.local) // Start things go intf.peer.start() - intf.reader.RecvFrom(nil, intf.reader._read) + intf.reader.Act(nil, intf.reader._read) // Wait for the reader to finish err = <-intf.reader.err if err != nil { @@ -251,7 +251,7 @@ const ( // notify the intf that we're currently sending func (intf *linkInterface) notifySending(size int, isLinkTraffic bool) { - intf.RecvFrom(&intf.writer, func() { + intf.Act(&intf.writer, func() { if !isLinkTraffic { intf.inSwitch = false } @@ -270,7 +270,7 @@ func (intf *linkInterface) _cancelStallTimer() { // called by an AfterFunc if we appear to have timed out func (intf *linkInterface) notifyBlockedSend() { - intf.RecvFrom(nil, func() { // Sent from a time.AfterFunc + intf.Act(nil, func() { // Sent from a time.AfterFunc if intf.sendTimer != nil { //As far as we know, we're still trying to send, and the timer fired. intf.link.core.switchTable.blockPeer(intf.peer.port) @@ -280,7 +280,7 @@ func (intf *linkInterface) notifyBlockedSend() { // notify the intf that we've finished sending, returning the peer to the switch func (intf *linkInterface) notifySent(size int, isLinkTraffic bool) { - intf.RecvFrom(&intf.writer, func() { + intf.Act(&intf.writer, func() { intf.sendTimer.Stop() intf.sendTimer = nil if !isLinkTraffic { @@ -296,7 +296,7 @@ func (intf *linkInterface) notifySent(size int, isLinkTraffic bool) { func (intf *linkInterface) _notifySwitch() { if !intf.inSwitch && !intf.stalled { intf.inSwitch = true - intf.link.core.switchTable.RecvFrom(intf, func() { + intf.link.core.switchTable.Act(intf, func() { intf.link.core.switchTable._idleIn(intf.peer.port) }) } @@ -304,7 +304,7 @@ func (intf *linkInterface) _notifySwitch() { // Set the peer as stalled, to prevent them from returning to the switch until a read succeeds func (intf *linkInterface) notifyStalled() { - intf.RecvFrom(nil, func() { // Sent from a time.AfterFunc + intf.Act(nil, func() { // Sent from a time.AfterFunc if intf.stallTimer != nil { intf.stallTimer.Stop() intf.stallTimer = nil @@ -316,7 +316,7 @@ func (intf *linkInterface) notifyStalled() { // reset the close timer func (intf *linkInterface) notifyReading() { - intf.RecvFrom(&intf.reader, func() { + intf.Act(&intf.reader, func() { if intf.closeTimer != nil { intf.closeTimer.Stop() } @@ -326,7 +326,7 @@ func (intf *linkInterface) notifyReading() { // wake up the link if it was stalled, and (if size > 0) prepare to send keep-alive traffic func (intf *linkInterface) notifyRead(size int) { - intf.RecvFrom(&intf.reader, func() { + intf.Act(&intf.reader, func() { if intf.stallTimer != nil { intf.stallTimer.Stop() intf.stallTimer = nil @@ -341,7 +341,7 @@ func (intf *linkInterface) notifyRead(size int) { // We need to send keep-alive traffic now func (intf *linkInterface) notifyDoKeepAlive() { - intf.RecvFrom(nil, func() { // Sent from a time.AfterFunc + intf.Act(nil, func() { // Sent from a time.AfterFunc if intf.stallTimer != nil { intf.stallTimer.Stop() intf.stallTimer = nil @@ -358,7 +358,7 @@ type linkWriter struct { } func (w *linkWriter) sendFrom(from phony.Actor, bss [][]byte, isLinkTraffic bool) { - w.RecvFrom(from, func() { + w.Act(from, func() { var size int for _, bs := range bss { size += len(bs) @@ -396,5 +396,5 @@ func (r *linkReader) _read() { return } // Now try to read again - r.RecvFrom(nil, r._read) + r.Act(nil, r._read) } diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 4cf0068b..d33c413a 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -178,7 +178,7 @@ func (ps *peers) sendSwitchMsgs(from phony.Actor) { if p.port == 0 { continue } - p.RecvFrom(from, p._sendSwitchMsg) + p.Act(from, p._sendSwitchMsg) } } @@ -187,7 +187,7 @@ func (ps *peers) sendSwitchMsgs(from phony.Actor) { func (p *peer) start() { var updateDHT func() updateDHT = func() { - <-p.SyncExec(func() { + phony.Block(p, func() { select { case <-p.done: default: @@ -198,7 +198,7 @@ func (p *peer) start() { } updateDHT() // Just for good measure, immediately send a switch message to this peer when we start - <-p.SyncExec(p._sendSwitchMsg) + p.Act(nil, p._sendSwitchMsg) } func (p *peer) _updateDHT() { @@ -208,7 +208,7 @@ func (p *peer) _updateDHT() { } func (p *peer) handlePacketFrom(from phony.Actor, packet []byte) { - p.RecvFrom(from, func() { + p.Act(from, func() { p._handlePacket(packet) }) } @@ -246,7 +246,7 @@ func (p *peer) _handleTraffic(packet []byte) { } func (p *peer) sendPacketsFrom(from phony.Actor, packets [][]byte) { - p.RecvFrom(from, func() { + p.Act(from, func() { p._sendPackets(packets) }) } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index fec55b7c..bbc87c51 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -77,7 +77,7 @@ func (r *router) reconfigure(e chan error) { defer close(e) var errs []error // Reconfigure the router - <-r.SyncExec(func() { + phony.Block(r, func() { current := r.core.config.GetCurrent() err := r.nodeinfo.setNodeInfo(current.NodeInfo, current.NodeInfoPrivacy) if err != nil { @@ -98,7 +98,7 @@ func (r *router) start() error { // In practice, the switch will call this with 1 packet func (r *router) handlePackets(from phony.Actor, packets [][]byte) { - r.RecvFrom(from, func() { + r.Act(from, func() { for _, packet := range packets { r._handlePacket(packet) } @@ -107,14 +107,14 @@ func (r *router) handlePackets(from phony.Actor, packets [][]byte) { // Insert a peer info into the dht, TODO? make the dht a separate actor func (r *router) insertPeer(from phony.Actor, info *dhtInfo) { - r.RecvFrom(from, func() { + r.Act(from, func() { r.dht.insertPeer(info) }) } // Reset sessions and DHT after the switch sees our coords change func (r *router) reset(from phony.Actor) { - r.RecvFrom(from, func() { + r.Act(from, func() { r.sessions.reset() r.dht.reset() }) @@ -123,7 +123,7 @@ func (r *router) reset(from phony.Actor) { // TODO remove reconfigure so this is just a ticker loop // and then find something better than a ticker loop to schedule things... func (r *router) doMaintenance() { - <-r.SyncExec(func() { + phony.Block(r, func() { // Any periodic maintenance stuff goes here r.core.switchTable.doMaintenance() r.dht.doMaintenance() @@ -252,7 +252,7 @@ func (r *router) _handleNodeInfo(bs []byte, fromKey *crypto.BoxPubKey) { r.nodeinfo.handleNodeInfo(&req) } -// TODO remove this, have things either be actors that send message or else call SyncExec directly +// TODO remove this, have things either be actors that send message or else call Block directly func (r *router) doAdmin(f func()) { - <-r.SyncExec(f) + phony.Block(r, f) } diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 9cf5f06e..ca357cc2 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -153,7 +153,7 @@ func (sinfo *searchInfo) continueSearch() { // Note that this will spawn multiple parallel searches as time passes // Any that die aren't restarted, but a new one will start later time.AfterFunc(search_RETRY_TIME, func() { - sinfo.searches.router.RecvFrom(nil, func() { + sinfo.searches.router.Act(nil, func() { // FIXME this keeps the search alive forever if not for the searches map, fix that newSearchInfo := sinfo.searches.searches[sinfo.dest] if newSearchInfo != sinfo { diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 0fc7ec80..06780e65 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -80,7 +80,7 @@ func (sinfo *sessionInfo) reconfigure(e chan error) { // TODO remove this, call SyncExec directly func (sinfo *sessionInfo) doFunc(f func()) { - <-sinfo.SyncExec(f) + phony.Block(sinfo, f) } // Represents a session ping/pong packet, andincludes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU. @@ -164,7 +164,7 @@ func (ss *sessions) init(r *router) { func (ss *sessions) reconfigure(e chan error) { defer close(e) responses := make(map[crypto.Handle]chan error) - <-ss.router.SyncExec(func() { + phony.Block(ss.router, func() { for index, session := range ss.sinfos { responses[index] = make(chan error) go session.reconfigure(responses[index]) @@ -287,7 +287,7 @@ func (ss *sessions) cleanup() { } func (sinfo *sessionInfo) doRemove() { - sinfo.sessions.router.RecvFrom(nil, func() { + sinfo.sessions.router.Act(nil, func() { sinfo.sessions.removeSession(sinfo) }) } @@ -341,7 +341,7 @@ func (ss *sessions) getSharedKey(myPriv *crypto.BoxPrivKey, // Sends a session ping by calling sendPingPong in ping mode. func (sinfo *sessionInfo) ping(from phony.Actor) { - sinfo.RecvFrom(from, func() { + sinfo.Act(from, func() { sinfo._sendPingPong(false) }) } @@ -362,14 +362,14 @@ func (sinfo *sessionInfo) _sendPingPong(isPong bool) { } packet := p.encode() // TODO rewrite the below if/when the peer struct becomes an actor, to not go through the router first - sinfo.sessions.router.RecvFrom(sinfo, func() { sinfo.sessions.router.out(packet) }) + sinfo.sessions.router.Act(sinfo, func() { sinfo.sessions.router.out(packet) }) if sinfo.pingTime.Before(sinfo.time) { sinfo.pingTime = time.Now() } } func (sinfo *sessionInfo) setConn(from phony.Actor, conn *Conn) { - sinfo.RecvFrom(from, func() { + sinfo.Act(from, func() { sinfo.conn = conn sinfo.conn.setMTU(sinfo, sinfo._getMTU()) }) @@ -404,7 +404,7 @@ func (ss *sessions) handlePing(ping *sessionPing) { ss.listenerMutex.Unlock() } if sinfo != nil { - sinfo.RecvFrom(ss.router, func() { + sinfo.Act(ss.router, func() { // Update the session if !sinfo._update(ping) { /*panic("Should not happen in testing")*/ return @@ -472,7 +472,7 @@ func (sinfo *sessionInfo) _updateNonce(theirNonce *crypto.BoxNonce) { // Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change. func (ss *sessions) reset() { for _, sinfo := range ss.sinfos { - sinfo.RecvFrom(ss.router, func() { + sinfo.Act(ss.router, func() { sinfo.reset = true }) } @@ -488,7 +488,7 @@ type FlowKeyMessage struct { } func (sinfo *sessionInfo) recv(from phony.Actor, packet *wire_trafficPacket) { - sinfo.RecvFrom(from, func() { + sinfo.Act(from, func() { sinfo._recvPacket(packet) }) } @@ -562,7 +562,7 @@ func (sinfo *sessionInfo) _send(msg FlowKeyMessage) { util.PutBytes(p.Payload) // Send the packet // TODO replace this with a send to the peer struct if that becomes an actor - sinfo.sessions.router.RecvFrom(sinfo, func() { + sinfo.sessions.router.Act(sinfo, func() { sinfo.sessions.router.out(packet) }) } @@ -574,7 +574,7 @@ func (sinfo *sessionInfo) _send(msg FlowKeyMessage) { } func (sinfo *sessionInfo) checkCallbacks() { - sinfo.RecvFrom(nil, func() { + sinfo.Act(nil, func() { if len(sinfo.callbacks) > 0 { select { case callback := <-sinfo.callbacks[0]: diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index b30e59d5..93d79d29 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -192,7 +192,7 @@ func (t *switchTable) init(core *Core) { t.updater.Store(&sync.Once{}) t.table.Store(lookupTable{}) t.drop = make(map[crypto.SigPubKey]int64) - <-t.SyncExec(func() { + phony.Block(t, func() { t.queues.totalMaxSize = SwitchQueueTotalMinSize t.queues.bufs = make(map[string]switch_buffer) t.idle = make(map[switchPort]time.Time) @@ -827,7 +827,7 @@ func (t *switchTable) _handleIdle(port switchPort) bool { } func (t *switchTable) packetInFrom(from phony.Actor, bytes []byte) { - t.RecvFrom(from, func() { + t.Act(from, func() { t._packetIn(bytes) }) } @@ -870,5 +870,5 @@ func (t *switchTable) _idleIn(port switchPort) { // Passed a function to call. // This will send the function to t.admin and block until it finishes. func (t *switchTable) doAdmin(f func()) { - <-t.SyncExec(f) + phony.Block(t, f) } From a8b323acdd2236fb3f984cee6829395829196d14 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Tue, 27 Aug 2019 20:01:37 -0500 Subject: [PATCH 0289/1109] have an actor manage the crypto worker pool instead of each session trying to use it directly, this should result in a fairer round-robin behavior in cases where crypto congestion is the bottleneck --- src/yggdrasil/session.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 06780e65..9abfeca6 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -482,6 +482,18 @@ func (ss *sessions) reset() { //////////////////////////// Worker Functions Below //////////////////////////// //////////////////////////////////////////////////////////////////////////////// +type sessionCryptoManager struct { + phony.Inbox +} + +func (m *sessionCryptoManager) workerGo(from phony.Actor, f func()) { + m.Act(from, func() { + util.WorkerGo(f) + }) +} + +var manager = sessionCryptoManager{} + type FlowKeyMessage struct { FlowKey uint64 Message []byte @@ -527,7 +539,7 @@ func (sinfo *sessionInfo) _recvPacket(p *wire_trafficPacket) { sinfo.checkCallbacks() } sinfo.callbacks = append(sinfo.callbacks, ch) - util.WorkerGo(poolFunc) + manager.workerGo(sinfo, poolFunc) } func (sinfo *sessionInfo) _send(msg FlowKeyMessage) { @@ -570,7 +582,7 @@ func (sinfo *sessionInfo) _send(msg FlowKeyMessage) { sinfo.checkCallbacks() } sinfo.callbacks = append(sinfo.callbacks, ch) - util.WorkerGo(poolFunc) + manager.workerGo(sinfo, poolFunc) } func (sinfo *sessionInfo) checkCallbacks() { From 5d7d84f82716c3689dca19d294ca239fa3c2f9f6 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 28 Aug 2019 12:17:19 +0100 Subject: [PATCH 0290/1109] Remove router.doAdmin and switchTable.doAdmin --- src/tuntap/ckr.go | 3 +-- src/yggdrasil/api.go | 11 +++++------ src/yggdrasil/conn.go | 6 ++++-- src/yggdrasil/core.go | 3 ++- src/yggdrasil/debug.go | 2 +- src/yggdrasil/peer.go | 2 +- src/yggdrasil/router.go | 5 ----- src/yggdrasil/switch.go | 8 +------- 8 files changed, 15 insertions(+), 25 deletions(-) diff --git a/src/tuntap/ckr.go b/src/tuntap/ckr.go index ad8c89d4..229d2607 100644 --- a/src/tuntap/ckr.go +++ b/src/tuntap/ckr.go @@ -56,8 +56,7 @@ func (c *cryptokey) init(tun *TunAdapter) { } } -// Configure the CKR routes - this must only ever be called from the router -// goroutine, e.g. through router.doAdmin +// Configure the CKR routes. func (c *cryptokey) configure() error { current := c.tun.config.GetCurrent() diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 44cbad1d..2f684646 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -178,7 +178,7 @@ func (c *Core) GetDHT() []DHTEntry { dhtentries = append(dhtentries, info) } } - c.router.doAdmin(getDHT) + phony.Block(c.router, getDHT) return dhtentries } @@ -205,9 +205,8 @@ func (c *Core) GetSwitchQueues() SwitchQueues { } switchqueues.Queues = append(switchqueues.Queues, queue) } - } - c.switchTable.doAdmin(getSwitchQueues) + phony.Block(c.switchTable, getSwitchQueues) return switchqueues } @@ -244,7 +243,7 @@ func (c *Core) GetSessions() []Session { sessions = append(sessions, session) } } - c.router.doAdmin(getSessions) + phony.Block(c.router, getSessions) return sessions } @@ -345,7 +344,7 @@ func (c *Core) GetNodeInfo(key crypto.BoxPubKey, coords []uint64, nocache bool) }) c.router.nodeinfo.sendNodeInfo(key, wire_coordsUint64stoBytes(coords), false) } - c.router.doAdmin(sendNodeInfoRequest) + phony.Block(c.router, sendNodeInfoRequest) timer := time.AfterFunc(6*time.Second, func() { close(response) }) defer timer.Stop() for res := range response { @@ -455,7 +454,7 @@ func (c *Core) DHTPing(key crypto.BoxPubKey, coords []uint64, target *crypto.Nod }) c.router.dht.ping(&info, &rq.dest) } - c.router.doAdmin(sendPing) + phony.Block(c.router, sendPing) // TODO: do something better than the below... res := <-resCh if res != nil { diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index c56c2a90..4c222266 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -92,7 +92,9 @@ func (c *Conn) setMTU(from phony.Actor, mtu uint16) { func (c *Conn) search() error { var sinfo *searchInfo var isIn bool - c.core.router.doAdmin(func() { sinfo, isIn = c.core.router.searches.searches[*c.nodeID] }) + phony.Block(c.core.router, func() { + sinfo, isIn = c.core.router.searches.searches[*c.nodeID] + }) if !isIn { done := make(chan struct{}, 1) var sess *sessionInfo @@ -106,7 +108,7 @@ func (c *Conn) search() error { default: } } - c.core.router.doAdmin(func() { + phony.Block(c.core.router, func() { sinfo = c.core.router.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) sinfo.continueSearch() }) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index ec530745..3719818b 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "time" + "github.com/Arceliar/phony" "github.com/gologme/log" "github.com/yggdrasil-network/yggdrasil-go/src/config" @@ -175,7 +176,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, c.config.Mutex.RLock() if c.config.Current.SwitchOptions.MaxTotalQueueSize >= SwitchQueueTotalMinSize { - c.switchTable.doAdmin(func() { + phony.Block(c.switchTable, func() { c.switchTable.queues.totalMaxSize = c.config.Current.SwitchOptions.MaxTotalQueueSize }) } diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index 9d2199d0..4502c11e 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -259,7 +259,7 @@ func DEBUG_wire_encode_coords(coords []byte) []byte { func (c *Core) DEBUG_getDHTSize() int { var total int - c.router.doAdmin(func() { + phony.Block(c.router, func() { total = len(c.router.dht.table) }) return total diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index d33c413a..72ff2847 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -149,7 +149,7 @@ func (ps *peers) removePeer(port switchPort) { if port == 0 { return } // Can't remove self peer - ps.core.router.doAdmin(func() { + phony.Block(ps.core.router, func() { ps.core.switchTable.forgetPeer(port) }) ps.mutex.Lock() diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index bbc87c51..d93ba433 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -251,8 +251,3 @@ func (r *router) _handleNodeInfo(bs []byte, fromKey *crypto.BoxPubKey) { req.SendPermPub = *fromKey r.nodeinfo.handleNodeInfo(&req) } - -// TODO remove this, have things either be actors that send message or else call Block directly -func (r *router) doAdmin(f func()) { - phony.Block(r, f) -} diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 93d79d29..5c613d8d 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -278,7 +278,7 @@ func (t *switchTable) blockPeer(port switchPort) { } // Removes a peer. -// Must be called by the router mainLoop goroutine, e.g. call router.doAdmin with a lambda that calls this. +// Must be called by the router actor with a lambda that calls this. // If the removed peer was this node's parent, it immediately tries to find a new parent. func (t *switchTable) forgetPeer(port switchPort) { t.mutex.Lock() @@ -866,9 +866,3 @@ func (t *switchTable) _idleIn(port switchPort) { t.idle[port] = time.Now() } } - -// Passed a function to call. -// This will send the function to t.admin and block until it finishes. -func (t *switchTable) doAdmin(f func()) { - phony.Block(t, f) -} From 607c9068201d10b49861a06b0d1fbd1cf2400582 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 28 Aug 2019 12:26:44 +0100 Subject: [PATCH 0291/1109] Pointer receivers for phony.Block --- src/yggdrasil/api.go | 10 +++++----- src/yggdrasil/conn.go | 4 ++-- src/yggdrasil/core.go | 2 +- src/yggdrasil/peer.go | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 2f684646..9e82ece1 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -178,7 +178,7 @@ func (c *Core) GetDHT() []DHTEntry { dhtentries = append(dhtentries, info) } } - phony.Block(c.router, getDHT) + phony.Block(&c.router, getDHT) return dhtentries } @@ -206,7 +206,7 @@ func (c *Core) GetSwitchQueues() SwitchQueues { switchqueues.Queues = append(switchqueues.Queues, queue) } } - phony.Block(c.switchTable, getSwitchQueues) + phony.Block(&c.switchTable, getSwitchQueues) return switchqueues } @@ -243,7 +243,7 @@ func (c *Core) GetSessions() []Session { sessions = append(sessions, session) } } - phony.Block(c.router, getSessions) + phony.Block(&c.router, getSessions) return sessions } @@ -344,7 +344,7 @@ func (c *Core) GetNodeInfo(key crypto.BoxPubKey, coords []uint64, nocache bool) }) c.router.nodeinfo.sendNodeInfo(key, wire_coordsUint64stoBytes(coords), false) } - phony.Block(c.router, sendNodeInfoRequest) + phony.Block(&c.router, sendNodeInfoRequest) timer := time.AfterFunc(6*time.Second, func() { close(response) }) defer timer.Stop() for res := range response { @@ -454,7 +454,7 @@ func (c *Core) DHTPing(key crypto.BoxPubKey, coords []uint64, target *crypto.Nod }) c.router.dht.ping(&info, &rq.dest) } - phony.Block(c.router, sendPing) + phony.Block(&c.router, sendPing) // TODO: do something better than the below... res := <-resCh if res != nil { diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 4c222266..0a4b84aa 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -92,7 +92,7 @@ func (c *Conn) setMTU(from phony.Actor, mtu uint16) { func (c *Conn) search() error { var sinfo *searchInfo var isIn bool - phony.Block(c.core.router, func() { + phony.Block(&c.core.router, func() { sinfo, isIn = c.core.router.searches.searches[*c.nodeID] }) if !isIn { @@ -108,7 +108,7 @@ func (c *Conn) search() error { default: } } - phony.Block(c.core.router, func() { + phony.Block(&c.core.router, func() { sinfo = c.core.router.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) sinfo.continueSearch() }) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 3719818b..b466e3e8 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -176,7 +176,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, c.config.Mutex.RLock() if c.config.Current.SwitchOptions.MaxTotalQueueSize >= SwitchQueueTotalMinSize { - phony.Block(c.switchTable, func() { + phony.Block(&c.switchTable, func() { c.switchTable.queues.totalMaxSize = c.config.Current.SwitchOptions.MaxTotalQueueSize }) } diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 72ff2847..8bd638c4 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -149,7 +149,7 @@ func (ps *peers) removePeer(port switchPort) { if port == 0 { return } // Can't remove self peer - phony.Block(ps.core.router, func() { + phony.Block(&ps.core.router, func() { ps.core.switchTable.forgetPeer(port) }) ps.mutex.Lock() From e553f3e013ecb5146f1fe9512a135446d8ea30ef Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 28 Aug 2019 12:46:12 +0100 Subject: [PATCH 0292/1109] Reconfigure functions now ran by actors --- src/yggdrasil/core.go | 42 +++++++++++++++++++++++++--------------- src/yggdrasil/link.go | 2 +- src/yggdrasil/router.go | 12 +++++------- src/yggdrasil/session.go | 10 ++++------ 4 files changed, 36 insertions(+), 30 deletions(-) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index b466e3e8..d48b6479 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -112,29 +112,39 @@ func (c *Core) addPeerLoop() { // config.NodeConfig and then signals the various module goroutines to // reconfigure themselves if needed. func (c *Core) UpdateConfig(config *config.NodeConfig) { - c.log.Debugln("Reloading node configuration...") + c.log.Infoln("Reloading node configuration...") c.config.Replace(*config) - errors := 0 // Each reconfigure function should pass any errors to the channel, then close it - components := []func(chan error){ - c.link.reconfigure, - c.peers.reconfigure, - c.router.reconfigure, - c.router.dht.reconfigure, - c.router.searches.reconfigure, - c.router.sessions.reconfigure, - c.switchTable.reconfigure, + components := map[phony.Actor][]func(chan error){ + &c.router: []func(chan error){ + c.router.reconfigure, + c.router.dht.reconfigure, + c.router.searches.reconfigure, + c.router.sessions.reconfigure, + }, + &c.switchTable: []func(chan error){ + c.switchTable.reconfigure, + c.link.reconfigure, + c.peers.reconfigure, + }, } - for _, component := range components { - response := make(chan error) - go component(response) - for err := range response { - c.log.Errorln(err) - errors++ + // TODO: We count errors here but honestly that provides us with absolutely no + // benefit over components reporting errors themselves, so maybe we can use + // actor.Act() here instead and stop counting errors + for actor, functions := range components { + for _, function := range functions { + response := make(chan error) + phony.Block(actor, func() { + function(response) + }) + for err := range response { + c.log.Errorln(err) + errors++ + } } } diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index e65055bc..ee4b9815 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -82,7 +82,7 @@ func (l *link) init(c *Core) error { func (l *link) reconfigure(e chan error) { defer close(e) tcpResponse := make(chan error) - go l.tcp.reconfigure(tcpResponse) + l.tcp.reconfigure(tcpResponse) for err := range tcpResponse { e <- err } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index d93ba433..5f894d9b 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -77,13 +77,11 @@ func (r *router) reconfigure(e chan error) { defer close(e) var errs []error // Reconfigure the router - phony.Block(r, func() { - current := r.core.config.GetCurrent() - err := r.nodeinfo.setNodeInfo(current.NodeInfo, current.NodeInfoPrivacy) - if err != nil { - errs = append(errs, err) - } - }) + current := r.core.config.GetCurrent() + err := r.nodeinfo.setNodeInfo(current.NodeInfo, current.NodeInfoPrivacy) + if err != nil { + errs = append(errs, err) + } for _, err := range errs { e <- err } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 9abfeca6..f6855b4d 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -164,12 +164,10 @@ func (ss *sessions) init(r *router) { func (ss *sessions) reconfigure(e chan error) { defer close(e) responses := make(map[crypto.Handle]chan error) - phony.Block(ss.router, func() { - for index, session := range ss.sinfos { - responses[index] = make(chan error) - go session.reconfigure(responses[index]) - } - }) + for index, session := range ss.sinfos { + responses[index] = make(chan error) + session.reconfigure(responses[index]) + } for _, response := range responses { for err := range response { e <- err From 881d0a1ada34fa6fc57cf1c182995a7ca1686954 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 28 Aug 2019 12:46:49 +0100 Subject: [PATCH 0293/1109] Fix DEBUG_getDHTSize --- src/yggdrasil/debug.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index 4502c11e..8fdb2786 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -259,7 +259,7 @@ func DEBUG_wire_encode_coords(coords []byte) []byte { func (c *Core) DEBUG_getDHTSize() int { var total int - phony.Block(c.router, func() { + phony.Block(&c.router, func() { total = len(c.router.dht.table) }) return total From 764f9c8e117f8ee6a3db99fbbc5b8e5ba7f94ba7 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 28 Aug 2019 17:24:41 +0100 Subject: [PATCH 0294/1109] Remove legacy debug functions --- src/yggdrasil/debug.go | 606 ----------------------------------------- 1 file changed, 606 deletions(-) diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index 8fdb2786..b9bd5cfb 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -2,20 +2,7 @@ package yggdrasil -// These are functions that should not exist -// They are (or were) used during development, to work around missing features -// They're also used to configure things from the outside -// It would be better to define and export a few config functions elsewhere -// Or define some remote API and call it to send/request configuration info - -import _ "golang.org/x/net/ipv6" // TODO put this somewhere better - -//import "golang.org/x/net/proxy" - import "fmt" -import "net" -import "regexp" -import "encoding/hex" import _ "net/http/pprof" import "net/http" @@ -24,13 +11,6 @@ import "os" import "github.com/gologme/log" -import "github.com/yggdrasil-network/yggdrasil-go/src/address" -import "github.com/yggdrasil-network/yggdrasil-go/src/config" -import "github.com/yggdrasil-network/yggdrasil-go/src/crypto" -import "github.com/yggdrasil-network/yggdrasil-go/src/defaults" - -import "github.com/Arceliar/phony" - // Start the profiler in debug builds, if the required environment variable is set. func init() { envVarName := "PPROFLISTEN" @@ -51,589 +31,3 @@ func StartProfiler(log *log.Logger) error { go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }() return nil } - -// This function is only called by the simulator to set up a node with random -// keys. It should not be used and may be removed in the future. -func (c *Core) Init() { - bpub, bpriv := crypto.NewBoxKeys() - spub, spriv := crypto.NewSigKeys() - hbpub := hex.EncodeToString(bpub[:]) - hbpriv := hex.EncodeToString(bpriv[:]) - hspub := hex.EncodeToString(spub[:]) - hspriv := hex.EncodeToString(spriv[:]) - cfg := config.NodeConfig{ - EncryptionPublicKey: hbpub, - EncryptionPrivateKey: hbpriv, - SigningPublicKey: hspub, - SigningPrivateKey: hspriv, - } - c.config = config.NodeState{ - Current: cfg, - Previous: cfg, - } - c.init() - c.switchTable.start() - c.router.start() -} - -//////////////////////////////////////////////////////////////////////////////// - -// Core - -func (c *Core) DEBUG_getSigningPublicKey() crypto.SigPubKey { - return (crypto.SigPubKey)(c.sigPub) -} - -func (c *Core) DEBUG_getEncryptionPublicKey() crypto.BoxPubKey { - return (crypto.BoxPubKey)(c.boxPub) -} - -/* -func (c *Core) DEBUG_getSend() chan<- []byte { - return c.router.tun.send -} - -func (c *Core) DEBUG_getRecv() <-chan []byte { - return c.router.tun.recv -} -*/ - -// Peer - -func (c *Core) DEBUG_getPeers() *peers { - return &c.peers -} - -func (ps *peers) DEBUG_newPeer(box crypto.BoxPubKey, sig crypto.SigPubKey, link crypto.BoxSharedKey) *peer { - sim := linkInterface{ - name: "(simulator)", - info: linkInfo{ - local: "(simulator)", - remote: "(simulator)", - linkType: "sim", - }, - } - return ps.newPeer(&box, &sig, &link, &sim, nil) -} - -/* -func (ps *peers) DEBUG_startPeers() { - ps.mutex.RLock() - defer ps.mutex.RUnlock() - for _, p := range ps.ports { - if p == nil { continue } - go p.MainLoop() - } -} -*/ - -func (ps *peers) DEBUG_hasPeer(key crypto.SigPubKey) bool { - ports := ps.ports.Load().(map[switchPort]*peer) - for _, p := range ports { - if p == nil { - continue - } - if p.sig == key { - return true - } - } - return false -} - -func (ps *peers) DEBUG_getPorts() map[switchPort]*peer { - ports := ps.ports.Load().(map[switchPort]*peer) - newPeers := make(map[switchPort]*peer) - for port, p := range ports { - newPeers[port] = p - } - return newPeers -} - -func (p *peer) DEBUG_getSigKey() crypto.SigPubKey { - return p.sig -} - -func (p *peer) DEEBUG_getPort() switchPort { - return p.port -} - -// Router - -func (c *Core) DEBUG_getSwitchTable() *switchTable { - return &c.switchTable -} - -func (c *Core) DEBUG_getLocator() switchLocator { - return c.switchTable.getLocator() -} - -func (l *switchLocator) DEBUG_getCoords() []byte { - return l.getCoords() -} - -func (c *Core) DEBUG_switchLookup(dest []byte) switchPort { - return c.switchTable.DEBUG_lookup(dest) -} - -// This does the switch layer lookups that decide how to route traffic. -// Traffic uses greedy routing in a metric space, where the metric distance between nodes is equal to the distance between them on the tree. -// Traffic must be routed to a node that is closer to the destination via the metric space distance. -// In the event that two nodes are equally close, it gets routed to the one with the longest uptime (due to the order that things are iterated over). -// The size of the outgoing packet queue is added to a node's tree distance when the cost of forwarding to a node, subject to the constraint that the real tree distance puts them closer to the destination than ourself. -// Doing so adds a limited form of backpressure routing, based on local information, which allows us to forward traffic around *local* bottlenecks, provided that another greedy path exists. -func (t *switchTable) DEBUG_lookup(dest []byte) switchPort { - table := t.getTable() - myDist := table.self.dist(dest) - if myDist == 0 { - return 0 - } - // cost is in units of (expected distance) + (expected queue size), where expected distance is used as an approximation of the minimum backpressure gradient needed for packets to flow - ports := t.core.peers.getPorts() - var best switchPort - bestCost := int64(^uint64(0) >> 1) - for _, info := range table.elems { - dist := info.locator.dist(dest) - if !(dist < myDist) { - continue - } - //p, isIn := ports[info.port] - _, isIn := ports[info.port] - if !isIn { - continue - } - cost := int64(dist) // + p.getQueueSize() - if cost < bestCost { - best = info.port - bestCost = cost - } - } - return best -} - -/* -func (t *switchTable) DEBUG_isDirty() bool { - //data := t.data.Load().(*tabledata) - t.mutex.RLock() - defer t.mutex.RUnlock() - data := t.data - return data.dirty -} -*/ - -func (t *switchTable) DEBUG_dumpTable() { - //data := t.data.Load().(*tabledata) - t.mutex.RLock() - defer t.mutex.RUnlock() - data := t.data - for _, peer := range data.peers { - //fmt.Println("DUMPTABLE:", t.treeID, peer.treeID, peer.port, - // peer.locator.Root, peer.coords, - // peer.reverse.Root, peer.reverse.Coords, peer.forward) - fmt.Println("DUMPTABLE:", t.key, peer.key, peer.locator.coords, peer.port /*, peer.forward*/) - } -} - -func (t *switchTable) DEBUG_getReversePort(port switchPort) switchPort { - // Returns Port(0) if it cannot get the reverse peer for any reason - //data := t.data.Load().(*tabledata) - t.mutex.RLock() - defer t.mutex.RUnlock() - data := t.data - if port >= switchPort(len(data.peers)) { - return switchPort(0) - } - pinfo := data.peers[port] - if len(pinfo.locator.coords) < 1 { - return switchPort(0) - } - return pinfo.locator.coords[len(pinfo.locator.coords)-1] -} - -// Wire - -func DEBUG_wire_encode_coords(coords []byte) []byte { - return wire_encode_coords(coords) -} - -// DHT, via core - -func (c *Core) DEBUG_getDHTSize() int { - var total int - phony.Block(&c.router, func() { - total = len(c.router.dht.table) - }) - return total -} - -// TUN defaults - -func (c *Core) DEBUG_GetTUNDefaultIfName() string { - return defaults.GetDefaults().DefaultIfName -} - -func (c *Core) DEBUG_GetTUNDefaultIfMTU() int { - return defaults.GetDefaults().DefaultIfMTU -} - -func (c *Core) DEBUG_GetTUNDefaultIfTAPMode() bool { - return defaults.GetDefaults().DefaultIfTAPMode -} - -// udpInterface -// FIXME udpInterface isn't exported -// So debug functions need to work differently... - -/* -func (c *Core) DEBUG_setupLoopbackUDPInterface() { - iface := udpInterface{} - iface.init(c, "[::1]:0") - c.ifaces = append(c.ifaces[:0], &iface) -} -*/ - -/* -func (c *Core) DEBUG_getLoopbackAddr() net.Addr { - iface := c.ifaces[0] - return iface.sock.LocalAddr() -} -*/ - -/* -func (c *Core) DEBUG_addLoopbackPeer(addr *net.UDPAddr, - in (chan<- []byte), - out (<-chan []byte)) { - iface := c.ifaces[0] - iface.addPeer(addr, in, out) -} -*/ - -/* -func (c *Core) DEBUG_startLoopbackUDPInterface() { - iface := c.ifaces[0] - go iface.reader() - for addr, chs := range iface.peers { - udpAddr, err := net.ResolveUDPAddr("udp6", addr) - if err != nil { panic(err) } - go iface.writer(udpAddr, chs.out) - } -} -*/ - -//////////////////////////////////////////////////////////////////////////////// - -func (c *Core) DEBUG_getAddr() *address.Address { - return address.AddrForNodeID(&c.router.dht.nodeID) -} - -/* -func (c *Core) DEBUG_startTun(ifname string, iftapmode bool) { - c.DEBUG_startTunWithMTU(ifname, iftapmode, 1280) -} - -func (c *Core) DEBUG_startTunWithMTU(ifname string, iftapmode bool, mtu int) { - addr := c.DEBUG_getAddr() - straddr := fmt.Sprintf("%s/%v", net.IP(addr[:]).String(), 8*len(address.GetPrefix())) - if ifname != "none" { - err := c.router.tun.setup(ifname, iftapmode, straddr, mtu) - if err != nil { - panic(err) - } - c.log.Println("Setup TUN/TAP:", c.router.tun.iface.Name(), straddr) - go func() { panic(c.router.tun.read()) }() - } - go func() { panic(c.router.tun.write()) }() -} - -func (c *Core) DEBUG_stopTun() { - c.router.tun.close() -} -*/ - -//////////////////////////////////////////////////////////////////////////////// - -func (c *Core) DEBUG_newBoxKeys() (*crypto.BoxPubKey, *crypto.BoxPrivKey) { - return crypto.NewBoxKeys() -} - -func (c *Core) DEBUG_getSharedKey(myPrivKey *crypto.BoxPrivKey, othersPubKey *crypto.BoxPubKey) *crypto.BoxSharedKey { - return crypto.GetSharedKey(myPrivKey, othersPubKey) -} - -func (c *Core) DEBUG_newSigKeys() (*crypto.SigPubKey, *crypto.SigPrivKey) { - return crypto.NewSigKeys() -} - -func (c *Core) DEBUG_getNodeID(pub *crypto.BoxPubKey) *crypto.NodeID { - return crypto.GetNodeID(pub) -} - -func (c *Core) DEBUG_getTreeID(pub *crypto.SigPubKey) *crypto.TreeID { - return crypto.GetTreeID(pub) -} - -func (c *Core) DEBUG_addrForNodeID(nodeID *crypto.NodeID) string { - return net.IP(address.AddrForNodeID(nodeID)[:]).String() -} - -func (c *Core) DEBUG_init(bpub []byte, - bpriv []byte, - spub []byte, - spriv []byte) { - /*var boxPub crypto.BoxPubKey - var boxPriv crypto.BoxPrivKey - var sigPub crypto.SigPubKey - var sigPriv crypto.SigPrivKey - copy(boxPub[:], bpub) - copy(boxPriv[:], bpriv) - copy(sigPub[:], spub) - copy(sigPriv[:], spriv) - c.init(&boxPub, &boxPriv, &sigPub, &sigPriv)*/ - hbpub := hex.EncodeToString(bpub[:]) - hbpriv := hex.EncodeToString(bpriv[:]) - hspub := hex.EncodeToString(spub[:]) - hspriv := hex.EncodeToString(spriv[:]) - cfg := config.NodeConfig{ - EncryptionPublicKey: hbpub, - EncryptionPrivateKey: hbpriv, - SigningPublicKey: hspub, - SigningPrivateKey: hspriv, - } - c.config = config.NodeState{ - Current: cfg, - Previous: cfg, - } - c.init() - - if err := c.router.start(); err != nil { - panic(err) - } - -} - -//////////////////////////////////////////////////////////////////////////////// - -/* -func (c *Core) DEBUG_setupAndStartGlobalUDPInterface(addrport string) { - if err := c.udp.init(c, addrport); err != nil { - c.log.Println("Failed to start UDP interface:", err) - panic(err) - } -} - -func (c *Core) DEBUG_getGlobalUDPAddr() *net.UDPAddr { - return c.udp.sock.LocalAddr().(*net.UDPAddr) -} - -func (c *Core) DEBUG_maybeSendUDPKeys(saddr string) { - udpAddr, err := net.ResolveUDPAddr("udp", saddr) - if err != nil { - panic(err) - } - var addr connAddr - addr.fromUDPAddr(udpAddr) - c.udp.mutex.RLock() - _, isIn := c.udp.conns[addr] - c.udp.mutex.RUnlock() - if !isIn { - c.udp.sendKeys(addr) - } -} -*/ - -//////////////////////////////////////////////////////////////////////////////// -/* -func (c *Core) DEBUG_addPeer(addr string) { - err := c.admin.addPeer(addr, "") - if err != nil { - panic(err) - } -} -*/ -/* -func (c *Core) DEBUG_addSOCKSConn(socksaddr, peeraddr string) { - go func() { - dialer, err := proxy.SOCKS5("tcp", socksaddr, nil, proxy.Direct) - if err == nil { - conn, err := dialer.Dial("tcp", peeraddr) - if err == nil { - c.tcp.callWithConn(&wrappedConn{ - c: conn, - raddr: &wrappedAddr{ - network: "tcp", - addr: peeraddr, - }, - }) - } - } - }() -} -*/ - -/* -func (c *Core) DEBUG_setupAndStartGlobalTCPInterface(addrport string) { - c.config.Listen = []string{addrport} - if err := c.link.init(c); err != nil { - c.log.Println("Failed to start interfaces:", err) - panic(err) - } -} - -func (c *Core) DEBUG_getGlobalTCPAddr() *net.TCPAddr { - return c.link.tcp.getAddr() -} - -func (c *Core) DEBUG_addTCPConn(saddr string) { - c.link.tcp.call(saddr, nil, "") -} - -//*/ - -/* -func (c *Core) DEBUG_startSelfPeer() { - c.Peers.mutex.RLock() - defer c.Peers.mutex.RUnlock() - p := c.Peers.ports[0] - go p.MainLoop() -} -*/ - -//////////////////////////////////////////////////////////////////////////////// - -/* -func (c *Core) DEBUG_setupAndStartGlobalKCPInterface(addrport string) { - iface := kcpInterface{} - iface.init(c, addrport) - c.kcp = &iface -} - -func (c *Core) DEBUG_getGlobalKCPAddr() net.Addr { - return c.kcp.serv.Addr() -} - -func (c *Core) DEBUG_addKCPConn(saddr string) { - c.kcp.call(saddr) -} -*/ - -//////////////////////////////////////////////////////////////////////////////// - -/* -func (c *Core) DEBUG_setupAndStartAdminInterface(addrport string) { - a := admin{} - c.config.AdminListen = addrport - a.init() - c.admin = a -} - -func (c *Core) DEBUG_setupAndStartMulticastInterface() { - m := multicast{} - m.init(c) - c.multicast = m - m.start() -} -*/ - -//////////////////////////////////////////////////////////////////////////////// - -func (c *Core) DEBUG_setLogger(log *log.Logger) { - c.log = log -} - -func (c *Core) DEBUG_setIfceExpr(expr *regexp.Regexp) { - c.log.Println("DEBUG_setIfceExpr no longer implemented") -} - -/* -func (c *Core) DEBUG_addAllowedEncryptionPublicKey(boxStr string) { - err := c.admin.addAllowedEncryptionPublicKey(boxStr) - if err != nil { - panic(err) - } -} -*/ -//////////////////////////////////////////////////////////////////////////////// - -func DEBUG_simLinkPeers(p, q *peer) { - // Sets q.out() to point to p and starts p.start() - goWorkers := func(source, dest *peer) { - linkOut := make(chan []byte, 1) - source.linkOut = func(bs []byte) { linkOut <- bs } - send := make(chan []byte, 1) - source.out = func(bss [][]byte) { - for _, bs := range bss { - send <- bs - } - } - go source.start() - go func() { - var packets [][]byte - for { - select { - case packet := <-linkOut: - packets = append(packets, packet) - continue - case packet := <-send: - packets = append(packets, packet) - phony.Block(&source.core.switchTable, func() { - source.core.switchTable._idleIn(source.port) - }) - continue - default: - } - if len(packets) > 0 { - phony.Block(dest, func() { dest._handlePacket(packets[0]) }) - packets = packets[1:] - continue - } - select { - case packet := <-linkOut: - packets = append(packets, packet) - case packet := <-send: - packets = append(packets, packet) - phony.Block(&source.core.switchTable, func() { - source.core.switchTable._idleIn(source.port) - }) - } - } - }() - } - goWorkers(p, q) - goWorkers(q, p) - phony.Block(&p.core.switchTable, func() { - p.core.switchTable._idleIn(p.port) - }) - phony.Block(&q.core.switchTable, func() { - q.core.switchTable._idleIn(q.port) - }) -} - -/* -func (c *Core) DEBUG_simFixMTU() { - c.router.tun.mtu = 65535 -} -*/ - -//////////////////////////////////////////////////////////////////////////////// - -func Util_testAddrIDMask() { - for idx := 0; idx < 16; idx++ { - var orig crypto.NodeID - orig[8] = 42 - for bidx := 0; bidx < idx; bidx++ { - orig[bidx/8] |= (0x80 >> uint8(bidx%8)) - } - addr := address.AddrForNodeID(&orig) - nid, mask := addr.GetNodeIDandMask() - for b := 0; b < len(mask); b++ { - nid[b] &= mask[b] - orig[b] &= mask[b] - } - if *nid != orig { - fmt.Println(orig) - fmt.Println(*addr) - fmt.Println(*nid) - fmt.Println(*mask) - panic(idx) - } - } -} From fc9a1c6c31b456fb55dd7b5c555975652573b44c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 28 Aug 2019 19:31:04 +0100 Subject: [PATCH 0295/1109] Simplify reconfiguration --- cmd/yggdrasil/main.go | 1 + src/multicast/multicast.go | 1 - src/tuntap/ckr.go | 66 +++++++++++++++----------------------- src/tuntap/tun.go | 25 +++------------ src/yggdrasil/core.go | 44 ++++--------------------- src/yggdrasil/dht.go | 3 +- src/yggdrasil/link.go | 9 ++---- src/yggdrasil/peer.go | 3 +- src/yggdrasil/router.go | 20 ++++++------ src/yggdrasil/search.go | 3 +- src/yggdrasil/session.go | 17 +++------- src/yggdrasil/switch.go | 5 +-- src/yggdrasil/tcp.go | 8 +++-- 13 files changed, 64 insertions(+), 141 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 2fa10720..e9a21c13 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -279,6 +279,7 @@ func main() { case _ = <-r: if *useconffile != "" { cfg = readConfig(useconf, useconffile, normaliseconf) + logger.Infoln("Reloading configuration from", *useconffile) n.core.UpdateConfig(cfg) n.tuntap.UpdateConfig(cfg) n.multicast.UpdateConfig(cfg) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 5ab9673e..22ac9356 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -83,7 +83,6 @@ func (m *Multicast) Stop() error { func (m *Multicast) UpdateConfig(config *config.NodeConfig) { m.log.Debugln("Reloading multicast configuration...") m.config.Replace(*config) - m.log.Infoln("Multicast configuration reloaded successfully") } // GetInterfaces returns the currently known/enabled multicast interfaces. It is diff --git a/src/tuntap/ckr.go b/src/tuntap/ckr.go index 229d2607..9af3564a 100644 --- a/src/tuntap/ckr.go +++ b/src/tuntap/ckr.go @@ -20,7 +20,6 @@ import ( type cryptokey struct { tun *TunAdapter enabled atomic.Value // bool - reconfigure chan chan error ipv4remotes []cryptokey_route ipv6remotes []cryptokey_route ipv4cache map[address.Address]cryptokey_route @@ -40,24 +39,11 @@ type cryptokey_route struct { // Initialise crypto-key routing. This must be done before any other CKR calls. func (c *cryptokey) init(tun *TunAdapter) { c.tun = tun - c.reconfigure = make(chan chan error, 1) - go func() { - for { - e := <-c.reconfigure - e <- nil - } - }() - - c.tun.log.Debugln("Configuring CKR...") - if err := c.configure(); err != nil { - c.tun.log.Errorln("CKR configuration failed:", err) - } else { - c.tun.log.Debugln("CKR configured") - } + c.configure() } -// Configure the CKR routes. -func (c *cryptokey) configure() error { +// Configure the CKR routes. This should only ever be ran by the TUN/TAP actor. +func (c *cryptokey) configure() { current := c.tun.config.GetCurrent() // Set enabled/disabled state @@ -72,14 +58,14 @@ func (c *cryptokey) configure() error { // Add IPv6 routes for ipv6, pubkey := range current.TunnelRouting.IPv6RemoteSubnets { if err := c.addRemoteSubnet(ipv6, pubkey); err != nil { - return err + c.tun.log.Errorln("Error adding CKR IPv6 remote subnet:", err) } } // Add IPv4 routes for ipv4, pubkey := range current.TunnelRouting.IPv4RemoteSubnets { if err := c.addRemoteSubnet(ipv4, pubkey); err != nil { - return err + c.tun.log.Errorln("Error adding CKR IPv4 remote subnet:", err) } } @@ -93,7 +79,7 @@ func (c *cryptokey) configure() error { c.ipv6locals = make([]net.IPNet, 0) for _, source := range current.TunnelRouting.IPv6LocalSubnets { if err := c.addLocalSubnet(source); err != nil { - return err + c.tun.log.Errorln("Error adding CKR IPv6 local subnet:", err) } } @@ -101,7 +87,7 @@ func (c *cryptokey) configure() error { c.ipv4locals = make([]net.IPNet, 0) for _, source := range current.TunnelRouting.IPv4LocalSubnets { if err := c.addLocalSubnet(source); err != nil { - return err + c.tun.log.Errorln("Error adding CKR IPv4 local subnet:", err) } } @@ -110,8 +96,6 @@ func (c *cryptokey) configure() error { c.ipv4cache = make(map[address.Address]cryptokey_route, 0) c.ipv6cache = make(map[address.Address]cryptokey_route, 0) c.mutexcaches.Unlock() - - return nil } // Enable or disable crypto-key routing. @@ -181,19 +165,19 @@ func (c *cryptokey) addLocalSubnet(cidr string) error { } else if prefixsize == net.IPv4len*8 { routingsources = &c.ipv4locals } else { - return errors.New("Unexpected prefix size") + return errors.New("unexpected prefix size") } // Check if we already have this CIDR for _, subnet := range *routingsources { if subnet.String() == ipnet.String() { - return errors.New("Source subnet already configured") + return errors.New("local subnet already configured") } } // Add the source subnet *routingsources = append(*routingsources, *ipnet) - c.tun.log.Infoln("Added CKR source subnet", cidr) + c.tun.log.Infoln("Added CKR local subnet", cidr) return nil } @@ -226,7 +210,7 @@ func (c *cryptokey) addRemoteSubnet(cidr string, dest string) error { routingtable = &c.ipv4remotes routingcache = &c.ipv4cache } else { - return errors.New("Unexpected prefix size") + return errors.New("unexpected prefix size") } // Is the route an Yggdrasil destination? @@ -235,19 +219,19 @@ func (c *cryptokey) addRemoteSubnet(cidr string, dest string) error { copy(addr[:], ipaddr) copy(snet[:], ipnet.IP) if addr.IsValid() || snet.IsValid() { - return errors.New("Can't specify Yggdrasil destination as crypto-key route") + return errors.New("can't specify Yggdrasil destination as crypto-key route") } // Do we already have a route for this subnet? for _, route := range *routingtable { if route.subnet.String() == ipnet.String() { - return errors.New(fmt.Sprintf("Route already exists for %s", cidr)) + return fmt.Errorf("remote subnet already exists for %s", cidr) } } // Decode the public key if bpk, err := hex.DecodeString(dest); err != nil { return err } else if len(bpk) != crypto.BoxPubKeyLen { - return errors.New(fmt.Sprintf("Incorrect key length for %s", dest)) + return fmt.Errorf("incorrect key length for %s", dest) } else { // Add the new crypto-key route var key crypto.BoxPubKey @@ -270,7 +254,7 @@ func (c *cryptokey) addRemoteSubnet(cidr string, dest string) error { delete(*routingcache, k) } - c.tun.log.Infoln("Added CKR destination subnet", cidr) + c.tun.log.Infoln("Added CKR remote subnet", cidr) return nil } } @@ -284,7 +268,7 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c // Check if the address is a valid Yggdrasil address - if so it // is exempt from all CKR checking if addr.IsValid() { - return crypto.BoxPubKey{}, errors.New("Cannot look up CKR for Yggdrasil addresses") + return crypto.BoxPubKey{}, errors.New("cannot look up CKR for Yggdrasil addresses") } // Build our references to the routing table and cache @@ -297,7 +281,7 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c } else if addrlen == net.IPv4len { routingcache = &c.ipv4cache } else { - return crypto.BoxPubKey{}, errors.New("Unexpected prefix size") + return crypto.BoxPubKey{}, errors.New("unexpected prefix size") } // Check if there's a cache entry for this addr @@ -317,7 +301,7 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c } else if addrlen == net.IPv4len { routingtable = &c.ipv4remotes } else { - return crypto.BoxPubKey{}, errors.New("Unexpected prefix size") + return crypto.BoxPubKey{}, errors.New("unexpected prefix size") } // No cache was found - start by converting the address into a net.IP @@ -378,18 +362,18 @@ func (c *cryptokey) removeLocalSubnet(cidr string) error { } else if prefixsize == net.IPv4len*8 { routingsources = &c.ipv4locals } else { - return errors.New("Unexpected prefix size") + return errors.New("unexpected prefix size") } // Check if we already have this CIDR for idx, subnet := range *routingsources { if subnet.String() == ipnet.String() { *routingsources = append((*routingsources)[:idx], (*routingsources)[idx+1:]...) - c.tun.log.Infoln("Removed CKR source subnet", cidr) + c.tun.log.Infoln("Removed CKR local subnet", cidr) return nil } } - return errors.New("Source subnet not found") + return errors.New("local subnet not found") } // Removes a destination route for the given CIDR to be tunnelled to the node @@ -421,7 +405,7 @@ func (c *cryptokey) removeRemoteSubnet(cidr string, dest string) error { routingtable = &c.ipv4remotes routingcache = &c.ipv4cache } else { - return errors.New("Unexpected prefix size") + return errors.New("unexpected prefix size") } // Decode the public key @@ -429,7 +413,7 @@ func (c *cryptokey) removeRemoteSubnet(cidr string, dest string) error { if err != nil { return err } else if len(bpk) != crypto.BoxPubKeyLen { - return errors.New(fmt.Sprintf("Incorrect key length for %s", dest)) + return fmt.Errorf("incorrect key length for %s", dest) } netStr := ipnet.String() @@ -439,9 +423,9 @@ func (c *cryptokey) removeRemoteSubnet(cidr string, dest string) error { for k := range *routingcache { delete(*routingcache, k) } - c.tun.log.Infof("Removed CKR destination subnet %s via %s\n", cidr, dest) + c.tun.log.Infof("Removed CKR remote subnet %s via %s\n", cidr, dest) return nil } } - return errors.New(fmt.Sprintf("Route does not exists for %s", cidr)) + return fmt.Errorf("route does not exists for %s", cidr) } diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 6317459c..8e1e5b0c 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -13,6 +13,7 @@ import ( "errors" "fmt" "net" + //"sync" "github.com/Arceliar/phony" @@ -200,29 +201,11 @@ func (tun *TunAdapter) _stop() error { func (tun *TunAdapter) UpdateConfig(config *config.NodeConfig) { tun.log.Debugln("Reloading TUN/TAP configuration...") + // Replace the active configuration with the supplied one tun.config.Replace(*config) - errors := 0 - - components := []chan chan error{ - tun.reconfigure, - tun.ckr.reconfigure, - } - - for _, component := range components { - response := make(chan error) - component <- response - if err := <-response; err != nil { - tun.log.Errorln(err) - errors++ - } - } - - if errors > 0 { - tun.log.Warnln(errors, "TUN/TAP module(s) reported errors during configuration reload") - } else { - tun.log.Infoln("TUN/TAP configuration reloaded successfully") - } + // Notify children about the configuration change + tun.Act(nil, tun.ckr.configure) } func (tun *TunAdapter) handler() error { diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index d48b6479..831109dd 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -20,6 +20,7 @@ type Core struct { // This is the main data structure that holds everything else for a node // We're going to keep our own copy of the provided config - that way we can // guarantee that it will be covered by the mutex + phony.Inbox config config.NodeState // Config boxPub crypto.BoxPubKey boxPriv crypto.BoxPrivKey @@ -112,47 +113,14 @@ func (c *Core) addPeerLoop() { // config.NodeConfig and then signals the various module goroutines to // reconfigure themselves if needed. func (c *Core) UpdateConfig(config *config.NodeConfig) { - c.log.Infoln("Reloading node configuration...") + c.log.Debugln("Reloading node configuration...") + // Replace the active configuration with the supplied one c.config.Replace(*config) - errors := 0 - // Each reconfigure function should pass any errors to the channel, then close it - components := map[phony.Actor][]func(chan error){ - &c.router: []func(chan error){ - c.router.reconfigure, - c.router.dht.reconfigure, - c.router.searches.reconfigure, - c.router.sessions.reconfigure, - }, - &c.switchTable: []func(chan error){ - c.switchTable.reconfigure, - c.link.reconfigure, - c.peers.reconfigure, - }, - } - - // TODO: We count errors here but honestly that provides us with absolutely no - // benefit over components reporting errors themselves, so maybe we can use - // actor.Act() here instead and stop counting errors - for actor, functions := range components { - for _, function := range functions { - response := make(chan error) - phony.Block(actor, func() { - function(response) - }) - for err := range response { - c.log.Errorln(err) - errors++ - } - } - } - - if errors > 0 { - c.log.Warnln(errors, "node module(s) reported errors during configuration reload") - } else { - c.log.Infoln("Node configuration reloaded successfully") - } + // Notify the router and switch about the new configuration + c.router.Act(c, c.router.reconfigure) + c.switchTable.Act(c, c.switchTable.reconfigure) } // Start starts up Yggdrasil using the provided config.NodeConfig, and outputs diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 4f380363..575c8b1a 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -82,8 +82,7 @@ func (t *dht) init(r *router) { t.reset() } -func (t *dht) reconfigure(e chan error) { - defer close(e) +func (t *dht) reconfigure() { // This is where reconfiguration would go, if we had anything to do } diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index ee4b9815..6e393514 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -79,13 +79,8 @@ func (l *link) init(c *Core) error { return nil } -func (l *link) reconfigure(e chan error) { - defer close(e) - tcpResponse := make(chan error) - l.tcp.reconfigure(tcpResponse) - for err := range tcpResponse { - e <- err - } +func (l *link) reconfigure() { + l.tcp.reconfigure() } func (l *link) call(uri string, sintf string) error { diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 8bd638c4..381e6917 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -34,8 +34,7 @@ func (ps *peers) init(c *Core) { ps.core = c } -func (ps *peers) reconfigure(e chan error) { - defer close(e) +func (ps *peers) reconfigure() { // This is where reconfiguration would go, if we had anything to do } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 5f894d9b..64c81701 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -73,18 +73,20 @@ func (r *router) init(core *Core) { r.sessions.init(r) } -func (r *router) reconfigure(e chan error) { - defer close(e) - var errs []error +// Reconfigures the router and any child modules. This should only ever be run +// by the router actor. +func (r *router) reconfigure() { // Reconfigure the router current := r.core.config.GetCurrent() - err := r.nodeinfo.setNodeInfo(current.NodeInfo, current.NodeInfoPrivacy) - if err != nil { - errs = append(errs, err) - } - for _, err := range errs { - e <- err + if err := r.nodeinfo.setNodeInfo(current.NodeInfo, current.NodeInfoPrivacy); err != nil { + r.core.log.Errorln("Error reloading NodeInfo:", err) + } else { + r.core.log.Infoln("NodeInfo updated") } + // Reconfigure children + r.dht.reconfigure() + r.searches.reconfigure() + r.sessions.reconfigure() } // Starts the tickerLoop goroutine. diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index ca357cc2..c128175b 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -55,8 +55,7 @@ func (s *searches) init(r *router) { s.searches = make(map[crypto.NodeID]*searchInfo) } -func (s *searches) reconfigure(e chan error) { - defer close(e) +func (s *searches) reconfigure() { // This is where reconfiguration would go, if we had anything to do } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index f6855b4d..0b55aac8 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -73,8 +73,7 @@ type sessionInfo struct { callbacks []chan func() // Finished work from crypto workers } -func (sinfo *sessionInfo) reconfigure(e chan error) { - defer close(e) +func (sinfo *sessionInfo) reconfigure() { // This is where reconfiguration would go, if we had anything to do } @@ -161,17 +160,9 @@ func (ss *sessions) init(r *router) { ss.lastCleanup = time.Now() } -func (ss *sessions) reconfigure(e chan error) { - defer close(e) - responses := make(map[crypto.Handle]chan error) - for index, session := range ss.sinfos { - responses[index] = make(chan error) - session.reconfigure(responses[index]) - } - for _, response := range responses { - for err := range response { - e <- err - } +func (ss *sessions) reconfigure() { + for _, session := range ss.sinfos { + session.reconfigure() } } diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 5c613d8d..b6bd5b96 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -199,9 +199,10 @@ func (t *switchTable) init(core *Core) { }) } -func (t *switchTable) reconfigure(e chan error) { - defer close(e) +func (t *switchTable) reconfigure() { // This is where reconfiguration would go, if we had anything useful to do. + t.core.link.reconfigure() + t.core.peers.reconfigure() } // Safely gets a copy of this node's locator. diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index ccb488f9..cce352bd 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -95,8 +95,7 @@ func (t *tcp) init(l *link) error { return nil } -func (t *tcp) reconfigure(e chan error) { - defer close(e) +func (t *tcp) reconfigure() { t.link.core.config.Mutex.RLock() added := util.Difference(t.link.core.config.Current.Listen, t.link.core.config.Previous.Listen) deleted := util.Difference(t.link.core.config.Previous.Listen, t.link.core.config.Current.Listen) @@ -107,7 +106,9 @@ func (t *tcp) reconfigure(e chan error) { continue } if _, err := t.listen(a[6:]); err != nil { - e <- err + 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 { @@ -118,6 +119,7 @@ func (t *tcp) reconfigure(e chan error) { if listener, ok := t.listeners[d[6:]]; ok { t.mutex.Unlock() listener.Stop <- true + t.link.core.log.Infoln("Stopped TCP listener:", d[6:]) } else { t.mutex.Unlock() } From aa0770546eae3b3931326d9df8fea9724715f95e Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 28 Aug 2019 19:39:23 +0100 Subject: [PATCH 0296/1109] Move responsibility for configuring max queue size into switch --- src/yggdrasil/core.go | 8 -------- src/yggdrasil/switch.go | 8 +++++++- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 831109dd..ba06899b 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -152,14 +152,6 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, return nil, err } - c.config.Mutex.RLock() - if c.config.Current.SwitchOptions.MaxTotalQueueSize >= SwitchQueueTotalMinSize { - phony.Block(&c.switchTable, func() { - c.switchTable.queues.totalMaxSize = c.config.Current.SwitchOptions.MaxTotalQueueSize - }) - } - c.config.Mutex.RUnlock() - if err := c.switchTable.start(); err != nil { c.log.Errorln("Failed to start switch") return nil, err diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index b6bd5b96..163c85cf 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -193,7 +193,13 @@ func (t *switchTable) init(core *Core) { t.table.Store(lookupTable{}) t.drop = make(map[crypto.SigPubKey]int64) phony.Block(t, func() { - t.queues.totalMaxSize = SwitchQueueTotalMinSize + core.config.Mutex.RLock() + if core.config.Current.SwitchOptions.MaxTotalQueueSize > SwitchQueueTotalMinSize { + t.queues.totalMaxSize = core.config.Current.SwitchOptions.MaxTotalQueueSize + } else { + t.queues.totalMaxSize = SwitchQueueTotalMinSize + } + core.config.Mutex.RUnlock() t.queues.bufs = make(map[string]switch_buffer) t.idle = make(map[switchPort]time.Time) }) From 1f658cce76c9180104dcbdf93d3b04cedc05f5b1 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 28 Aug 2019 19:53:52 +0100 Subject: [PATCH 0297/1109] Add Core actor --- src/yggdrasil/core.go | 72 ++++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index ba06899b..754d7d64 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -33,7 +33,7 @@ type Core struct { log *log.Logger } -func (c *Core) init() error { +func (c *Core) _init() error { // TODO separate init and start functions // Init sets up structs // Start launches goroutines that depend on structs being set up @@ -85,42 +85,44 @@ func (c *Core) init() error { // If any static peers were provided in the configuration above then we should // configure them. The loop ensures that disconnected peers will eventually // be reconnected with. -func (c *Core) addPeerLoop() { - for { - // the peers from the config - these could change! - current := c.config.GetCurrent() +func (c *Core) _addPeerLoop() { + // Get the peers from the config - these could change! + current := c.config.GetCurrent() - // Add peers from the Peers section - for _, peer := range current.Peers { - go c.AddPeer(peer, "") + // Add peers from the Peers section + for _, peer := range current.Peers { + go c.AddPeer(peer, "") // TODO: this should be acted and not in a goroutine? + time.Sleep(time.Second) + } + + // Add peers from the InterfacePeers section + for intf, intfpeers := range current.InterfacePeers { + for _, peer := range intfpeers { + go c.AddPeer(peer, intf) // TODO: this should be acted and not in a goroutine? time.Sleep(time.Second) } - - // Add peers from the InterfacePeers section - for intf, intfpeers := range current.InterfacePeers { - for _, peer := range intfpeers { - go c.AddPeer(peer, intf) - time.Sleep(time.Second) - } - } - - // Sit for a while - time.Sleep(time.Minute) } + + // Sit for a while + time.AfterFunc(time.Minute, func() { + c.Act(c, c._addPeerLoop) + }) } // UpdateConfig updates the configuration in Core with the provided // config.NodeConfig and then signals the various module goroutines to // reconfigure themselves if needed. func (c *Core) UpdateConfig(config *config.NodeConfig) { - c.log.Debugln("Reloading node configuration...") + c.Act(nil, func() { + c.log.Debugln("Reloading node configuration...") - // Replace the active configuration with the supplied one - c.config.Replace(*config) + // Replace the active configuration with the supplied one + c.config.Replace(*config) - // Notify the router and switch about the new configuration - c.router.Act(c, c.router.reconfigure) - c.switchTable.Act(c, c.switchTable.reconfigure) + // Notify the router and switch about the new configuration + c.router.Act(c, c.router.reconfigure) + c.switchTable.Act(c, c.switchTable.reconfigure) + }) } // Start starts up Yggdrasil using the provided config.NodeConfig, and outputs @@ -128,7 +130,15 @@ func (c *Core) UpdateConfig(config *config.NodeConfig) { // TCP and UDP sockets, a multicast discovery socket, an admin socket, router, // switch and DHT node. A config.NodeState is returned which contains both the // current and previous configurations (from reconfigures). -func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, error) { +func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (conf *config.NodeState, err error) { + phony.Block(c, func() { + conf, err = c._start(nc, log) + }) + return +} + +// This function is unsafe and should only be ran by the core actor. +func (c *Core) _start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, error) { c.log = log c.config = config.NodeState{ @@ -144,8 +154,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, } c.log.Infoln("Starting up...") - - c.init() + c._init() if err := c.link.init(c); err != nil { c.log.Errorln("Failed to start link interfaces") @@ -162,7 +171,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, return nil, err } - go c.addPeerLoop() + c.Act(c, c._addPeerLoop) c.log.Infoln("Startup complete") return &c.config, nil @@ -170,5 +179,10 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, // Stop shuts down the Yggdrasil node. func (c *Core) Stop() { + phony.Block(c, c._stop) +} + +// This function is unsafe and should only be ran by the core actor. +func (c *Core) _stop() { c.log.Infoln("Stopping...") } From 7649ea0f9fe25f191018cc862a3c9341dac234ed Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 29 Aug 2019 21:59:28 -0500 Subject: [PATCH 0298/1109] remove sessionInfo.doFunc, have the api just use phony.Block instead --- src/yggdrasil/api.go | 13 +------------ src/yggdrasil/session.go | 5 ----- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 9e82ece1..d1753b69 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -227,18 +227,7 @@ func (c *Core) GetSessions() []Session { } copy(session.PublicKey[:], sinfo.theirPermPub[:]) } - var skip bool - func() { - defer func() { - if recover() != nil { - skip = true - } - }() - sinfo.doFunc(workerFunc) - }() - if skip { - continue - } + phony.Block(sinfo, workerFunc) // TODO? skipped known but timed out sessions? sessions = append(sessions, session) } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 0b55aac8..d209a0de 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -77,11 +77,6 @@ func (sinfo *sessionInfo) reconfigure() { // This is where reconfiguration would go, if we had anything to do } -// TODO remove this, call SyncExec directly -func (sinfo *sessionInfo) doFunc(f func()) { - phony.Block(sinfo, f) -} - // Represents a session ping/pong packet, andincludes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU. type sessionPing struct { SendPermPub crypto.BoxPubKey // Sender's permanent key From 9e4d4f33bab6186a6415801ef80e361c49765037 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 29 Aug 2019 23:30:39 -0500 Subject: [PATCH 0299/1109] upgrade to latest phony --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9ff6031a..fc9549ff 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/yggdrasil-network/yggdrasil-go require ( - github.com/Arceliar/phony v0.0.0-20190828002416-0337564e2c44 + github.com/Arceliar/phony v0.0.0-20190830042734-c3fdbc251992 github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 github.com/hashicorp/go-syslog v1.0.0 github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 diff --git a/go.sum b/go.sum index 60388964..7499e926 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/phony v0.0.0-20190828002416-0337564e2c44 h1:8EiuIp65v8aLkLc4LWxtn4NTH+P2LwDmrKKWdBzn9cI= -github.com/Arceliar/phony v0.0.0-20190828002416-0337564e2c44/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= +github.com/Arceliar/phony v0.0.0-20190830042734-c3fdbc251992 h1:sT4p8AJps69R/8Tio29ywFQSKcKGvrp3LvnvezxUDJU= +github.com/Arceliar/phony v0.0.0-20190830042734-c3fdbc251992/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/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= From 32633011efa20399d83852e5b64dfd71ed9c4084 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 30 Aug 2019 22:10:34 -0500 Subject: [PATCH 0300/1109] upgrade phony dependency --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fc9549ff..44227a4f 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/yggdrasil-network/yggdrasil-go require ( - github.com/Arceliar/phony v0.0.0-20190830042734-c3fdbc251992 + github.com/Arceliar/phony v0.0.0-20190831030740-7cac84315954 github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 github.com/hashicorp/go-syslog v1.0.0 github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 diff --git a/go.sum b/go.sum index 7499e926..40977e6a 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/phony v0.0.0-20190830042734-c3fdbc251992 h1:sT4p8AJps69R/8Tio29ywFQSKcKGvrp3LvnvezxUDJU= -github.com/Arceliar/phony v0.0.0-20190830042734-c3fdbc251992/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= +github.com/Arceliar/phony v0.0.0-20190831030740-7cac84315954 h1:kwhCKZy6GHYxuqUvmBbicaLlWsYXwqph8tkuaMrl5T4= +github.com/Arceliar/phony v0.0.0-20190831030740-7cac84315954/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/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= From 08f69de1e266c30fb0bdb8d24f8b9b4054903c73 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 31 Aug 2019 00:04:35 -0500 Subject: [PATCH 0301/1109] another phony update --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 44227a4f..3b0af5a4 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/yggdrasil-network/yggdrasil-go require ( - github.com/Arceliar/phony v0.0.0-20190831030740-7cac84315954 + github.com/Arceliar/phony v0.0.0-20190831050304-94a6d3da9ba4 github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 github.com/hashicorp/go-syslog v1.0.0 github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 diff --git a/go.sum b/go.sum index 40977e6a..df7ff630 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/phony v0.0.0-20190831030740-7cac84315954 h1:kwhCKZy6GHYxuqUvmBbicaLlWsYXwqph8tkuaMrl5T4= -github.com/Arceliar/phony v0.0.0-20190831030740-7cac84315954/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= +github.com/Arceliar/phony v0.0.0-20190831050304-94a6d3da9ba4 h1:OePImnPRBqS6JiHuVVq4Rfvt2yyhqMpWCB63PrwGrJE= +github.com/Arceliar/phony v0.0.0-20190831050304-94a6d3da9ba4/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/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= From a64f7320d8ce8cd8c3f079e9bf7d7d92b7cb00b0 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 31 Aug 2019 16:27:36 -0500 Subject: [PATCH 0302/1109] update phony, add mobile versions of util bytes functions that don't try to store anything --- go.mod | 2 +- go.sum | 4 ++-- src/util/bytes_mobile.go | 13 +++++++++++++ src/util/bytes_other.go | 18 ++++++++++++++++++ src/util/util.go | 14 -------------- 5 files changed, 34 insertions(+), 17 deletions(-) create mode 100644 src/util/bytes_mobile.go create mode 100644 src/util/bytes_other.go diff --git a/go.mod b/go.mod index 3b0af5a4..7a96c97e 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/yggdrasil-network/yggdrasil-go require ( - github.com/Arceliar/phony v0.0.0-20190831050304-94a6d3da9ba4 + github.com/Arceliar/phony v0.0.0-20190831212216-7018ff05d824 github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 github.com/hashicorp/go-syslog v1.0.0 github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 diff --git a/go.sum b/go.sum index df7ff630..b0b891e9 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/phony v0.0.0-20190831050304-94a6d3da9ba4 h1:OePImnPRBqS6JiHuVVq4Rfvt2yyhqMpWCB63PrwGrJE= -github.com/Arceliar/phony v0.0.0-20190831050304-94a6d3da9ba4/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= +github.com/Arceliar/phony v0.0.0-20190831212216-7018ff05d824 h1:JY7qh6yR87H8xgPUTYrrqa5cajb7zynKsbAdjhsVkyU= +github.com/Arceliar/phony v0.0.0-20190831212216-7018ff05d824/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/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= diff --git a/src/util/bytes_mobile.go b/src/util/bytes_mobile.go new file mode 100644 index 00000000..ceab763c --- /dev/null +++ b/src/util/bytes_mobile.go @@ -0,0 +1,13 @@ +//+build mobile + +package util + +// On mobile, just return a nil slice. +func GetBytes() []byte { + return nil +} + +// On mobile, don't do anything. +func PutBytes(bs []byte) { + return +} diff --git a/src/util/bytes_other.go b/src/util/bytes_other.go new file mode 100644 index 00000000..41b8bec0 --- /dev/null +++ b/src/util/bytes_other.go @@ -0,0 +1,18 @@ +//+build !mobile + +package util + +import "sync" + +// This is used to buffer recently used slices of bytes, to prevent allocations in the hot loops. +var byteStore = sync.Pool{New: func() interface{} { return []byte(nil) }} + +// Gets an empty slice from the byte store. +func GetBytes() []byte { + return byteStore.Get().([]byte)[:0] +} + +// Puts a slice in the store. +func PutBytes(bs []byte) { + byteStore.Put(bs) +} diff --git a/src/util/util.go b/src/util/util.go index a588a35c..97250122 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -6,7 +6,6 @@ import ( "runtime" "strconv" "strings" - "sync" "time" ) @@ -25,19 +24,6 @@ func UnlockThread() { runtime.UnlockOSThread() } -// This is used to buffer recently used slices of bytes, to prevent allocations in the hot loops. -var byteStore = sync.Pool{New: func() interface{} { return []byte(nil) }} - -// Gets an empty slice from the byte store. -func GetBytes() []byte { - return byteStore.Get().([]byte)[:0] -} - -// Puts a slice in the store. -func PutBytes(bs []byte) { - byteStore.Put(bs) -} - // Gets a slice of the appropriate length, reusing existing slice capacity when possible func ResizeBytes(bs []byte, length int) []byte { if cap(bs) >= length { From 0806f3e6eacc965a8019dbe04f93046eecd5809d Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 31 Aug 2019 16:49:13 -0500 Subject: [PATCH 0303/1109] upgrade phony --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7a96c97e..d2ca6b7c 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/yggdrasil-network/yggdrasil-go require ( - github.com/Arceliar/phony v0.0.0-20190831212216-7018ff05d824 + github.com/Arceliar/phony v0.0.0-20190831214819-9b642ea019ad github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 github.com/hashicorp/go-syslog v1.0.0 github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 diff --git a/go.sum b/go.sum index b0b891e9..f0fbacaf 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/phony v0.0.0-20190831212216-7018ff05d824 h1:JY7qh6yR87H8xgPUTYrrqa5cajb7zynKsbAdjhsVkyU= -github.com/Arceliar/phony v0.0.0-20190831212216-7018ff05d824/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= +github.com/Arceliar/phony v0.0.0-20190831214819-9b642ea019ad h1:670inqspOp+tAnSvkOBgrKGOIT4605Jt+6KGi2j2/S8= +github.com/Arceliar/phony v0.0.0-20190831214819-9b642ea019ad/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/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= From cabdc27a54cee361ef6635295dd9632086df017b Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 31 Aug 2019 17:39:05 -0500 Subject: [PATCH 0304/1109] change how nonce is tracked, so we allow packets if we've recently received a highest nonce ever, but don't bother tracking all received nonce values, this means duplicate packets are possible but only for a small window of time (and significantly reduces memory usage per session) --- src/yggdrasil/session.go | 107 ++++++++++++--------------------------- 1 file changed, 33 insertions(+), 74 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index d209a0de..8a6d16fc 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -6,7 +6,6 @@ package yggdrasil import ( "bytes" - "container/heap" "sync" "time" @@ -20,57 +19,40 @@ import ( // Duration that we keep track of old nonces per session, to allow some out-of-order packet delivery const nonceWindow = time.Second -// A heap of nonces, used with a map[nonce]time to allow out-of-order packets a little time to arrive without rejecting them -type nonceHeap []crypto.BoxNonce - -func (h nonceHeap) Len() int { return len(h) } -func (h nonceHeap) Less(i, j int) bool { return h[i].Minus(&h[j]) < 0 } -func (h nonceHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } -func (h *nonceHeap) Push(x interface{}) { *h = append(*h, x.(crypto.BoxNonce)) } -func (h *nonceHeap) Pop() interface{} { - l := len(*h) - var n crypto.BoxNonce - n, *h = (*h)[l-1], (*h)[:l-1] - return n -} -func (h nonceHeap) peek() *crypto.BoxNonce { return &h[0] } - // All the information we know about an active session. // This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API. type sessionInfo struct { - phony.Inbox // Protects all of the below, use it any time you read/change the contents of a session - sessions *sessions // - theirAddr address.Address // - theirSubnet address.Subnet // - theirPermPub crypto.BoxPubKey // - theirSesPub crypto.BoxPubKey // - mySesPub crypto.BoxPubKey // - mySesPriv crypto.BoxPrivKey // - sharedPermKey crypto.BoxSharedKey // used for session pings - sharedSesKey crypto.BoxSharedKey // derived from session keys - theirHandle crypto.Handle // - myHandle crypto.Handle // - theirNonce crypto.BoxNonce // - theirNonceHeap nonceHeap // priority queue to keep track of the lowest nonce we recently accepted - theirNonceMap map[crypto.BoxNonce]time.Time // time we added each nonce to the heap - myNonce crypto.BoxNonce // - theirMTU uint16 // - myMTU uint16 // - wasMTUFixed bool // Was the MTU fixed by a receive error? - timeOpened time.Time // Time the sessino was opened - time time.Time // Time we last received a packet - mtuTime time.Time // time myMTU was last changed - pingTime time.Time // time the first ping was sent since the last received packet - pingSend time.Time // time the last ping was sent - coords []byte // coords of destination - reset bool // reset if coords change - tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation - bytesSent uint64 // Bytes of real traffic sent in this session - bytesRecvd uint64 // Bytes of real traffic received in this session - init chan struct{} // Closed when the first session pong arrives, used to signal that the session is ready for initial use - cancel util.Cancellation // Used to terminate workers - conn *Conn // The associated Conn object - callbacks []chan func() // Finished work from crypto workers + phony.Inbox // Protects all of the below, use it any time you read/change the contents of a session + sessions *sessions // + theirAddr address.Address // + theirSubnet address.Subnet // + theirPermPub crypto.BoxPubKey // + theirSesPub crypto.BoxPubKey // + mySesPub crypto.BoxPubKey // + mySesPriv crypto.BoxPrivKey // + sharedPermKey crypto.BoxSharedKey // used for session pings + sharedSesKey crypto.BoxSharedKey // derived from session keys + theirHandle crypto.Handle // + myHandle crypto.Handle // + theirNonce crypto.BoxNonce // + myNonce crypto.BoxNonce // + theirMTU uint16 // + myMTU uint16 // + wasMTUFixed bool // Was the MTU fixed by a receive error? + timeOpened time.Time // Time the sessino was opened + time time.Time // Time we last received a packet + mtuTime time.Time // time myMTU was last changed + pingTime time.Time // time the first ping was sent since the last received packet + pingSend time.Time // time the last ping was sent + coords []byte // coords of destination + reset bool // reset if coords change + tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation + bytesSent uint64 // Bytes of real traffic sent in this session + bytesRecvd uint64 // Bytes of real traffic received in this session + init chan struct{} // Closed when the first session pong arrives, used to signal that the session is ready for initial use + cancel util.Cancellation // Used to terminate workers + conn *Conn // The associated Conn object + callbacks []chan func() // Finished work from crypto workers } func (sinfo *sessionInfo) reconfigure() { @@ -105,8 +87,6 @@ func (s *sessionInfo) _update(p *sessionPing) bool { s.theirHandle = p.Handle s.sharedSesKey = *crypto.GetSharedKey(&s.mySesPriv, &s.theirSesPub) s.theirNonce = crypto.BoxNonce{} - s.theirNonceHeap = nil - s.theirNonceMap = make(map[crypto.BoxNonce]time.Time) } if p.MTU >= 1280 || p.MTU == 0 { s.theirMTU = p.MTU @@ -420,36 +400,16 @@ func (sinfo *sessionInfo) _nonceIsOK(theirNonce *crypto.BoxNonce) bool { // This is newer than the newest nonce we've seen return true } - if len(sinfo.theirNonceHeap) > 0 { - if theirNonce.Minus(sinfo.theirNonceHeap.peek()) > 0 { - if _, isIn := sinfo.theirNonceMap[*theirNonce]; !isIn { - // This nonce is recent enough that we keep track of older nonces, but it's not one we've seen yet - return true - } - } - } - return false + return time.Since(sinfo.time) < nonceWindow } // Updates the nonce mask by (possibly) shifting the bitmask and setting the bit corresponding to this nonce to 1, and then updating the most recent nonce func (sinfo *sessionInfo) _updateNonce(theirNonce *crypto.BoxNonce) { - // Start with some cleanup - for len(sinfo.theirNonceHeap) > 64 { - if time.Since(sinfo.theirNonceMap[*sinfo.theirNonceHeap.peek()]) < nonceWindow { - // This nonce is still fairly new, so keep it around - break - } - // TODO? reallocate the map in some cases, to free unused map space? - delete(sinfo.theirNonceMap, *sinfo.theirNonceHeap.peek()) - heap.Pop(&sinfo.theirNonceHeap) - } if theirNonce.Minus(&sinfo.theirNonce) > 0 { // This nonce is the newest we've seen, so make a note of that sinfo.theirNonce = *theirNonce + sinfo.time = time.Now() } - // Add it to the heap/map so we know not to allow it again - heap.Push(&sinfo.theirNonceHeap, *theirNonce) - sinfo.theirNonceMap[*theirNonce] = time.Now() } // Resets all sessions to an uninitialized state. @@ -515,7 +475,6 @@ func (sinfo *sessionInfo) _recvPacket(p *wire_trafficPacket) { return } sinfo._updateNonce(&p.Nonce) - sinfo.time = time.Now() sinfo.bytesRecvd += uint64(len(bs)) sinfo.conn.recvMsg(sinfo, bs) } From 3a493fe8945227b20ecbb101af014c847d15d912 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 1 Sep 2019 11:08:25 -0500 Subject: [PATCH 0305/1109] gc more often on mobile --- src/util/bytes_mobile.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/util/bytes_mobile.go b/src/util/bytes_mobile.go index ceab763c..09e78050 100644 --- a/src/util/bytes_mobile.go +++ b/src/util/bytes_mobile.go @@ -2,6 +2,12 @@ package util +import "runtime/debug" + +func init() { + debug.SetGCPercent(25) +} + // On mobile, just return a nil slice. func GetBytes() []byte { return nil From e0ea845cdc26f47eeb2ac41e233d76b744f69918 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 1 Sep 2019 17:50:15 +0100 Subject: [PATCH 0306/1109] Update build --- build | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/build b/build index c1e5f863..7ca5f4fa 100755 --- a/build +++ b/build @@ -34,13 +34,15 @@ if [ $IOS ]; then gomobile bind -target ios -tags mobile -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \ github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil \ github.com/yggdrasil-network/yggdrasil-go/src/config \ - github.com/yggdrasil-network/yggdrasil-extras/src/mobile + github.com/yggdrasil-network/yggdrasil-extras/src/mobile \ + github.com/yggdrasil-network/yggdrasil-extras/src/dummy elif [ $ANDROID ]; then echo "Building aar for Android" gomobile bind -target android -tags mobile -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \ github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil \ github.com/yggdrasil-network/yggdrasil-go/src/config \ - github.com/yggdrasil-network/yggdrasil-extras/src/mobile + github.com/yggdrasil-network/yggdrasil-extras/src/mobile \ + github.com/yggdrasil-network/yggdrasil-extras/src/dummy else for CMD in `ls cmd/` ; do echo "Building: $CMD" From d08c2eb2375b50044b18c61c8dc83e5aa3e0b91a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 1 Sep 2019 13:04:10 -0500 Subject: [PATCH 0307/1109] stop exporting ReadNoCopy and WriteNoCopy, since we use the actor functions / callbacks and everything else should use Read and Write instead... --- src/yggdrasil/conn.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 0a4b84aa..352cf9d9 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -194,7 +194,7 @@ func (c *Conn) recvMsg(from phony.Actor, msg []byte) { } // Used internally by Read, the caller is responsible for util.PutBytes when they're done. -func (c *Conn) ReadNoCopy() ([]byte, error) { +func (c *Conn) readNoCopy() ([]byte, error) { var cancel util.Cancellation var doCancel bool phony.Block(c, func() { cancel, doCancel = c._getDeadlineCancellation(c.readDeadline) }) @@ -216,7 +216,7 @@ func (c *Conn) ReadNoCopy() ([]byte, error) { // Implements net.Conn.Read func (c *Conn) Read(b []byte) (int, error) { - bs, err := c.ReadNoCopy() + bs, err := c.readNoCopy() if err != nil { return 0, err } @@ -257,7 +257,7 @@ func (c *Conn) _write(msg FlowKeyMessage) error { } // WriteFrom should be called by a phony.Actor, and tells the Conn to send a message. -// This is used internaly by WriteNoCopy and Write. +// This is used internaly by Write. // If the callback is called with a non-nil value, then it is safe to reuse the argument FlowKeyMessage. func (c *Conn) WriteFrom(from phony.Actor, msg FlowKeyMessage, callback func(error)) { c.Act(from, func() { @@ -265,9 +265,9 @@ func (c *Conn) WriteFrom(from phony.Actor, msg FlowKeyMessage, callback func(err }) } -// WriteNoCopy is used internally by Write and makes use of WriteFrom under the hood. +// writeNoCopy is used internally by Write and makes use of WriteFrom under the hood. // The caller must not reuse the argument FlowKeyMessage when a nil error is returned. -func (c *Conn) WriteNoCopy(msg FlowKeyMessage) error { +func (c *Conn) writeNoCopy(msg FlowKeyMessage) error { var cancel util.Cancellation var doCancel bool phony.Block(c, func() { cancel, doCancel = c._getDeadlineCancellation(c.writeDeadline) }) @@ -292,7 +292,7 @@ func (c *Conn) WriteNoCopy(msg FlowKeyMessage) error { func (c *Conn) Write(b []byte) (int, error) { written := len(b) msg := FlowKeyMessage{Message: append(util.GetBytes(), b...)} - err := c.WriteNoCopy(msg) + err := c.writeNoCopy(msg) if err != nil { util.PutBytes(msg.Message) written = 0 From c53831696b5aee61823c0da62a96055d3f0e02fd Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 1 Sep 2019 13:06:25 -0500 Subject: [PATCH 0308/1109] make tun stop check that iface is not nil, in case it wasn't set for some reason (windows bugs) --- src/tuntap/tun.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 8e1e5b0c..74d055ee 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -189,9 +189,11 @@ func (tun *TunAdapter) Stop() error { func (tun *TunAdapter) _stop() error { tun.isOpen = false - // TODO: we have nothing that cleanly stops all the various goroutines opened // by TUN/TAP, e.g. readers/writers, sessions - tun.iface.Close() + if tun.iface != nil { + // Just in case we failed to start up the iface for some reason, this can apparently happen on Windows + tun.iface.Close() + } return nil } From 8d2c31d39cbeeb968976f94013933c904e233147 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 1 Sep 2019 13:20:48 -0500 Subject: [PATCH 0309/1109] add some artifical delay to windows netsh commands, since it seems like maybe they don't take effect immediately, and this was leading to races when setting MTU --- src/tuntap/tun_windows.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tuntap/tun_windows.go b/src/tuntap/tun_windows.go index a826c7ad..ea1515ff 100644 --- a/src/tuntap/tun_windows.go +++ b/src/tuntap/tun_windows.go @@ -5,6 +5,7 @@ import ( "fmt" "os/exec" "strings" + "time" water "github.com/yggdrasil-network/water" ) @@ -42,6 +43,7 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int tun.log.Traceln(string(output)) return err } + time.Sleep(time.Second) // FIXME artifical delay to give netsh time to take effect // Bring the interface back up cmd = exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=ENABLED") tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " ")) @@ -51,6 +53,7 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int tun.log.Traceln(string(output)) return err } + time.Sleep(time.Second) // FIXME artifical delay to give netsh time to take effect // Get a new iface iface, err = water.New(config) if err != nil { @@ -86,6 +89,7 @@ func (tun *TunAdapter) setupMTU(mtu int) error { tun.log.Traceln(string(output)) return err } + time.Sleep(time.Second) // FIXME artifical delay to give netsh time to take effect return nil } @@ -106,5 +110,6 @@ func (tun *TunAdapter) setupAddress(addr string) error { tun.log.Traceln(string(output)) return err } + time.Sleep(time.Second) // FIXME artifical delay to give netsh time to take effect return nil } From 8c52ccadf9fe4aeae0e2f29c81905f9efb7e2716 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 1 Sep 2019 14:07:00 -0500 Subject: [PATCH 0310/1109] make dial fail if a session to the same node already exists, fixes race between simultaneous connections to a node's 200 address and one of its 300 addresses, should also fix races between a search and an accepted listen --- src/yggdrasil/search.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index c128175b..322131ed 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -204,6 +204,11 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { if !isIn { panic("This should never happen") } + } else { + sinfo.callback(nil, 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)? sess.coords = res.Coords From 174ebceaacae482df95e7bb96f0848bb1588a4ff Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 1 Sep 2019 21:32:40 +0100 Subject: [PATCH 0311/1109] Fix hjson-go import in go.mod/go.sum --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index d2ca6b7c..6703d5bd 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ require ( github.com/Arceliar/phony v0.0.0-20190831214819-9b642ea019ad github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 github.com/hashicorp/go-syslog v1.0.0 - github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 + github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/mitchellh/mapstructure v1.1.2 github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 diff --git a/go.sum b/go.sum index f0fbacaf..60e6090f 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwM github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 h1:xmvkbxXDeN1ffWq8kvrhyqVYAO2aXuRBsbpxVTR+JyU= github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= +github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible h1:bLQ2Ve+eW65id3b8xEMQiAwJT4qGZeywAEMLvXjznvw= +github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk8851WTVWlqMC1ecJH07Ctz+Ezxx4u54g= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0/go.mod h1:rUi0/YffDo1oXBOGn1KRq7Fr07LX48XEBecQnmwjsAo= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= From 01517e5dc3b04ccdfd726fd7c011f437285e6df4 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 1 Sep 2019 22:43:27 +0100 Subject: [PATCH 0312/1109] Create doc.go for godoc preamble --- src/yggdrasil/doc.go | 156 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 src/yggdrasil/doc.go diff --git a/src/yggdrasil/doc.go b/src/yggdrasil/doc.go new file mode 100644 index 00000000..3010765a --- /dev/null +++ b/src/yggdrasil/doc.go @@ -0,0 +1,156 @@ +/* +Package yggdrasil implements the core functionality of the Yggdrasil Network. + +Introduction + +Yggdrasil is a proof-of-concept mesh network which provides end-to-end encrypted +communication between nodes in a decentralised fashion. The network is arranged +using a globally-agreed spanning tree which provides each node with a locator +(coordinates relative to the root) and a distributed hash table (DHT) mechanism +for finding other nodes. + +Each node also implements a router, which is responsible for encryption of +traffic, searches and connections, and a switch, which is responsible ultimately +for forwarding traffic across the network. + +While many Yggdrasil nodes in existence today are IP nodes - that is, they are +transporting IPv6 packets, like a kind of mesh VPN - it is also possible to +integrate Yggdrasil into your own applications and use it as a generic data +transport, similar to UDP. + +This library is what you need to integrate and use Yggdrasil in your own +application. + +Basics + +In order to start an Yggdrasil node, you should start by generating node +configuration, which amongst other things, includes encryption keypairs which +are used to generate the node's identity, and supply a logger which Yggdrasil's +output will be written to. + +This may look something like this: + + import ( + "os" + "github.com/gologme/log" + "github.com/yggdrasil-network/yggdrasil-go/src/config" + "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" + ) + + type node struct { + core yggdrasil.Core + config *config.NodeConfig + log *log.Logger + } + +You then can supply node configuration and a logger: + + n := node{} + n.log = log.New(os.Stdout, "", log.Flags()) + n.config = config.GenerateConfig() + +In the above example, we ask the config package to supply new configuration each +time, which results in fresh encryption keys and therefore a new identity. It is +normally preferable in most cases to persist node configuration onto the +filesystem or into some configuration store so that the node's identity does not +change each time that the program starts. Note that Yggdrasil will automatically +fill in any missing configuration items with sane defaults. + +Once you have supplied a logger and some node configuration, you can then start +the node: + + n.core.Start(n.config, n.log) + +Add some peers to connect to the network: + + n.core.AddPeer("tcp://some-host.net:54321", "") + n.core.AddPeer("tcp://[2001::1:2:3]:54321", "") + n.core.AddPeer("tcp://1.2.3.4:54321", "") + +You can also ask the API for information about our node: + + n.log.Println("My node ID is", n.core.NodeID()) + n.log.Println("My public key is", n.core.EncryptionPublicKey()) + n.log.Println("My coords are", n.core.Coords()) + +Incoming Connections + +Once your node is started, you can then listen for connections from other nodes +by asking the API for a Listener: + + listener, err := n.core.ConnListen() + if err != nil { + // ... + } + +The Listener has a blocking Accept function which will wait for incoming +connections from remote nodes. It will return a Conn when a connection is +received. If the node never receives any incoming connections then this function +can block forever, so be prepared for that, perhaps by listening in a separate +goroutine. + +Assuming that you have defined a myConnectionHandler function to deal with +incoming connections: + + for { + conn, err := listener.Accept() + if err != nil { + // ... + } + + // We've got a new connection + go myConnectionHandler(conn) + } + +Outgoing Connections + +If you know the node ID of the remote node that you want to talk to, you can +dial an outbound connection to it. To do this, you should first ask the API for +a Dialer: + + dialer, err := n.core.ConnDialer() + if err != nil { + // ... + } + +You can then dial using the 16-byte node ID in hexadecimal format, for example: + + conn, err := dialer.Dial("nodeid", "24a58cfce691ec016b0f698f7be1bee983cea263781017e99ad3ef62b4ef710a45d6c1a072c5ce46131bd574b78818c9957042cafeeed13966f349e94eb771bf") + if err != nil { + // ... + } + +Using Connections + +Conn objects are implementations of io.ReadWriteCloser, and as such, you can +Read, Write and Close them as necessary. + +For example, to write to the Conn from the supplied buffer: + + buf := []byte{1, 2, 3, 4, 5} + w, err := conn.Write(buf) + if err != nil { + // ... + } else { + // written w bytes + } + +Reading from the Conn into the supplied buffer: + + buf := make([]byte, 65535) + r, err := conn.Read(buf) + if err != nil { + // ... + } else { + // read r bytes + } + +When you are happy that a connection is no longer required, you can discard it: + + err := conn.Close() + if err != nil { + // ... + } + +*/ +package yggdrasil From 9e8e1c5a41cbceff8861479ec32e5ab723a26491 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 1 Sep 2019 23:10:46 +0100 Subject: [PATCH 0313/1109] Documentation updates --- src/yggdrasil/api.go | 84 +++++++++++++++++++++++++------------------- src/yggdrasil/doc.go | 20 +++++++++++ 2 files changed, 68 insertions(+), 36 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index d1753b69..f69ef7e9 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -16,29 +16,37 @@ import ( ) // Peer represents a single peer object. This contains information from the -// preferred switch port for this peer, although there may be more than one in -// reality. +// preferred switch port for this peer, although there may be more than one +// active switch port connection to the peer in reality. +// +// This struct is informational only - you cannot manipulate peer connections +// using instances of this struct. You should use the AddPeer or RemovePeer +// functions instead. type Peer struct { - PublicKey crypto.BoxPubKey - Endpoint string - BytesSent uint64 - BytesRecvd uint64 - Protocol string - Port uint64 - Uptime time.Duration + PublicKey crypto.BoxPubKey // The public key of the remote node + Endpoint string // The connection string used to connect to the peer + BytesSent uint64 // Number of bytes sent to this peer + BytesRecvd uint64 // Number of bytes received from this peer + Protocol string // The transport protocol that this peer is connected with, typically "tcp" + Port uint64 // Switch port number for this peer connection + Uptime time.Duration // How long this peering has been active for } // SwitchPeer represents a switch connection to a peer. Note that there may be // multiple switch peers per actual peer, e.g. if there are multiple connections // to a given node. +// +// This struct is informational only - you cannot manipulate switch peer +// connections using instances of this struct. You should use the AddPeer or +// RemovePeer functions instead. type SwitchPeer struct { - PublicKey crypto.BoxPubKey - Coords []uint64 - BytesSent uint64 - BytesRecvd uint64 - Port uint64 - Protocol string - Endpoint string + PublicKey crypto.BoxPubKey // The public key of the remote node + Coords []uint64 // The coordinates of the remote node + BytesSent uint64 // Number of bytes sent via this switch port + BytesRecvd uint64 // Number of bytes received via this switch port + Port uint64 // Switch port number for this switch peer + Protocol string // The transport protocol that this switch port is connected with, typically "tcp" + Endpoint string // The connection string used to connect to the switch peer } // DHTEntry represents a single DHT entry that has been learned or cached from @@ -64,32 +72,36 @@ type NodeInfoPayload []byte // congestion and a list of switch queues created in response to congestion on a // given link. type SwitchQueues struct { - Queues []SwitchQueue - Count uint64 - Size uint64 - HighestCount uint64 - HighestSize uint64 - MaximumSize uint64 + Queues []SwitchQueue // An array of SwitchQueue objects containing information about individual queues + Count uint64 // The current number of active switch queues + Size uint64 // The current total size of active switch queues + HighestCount uint64 // The highest recorded number of switch queues so far + HighestSize uint64 // The highest recorded total size of switch queues so far + MaximumSize uint64 // The maximum allowed total size of switch queues, as specified by config } -// SwitchQueue represents a single switch queue, which is created in response -// to congestion on a given link. +// SwitchQueue represents a single switch queue. Switch queues are only created +// in response to congestion on a given link and represent how much data has +// been temporarily cached for sending once the congestion has cleared. type SwitchQueue struct { - ID string - Size uint64 - Packets uint64 - Port uint64 + ID string // The ID of the switch queue + Size uint64 // The total size, in bytes, of the queue + Packets uint64 // The number of packets in the queue + Port uint64 // The switch port to which the queue applies } -// Session represents an open session with another node. +// Session represents an open session with another node. Sessions are opened in +// response to traffic being exchanged between two nodes using Conn objects. +// Note that sessions will automatically be closed by Yggdrasil if no traffic is +// exchanged for around two minutes. type Session struct { - PublicKey crypto.BoxPubKey - Coords []uint64 - BytesSent uint64 - BytesRecvd uint64 - MTU uint16 - Uptime time.Duration - WasMTUFixed bool + PublicKey crypto.BoxPubKey // The public key of the remote node + Coords []uint64 // The coordinates of the remote node + BytesSent uint64 // Bytes sent to the session + BytesRecvd uint64 // Bytes received from the session + MTU uint16 // The maximum supported message size of the session + Uptime time.Duration // How long this session has been active for + WasMTUFixed bool // This field is no longer used } // GetPeers returns one or more Peer objects containing information about active diff --git a/src/yggdrasil/doc.go b/src/yggdrasil/doc.go index 3010765a..44d39b66 100644 --- a/src/yggdrasil/doc.go +++ b/src/yggdrasil/doc.go @@ -125,6 +125,9 @@ Using Connections Conn objects are implementations of io.ReadWriteCloser, and as such, you can Read, Write and Close them as necessary. +Each Read or Write operation can deal with a buffer with a maximum size of 65535 +bytes - any bigger than this and the operation will return an error. + For example, to write to the Conn from the supplied buffer: buf := []byte{1, 2, 3, 4, 5} @@ -152,5 +155,22 @@ When you are happy that a connection is no longer required, you can discard it: // ... } +Limitations + +You should be aware of the following limitations when working with the Yggdrasil +library: + +Individual messages written through Yggdrasil connections can not exceed 65535 +bytes in size. Yggdrasil has no concept of fragmentation, so if you try to send +a message that exceeds 65535 bytes in size, it will be dropped altogether and +an error will be returned. + +Yggdrasil connections are unreliable by nature. Messages are delivered on a +best-effort basis, and employs congestion control where appropriate to ensure +that congestion does not affect message transport, but Yggdrasil will not +retransmit any messages that have been lost. If reliable delivery is important +then you should manually implement acknowledgement and retransmission of +messages. + */ package yggdrasil From 935324efe12b485f135a90e75f8adbf4189e3628 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 1 Sep 2019 23:33:51 +0100 Subject: [PATCH 0314/1109] Update conn.go godoc --- src/yggdrasil/conn.go | 57 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 352cf9d9..a2dc1fa4 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -53,6 +53,9 @@ func (e *ConnError) Closed() bool { return e.closed } +// The Conn struct is a reference to an active connection session between the +// local node and a remote node. Conn implements the io.ReadWriteCloser +// interface and is used to send and receive traffic with a remote node. type Conn struct { phony.Inbox core *Core @@ -78,6 +81,11 @@ func newConn(core *Core, nodeID *crypto.NodeID, nodeMask *crypto.NodeID, session return &conn } +// String returns a string that uniquely identifies a connection. Currently this +// takes a form similar to "conn=0x0000000", which contains a memory reference +// to the Conn object. While this value should always be unique for each Conn +// object, the format of this is not strictly defined and may change in the +// future. func (c *Conn) String() string { var s string phony.Block(c, func() { s = fmt.Sprintf("conn=%p", c) }) @@ -159,7 +167,12 @@ func (c *Conn) _getDeadlineCancellation(t *time.Time) (util.Cancellation, bool) } } -// SetReadCallback sets a callback which will be called whenever a packet is received. +// SetReadCallback allows you to specify a function that will be called whenever +// a packet is received. This should be used if you wish to implement +// asynchronous patterns for receiving data from the remote node. +// +// Note that if a read callback has been supplied, you should no longer attempt +// to use the synchronous Read function. func (c *Conn) SetReadCallback(callback func([]byte)) { c.Act(nil, func() { c.readCallback = callback @@ -214,7 +227,14 @@ func (c *Conn) readNoCopy() ([]byte, error) { } } -// Implements net.Conn.Read +// Read allows you to read from the connection in a synchronous fashion. The +// function will block up until the point that either new data is available, the +// connection has been closed or the read deadline has been reached. If the +// function succeeds, the number of bytes read from the connection will be +// returned. Otherwise, an error condition will be returned. +// +// Note that you can also implement asynchronous reads by using SetReadCallback. +// If you do that, you should no longer attempt to use the Read function. func (c *Conn) Read(b []byte) (int, error) { bs, err := c.readNoCopy() if err != nil { @@ -256,9 +276,9 @@ func (c *Conn) _write(msg FlowKeyMessage) error { return nil } -// WriteFrom should be called by a phony.Actor, and tells the Conn to send a message. -// This is used internaly by Write. -// If the callback is called with a non-nil value, then it is safe to reuse the argument FlowKeyMessage. +// WriteFrom should be called by a phony.Actor, and tells the Conn to send a +// message. This is used internaly by Write. If the callback is called with a +// non-nil value, then it is safe to reuse the argument FlowKeyMessage. func (c *Conn) WriteFrom(from phony.Actor, msg FlowKeyMessage, callback func(error)) { c.Act(from, func() { callback(c._write(msg)) @@ -288,7 +308,11 @@ func (c *Conn) writeNoCopy(msg FlowKeyMessage) error { return err } -// Write implement the Write function of a net.Conn, and makes use of WriteNoCopy under the hood. +// Write allows you to write to the connection in a synchronous fashion. This +// function may block until either the write has completed, the connection has +// been closed or the write deadline has been reached. If the function succeeds, +// the number of written bytes is returned. Otherwise, an error condition is +// returned. func (c *Conn) Write(b []byte) (int, error) { written := len(b) msg := FlowKeyMessage{Message: append(util.GetBytes(), b...)} @@ -300,6 +324,10 @@ func (c *Conn) Write(b []byte) (int, error) { return written, err } +// Close will close an open connection and any blocking operations on the +// connection will unblock and return. From this point forward, the connection +// can no longer be used and you should no longer attempt to Read or Write to +// the connection. func (c *Conn) Close() (err error) { phony.Block(c, func() { if c.session != nil { @@ -314,10 +342,13 @@ func (c *Conn) Close() (err error) { return } +// 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. func (c *Conn) LocalAddr() crypto.NodeID { return *crypto.GetNodeID(&c.core.boxPub) } +// RemoteAddr returns the complete node ID of the remote side of the connection. 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... var n crypto.NodeID @@ -325,18 +356,32 @@ func (c *Conn) RemoteAddr() crypto.NodeID { return n } +// SetDeadline is equivalent to calling both SetReadDeadline and +// SetWriteDeadline with the same value, configuring the maximum amount of time +// that synchronous Read and Write operations can block for. If no deadline is +// configured, Read and Write operations can potentially block indefinitely. func (c *Conn) SetDeadline(t time.Time) error { c.SetReadDeadline(t) c.SetWriteDeadline(t) return nil } +// SetReadDeadline configures the maximum amount of time that a synchronous Read +// operation can block for. A Read operation will unblock at the point that the +// read deadline is reached if no other condition (such as data arrival or +// connection closure) happens first. If no deadline is configured, Read +// operations can potentially block indefinitely. func (c *Conn) SetReadDeadline(t time.Time) error { // TODO warn that this can block while waiting for the Conn actor to run, so don't call it from other actors... phony.Block(c, func() { c.readDeadline = &t }) return nil } +// SetWriteDeadline configures the maximum amount of time that a synchronous +// Write operation can block for. A Write operation will unblock at the point +// that the read deadline is reached if no other condition (such as data sending +// or connection closure) happens first. If no deadline is configured, Write +// operations can potentially block indefinitely. func (c *Conn) SetWriteDeadline(t time.Time) error { // TODO warn that this can block while waiting for the Conn actor to run, so don't call it from other actors... phony.Block(c, func() { c.writeDeadline = &t }) From 903a8921fcf975c88eea561412d48c8da3ea7982 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 1 Sep 2019 23:47:47 +0100 Subject: [PATCH 0315/1109] Update api.go godoc --- src/yggdrasil/api.go | 58 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index f69ef7e9..35269ebb 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -248,7 +248,10 @@ func (c *Core) GetSessions() []Session { return sessions } -// ConnListen returns a listener for Yggdrasil session connections. +// ConnListen returns a listener for Yggdrasil session connections. You can only +// call this function once as each Yggdrasil node can only have a single +// ConnListener. Make sure to keep the reference to this for as long as it is +// needed. func (c *Core) ConnListen() (*Listener, error) { c.router.sessions.listenerMutex.Lock() defer c.router.sessions.listenerMutex.Unlock() @@ -263,7 +266,10 @@ func (c *Core) ConnListen() (*Listener, error) { return c.router.sessions.listener, nil } -// ConnDialer returns a dialer for Yggdrasil session connections. +// ConnDialer returns a dialer for Yggdrasil session connections. Since +// ConnDialers are stateless, you can request as many dialers as you like, +// although ideally you should request only one and keep the reference to it for +// as long as it is needed. func (c *Core) ConnDialer() (*Dialer, error) { return &Dialer{ core: c, @@ -277,48 +283,69 @@ func (c *Core) ListenTCP(uri string) (*TcpListener, error) { return c.link.tcp.listen(uri) } -// NodeID gets the node ID. +// NodeID gets the node ID. This is derived from your router encryption keys. +// Remote nodes wanting to open connections to your node will need to know your +// node ID. func (c *Core) NodeID() *crypto.NodeID { return crypto.GetNodeID(&c.boxPub) } -// TreeID gets the tree ID. +// TreeID gets the tree ID. This is derived from your switch signing keys. There +// is typically no need to share this key. func (c *Core) TreeID() *crypto.TreeID { return crypto.GetTreeID(&c.sigPub) } -// SigningPublicKey gets the node's signing public key. +// SigningPublicKey gets the node's signing public key, as used by the switch. func (c *Core) SigningPublicKey() string { return hex.EncodeToString(c.sigPub[:]) } -// EncryptionPublicKey gets the node's encryption public key. +// EncryptionPublicKey gets the node's encryption public key, as used by the +// router. func (c *Core) EncryptionPublicKey() string { return hex.EncodeToString(c.boxPub[:]) } -// Coords returns the current coordinates of the node. +// Coords returns the current coordinates of the node. Note that these can +// change at any time for a number of reasons, not limited to but including +// changes to peerings (either yours or a parent nodes) or changes to the network +// root. +// +// This function may return an empty array - this is normal behaviour if either +// you are the root of the network that you are connected to, or you are not +// connected to any other nodes (effectively making you the root of a +// single-node network). func (c *Core) Coords() []uint64 { table := c.switchTable.table.Load().(lookupTable) return wire_coordsBytestoUint64s(table.self.getCoords()) } // Address gets the IPv6 address of the Yggdrasil node. This is always a /128 -// address. +// address. The IPv6 address is only relevant when the node is operating as an +// IP router and often is meaningless when embedded into an application, unless +// that application also implements either VPN functionality or deals with IP +// packets specifically. func (c *Core) Address() net.IP { address := net.IP(address.AddrForNodeID(c.NodeID())[:]) return address } // Subnet gets the routed IPv6 subnet of the Yggdrasil node. This is always a -// /64 subnet. +// /64 subnet. The IPv6 subnet is only relevant when the node is operating as an +// IP router and often is meaningless when embedded into an application, unless +// that application also implements either VPN functionality or deals with IP +// packets specifically. func (c *Core) Subnet() net.IPNet { subnet := address.SubnetForNodeID(c.NodeID())[:] subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0) return net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)} } -// MyNodeInfo gets the currently configured nodeinfo. +// MyNodeInfo gets the currently configured nodeinfo. NodeInfo is typically +// specified through the "NodeInfo" option in the node configuration or using +// the SetNodeInfo function, although it may also contain other built-in values +// such as "buildname", "buildversion" etc. func (c *Core) MyNodeInfo() NodeInfoPayload { return c.router.nodeinfo.getNodeInfo() } @@ -368,7 +395,9 @@ func (c *Core) SetSessionGatekeeper(f func(pubkey *crypto.BoxPubKey, initiator b } // SetLogger sets the output logger of the Yggdrasil node after startup. This -// may be useful if you want to redirect the output later. +// may be useful if you want to redirect the output later. Note that this +// expects a Logger from the github.com/gologme/log package and not from Go's +// built-in log package. func (c *Core) SetLogger(log *log.Logger) { c.log = log } @@ -418,12 +447,17 @@ func (c *Core) DisconnectPeer(port uint64) error { } // GetAllowedEncryptionPublicKeys returns the public keys permitted for incoming -// peer connections. +// peer connections. If this list is empty then all incoming peer connections +// are accepted by default. func (c *Core) GetAllowedEncryptionPublicKeys() []string { return c.peers.getAllowedEncryptionPublicKeys() } // AddAllowedEncryptionPublicKey whitelists a key for incoming peer connections. +// By default all incoming peer connections are accepted, but adding public keys +// to the whitelist using this function enables strict checking from that point +// forward. Once the whitelist is enabled, only peer connections from +// whitelisted public keys will be accepted. func (c *Core) AddAllowedEncryptionPublicKey(bstr string) (err error) { c.peers.addAllowedEncryptionPublicKey(bstr) return nil From cd99d04bd41632c8541bfc6b5c1b62549f47db67 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 1 Sep 2019 18:53:45 -0500 Subject: [PATCH 0316/1109] document address, crypto, and util --- src/address/address.go | 34 ++++++++++++------------ src/crypto/crypto.go | 57 +++++++++++++++++++++++++++++++++++++--- src/util/bytes_mobile.go | 6 +++-- src/util/bytes_other.go | 4 +-- src/util/cancellation.go | 20 +++++++++++--- src/util/util.go | 23 +++++++--------- 6 files changed, 104 insertions(+), 40 deletions(-) diff --git a/src/address/address.go b/src/address/address.go index 5c13257b..c30367e8 100644 --- a/src/address/address.go +++ b/src/address/address.go @@ -2,21 +2,21 @@ package address import "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 -// subnet represents an IPv6 /64 subnet in the yggdrasil subnet range. +// Subnet represents an IPv6 /64 subnet in the yggdrasil subnet range. type Subnet [8]byte -// address_prefix is the prefix used for all addresses and subnets in the network. +// GetPrefix returns the address prefix used by yggdrasil. // The current implementation requires this to be a muliple of 8 bits + 7 bits. // The 8th bit of the last byte is used to signal nodes (0) or /64 prefixes (1). -// Nodes that configure this differently will be unable to communicate with eachother, though routing and the DHT machinery *should* still work. +// Nodes that configure this differently will be unable to communicate with eachother using IP packets, though routing and the DHT machinery *should* still work. func GetPrefix() [1]byte { return [...]byte{0x02} } -// isValid returns true if an address falls within the range used by nodes in the network. +// IsValid returns true if an address falls within the range used by nodes in the network. func (a *Address) IsValid() bool { prefix := GetPrefix() for idx := range prefix { @@ -27,7 +27,7 @@ func (a *Address) IsValid() bool { return true } -// isValid returns true if a prefix falls within the range usable by the network. +// IsValid returns true if a prefix falls within the range usable by the network. func (s *Subnet) IsValid() bool { prefix := GetPrefix() l := len(prefix) @@ -39,8 +39,8 @@ func (s *Subnet) IsValid() bool { return (*s)[l-1] == prefix[l-1]|0x01 } -// address_addrForNodeID takes a *NodeID as an argument and returns an *address. -// This subnet begins with the address prefix, with the last bit set to 0 to indicate an address. +// AddrForNodeID takes a *NodeID as an argument and returns an *Address. +// This address begins with the contents of GetPrefix(), with the last bit set to 0 to indicate an address. // The following 8 bits are set to the number of leading 1 bits in the NodeID. // The NodeID, excluding the leading 1 bits and the first leading 0 bit, is truncated to the appropriate length and makes up the remainder of the address. func AddrForNodeID(nid *crypto.NodeID) *Address { @@ -80,7 +80,7 @@ func AddrForNodeID(nid *crypto.NodeID) *Address { return &addr } -// address_subnetForNodeID takes a *NodeID as an argument and returns a *subnet. +// SubnetForNodeID takes a *NodeID as an argument and returns an *Address. // This subnet begins with the address prefix, with the last bit set to 1 to indicate a prefix. // The following 8 bits are set to the number of leading 1 bits in the NodeID. // The NodeID, excluding the leading 1 bits and the first leading 0 bit, is truncated to the appropriate length and makes up the remainder of the subnet. @@ -96,10 +96,10 @@ func SubnetForNodeID(nid *crypto.NodeID) *Subnet { return &snet } -// getNodeIDandMask returns two *NodeID. -// The first is a NodeID with all the bits known from the address set to their correct values. -// The second is a bitmask with 1 bit set for each bit that was known from the address. -// This is used to look up NodeIDs in the DHT and tell if they match an address. +// GetNodeIDandMask returns two *NodeID. +// The first is a NodeID with all the bits known from the Address set to their correct values. +// The second is a bitmask with 1 bit set for each bit that was known from the Address. +// This is used to look up NodeIDs in the DHT and tell if they match an Address. func (a *Address) GetNodeIDandMask() (*crypto.NodeID, *crypto.NodeID) { // Mask is a bitmask to mark the bits visible from the address // This means truncated leading 1s, first leading 0, and visible part of addr @@ -126,10 +126,10 @@ func (a *Address) GetNodeIDandMask() (*crypto.NodeID, *crypto.NodeID) { return &nid, &mask } -// getNodeIDandMask returns two *NodeID. -// The first is a NodeID with all the bits known from the address set to their correct values. -// The second is a bitmask with 1 bit set for each bit that was known from the subnet. -// This is used to look up NodeIDs in the DHT and tell if they match a subnet. +// GetNodeIDandMask returns two *NodeID. +// 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. +// This is used to look up NodeIDs in the DHT and tell if they match a Subnet. func (s *Subnet) GetNodeIDandMask() (*crypto.NodeID, *crypto.NodeID) { // As with the address version, but visible parts of the subnet prefix instead var nid crypto.NodeID diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go index 75736ba7..55f26f74 100644 --- a/src/crypto/crypto.go +++ b/src/crypto/crypto.go @@ -26,12 +26,21 @@ import ( // NodeID and TreeID +// NodeIDLen is the length (in bytes) of a NodeID. const NodeIDLen = sha512.Size + +// TreeIDLen is the length (in bytes) of a TreeID. const TreeIDLen = sha512.Size + +// handleLen is the length (in bytes) of a Handle. const handleLen = 8 +// NodeID is how a yggdrasil node is identified in the DHT, and is used to derive IPv6 addresses and subnets in the main executable. It is a sha512sum hash of the node's BoxPubKey type NodeID [NodeIDLen]byte + +// TreeID is how a yggdrasil node is identified in the root selection algorithm used to construct the spanning tree. type TreeID [TreeIDLen]byte + type Handle [handleLen]byte func (n *NodeID) String() string { @@ -69,16 +78,19 @@ func (n *NodeID) PrefixLength() int { return len } +// GetNodeID returns the NodeID associated with a BoxPubKey. func GetNodeID(pub *BoxPubKey) *NodeID { h := sha512.Sum512(pub[:]) return (*NodeID)(&h) } +// GetTreeID returns the TreeID associated with a BoxPubKey func GetTreeID(pub *SigPubKey) *TreeID { h := sha512.Sum512(pub[:]) return (*TreeID)(&h) } +// NewHandle returns a new (cryptographically random) Handle, used by the session code to identify which session an incoming packet is associated with. func NewHandle() *Handle { var h Handle _, err := rand.Read(h[:]) @@ -92,14 +104,25 @@ func NewHandle() *Handle { // Signatures +// SigPubKeyLen is the length of a SigPubKey in bytes. const SigPubKeyLen = ed25519.PublicKeySize + +// SigPrivKeyLen is the length of a SigPrivKey in bytes. const SigPrivKeyLen = ed25519.PrivateKeySize + +// SigLen is the length of SigBytes. const SigLen = ed25519.SignatureSize +// SigPubKey is a public ed25519 signing key. type SigPubKey [SigPubKeyLen]byte + +// SigPrivKey is a private ed25519 signing key. type SigPrivKey [SigPrivKeyLen]byte + +// SigBytes is an ed25519 signature. type SigBytes [SigLen]byte +// NewSigKeys generates a public/private ed25519 key pair. func NewSigKeys() (*SigPubKey, *SigPrivKey) { var pub SigPubKey var priv SigPrivKey @@ -112,6 +135,7 @@ func NewSigKeys() (*SigPubKey, *SigPrivKey) { return &pub, &priv } +// Sign returns the SigBytes signing a message. func Sign(priv *SigPrivKey, msg []byte) *SigBytes { var sig SigBytes sigSlice := ed25519.Sign(priv[:], msg) @@ -119,12 +143,14 @@ func Sign(priv *SigPrivKey, msg []byte) *SigBytes { return &sig } +// Verify returns true if the provided signature matches the key and message. func Verify(pub *SigPubKey, msg []byte, sig *SigBytes) bool { // Should sig be an array instead of a slice?... // It's fixed size, but return ed25519.Verify(pub[:], msg, sig[:]) } +// Public returns the SigPubKey associated with this SigPrivKey. func (p SigPrivKey) Public() SigPubKey { priv := make(ed25519.PrivateKey, ed25519.PrivateKeySize) copy(priv[:], p[:]) @@ -138,17 +164,34 @@ func (p SigPrivKey) Public() SigPubKey { // NaCl-like crypto "box" (curve25519+xsalsa20+poly1305) +// BoxPubKeyLen is the length of a BoxPubKey in bytes. const BoxPubKeyLen = 32 + +// BoxPrivKeyLen is the length of a BoxPrivKey in bytes. const BoxPrivKeyLen = 32 + +// BoxSharedKeyLen is the length of a BoxSharedKey in bytes. const BoxSharedKeyLen = 32 + +// BoxNonceLen is the length of a BoxNonce in bytes. const BoxNonceLen = 24 + +// BoxOverhead is the length of the overhead from boxing something. const BoxOverhead = box.Overhead +// BoxPubKey is a NaCl-like "box" public key (curve25519+xsalsa20+poly1305). type BoxPubKey [BoxPubKeyLen]byte + +// BoxPrivKey is a NaCl-like "box" private key (curve25519+xsalsa20+poly1305). type BoxPrivKey [BoxPrivKeyLen]byte + +// BoxSharedKey is a NaCl-like "box" shared key (curve25519+xsalsa20+poly1305). type BoxSharedKey [BoxSharedKeyLen]byte + +// BoxNonce is the nonce used in NaCl-like crypto "box" operations (curve25519+xsalsa20+poly1305), and must not be reused for different messages encrypted using the same BoxSharedKey. type BoxNonce [BoxNonceLen]byte +// NewBoxKeys generates a new pair of public/private crypto box keys. func NewBoxKeys() (*BoxPubKey, *BoxPrivKey) { pubBytes, privBytes, err := box.GenerateKey(rand.Reader) if err != nil { @@ -159,6 +202,7 @@ func NewBoxKeys() (*BoxPubKey, *BoxPrivKey) { return pub, priv } +// GetSharedKey returns the shared key derived from your private key and the destination's public key. func GetSharedKey(myPrivKey *BoxPrivKey, othersPubKey *BoxPubKey) *BoxSharedKey { var shared [BoxSharedKeyLen]byte @@ -168,6 +212,7 @@ func GetSharedKey(myPrivKey *BoxPrivKey, return (*BoxSharedKey)(&shared) } +// BoxOpen returns a message and true if it successfull opens a crypto box using the provided shared key and nonce. func BoxOpen(shared *BoxSharedKey, boxed []byte, nonce *BoxNonce) ([]byte, bool) { @@ -178,6 +223,9 @@ func BoxOpen(shared *BoxSharedKey, return unboxed, success } +// BoxSeal seals a crypto box using the provided shared key, returning the box and the nonce needed to decrypt it. +// If nonce is nil, a random BoxNonce will be used and returned. +// If nonce is non-nil, then nonce.Increment() will be called before using it, and the incremented BoxNonce is what is returned. func BoxSeal(shared *BoxSharedKey, unboxed []byte, nonce *BoxNonce) ([]byte, *BoxNonce) { if nonce == nil { nonce = NewBoxNonce() @@ -190,6 +238,7 @@ func BoxSeal(shared *BoxSharedKey, unboxed []byte, nonce *BoxNonce) ([]byte, *Bo return boxed, nonce } +// NewBoxNonce generates a (cryptographically) random BoxNonce. func NewBoxNonce() *BoxNonce { var nonce BoxNonce _, err := rand.Read(nonce[:]) @@ -204,6 +253,7 @@ func NewBoxNonce() *BoxNonce { return &nonce } +// Increment adds 2 to a BoxNonce, which is useful if one node intends to send only with odd BoxNonce values, and the other only with even BoxNonce values. func (n *BoxNonce) Increment() { oldNonce := *n n[len(n)-1] += 2 @@ -214,6 +264,7 @@ func (n *BoxNonce) Increment() { } } +// Public returns the BoxPubKey associated with this BoxPrivKey. func (p BoxPrivKey) Public() BoxPubKey { var boxPub [BoxPubKeyLen]byte var boxPriv [BoxPrivKeyLen]byte @@ -222,9 +273,9 @@ func (p BoxPrivKey) Public() BoxPubKey { return boxPub } -// Used to subtract one nonce from another, staying in the range +- 64. -// This is used by the nonce progression machinery to advance the bitmask of recently received packets (indexed by nonce), or to check the appropriate bit of the bitmask. -// It's basically part of the machinery that prevents replays and duplicate packets. +// Minus is the result of subtracting the provided BoNonce from this BoxNonce, bounded at +- 64. +// It's primarily used to determine if a new BoxNonce is higher than the last known BoxNonce from a crypto session, and by how much. +// This is used in the machinery that makes sure replayed packets can't keep a session open indefinitely or stuck using old/bad information about a node. func (n *BoxNonce) Minus(m *BoxNonce) int64 { diff := int64(0) for idx := range n { diff --git a/src/util/bytes_mobile.go b/src/util/bytes_mobile.go index 09e78050..f862c0cd 100644 --- a/src/util/bytes_mobile.go +++ b/src/util/bytes_mobile.go @@ -8,12 +8,14 @@ func init() { debug.SetGCPercent(25) } -// On mobile, just return a nil slice. +// GetBytes always returns a nil slice on mobile platforms. func GetBytes() []byte { return nil } -// On mobile, don't do anything. +// PutBytes does literally nothing on mobile platforms. +// This is done rather than keeping a free list of bytes on platforms with memory constraints. +// It's needed to help keep memory usage low enough to fall under the limits set for e.g. iOS NEPacketTunnelProvider apps. func PutBytes(bs []byte) { return } diff --git a/src/util/bytes_other.go b/src/util/bytes_other.go index 41b8bec0..7c966087 100644 --- a/src/util/bytes_other.go +++ b/src/util/bytes_other.go @@ -7,12 +7,12 @@ import "sync" // This is used to buffer recently used slices of bytes, to prevent allocations in the hot loops. var byteStore = sync.Pool{New: func() interface{} { return []byte(nil) }} -// Gets an empty slice from the byte store. +// GetBytes returns a 0-length (possibly nil) slice of bytes from a free list, so it may have a larger capacity. func GetBytes() []byte { return byteStore.Get().([]byte)[:0] } -// Puts a slice in the store. +// PutBytes stores a slice in a free list, where it can potentially be reused to prevent future allocations. func PutBytes(bs []byte) { byteStore.Put(bs) } diff --git a/src/util/cancellation.go b/src/util/cancellation.go index af4721bb..6b2002c8 100644 --- a/src/util/cancellation.go +++ b/src/util/cancellation.go @@ -7,15 +7,22 @@ import ( "time" ) +// Cancellation is used to signal when things should shut down, such as signaling anything associated with a Conn to exit. +// This is and is similar to a context, but with an error to specify the reason for the cancellation. type Cancellation interface { - Finished() <-chan struct{} - Cancel(error) error - Error() error + Finished() <-chan struct{} // Finished returns a channel which will be closed when Cancellation.Cancel is first called. + Cancel(error) error // Cancel closes the channel returned by Finished and sets the error returned by error, or else returns the existing error if the Cancellation has already run. + Error() error // Error returns the error provided to Cancel, or nil if no error has been provided. } +// CancellationFinalized is an error returned if a cancellation object was garbage collected and the finalizer was run. +// If you ever see this, then you're probably doing something wrong with your code. var CancellationFinalized = errors.New("finalizer called") + +// CancellationTimeoutError is used when a CancellationWithTimeout or CancellationWithDeadline is cancelled due to said timeout. var CancellationTimeoutError = errors.New("timeout") +// CancellationFinalizer is set as a finalizer when creating a new cancellation with NewCancellation(), and generally shouldn't be needed by the user, but is included in case other implementations of the same interface want to make use of it. func CancellationFinalizer(c Cancellation) { c.Cancel(CancellationFinalized) } @@ -27,6 +34,7 @@ type cancellation struct { done bool } +// NewCancellation returns a pointer to a struct satisfying the Cancellation interface. func NewCancellation() Cancellation { c := cancellation{ cancel: make(chan struct{}), @@ -35,10 +43,12 @@ func NewCancellation() Cancellation { return &c } +// Finished returns a channel which will be closed when Cancellation.Cancel is first called. func (c *cancellation) Finished() <-chan struct{} { return c.cancel } +// Cancel closes the channel returned by Finished and sets the error returned by error, or else returns the existing error if the Cancellation has already run. func (c *cancellation) Cancel(err error) error { c.mutex.Lock() defer c.mutex.Unlock() @@ -52,6 +62,7 @@ func (c *cancellation) Cancel(err error) error { } } +// Error returns the error provided to Cancel, or nil if no error has been provided. func (c *cancellation) Error() error { c.mutex.RLock() err := c.err @@ -59,6 +70,7 @@ func (c *cancellation) Error() error { return err } +// CancellationChild returns a new Cancellation which can be Cancelled independently of the parent, but which will also be Cancelled if the parent is Cancelled first. func CancellationChild(parent Cancellation) Cancellation { child := NewCancellation() go func() { @@ -71,6 +83,7 @@ func CancellationChild(parent Cancellation) Cancellation { return child } +// CancellationWithTimeout returns a ChildCancellation that will automatically be Cancelled with a CancellationTimeoutError after the timeout. func CancellationWithTimeout(parent Cancellation, timeout time.Duration) Cancellation { child := CancellationChild(parent) go func() { @@ -85,6 +98,7 @@ func CancellationWithTimeout(parent Cancellation, timeout time.Duration) Cancell return child } +// CancellationWithTimeout returns a ChildCancellation that will automatically be Cancelled with a CancellationTimeoutError after the specified deadline. func CancellationWithDeadline(parent Cancellation, deadline time.Time) Cancellation { return CancellationWithTimeout(parent, deadline.Sub(time.Now())) } diff --git a/src/util/util.go b/src/util/util.go index 97250122..c73faf68 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -9,22 +9,22 @@ import ( "time" ) -// A wrapper around runtime.Gosched() so it doesn't need to be imported elsewhere. +// Yield just executes runtime.Gosched(), and is included so we don't need to explicitly import runtime elsewhere. func Yield() { runtime.Gosched() } -// A wrapper around runtime.LockOSThread() so it doesn't need to be imported elsewhere. +// LockThread executes runtime.LockOSThread(), and is included so we don't need to explicitly import runtime elsewhere. func LockThread() { runtime.LockOSThread() } -// A wrapper around runtime.UnlockOSThread() so it doesn't need to be imported elsewhere. +// UnlockThread executes runtime.UnlockOSThread(), and is included so we don't need to explicitly import runtime elsewhere. func UnlockThread() { runtime.UnlockOSThread() } -// Gets a slice of the appropriate length, reusing existing slice capacity when possible +// ResizeBytes returns a slice of the specified length. If the provided slice has sufficient capacity, it will be resized and returned rather than allocating a new slice. func ResizeBytes(bs []byte, length int) []byte { if cap(bs) >= length { return bs[:length] @@ -33,7 +33,7 @@ func ResizeBytes(bs []byte, length int) []byte { } } -// This is a workaround to go's broken timer implementation +// TimerStop stops a timer and makes sure the channel is drained, returns true if the timer was stopped before firing. func TimerStop(t *time.Timer) bool { stopped := t.Stop() select { @@ -43,10 +43,8 @@ func TimerStop(t *time.Timer) bool { return stopped } -// Run a blocking function with a timeout. -// Returns true if the function returns. -// Returns false if the timer fires. -// The blocked function remains blocked--the caller is responsible for somehow killing it. +// FuncTimeout runs the provided function in a separate goroutine, and returns true if the function finishes executing before the timeout passes, or false if the timeout passes. +// It includes no mechanism to stop the function if the timeout fires, so the user is expected to do so on their own (such as with a Cancellation or a context). func FuncTimeout(f func(), timeout time.Duration) bool { success := make(chan struct{}) go func() { @@ -63,9 +61,8 @@ func FuncTimeout(f func(), timeout time.Duration) bool { } } -// This calculates the difference between two arrays and returns items -// that appear in A but not in B - useful somewhat when reconfiguring -// and working out what configuration items changed +// Difference loops over two strings and returns the elements of A which do not appear in B. +// This is somewhat useful when needing to determine which elements of a configuration file have changed. func Difference(a, b []string) []string { ab := []string{} mb := map[string]bool{} @@ -93,7 +90,7 @@ func DecodeCoordString(in string) (out []uint64) { return out } -// GetFlowLabel takes an IP packet as an argument and returns some information about the traffic flow. +// GetFlowKey takes an IP packet as an argument and returns some information about the traffic flow. // For IPv4 packets, this is derived from the source and destination protocol and port numbers. // For IPv6 packets, this is derived from the FlowLabel field of the packet if this was set, otherwise it's handled like IPv4. // The FlowKey is then used internally by Yggdrasil for congestion control. From b3361d4bbc15fa78a9fb08a271a2fb731518dd68 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 1 Sep 2019 19:01:33 -0500 Subject: [PATCH 0317/1109] package level documentation for address/crypto/util --- src/address/address.go | 2 ++ src/crypto/crypto.go | 3 +++ src/util/util.go | 2 ++ 3 files changed, 7 insertions(+) diff --git a/src/address/address.go b/src/address/address.go index c30367e8..3960b783 100644 --- a/src/address/address.go +++ b/src/address/address.go @@ -1,3 +1,5 @@ +// Package address contains the types used by yggdrasil to represent IPv6 addresses or prefixes, as well as functions for working with these types. +// 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 import "github.com/yggdrasil-network/yggdrasil-go/src/crypto" diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go index 55f26f74..6c10a2ef 100644 --- a/src/crypto/crypto.go +++ b/src/crypto/crypto.go @@ -1,3 +1,6 @@ +// Package crypto is a wrapper around packages under golang.org/x/crypto/, particulaly curve25519, ed25519, and nacl/box. +// This is used to avoid explicitly importing and using these packages throughout yggdrasil. +// It also includes the all-important NodeID and TreeID types, which are used to identify nodes in the DHT and in the spanning tree's root selection algorithm, respectively. package crypto /* diff --git a/src/util/util.go b/src/util/util.go index c73faf68..a7a54562 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -1,3 +1,5 @@ +// Package util contains miscellaneous utilities used by yggdrasil. +// In particular, this includes a crypto worker pool, Cancellation machinery, and a sync.Pool used to reuse []byte. package util // These are misc. utility functions that didn't really fit anywhere else From af3dcb44d88cb7d70023a9af77a07eb2d5ab1bb1 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 2 Sep 2019 09:45:11 +0100 Subject: [PATCH 0318/1109] Update config.go godoc --- src/config/config.go | 61 +++++++++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/src/config/config.go b/src/config/config.go index 6c127552..ac88bfc5 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -1,3 +1,19 @@ +/* +The config package contains structures related to the configuration of an +Yggdrasil node. + +The configuration contains, amongst other things, encryption keys which are used +to derive a node's identity, information about peerings and node information +that is shared with the network. There are also some module-specific options +related to TUN/TAP, multicast and the admin socket. + +In order for a node to maintain the same identity across restarts, you should +persist the configuration onto the filesystem or into some configuration storage +so that the encryption keys (and therefore the node ID) do not change. + +Note that Yggdrasil will automatically populate sane defaults for any +configuration option that is not provided. +*/ package config import ( @@ -8,30 +24,30 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/defaults" ) -// NodeState represents the active and previous configuration of the node and -// protects it with a mutex +// NodeState represents the active and previous configuration of an Yggdrasil +// node. A NodeState object is returned when starting an Yggdrasil node. Note +// that this structure and related functions are likely to disappear soon. type NodeState struct { Current NodeConfig Previous NodeConfig Mutex sync.RWMutex } -// Current returns the current node config +// Current returns the active node configuration. func (s *NodeState) GetCurrent() NodeConfig { s.Mutex.RLock() defer s.Mutex.RUnlock() return s.Current } -// Previous returns the previous node config +// Previous returns the previous node configuration. func (s *NodeState) GetPrevious() NodeConfig { s.Mutex.RLock() defer s.Mutex.RUnlock() return s.Previous } -// Replace the node configuration with new configuration. This method returns -// both the new and the previous node configs +// Replace the node configuration with new configuration. func (s *NodeState) Replace(n NodeConfig) { s.Mutex.Lock() defer s.Mutex.Unlock() @@ -39,7 +55,9 @@ func (s *NodeState) Replace(n NodeConfig) { s.Current = n } -// NodeConfig defines all configuration values needed to run a signle yggdrasil node +// NodeConfig is the main configuration structure, containing configuration +// options that are necessary for an Yggdrasil node to run. You will need to +// supply one of these structs to the Yggdrasil core when starting a node. type NodeConfig struct { Peers []string `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j. These connections\nwill obey the operating system routing table, therefore you should\nuse this section when you may connect via different interfaces."` InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ tcp://a.b.c.d:e ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."` @@ -62,7 +80,7 @@ type NodeConfig struct { NodeInfo map[string]interface{} `comment:"Optional node info. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."` } -// SessionFirewall controls the session firewall configuration +// SessionFirewall controls the session firewall configuration. type SessionFirewall struct { Enable bool `comment:"Enable or disable the session firewall. If disabled, network traffic\nfrom any node will be allowed. If enabled, the below rules apply."` AllowFromDirect bool `comment:"Allow network traffic from directly connected peers."` @@ -72,7 +90,8 @@ type SessionFirewall struct { BlacklistEncryptionPublicKeys []string `comment:"List of public keys from which network traffic is always rejected,\nregardless of the whitelist, AllowFromDirect or AllowFromRemote."` } -// TunnelRouting contains the crypto-key routing tables for tunneling +// TunnelRouting contains the crypto-key routing tables for tunneling regular +// IPv4 or IPv6 subnets across the Yggdrasil network. type TunnelRouting struct { Enable bool `comment:"Enable or disable tunnel routing."` IPv6RemoteSubnets map[string]string `comment:"IPv6 subnets belonging to remote nodes, mapped to the node's public\nkey, e.g. { \"aaaa:bbbb:cccc::/e\": \"boxpubkey\", ... }"` @@ -81,18 +100,15 @@ type TunnelRouting struct { IPv4LocalSubnets []string `comment:"IPv4 subnets belonging to this node's end of the tunnels. Only traffic\nfrom these ranges will be tunnelled."` } -// SwitchOptions contains tuning options for the switch +// SwitchOptions contains tuning options for the switch. These are advanced +// options and shouldn't be changed unless necessary. type SwitchOptions struct { MaxTotalQueueSize uint64 `comment:"Maximum size of all switch queues combined (in bytes)."` } -// Generates default configuration. This is used when outputting the -genconf -// parameter and also when using -autoconf. The isAutoconf flag is used to -// determine whether the operating system should select a free port by itself -// (which guarantees that there will not be a conflict with any other services) -// or whether to generate a random port number. The only side effect of setting -// isAutoconf is that the TCP and UDP ports will likely end up with different -// port numbers. +// Generates default configuration and returns a pointer to the resulting +// NodeConfig. This is used when outputting the -genconf parameter and also when +// using -autoconf. func GenerateConfig() *NodeConfig { // Generate encryption keys. bpub, bpriv := crypto.NewBoxKeys() @@ -122,16 +138,19 @@ func GenerateConfig() *NodeConfig { return &cfg } -// NewEncryptionKeys generates a new encryption keypair. The encryption keys are -// used to encrypt traffic and to derive the IPv6 address/subnet of the node. +// NewEncryptionKeys replaces the encryption keypair in the NodeConfig with a +// new encryption keypair. The encryption keys are used by the router to encrypt +// traffic and to derive the node ID and IPv6 address/subnet of the node, so +// this is equivalent to discarding the node's identity on the network. func (cfg *NodeConfig) NewEncryptionKeys() { bpub, bpriv := crypto.NewBoxKeys() cfg.EncryptionPublicKey = hex.EncodeToString(bpub[:]) cfg.EncryptionPrivateKey = hex.EncodeToString(bpriv[:]) } -// NewSigningKeys generates a new signing keypair. The signing keys are used to -// derive the structure of the spanning tree. +// NewSigningKeys replaces the signing keypair in the NodeConfig with a new +// signing keypair. The signing keys are used by the switch to derive the +// structure of the spanning tree. func (cfg *NodeConfig) NewSigningKeys() { spub, spriv := crypto.NewSigKeys() cfg.SigningPublicKey = hex.EncodeToString(spub[:]) From 2426a87ccccc36141d3a6f31aeec8a4126494f12 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Tue, 3 Sep 2019 19:03:12 -0500 Subject: [PATCH 0319/1109] really finish initializing the session before returning it / giving up control of the router, in the Conn.search function used by Dial --- src/yggdrasil/conn.go | 70 ++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 352cf9d9..cb28a6f2 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -90,45 +90,47 @@ func (c *Conn) setMTU(from phony.Actor, mtu uint16) { // This should never be called from the router goroutine, used in the dial functions func (c *Conn) search() error { - var sinfo *searchInfo - var isIn bool + var err error + done := make(chan struct{}) phony.Block(&c.core.router, func() { - sinfo, isIn = c.core.router.searches.searches[*c.nodeID] - }) - if !isIn { - done := make(chan struct{}, 1) - var sess *sessionInfo - var err error - searchCompleted := func(sinfo *sessionInfo, e error) { - sess = sinfo - err = e - // FIXME close can be called multiple times, do a non-blocking send instead - select { - case done <- struct{}{}: - default: + _, isIn := c.core.router.searches.searches[*c.nodeID] + if !isIn { + searchCompleted := func(sinfo *sessionInfo, e error) { + select { + case <-done: + // Somehow this was called multiple times, TODO don't let that happen + if sinfo != nil { + // Need to clean up to avoid a session leak + sinfo.cancel.Cancel(nil) + } + default: + if sinfo != nil { + // Finish initializing the session + sinfo.conn = c + } + c.session = sinfo + err = e + close(done) + } } - } - phony.Block(&c.core.router, func() { - sinfo = c.core.router.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) + sinfo := c.core.router.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) sinfo.continueSearch() - }) - <-done - c.session = sess - if c.session == nil && err == nil { - panic("search failed but returned no error") + } else { + err = errors.New("search already exists") + close(done) } - if c.session != nil { - c.nodeID = crypto.GetNodeID(&c.session.theirPermPub) - for i := range c.nodeMask { - c.nodeMask[i] = 0xFF - } - c.session.conn = c - } - return err - } else { - return errors.New("search already exists") + }) + <-done + if c.session == nil && err == nil { + panic("search failed but returned no error") } - return nil + if c.session != nil { + c.nodeID = crypto.GetNodeID(&c.session.theirPermPub) + for i := range c.nodeMask { + c.nodeMask[i] = 0xFF + } + } + return err } // Used in session keep-alive traffic From eec055313dffba04633db0e21a8639e9590a8786 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 6 Sep 2019 22:20:36 -0500 Subject: [PATCH 0320/1109] update phony dependency --- go.mod | 2 +- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 6703d5bd..d86101bf 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/yggdrasil-network/yggdrasil-go require ( - github.com/Arceliar/phony v0.0.0-20190831214819-9b642ea019ad + github.com/Arceliar/phony v0.0.0-20190907031509-af5bdbeecab6 github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 github.com/hashicorp/go-syslog v1.0.0 github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible diff --git a/go.sum b/go.sum index 60e6090f..cdabc402 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,9 @@ -github.com/Arceliar/phony v0.0.0-20190831214819-9b642ea019ad h1:670inqspOp+tAnSvkOBgrKGOIT4605Jt+6KGi2j2/S8= -github.com/Arceliar/phony v0.0.0-20190831214819-9b642ea019ad/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= +github.com/Arceliar/phony v0.0.0-20190907031509-af5bdbeecab6 h1:zMj5Q1V0yF4WNfV/FpXG6iXfPJ965Xc5asR2vHXanXc= +github.com/Arceliar/phony v0.0.0-20190907031509-af5bdbeecab6/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/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 h1:xmvkbxXDeN1ffWq8kvrhyqVYAO2aXuRBsbpxVTR+JyU= -github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible h1:bLQ2Ve+eW65id3b8xEMQiAwJT4qGZeywAEMLvXjznvw= github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk8851WTVWlqMC1ecJH07Ctz+Ezxx4u54g= From 10a828af2c42b42fdd9f35563b0badfe1039f6af Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 9 Sep 2019 19:20:46 -0500 Subject: [PATCH 0321/1109] when forwarding traffic, break distance ties by favoring the link that sent the most recent switch update the fastest --- src/yggdrasil/switch.go | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 163c85cf..5ed70323 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -143,6 +143,7 @@ type switchPort uint64 type tableElem struct { port switchPort locator switchLocator + time time.Time } // This is the subset of the information about all peers needed to make routing decisions, and it stored separately in an atomically accessed table, which gets hammered in the "hot loop" of the routing logic (see: peer.handleTraffic in peers.go). @@ -562,6 +563,7 @@ func (t *switchTable) updateTable() { newTable.elems[pinfo.port] = tableElem{ locator: loc, port: pinfo.port, + time: pinfo.time, } } t.table.Store(newTable) @@ -581,7 +583,7 @@ func (t *switchTable) start() error { } type closerInfo struct { - port switchPort + elem tableElem dist int } @@ -598,7 +600,7 @@ func (t *switchTable) getCloser(dest []byte) []closerInfo { for _, info := range table.elems { dist := info.locator.dist(dest) if dist < myDist { - t.queues.closer = append(t.queues.closer, closerInfo{info.port, dist}) + t.queues.closer = append(t.queues.closer, closerInfo{info, dist}) } } return t.queues.closer @@ -671,13 +673,12 @@ func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]time.Time) bo self.sendPacketsFrom(t, [][]byte{packet}) return true } - var best *peer - var bestDist int + var best *closerInfo var bestTime time.Time ports := t.core.peers.getPorts() for _, cinfo := range closer { - to := ports[cinfo.port] - thisTime, isIdle := idle[cinfo.port] + to := ports[cinfo.elem.port] + thisTime, isIdle := idle[cinfo.elem.port] var update bool switch { case to == nil: @@ -688,13 +689,24 @@ func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]time.Time) bo // this is the first idle port we've found, so select it until we find a // better candidate port to use instead update = true - case cinfo.dist < bestDist: + case cinfo.dist < best.dist: // the port takes a shorter path/is more direct than our current // candidate, so select that instead update = true - case cinfo.dist > bestDist: + case cinfo.dist > best.dist: // the port takes a longer path/is less direct than our current candidate, // ignore it + case cinfo.elem.locator.tstamp > best.elem.locator.tstamp: + // has a newer tstamp from the root, so presumably a better path + update = true + case cinfo.elem.locator.tstamp < best.elem.locator.tstamp: + // has a n older tstamp, so presumably a worse path + case cinfo.elem.time.Before(best.elem.time): + // same tstamp, but got it earlier, so presumably a better path + update = true + case cinfo.elem.time.After(best.elem.time): + // same tstamp, but got it later, so presumably a worse path + // I do not expect the remaining cases to ever be reached... TODO cleanup case thisTime.After(bestTime): // all else equal, this port was used more recently than our current // candidate, so choose that instead. this should mean that, in low @@ -705,15 +717,15 @@ func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]time.Time) bo // the search for a port has finished } if update { - best = to - bestDist = cinfo.dist + b := cinfo // because cinfo gets mutated by the iteration + best = &b bestTime = thisTime } } if best != nil { // Send to the best idle next hop - delete(idle, best.port) - best.sendPacketsFrom(t, [][]byte{packet}) + delete(idle, best.elem.port) + ports[best.elem.port].sendPacketsFrom(t, [][]byte{packet}) return true } // Didn't find anyone idle to send it to From 0141180279cc896f70f8f8b1b80d3e895c24b16a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 9 Sep 2019 19:25:10 -0500 Subject: [PATCH 0322/1109] cleanup --- src/yggdrasil/switch.go | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 5ed70323..f4df60d5 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -176,7 +176,7 @@ type switchTable struct { table atomic.Value // lookupTable phony.Inbox // Owns the below queues switch_buffers // Queues - not atomic so ONLY use through the actor - idle map[switchPort]time.Time // idle peers - not atomic so ONLY use through the actor + idle map[switchPort]struct{} // idle peers - not atomic so ONLY use through the actor } // Minimum allowed total size of switch queues. @@ -202,7 +202,7 @@ func (t *switchTable) init(core *Core) { } core.config.Mutex.RUnlock() t.queues.bufs = make(map[string]switch_buffer) - t.idle = make(map[switchPort]time.Time) + t.idle = make(map[switchPort]struct{}) }) } @@ -664,7 +664,7 @@ func (t *switchTable) bestPortForCoords(coords []byte) switchPort { // Handle an incoming packet // Either send it to ourself, or to the first idle peer that's free // Returns true if the packet has been handled somehow, false if it should be queued -func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]time.Time) bool { +func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]struct{}) bool { coords := switch_getPacketCoords(packet) closer := t.getCloser(coords) if len(closer) == 0 { @@ -674,11 +674,10 @@ func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]time.Time) bo return true } var best *closerInfo - var bestTime time.Time ports := t.core.peers.getPorts() for _, cinfo := range closer { to := ports[cinfo.elem.port] - thisTime, isIdle := idle[cinfo.elem.port] + _, isIdle := idle[cinfo.elem.port] var update bool switch { case to == nil: @@ -704,22 +703,12 @@ func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]time.Time) bo case cinfo.elem.time.Before(best.elem.time): // same tstamp, but got it earlier, so presumably a better path update = true - case cinfo.elem.time.After(best.elem.time): - // same tstamp, but got it later, so presumably a worse path - // I do not expect the remaining cases to ever be reached... TODO cleanup - case thisTime.After(bestTime): - // all else equal, this port was used more recently than our current - // candidate, so choose that instead. this should mean that, in low - // traffic scenarios, we consistently pick the same link which helps with - // packet ordering - update = true default: // the search for a port has finished } if update { b := cinfo // because cinfo gets mutated by the iteration best = &b - bestTime = thisTime } } if best != nil { @@ -882,6 +871,6 @@ func (t *switchTable) _idleIn(port switchPort) { // Try to find something to send to this peer if !t._handleIdle(port) { // Didn't find anything ready to send yet, so stay idle - t.idle[port] = time.Now() + t.idle[port] = struct{}{} } } From 8ca11874517b032c446f434792ae0e41a75263df Mon Sep 17 00:00:00 2001 From: William Fleurant Date: Wed, 11 Sep 2019 06:52:03 -0400 Subject: [PATCH 0323/1109] README: update platforms link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ec7fe74c..07b202f1 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ some of the below: - NetBSD - OpenWrt -Please see our [Platforms](https://yggdrasil-network.github.io/) pages for more +Please see our [Platforms](https://yggdrasil-network.github.io/platforms.html) pages for more specific information about each of our supported platforms, including installation steps and caveats. From 80ba24d51255c3751e2b25aceee52b20d59ff746 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Tue, 17 Sep 2019 19:42:07 -0500 Subject: [PATCH 0324/1109] force things to buffer in the switch if the best link is currently busy. note that other links can end up sending if they become non-idle for other reasons. this is a temporary workaround to packet reordering, until we can figure out a better solution --- src/yggdrasil/switch.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index f4df60d5..4a1999a5 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -677,13 +677,10 @@ func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]struct{}) boo ports := t.core.peers.getPorts() for _, cinfo := range closer { to := ports[cinfo.elem.port] - _, isIdle := idle[cinfo.elem.port] var update bool switch { case to == nil: // no port was found, ignore it - case !isIdle: - // the port is busy, ignore it case best == nil: // this is the first idle port we've found, so select it until we find a // better candidate port to use instead @@ -713,6 +710,9 @@ func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]struct{}) boo } if best != nil { // Send to the best idle next hop + if _, isIdle := idle[best.elem.port]; !isIdle { + return false + } delete(idle, best.elem.port) ports[best.elem.port].sendPacketsFrom(t, [][]byte{packet}) return true From be35675d0f01aa5e22571f9c71ed36ba4a87b8ba Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 18 Sep 2019 13:37:01 +0100 Subject: [PATCH 0325/1109] Catch a nil pointer when sending a session packet to a conn, this shouldn't happen but it's caused multiple crashes in conn.recvMsg --- src/yggdrasil/session.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 8a6d16fc..49808623 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -470,7 +470,14 @@ func (sinfo *sessionInfo) _recvPacket(p *wire_trafficPacket) { callback := func() { util.PutBytes(p.Payload) if !isOK || k != sinfo.sharedSesKey || !sinfo._nonceIsOK(&p.Nonce) { - // Either we failed to decrypt, or the session was updated, or we received this packet in the mean time + // Either we failed to decrypt, or the session was updated, or we + // received this packet in the mean time + util.PutBytes(bs) + return + } + if sinfo.conn == nil { + // There's no connection associated with this session for some reason + // TODO: Figure out why this happens sometimes, it shouldn't util.PutBytes(bs) return } From 40204caab6c9a29a2d56ba9c18f8c75ce4850bed Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 18 Sep 2019 14:03:31 +0100 Subject: [PATCH 0326/1109] Try to fix race condition in sessions.reset --- src/yggdrasil/session.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 8a6d16fc..778fa2a9 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -414,11 +414,10 @@ func (sinfo *sessionInfo) _updateNonce(theirNonce *crypto.BoxNonce) { // Resets all sessions to an uninitialized state. // Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change. +// Only call this from the router actor. func (ss *sessions) reset() { for _, sinfo := range ss.sinfos { - sinfo.Act(ss.router, func() { - sinfo.reset = true - }) + sinfo.reset = true } } From c3016e680c77e1dfb018d22422c7d6621f04505a Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 18 Sep 2019 14:05:18 +0100 Subject: [PATCH 0327/1109] Fix panic where slice goes out of bounds because iface.Read returns less than zero (which might happen when the TUN/TAP interface is closed) --- src/tuntap/iface.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 92ba36ab..753a8d02 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -111,7 +111,7 @@ func (r *tunReader) _read() { recvd := util.ResizeBytes(util.GetBytes(), 65535+tun_ETHER_HEADER_LENGTH) // Wait for a packet to be delivered to us through the TUN/TAP adapter n, err := r.tun.iface.Read(recvd) - if n == 0 { + if n <= 0 { util.PutBytes(recvd) } else { r.tun.handlePacketFrom(r, recvd[:n], err) From e9bacda0b328a67871b3b66c9328778348b1a268 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 18 Sep 2019 13:37:01 +0100 Subject: [PATCH 0328/1109] Catch a nil pointer when sending a session packet to a conn, this shouldn't happen but it's caused multiple crashes in conn.recvMsg --- src/yggdrasil/session.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 778fa2a9..0b3947e2 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -469,7 +469,14 @@ func (sinfo *sessionInfo) _recvPacket(p *wire_trafficPacket) { callback := func() { util.PutBytes(p.Payload) if !isOK || k != sinfo.sharedSesKey || !sinfo._nonceIsOK(&p.Nonce) { - // Either we failed to decrypt, or the session was updated, or we received this packet in the mean time + // Either we failed to decrypt, or the session was updated, or we + // received this packet in the mean time + util.PutBytes(bs) + return + } + if sinfo.conn == nil { + // There's no connection associated with this session for some reason + // TODO: Figure out why this happens sometimes, it shouldn't util.PutBytes(bs) return } From 200b3623b21cbf05e1254a1ca122d301166e3e71 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 18 Sep 2019 14:32:28 +0100 Subject: [PATCH 0329/1109] Fix #539 --- src/yggdrasil/tcp.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index cce352bd..5ac921c4 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -249,7 +249,7 @@ func (t *tcp) call(saddr string, options interface{}, sintf string) { if err != nil { return } - t.handler(conn, false, dialerdst.String()) + t.handler(conn, false, saddr) } else { dst, err := net.ResolveTCPAddr("tcp", saddr) if err != nil { @@ -322,18 +322,19 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}) { t.setExtraOptions(sock) stream := stream{} stream.init(sock) - local, _, _ := net.SplitHostPort(sock.LocalAddr().String()) - remote, _, _ := net.SplitHostPort(sock.RemoteAddr().String()) - force := net.ParseIP(strings.Split(remote, "%")[0]).IsLinkLocalUnicast() - var name string - var proto string + var name, proto, local, remote string if socksaddr, issocks := options.(string); issocks { - name = "socks://" + socksaddr + "/" + sock.RemoteAddr().String() + name = "socks://" + sock.RemoteAddr().String() + "/" + socksaddr proto = "socks" + local, _, _ = net.SplitHostPort(sock.LocalAddr().String()) + remote, _, _ = net.SplitHostPort(socksaddr) } else { name = "tcp://" + sock.RemoteAddr().String() proto = "tcp" + local, _, _ = net.SplitHostPort(sock.LocalAddr().String()) + remote, _, _ = net.SplitHostPort(sock.RemoteAddr().String()) } + force := net.ParseIP(strings.Split(remote, "%")[0]).IsLinkLocalUnicast() link, err := t.link.core.link.create(&stream, name, proto, local, remote, incoming, force) if err != nil { t.link.core.log.Println(err) From 27158d7b44993fea0ba2bff666c4ef8192aa99b1 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 18 Sep 2019 14:35:11 +0100 Subject: [PATCH 0330/1109] Fix #509 --- build | 2 -- 1 file changed, 2 deletions(-) diff --git a/build b/build index 7ca5f4fa..d11f991d 100755 --- a/build +++ b/build @@ -1,7 +1,5 @@ #!/bin/sh -set -ef - PKGSRC=${PKGSRC:-github.com/yggdrasil-network/yggdrasil-go/src/version} PKGNAME=${PKGNAME:-$(sh contrib/semver/name.sh)} PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)} From a62e029e21eadf1b40fd0c0931727e97c6c5999b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 18 Sep 2019 14:37:25 +0100 Subject: [PATCH 0331/1109] Update apt before trying to pull in RPM dependencies --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index f9cd0720..5778ffc9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -29,6 +29,7 @@ jobs: - run: name: Install RPM utilities command: | + sudo apt-get update sudo apt-get install -y rpm file mkdir -p ~/rpmbuild/BUILD ~/rpmbuild/RPMS ~/rpmbuild/SOURCES ~/rpmbuild/SPECS ~/rpmbuild/SRPMS From 846df4789a0f3e74c13a6f2c6cf302e761e46ad5 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 18 Sep 2019 15:01:19 +0100 Subject: [PATCH 0332/1109] Be more verbose when a peer or listener is badly formatted --- src/yggdrasil/core.go | 14 ++++++++++---- src/yggdrasil/link.go | 4 ++-- src/yggdrasil/tcp.go | 3 +++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 754d7d64..4dcd16fd 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -91,15 +91,21 @@ func (c *Core) _addPeerLoop() { // Add peers from the Peers section for _, peer := range current.Peers { - go c.AddPeer(peer, "") // TODO: this should be acted and not in a goroutine? - time.Sleep(time.Second) + go func() { + if err := c.AddPeer(peer, ""); err != nil { + c.log.Errorln("Failed to add peer:", err) + } + }() // TODO: this should be acted and not in a goroutine? } // Add peers from the InterfacePeers section for intf, intfpeers := range current.InterfacePeers { for _, peer := range intfpeers { - go c.AddPeer(peer, intf) // TODO: this should be acted and not in a goroutine? - time.Sleep(time.Second) + go func() { + if err := c.AddPeer(peer, intf); err != nil { + c.log.Errorln("Failed to add peer:", err) + } + }() // TODO: this should be acted and not in a goroutine? } } diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 6e393514..bdf15547 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -86,7 +86,7 @@ func (l *link) reconfigure() { func (l *link) call(uri string, sintf string) error { u, err := url.Parse(uri) if err != nil { - return err + return fmt.Errorf("peer %s is not correctly formatted (%s)", uri, err) } pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/") switch u.Scheme { @@ -103,7 +103,7 @@ func (l *link) call(uri string, sintf string) error { func (l *link) listen(uri string) error { u, err := url.Parse(uri) if err != nil { - return err + return fmt.Errorf("listener %s is not correctly formatted (%s)", uri, err) } switch u.Scheme { case "tcp": diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 5ac921c4..93e39e40 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -85,6 +85,7 @@ func (t *tcp) init(l *link) error { defer t.link.core.config.Mutex.RUnlock() for _, listenaddr := range t.link.core.config.Current.Listen { if listenaddr[:6] != "tcp://" { + 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 { @@ -103,6 +104,7 @@ func (t *tcp) reconfigure() { if len(added) > 0 || len(deleted) > 0 { for _, a := range added { if a[:6] != "tcp://" { + 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 { @@ -113,6 +115,7 @@ func (t *tcp) reconfigure() { } for _, d := range deleted { if d[:6] != "tcp://" { + t.link.core.log.Errorln("Failed to delete listener: listener", d, "is not correctly formatted, ignoring") continue } t.mutex.Lock() From 00a972b74ed838c2d7b8f634ec316a39eabbac10 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 18 Sep 2019 15:22:17 +0100 Subject: [PATCH 0333/1109] Disconnect peers when stopping, stop modules before core --- cmd/yggdrasil/main.go | 2 +- src/yggdrasil/core.go | 31 +++++++++++++++++++------------ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index e9a21c13..3b7c9220 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -292,10 +292,10 @@ exit: } func (n *node) shutdown() { - n.core.Stop() n.admin.Stop() n.multicast.Stop() n.tuntap.Stop() + n.core.Stop() os.Exit(0) } diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 4dcd16fd..2857de1d 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -21,16 +21,17 @@ type Core struct { // We're going to keep our own copy of the provided config - that way we can // guarantee that it will be covered by the mutex phony.Inbox - config config.NodeState // Config - boxPub crypto.BoxPubKey - boxPriv crypto.BoxPrivKey - sigPub crypto.SigPubKey - sigPriv crypto.SigPrivKey - switchTable switchTable - peers peers - router router - link link - log *log.Logger + config config.NodeState // Config + boxPub crypto.BoxPubKey + boxPriv crypto.BoxPrivKey + sigPub crypto.SigPubKey + sigPriv crypto.SigPrivKey + switchTable switchTable + peers peers + router router + link link + log *log.Logger + addPeerTimer *time.Timer } func (c *Core) _init() error { @@ -110,7 +111,7 @@ func (c *Core) _addPeerLoop() { } // Sit for a while - time.AfterFunc(time.Minute, func() { + c.addPeerTimer = time.AfterFunc(time.Minute, func() { c.Act(c, c._addPeerLoop) }) } @@ -177,7 +178,9 @@ func (c *Core) _start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState return nil, err } - c.Act(c, c._addPeerLoop) + c.addPeerTimer = time.AfterFunc(time.Second, func() { + c.Act(c, c._addPeerLoop) + }) c.log.Infoln("Startup complete") return &c.config, nil @@ -191,4 +194,8 @@ func (c *Core) Stop() { // This function is unsafe and should only be ran by the core actor. func (c *Core) _stop() { c.log.Infoln("Stopping...") + c.addPeerTimer.Stop() + for _, peer := range c.GetPeers() { + c.DisconnectPeer(peer.Port) + } } From 366fe7e772376a51b7937bc451f63cd7c6aa899a Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 18 Sep 2019 15:31:43 +0100 Subject: [PATCH 0334/1109] Allow multicast to be shut down more sanely --- src/multicast/multicast.go | 212 +++++++++++++++--------------- src/multicast/multicast_darwin.go | 19 +-- 2 files changed, 113 insertions(+), 118 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 22ac9356..2102a4d9 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -19,14 +19,16 @@ import ( // configured multicast interface, Yggdrasil will attempt to peer with that node // automatically. type Multicast struct { - core *yggdrasil.Core - config *config.NodeState - log *log.Logger - sock *ipv6.PacketConn - groupAddr string - listeners map[string]*yggdrasil.TcpListener - listenPort uint16 - isOpen bool + core *yggdrasil.Core + config *config.NodeState + log *log.Logger + sock *ipv6.PacketConn + groupAddr string + listeners map[string]*yggdrasil.TcpListener + listenPort uint16 + isOpen bool + announcer *time.Timer + platformhandler *time.Timer } // Init prepares the multicast interface for use. @@ -63,9 +65,9 @@ func (m *Multicast) Start() error { } m.isOpen = true - go m.multicastStarted() go m.listen() - go m.announce() + m.multicastStarted() + m.announce() return nil } @@ -73,6 +75,8 @@ func (m *Multicast) Start() error { // Stop is not implemented for multicast yet. func (m *Multicast) Stop() error { m.isOpen = false + m.announcer.Stop() + m.platformhandler.Stop() m.sock.Close() return nil } @@ -136,108 +140,106 @@ func (m *Multicast) announce() { if err != nil { panic(err) } - for { - interfaces := m.Interfaces() - // There might be interfaces that we configured listeners for but are no - // longer up - if that's the case then we should stop the listeners - for name, listener := range m.listeners { - // Prepare our stop function! - stop := func() { - listener.Stop <- true - delete(m.listeners, name) - m.log.Debugln("No longer multicasting on", name) - } - // If the interface is no longer visible on the system then stop the - // listener, as another one will be started further down - if _, ok := interfaces[name]; !ok { - stop() - continue - } - // It's possible that the link-local listener address has changed so if - // that is the case then we should clean up the interface listener - found := false - listenaddr, err := net.ResolveTCPAddr("tcp6", listener.Listener.Addr().String()) - if err != nil { - stop() - continue - } - // Find the interface that matches the listener - if intf, err := net.InterfaceByName(name); err == nil { - if addrs, err := intf.Addrs(); err == nil { - // Loop through the addresses attached to that listener and see if any - // of them match the current address of the listener - for _, addr := range addrs { - if ip, _, err := net.ParseCIDR(addr.String()); err == nil { - // Does the interface address match our listener address? - if ip.Equal(listenaddr.IP) { - found = true - break - } + interfaces := m.Interfaces() + // There might be interfaces that we configured listeners for but are no + // longer up - if that's the case then we should stop the listeners + for name, listener := range m.listeners { + // Prepare our stop function! + stop := func() { + listener.Stop <- true + delete(m.listeners, name) + m.log.Debugln("No longer multicasting on", name) + } + // If the interface is no longer visible on the system then stop the + // listener, as another one will be started further down + if _, ok := interfaces[name]; !ok { + stop() + continue + } + // It's possible that the link-local listener address has changed so if + // that is the case then we should clean up the interface listener + found := false + listenaddr, err := net.ResolveTCPAddr("tcp6", listener.Listener.Addr().String()) + if err != nil { + stop() + continue + } + // Find the interface that matches the listener + if intf, err := net.InterfaceByName(name); err == nil { + if addrs, err := intf.Addrs(); err == nil { + // Loop through the addresses attached to that listener and see if any + // of them match the current address of the listener + for _, addr := range addrs { + if ip, _, err := net.ParseCIDR(addr.String()); err == nil { + // Does the interface address match our listener address? + if ip.Equal(listenaddr.IP) { + found = true + break } } } } - // If the address has not been found on the adapter then we should stop - // and clean up the TCP listener. A new one will be created below if a - // suitable link-local address is found - if !found { - stop() - } } - // Now that we have a list of valid interfaces from the operating system, - // we can start checking if we can send multicasts on them - for _, iface := range interfaces { - // Find interface addresses - addrs, err := iface.Addrs() - if err != nil { - panic(err) - } - for _, addr := range addrs { - addrIP, _, _ := net.ParseCIDR(addr.String()) - // Ignore IPv4 addresses - if addrIP.To4() != nil { - continue - } - // Ignore non-link-local addresses - if !addrIP.IsLinkLocalUnicast() { - continue - } - // Join the multicast group - m.sock.JoinGroup(&iface, groupAddr) - // Try and see if we already have a TCP listener for this interface - var listener *yggdrasil.TcpListener - if l, ok := m.listeners[iface.Name]; !ok || l.Listener == nil { - // No listener was found - let's create one - listenaddr := fmt.Sprintf("[%s%%%s]:%d", addrIP, iface.Name, m.listenPort) - if li, err := m.core.ListenTCP(listenaddr); err == nil { - m.log.Debugln("Started multicasting on", iface.Name) - // Store the listener so that we can stop it later if needed - m.listeners[iface.Name] = li - listener = li - } else { - m.log.Warnln("Not multicasting on", iface.Name, "due to error:", err) - } - } else { - // An existing listener was found - listener = m.listeners[iface.Name] - } - // Make sure nothing above failed for some reason - if listener == nil { - continue - } - // Get the listener details and construct the multicast beacon - lladdr := listener.Listener.Addr().String() - if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil { - a.Zone = "" - destAddr.Zone = iface.Name - msg := []byte(a.String()) - m.sock.WriteTo(msg, nil, destAddr) - } - break - } + // If the address has not been found on the adapter then we should stop + // and clean up the TCP listener. A new one will be created below if a + // suitable link-local address is found + if !found { + stop() } - time.Sleep(time.Second * 15) } + // Now that we have a list of valid interfaces from the operating system, + // we can start checking if we can send multicasts on them + for _, iface := range interfaces { + // Find interface addresses + addrs, err := iface.Addrs() + if err != nil { + panic(err) + } + for _, addr := range addrs { + addrIP, _, _ := net.ParseCIDR(addr.String()) + // Ignore IPv4 addresses + if addrIP.To4() != nil { + continue + } + // Ignore non-link-local addresses + if !addrIP.IsLinkLocalUnicast() { + continue + } + // Join the multicast group + m.sock.JoinGroup(&iface, groupAddr) + // Try and see if we already have a TCP listener for this interface + var listener *yggdrasil.TcpListener + if l, ok := m.listeners[iface.Name]; !ok || l.Listener == nil { + // No listener was found - let's create one + listenaddr := fmt.Sprintf("[%s%%%s]:%d", addrIP, iface.Name, m.listenPort) + if li, err := m.core.ListenTCP(listenaddr); err == nil { + m.log.Debugln("Started multicasting on", iface.Name) + // Store the listener so that we can stop it later if needed + m.listeners[iface.Name] = li + listener = li + } else { + m.log.Warnln("Not multicasting on", iface.Name, "due to error:", err) + } + } else { + // An existing listener was found + listener = m.listeners[iface.Name] + } + // Make sure nothing above failed for some reason + if listener == nil { + continue + } + // Get the listener details and construct the multicast beacon + lladdr := listener.Listener.Addr().String() + if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil { + a.Zone = "" + destAddr.Zone = iface.Name + msg := []byte(a.String()) + m.sock.WriteTo(msg, nil, destAddr) + } + break + } + } + m.announcer = time.AfterFunc(time.Second*15, m.announce) } func (m *Multicast) listen() { diff --git a/src/multicast/multicast_darwin.go b/src/multicast/multicast_darwin.go index c88b4a81..6fdccb2b 100644 --- a/src/multicast/multicast_darwin.go +++ b/src/multicast/multicast_darwin.go @@ -32,21 +32,14 @@ import ( var awdlGoroutineStarted bool func (m *Multicast) multicastStarted() { - if awdlGoroutineStarted { - return - } - awdlGoroutineStarted = true - for { - C.StopAWDLBrowsing() - for intf := range m.Interfaces() { - if intf == "awdl0" { - m.log.Infoln("Multicast discovery is using AWDL discovery") - C.StartAWDLBrowsing() - break - } + C.StopAWDLBrowsing() + for intf := range m.Interfaces() { + if intf == "awdl0" { + C.StartAWDLBrowsing() + break } - time.Sleep(time.Minute) } + m.platformhandler = time.AfterFunc(time.Minute, m.multicastStarted) } func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error { From c78a4cb28fa1fe572adb0770539633b02ee41674 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 18 Sep 2019 15:34:26 +0100 Subject: [PATCH 0335/1109] Only stop timers if they are running --- src/multicast/multicast.go | 8 ++++++-- src/yggdrasil/core.go | 9 ++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 2102a4d9..08b7180e 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -75,8 +75,12 @@ func (m *Multicast) Start() error { // Stop is not implemented for multicast yet. func (m *Multicast) Stop() error { m.isOpen = false - m.announcer.Stop() - m.platformhandler.Stop() + if m.announcer != nil { + m.announcer.Stop() + } + if m.platformhandler != nil { + m.platformhandler.Stop() + } m.sock.Close() return nil } diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 2857de1d..4cdcd8e4 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -110,7 +110,6 @@ func (c *Core) _addPeerLoop() { } } - // Sit for a while c.addPeerTimer = time.AfterFunc(time.Minute, func() { c.Act(c, c._addPeerLoop) }) @@ -178,9 +177,7 @@ func (c *Core) _start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState return nil, err } - c.addPeerTimer = time.AfterFunc(time.Second, func() { - c.Act(c, c._addPeerLoop) - }) + c.Act(c, c._addPeerLoop) c.log.Infoln("Startup complete") return &c.config, nil @@ -194,7 +191,9 @@ func (c *Core) Stop() { // This function is unsafe and should only be ran by the core actor. func (c *Core) _stop() { c.log.Infoln("Stopping...") - c.addPeerTimer.Stop() + if c.addPeerTimer != nil { + c.addPeerTimer.Stop() + } for _, peer := range c.GetPeers() { c.DisconnectPeer(peer.Port) } From b0df9e2f31020373b62d15600410672fe3a2a34c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 18 Sep 2019 16:15:33 +0100 Subject: [PATCH 0336/1109] Fix race when adding peers --- src/yggdrasil/core.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 4cdcd8e4..598d8121 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -92,21 +92,21 @@ func (c *Core) _addPeerLoop() { // Add peers from the Peers section for _, peer := range current.Peers { - go func() { - if err := c.AddPeer(peer, ""); err != nil { + go func(peer, intf string) { + if err := c.AddPeer(peer, intf); err != nil { c.log.Errorln("Failed to add peer:", err) } - }() // TODO: this should be acted and not in a goroutine? + }(peer, "") // TODO: this should be acted and not in a goroutine? } // Add peers from the InterfacePeers section for intf, intfpeers := range current.InterfacePeers { for _, peer := range intfpeers { - go func() { + go func(peer, intf string) { if err := c.AddPeer(peer, intf); err != nil { c.log.Errorln("Failed to add peer:", err) } - }() // TODO: this should be acted and not in a goroutine? + }(peer, intf) // TODO: this should be acted and not in a goroutine? } } From b959f53fee55783a9b2c821efb08b67eca064fb3 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 18 Sep 2019 16:32:22 +0100 Subject: [PATCH 0337/1109] Shut down listeners when stopping --- src/yggdrasil/core.go | 2 ++ src/yggdrasil/link.go | 7 +++++++ src/yggdrasil/tcp.go | 7 +++++++ 3 files changed, 16 insertions(+) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 598d8121..cdc8ad44 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -194,7 +194,9 @@ func (c *Core) _stop() { if c.addPeerTimer != nil { c.addPeerTimer.Stop() } + c.link.stop() for _, peer := range c.GetPeers() { c.DisconnectPeer(peer.Port) } + c.log.Infoln("Stopped") } diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index bdf15547..df73cc4c 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -134,6 +134,13 @@ func (l *link) create(msgIO linkInterfaceMsgIO, name, linkType, local, remote st return &intf, nil } +func (l *link) stop() error { + if err := l.tcp.stop(); err != nil { + return err + } + return nil +} + func (intf *linkInterface) handler() error { // TODO split some of this into shorter functions, so it's easier to read, and for the FIXME duplicate peer issue mentioned later myLinkPub, myLinkPriv := crypto.NewBoxKeys() diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 93e39e40..a8c23623 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -96,6 +96,13 @@ func (t *tcp) init(l *link) error { return nil } +func (t *tcp) stop() error { + for _, listener := range t.listeners { + close(listener.Stop) + } + return nil +} + func (t *tcp) reconfigure() { t.link.core.config.Mutex.RLock() added := util.Difference(t.link.core.config.Current.Listen, t.link.core.config.Previous.Listen) From 2dc136f94a64fdce5a1c08c04c51ba66b24374f0 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 18 Sep 2019 16:51:46 +0100 Subject: [PATCH 0338/1109] Multicast actor to prevent races --- src/multicast/multicast.go | 10 +++++++--- src/multicast/multicast_darwin.go | 4 +++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 08b7180e..63156d82 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -7,6 +7,7 @@ import ( "regexp" "time" + "github.com/Arceliar/phony" "github.com/gologme/log" "github.com/yggdrasil-network/yggdrasil-go/src/config" @@ -19,6 +20,7 @@ import ( // configured multicast interface, Yggdrasil will attempt to peer with that node // automatically. type Multicast struct { + phony.Inbox core *yggdrasil.Core config *config.NodeState log *log.Logger @@ -66,8 +68,8 @@ func (m *Multicast) Start() error { m.isOpen = true go m.listen() - m.multicastStarted() - m.announce() + m.Act(m, m.multicastStarted) + m.Act(m, m.announce) return nil } @@ -243,7 +245,9 @@ func (m *Multicast) announce() { break } } - m.announcer = time.AfterFunc(time.Second*15, m.announce) + m.announcer = time.AfterFunc(time.Second*15, func() { + m.Act(m, m.announce) + }) } func (m *Multicast) listen() { diff --git a/src/multicast/multicast_darwin.go b/src/multicast/multicast_darwin.go index 6fdccb2b..4cfef9e9 100644 --- a/src/multicast/multicast_darwin.go +++ b/src/multicast/multicast_darwin.go @@ -39,7 +39,9 @@ func (m *Multicast) multicastStarted() { break } } - m.platformhandler = time.AfterFunc(time.Minute, m.multicastStarted) + m.platformhandler = time.AfterFunc(time.Minute, func() { + m.Act(m, m.multicastStarted) + }) } func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error { From ae0b2672ff819dbea4a556525f9972556ca51768 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 18 Sep 2019 14:32:28 +0100 Subject: [PATCH 0339/1109] Fix #539 --- src/yggdrasil/tcp.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index cce352bd..5ac921c4 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -249,7 +249,7 @@ func (t *tcp) call(saddr string, options interface{}, sintf string) { if err != nil { return } - t.handler(conn, false, dialerdst.String()) + t.handler(conn, false, saddr) } else { dst, err := net.ResolveTCPAddr("tcp", saddr) if err != nil { @@ -322,18 +322,19 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}) { t.setExtraOptions(sock) stream := stream{} stream.init(sock) - local, _, _ := net.SplitHostPort(sock.LocalAddr().String()) - remote, _, _ := net.SplitHostPort(sock.RemoteAddr().String()) - force := net.ParseIP(strings.Split(remote, "%")[0]).IsLinkLocalUnicast() - var name string - var proto string + var name, proto, local, remote string if socksaddr, issocks := options.(string); issocks { - name = "socks://" + socksaddr + "/" + sock.RemoteAddr().String() + name = "socks://" + sock.RemoteAddr().String() + "/" + socksaddr proto = "socks" + local, _, _ = net.SplitHostPort(sock.LocalAddr().String()) + remote, _, _ = net.SplitHostPort(socksaddr) } else { name = "tcp://" + sock.RemoteAddr().String() proto = "tcp" + local, _, _ = net.SplitHostPort(sock.LocalAddr().String()) + remote, _, _ = net.SplitHostPort(sock.RemoteAddr().String()) } + force := net.ParseIP(strings.Split(remote, "%")[0]).IsLinkLocalUnicast() link, err := t.link.core.link.create(&stream, name, proto, local, remote, incoming, force) if err != nil { t.link.core.log.Println(err) From 368f499f1df53df85a1fc9ede1f662635b32f3e2 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 18 Sep 2019 14:37:25 +0100 Subject: [PATCH 0340/1109] Update apt before trying to pull in RPM dependencies --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index f9cd0720..5778ffc9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -29,6 +29,7 @@ jobs: - run: name: Install RPM utilities command: | + sudo apt-get update sudo apt-get install -y rpm file mkdir -p ~/rpmbuild/BUILD ~/rpmbuild/RPMS ~/rpmbuild/SOURCES ~/rpmbuild/SPECS ~/rpmbuild/SRPMS From 94cf2854a93575db75aed221ec4ecb24a609a85b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 18 Sep 2019 14:05:18 +0100 Subject: [PATCH 0341/1109] Fix panic where slice goes out of bounds because iface.Read returns less than zero (which might happen when the TUN/TAP interface is closed) --- src/tuntap/iface.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 92ba36ab..753a8d02 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -111,7 +111,7 @@ func (r *tunReader) _read() { recvd := util.ResizeBytes(util.GetBytes(), 65535+tun_ETHER_HEADER_LENGTH) // Wait for a packet to be delivered to us through the TUN/TAP adapter n, err := r.tun.iface.Read(recvd) - if n == 0 { + if n <= 0 { util.PutBytes(recvd) } else { r.tun.handlePacketFrom(r, recvd[:n], err) From ddaaa865cb264b375ff836b91549be4768569fe7 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 18 Sep 2019 15:01:19 +0100 Subject: [PATCH 0342/1109] Be more verbose when a peer or listener is badly formatted --- src/yggdrasil/core.go | 14 ++++++++++---- src/yggdrasil/link.go | 4 ++-- src/yggdrasil/tcp.go | 3 +++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 754d7d64..4dcd16fd 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -91,15 +91,21 @@ func (c *Core) _addPeerLoop() { // Add peers from the Peers section for _, peer := range current.Peers { - go c.AddPeer(peer, "") // TODO: this should be acted and not in a goroutine? - time.Sleep(time.Second) + go func() { + if err := c.AddPeer(peer, ""); err != nil { + c.log.Errorln("Failed to add peer:", err) + } + }() // TODO: this should be acted and not in a goroutine? } // Add peers from the InterfacePeers section for intf, intfpeers := range current.InterfacePeers { for _, peer := range intfpeers { - go c.AddPeer(peer, intf) // TODO: this should be acted and not in a goroutine? - time.Sleep(time.Second) + go func() { + if err := c.AddPeer(peer, intf); err != nil { + c.log.Errorln("Failed to add peer:", err) + } + }() // TODO: this should be acted and not in a goroutine? } } diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 6e393514..bdf15547 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -86,7 +86,7 @@ func (l *link) reconfigure() { func (l *link) call(uri string, sintf string) error { u, err := url.Parse(uri) if err != nil { - return err + return fmt.Errorf("peer %s is not correctly formatted (%s)", uri, err) } pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/") switch u.Scheme { @@ -103,7 +103,7 @@ func (l *link) call(uri string, sintf string) error { func (l *link) listen(uri string) error { u, err := url.Parse(uri) if err != nil { - return err + return fmt.Errorf("listener %s is not correctly formatted (%s)", uri, err) } switch u.Scheme { case "tcp": diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 5ac921c4..93e39e40 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -85,6 +85,7 @@ func (t *tcp) init(l *link) error { defer t.link.core.config.Mutex.RUnlock() for _, listenaddr := range t.link.core.config.Current.Listen { if listenaddr[:6] != "tcp://" { + 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 { @@ -103,6 +104,7 @@ func (t *tcp) reconfigure() { if len(added) > 0 || len(deleted) > 0 { for _, a := range added { if a[:6] != "tcp://" { + 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 { @@ -113,6 +115,7 @@ func (t *tcp) reconfigure() { } for _, d := range deleted { if d[:6] != "tcp://" { + t.link.core.log.Errorln("Failed to delete listener: listener", d, "is not correctly formatted, ignoring") continue } t.mutex.Lock() From d44a7faa04375190f1b72c9f55c8f1a721df2319 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 18 Sep 2019 20:09:53 +0100 Subject: [PATCH 0343/1109] semver: Don't return failure codes when git history is not present --- contrib/semver/name.sh | 2 +- contrib/semver/version.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/semver/name.sh b/contrib/semver/name.sh index 1fa2ce07..308e07bc 100644 --- a/contrib/semver/name.sh +++ b/contrib/semver/name.sh @@ -6,7 +6,7 @@ BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null) # Complain if the git history is not available if [ $? != 0 ] || [ -z "$BRANCH" ]; then printf "yggdrasil" - exit 1 + exit 0 fi # Remove "/" characters from the branch name if present diff --git a/contrib/semver/version.sh b/contrib/semver/version.sh index 3052094a..db96e339 100644 --- a/contrib/semver/version.sh +++ b/contrib/semver/version.sh @@ -6,7 +6,7 @@ TAG=$(git describe --abbrev=0 --tags --match="v[0-9]*\.[0-9]*\.[0-9]*" 2>/dev/nu # Did getting the tag succeed? if [ $? != 0 ] || [ -z "$TAG" ]; then printf -- "unknown" - exit 1 + exit 0 fi # Get the current branch @@ -36,7 +36,7 @@ if [ "$BRANCH" != "master" ]; then # Did getting the count of commits since the tag succeed? if [ $? != 0 ] || [ -z "$BUILD" ]; then printf -- "-unknown" - exit 1 + exit 0 fi # Is the build greater than zero? From 0a12e4b1c1d50c910defbf0607ccb86165c16791 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 18 Sep 2019 20:26:06 +0100 Subject: [PATCH 0344/1109] Revert "Catch a nil pointer when sending a session packet to a conn, this shouldn't happen but it's caused multiple crashes in conn.recvMsg" This reverts commit be35675d0f01aa5e22571f9c71ed36ba4a87b8ba. --- src/yggdrasil/session.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 49808623..8a6d16fc 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -470,14 +470,7 @@ func (sinfo *sessionInfo) _recvPacket(p *wire_trafficPacket) { callback := func() { util.PutBytes(p.Payload) if !isOK || k != sinfo.sharedSesKey || !sinfo._nonceIsOK(&p.Nonce) { - // Either we failed to decrypt, or the session was updated, or we - // received this packet in the mean time - util.PutBytes(bs) - return - } - if sinfo.conn == nil { - // There's no connection associated with this session for some reason - // TODO: Figure out why this happens sometimes, it shouldn't + // Either we failed to decrypt, or the session was updated, or we received this packet in the mean time util.PutBytes(bs) return } From 909e4e29a8f1de08b33afdd1e97c731a21211b37 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 18 Sep 2019 23:44:28 +0100 Subject: [PATCH 0345/1109] Don't spawn goroutines for addPeerLoop, TCP connect timeout of 5 seconds for now --- src/yggdrasil/core.go | 16 ++++++---------- src/yggdrasil/link.go | 2 +- src/yggdrasil/tcp.go | 1 + 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 4dcd16fd..884ef9e0 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -91,21 +91,17 @@ func (c *Core) _addPeerLoop() { // Add peers from the Peers section for _, peer := range current.Peers { - go func() { - if err := c.AddPeer(peer, ""); err != nil { - c.log.Errorln("Failed to add peer:", err) - } - }() // TODO: this should be acted and not in a goroutine? + if err := c.AddPeer(peer, ""); err != nil { + c.log.Errorln("Failed to add peer:", err) + } } // Add peers from the InterfacePeers section for intf, intfpeers := range current.InterfacePeers { for _, peer := range intfpeers { - go func() { - if err := c.AddPeer(peer, intf); err != nil { - c.log.Errorln("Failed to add peer:", err) - } - }() // TODO: this should be acted and not in a goroutine? + if err := c.AddPeer(peer, intf); err != nil { + c.log.Errorln("Failed to add peer:", err) + } } } diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index bdf15547..3686ab9c 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -86,7 +86,7 @@ func (l *link) reconfigure() { func (l *link) call(uri string, sintf string) error { u, err := url.Parse(uri) if err != nil { - return fmt.Errorf("peer %s is not correctly formatted (%s)", uri, err) + return fmt.Errorf("peer %s is not correctly formatted", uri) } pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/") switch u.Scheme { diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 93e39e40..f81e49ad 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -266,6 +266,7 @@ func (t *tcp) call(saddr string, options interface{}, sintf string) { } dialer := net.Dialer{ Control: t.tcpContext, + Timeout: time.Second * 5, } if sintf != "" { ief, err := net.InterfaceByName(sintf) From 2d64a6380ab26f91080267254d167ec2ba6074b5 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 18 Sep 2019 18:33:51 -0500 Subject: [PATCH 0346/1109] misc other fixes --- src/yggdrasil/conn.go | 3 ++- src/yggdrasil/session.go | 13 +++++-------- src/yggdrasil/tcp.go | 10 ++++------ 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index cb28a6f2..9af5d240 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -102,11 +102,12 @@ func (c *Conn) search() error { if sinfo != nil { // Need to clean up to avoid a session leak sinfo.cancel.Cancel(nil) + sinfo.sessions.removeSession(sinfo) } default: if sinfo != nil { // Finish initializing the session - sinfo.conn = c + sinfo.setConn(nil, c) } c.session = sinfo err = e diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 0b3947e2..2f5e0af3 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -416,8 +416,11 @@ func (sinfo *sessionInfo) _updateNonce(theirNonce *crypto.BoxNonce) { // Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change. // Only call this from the router actor. func (ss *sessions) reset() { - for _, sinfo := range ss.sinfos { - sinfo.reset = true + for _, _sinfo := range ss.sinfos { + sinfo := _sinfo // So we can safely put it in a closure + sinfo.Act(ss.router, func() { + sinfo.reset = true + }) } } @@ -474,12 +477,6 @@ func (sinfo *sessionInfo) _recvPacket(p *wire_trafficPacket) { util.PutBytes(bs) return } - if sinfo.conn == nil { - // There's no connection associated with this session for some reason - // TODO: Figure out why this happens sometimes, it shouldn't - util.PutBytes(bs) - return - } sinfo._updateNonce(&p.Nonce) sinfo.bytesRecvd += uint64(len(bs)) sinfo.conn.recvMsg(sinfo, bs) diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index a8c23623..ffda6b76 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -207,11 +207,12 @@ func (t *tcp) listener(l *TcpListener, listenaddr string) { } // Checks if we already are calling this address -func (t *tcp) isAlreadyCalling(saddr string) bool { +func (t *tcp) startCalling(saddr string) bool { t.mutex.Lock() defer t.mutex.Unlock() _, isIn := t.calls[saddr] - return isIn + t.calls[saddr] = struct{}{} + return !isIn } // Checks if a connection already exists. @@ -225,12 +226,9 @@ func (t *tcp) call(saddr string, options interface{}, sintf string) { if sintf != "" { callname = fmt.Sprintf("%s/%s", saddr, sintf) } - if t.isAlreadyCalling(callname) { + if !t.startCalling(callname) { return } - t.mutex.Lock() - t.calls[callname] = struct{}{} - t.mutex.Unlock() defer func() { // Block new calls for a little while, to mitigate livelock scenarios time.Sleep(default_timeout) From 995d67cca8fda11457a28e22cd5380656935c16f Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 18 Sep 2019 18:46:03 -0500 Subject: [PATCH 0347/1109] fix leak in _addPeerLoop --- src/yggdrasil/core.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index cdc8ad44..98a5c6e1 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -93,7 +93,7 @@ func (c *Core) _addPeerLoop() { // Add peers from the Peers section for _, peer := range current.Peers { go func(peer, intf string) { - if err := c.AddPeer(peer, intf); err != nil { + if err := c.CallPeer(peer, intf); err != nil { c.log.Errorln("Failed to add peer:", err) } }(peer, "") // TODO: this should be acted and not in a goroutine? @@ -103,7 +103,7 @@ func (c *Core) _addPeerLoop() { for intf, intfpeers := range current.InterfacePeers { for _, peer := range intfpeers { go func(peer, intf string) { - if err := c.AddPeer(peer, intf); err != nil { + if err := c.CallPeer(peer, intf); err != nil { c.log.Errorln("Failed to add peer:", err) } }(peer, intf) // TODO: this should be acted and not in a goroutine? @@ -111,7 +111,7 @@ func (c *Core) _addPeerLoop() { } c.addPeerTimer = time.AfterFunc(time.Minute, func() { - c.Act(c, c._addPeerLoop) + c.Act(nil, c._addPeerLoop) }) } From 5a382e7e0bf2c86195a689419c7a7ab186c53f6e Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 19 Sep 2019 08:55:55 +0100 Subject: [PATCH 0348/1109] Cherrypick fixes for _addPeerLoop memory leak for now --- src/yggdrasil/api.go | 23 ++++++++++++++++++++++- src/yggdrasil/core.go | 28 +++++++++++++++------------- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index d1753b69..b54d7a11 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -368,13 +368,34 @@ func (c *Core) SetLogger(log *log.Logger) { // connection drops. func (c *Core) AddPeer(addr string, sintf string) error { if err := c.CallPeer(addr, sintf); err != nil { + // TODO: We maybe want this to write the peer to the persistent + // configuration even if a connection attempt fails, but first we'll need to + // move the code to check the peer URI so that we don't deliberately save a + // peer with a known bad URI. Loading peers from config should really do the + // same thing too but I don't think that happens today return err } c.config.Mutex.Lock() if sintf == "" { + for _, peer := range c.config.Current.Peers { + if peer == addr { + return errors.New("peer already added") + } + } c.config.Current.Peers = append(c.config.Current.Peers, addr) } else { - c.config.Current.InterfacePeers[sintf] = append(c.config.Current.InterfacePeers[sintf], addr) + if _, ok := c.config.Current.InterfacePeers[sintf]; ok { + for _, peer := range c.config.Current.InterfacePeers[sintf] { + if peer == addr { + return errors.New("peer already added") + } + } + } + if _, ok := c.config.Current.InterfacePeers[sintf]; !ok { + c.config.Current.InterfacePeers[sintf] = []string{addr} + } else { + c.config.Current.InterfacePeers[sintf] = append(c.config.Current.InterfacePeers[sintf], addr) + } } c.config.Mutex.Unlock() return nil diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 884ef9e0..907db699 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -21,16 +21,17 @@ type Core struct { // We're going to keep our own copy of the provided config - that way we can // guarantee that it will be covered by the mutex phony.Inbox - config config.NodeState // Config - boxPub crypto.BoxPubKey - boxPriv crypto.BoxPrivKey - sigPub crypto.SigPubKey - sigPriv crypto.SigPrivKey - switchTable switchTable - peers peers - router router - link link - log *log.Logger + config config.NodeState // Config + boxPub crypto.BoxPubKey + boxPriv crypto.BoxPrivKey + sigPub crypto.SigPubKey + sigPriv crypto.SigPrivKey + switchTable switchTable + peers peers + router router + link link + log *log.Logger + addPeerTimer *time.Timer } func (c *Core) _init() error { @@ -91,7 +92,7 @@ func (c *Core) _addPeerLoop() { // Add peers from the Peers section for _, peer := range current.Peers { - if err := c.AddPeer(peer, ""); err != nil { + if err := c.CallPeer(peer, ""); err != nil { c.log.Errorln("Failed to add peer:", err) } } @@ -99,14 +100,14 @@ func (c *Core) _addPeerLoop() { // Add peers from the InterfacePeers section for intf, intfpeers := range current.InterfacePeers { for _, peer := range intfpeers { - if err := c.AddPeer(peer, intf); err != nil { + if err := c.CallPeer(peer, intf); err != nil { c.log.Errorln("Failed to add peer:", err) } } } // Sit for a while - time.AfterFunc(time.Minute, func() { + c.addPeerTimer = time.AfterFunc(time.Minute, func() { c.Act(c, c._addPeerLoop) }) } @@ -187,4 +188,5 @@ func (c *Core) Stop() { // This function is unsafe and should only be ran by the core actor. func (c *Core) _stop() { c.log.Infoln("Stopping...") + c.addPeerTimer.Stop() } From 7b1678a11d6261e4adc9c7c62489bf644f75a3fa Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 19 Sep 2019 09:04:25 +0100 Subject: [PATCH 0349/1109] Goroutines in _addPeerLoop from bugfixes --- src/yggdrasil/core.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 907db699..42910aa7 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -92,17 +92,21 @@ func (c *Core) _addPeerLoop() { // Add peers from the Peers section for _, peer := range current.Peers { - if err := c.CallPeer(peer, ""); err != nil { - c.log.Errorln("Failed to add peer:", err) - } + go func(peer, intf string) { + if err := c.CallPeer(peer, intf); err != nil { + c.log.Errorln("Failed to add peer:", err) + } + }(peer, "") } // Add peers from the InterfacePeers section for intf, intfpeers := range current.InterfacePeers { for _, peer := range intfpeers { - if err := c.CallPeer(peer, intf); err != nil { - c.log.Errorln("Failed to add peer:", err) - } + go func(peer, intf string) { + if err := c.CallPeer(peer, intf); err != nil { + c.log.Errorln("Failed to add peer:", err) + } + }(peer, intf) } } From 39461cb60363d8d1c0b5139a4a54e2c9b3986439 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 19 Sep 2019 09:56:27 +0100 Subject: [PATCH 0350/1109] Don't os.Exit --- build | 2 ++ cmd/yggdrasil/main.go | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/build b/build index d11f991d..7ca5f4fa 100755 --- a/build +++ b/build @@ -1,5 +1,7 @@ #!/bin/sh +set -ef + PKGSRC=${PKGSRC:-github.com/yggdrasil-network/yggdrasil-go/src/version} PKGNAME=${PKGNAME:-$(sh contrib/semver/name.sh)} PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)} diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 3b7c9220..33a8769d 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -296,7 +296,6 @@ func (n *node) shutdown() { n.multicast.Stop() n.tuntap.Stop() n.core.Stop() - os.Exit(0) } func (n *node) sessionFirewall(pubkey *crypto.BoxPubKey, initiator bool) bool { From 93e81867fdd986f2812d72923d28348bc7bf9e8a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 19 Sep 2019 19:15:59 -0500 Subject: [PATCH 0351/1109] have link.stop signal active links to close, have tcp.stop wait for all listeners and active connections to close --- src/yggdrasil/link.go | 14 ++++++++++++++ src/yggdrasil/tcp.go | 11 +++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index df73cc4c..a4a41e7f 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -25,6 +25,7 @@ type link struct { mutex sync.RWMutex // protects interfaces below interfaces map[linkInfo]*linkInterface tcp tcp // TCP interface support + stopped chan struct{} // TODO timeout (to remove from switch), read from config.ReadTimeout } @@ -70,6 +71,7 @@ func (l *link) init(c *Core) error { l.mutex.Lock() l.interfaces = make(map[linkInfo]*linkInterface) l.mutex.Unlock() + l.stopped = make(chan struct{}) if err := l.tcp.init(l); err != nil { c.log.Errorln("Failed to start TCP interface") @@ -135,6 +137,7 @@ func (l *link) create(msgIO linkInterfaceMsgIO, name, linkType, local, remote st } func (l *link) stop() error { + close(l.stopped) if err := l.tcp.stop(); err != nil { return err } @@ -231,7 +234,18 @@ func (intf *linkInterface) handler() error { go intf.peer.start() intf.reader.Act(nil, intf.reader._read) // Wait for the reader to finish + // TODO find a way to do this without keeping live goroutines around + done := make(chan struct{}) + defer close(done) + go func() { + select { + case <-intf.link.stopped: + intf.msgIO.close() + case <-done: + } + }() err = <-intf.reader.err + // TODO don't report an error if it's just a 'use of closed network connection' if err != nil { intf.link.core.log.Infof("Disconnected %s: %s, source %s; error: %s", strings.ToUpper(intf.info.linkType), themString, intf.info.local, err) diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 8389ecc6..36d80589 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -34,6 +34,7 @@ const tcp_ping_interval = (default_timeout * 2 / 3) // The TCP listener and information about active TCP connections, to avoid duplication. type tcp struct { link *link + waitgroup sync.WaitGroup mutex sync.Mutex // Protecting the below listeners map[string]*TcpListener calls map[string]struct{} @@ -97,9 +98,12 @@ func (t *tcp) init(l *link) error { } func (t *tcp) stop() error { + t.mutex.Lock() for _, listener := range t.listeners { close(listener.Stop) } + t.mutex.Unlock() + t.waitgroup.Wait() return nil } @@ -150,6 +154,7 @@ func (t *tcp) listen(listenaddr string) (*TcpListener, error) { Listener: listener, Stop: make(chan bool), } + t.waitgroup.Add(1) go t.listener(&l, listenaddr) return &l, nil } @@ -159,6 +164,7 @@ func (t *tcp) listen(listenaddr string) (*TcpListener, error) { // Runs the listener, which spawns off goroutines for incoming connections. func (t *tcp) listener(l *TcpListener, listenaddr string) { + defer t.waitgroup.Done() if l == nil { return } @@ -199,8 +205,10 @@ func (t *tcp) listener(l *TcpListener, listenaddr string) { t.link.core.log.Errorln("Failed to accept connection:", err) return } + t.waitgroup.Add(1) go t.handler(sock, true, nil) case <-l.Stop: + // FIXME this races with the goroutine that Accepts a TCP connection, may leak connections when a listener is removed return } } @@ -257,6 +265,7 @@ func (t *tcp) call(saddr string, options interface{}, sintf string) { if err != nil { return } + t.waitgroup.Add(1) t.handler(conn, false, saddr) } else { dst, err := net.ResolveTCPAddr("tcp", saddr) @@ -321,12 +330,14 @@ func (t *tcp) call(saddr string, options interface{}, sintf string) { t.link.core.log.Debugln("Failed to dial TCP:", err) return } + t.waitgroup.Add(1) t.handler(conn, false, nil) } }() } func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}) { + defer t.waitgroup.Done() // Happens after sock.close defer sock.Close() t.setExtraOptions(sock) stream := stream{} From eeb34ce4e4f0cdab87b68d44d6caa524f30993d4 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 19 Sep 2019 19:45:17 -0500 Subject: [PATCH 0352/1109] modify TcpListener --- src/multicast/multicast.go | 2 +- src/yggdrasil/tcp.go | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 63156d82..7f2f5915 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -152,7 +152,7 @@ func (m *Multicast) announce() { for name, listener := range m.listeners { // Prepare our stop function! stop := func() { - listener.Stop <- true + listener.Stop() delete(m.listeners, name) m.log.Debugln("No longer multicasting on", name) } diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 36d80589..01185e54 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -47,7 +47,12 @@ type tcp struct { // multicast interfaces. type TcpListener struct { Listener net.Listener - Stop chan bool + stop chan struct{} +} + +func (l *TcpListener) Stop() { + defer func() { recover() }() + close(l.stop) } // Wrapper function to set additional options for specific connection types. @@ -100,7 +105,7 @@ func (t *tcp) init(l *link) error { func (t *tcp) stop() error { t.mutex.Lock() for _, listener := range t.listeners { - close(listener.Stop) + listener.Stop() } t.mutex.Unlock() t.waitgroup.Wait() @@ -132,7 +137,7 @@ func (t *tcp) reconfigure() { t.mutex.Lock() if listener, ok := t.listeners[d[6:]]; ok { t.mutex.Unlock() - listener.Stop <- true + listener.Stop() t.link.core.log.Infoln("Stopped TCP listener:", d[6:]) } else { t.mutex.Unlock() @@ -152,7 +157,7 @@ func (t *tcp) listen(listenaddr string) (*TcpListener, error) { if err == nil { l := TcpListener{ Listener: listener, - Stop: make(chan bool), + stop: make(chan struct{}), } t.waitgroup.Add(1) go t.listener(&l, listenaddr) @@ -207,7 +212,7 @@ func (t *tcp) listener(l *TcpListener, listenaddr string) { } t.waitgroup.Add(1) go t.handler(sock, true, nil) - case <-l.Stop: + case <-l.stop: // FIXME this races with the goroutine that Accepts a TCP connection, may leak connections when a listener is removed return } From f9163a56b64e90a7085afc20e9123027960b7042 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 19 Sep 2019 19:50:45 -0500 Subject: [PATCH 0353/1109] fix race between listener accepting and shutting down --- src/yggdrasil/tcp.go | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 01185e54..ed8f7b9b 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -184,7 +184,6 @@ func (t *tcp) listener(l *TcpListener, listenaddr string) { t.mutex.Unlock() } // And here we go! - accepted := make(chan bool) defer func() { t.link.core.log.Infoln("Stopping TCP listener on:", l.Listener.Addr().String()) l.Listener.Close() @@ -193,29 +192,19 @@ func (t *tcp) listener(l *TcpListener, listenaddr string) { t.mutex.Unlock() }() t.link.core.log.Infoln("Listening for TCP on:", l.Listener.Addr().String()) + go func() { + <-l.stop + l.Listener.Close() + }() + defer l.Stop() for { - var sock net.Conn - var err error - // Listen in a separate goroutine, as that way it does not block us from - // receiving "stop" events - go func() { - sock, err = l.Listener.Accept() - accepted <- true - }() - // Wait for either an accepted connection, or a message telling us to stop - // the TCP listener - select { - case <-accepted: - if err != nil { - t.link.core.log.Errorln("Failed to accept connection:", err) - return - } - t.waitgroup.Add(1) - go t.handler(sock, true, nil) - case <-l.stop: - // FIXME this races with the goroutine that Accepts a TCP connection, may leak connections when a listener is removed + sock, err := l.Listener.Accept() + if err != nil { + t.link.core.log.Errorln("Failed to accept connection:", err) return } + t.waitgroup.Add(1) + go t.handler(sock, true, nil) } } From 1cd4b6e8ddc8d72b67bb6fadf3896f2246e57425 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 20 Sep 2019 10:08:41 +0100 Subject: [PATCH 0354/1109] Increase multicast interval at startup from 1s to 15s --- src/multicast/multicast.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 7f2f5915..cc7b9af5 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -29,6 +29,7 @@ type Multicast struct { listeners map[string]*yggdrasil.TcpListener listenPort uint16 isOpen bool + interval time.Duration announcer *time.Timer platformhandler *time.Timer } @@ -42,6 +43,7 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log current := m.config.GetCurrent() m.listenPort = current.LinkLocalTCPPort m.groupAddr = "[ff02::114]:9001" + m.interval = time.Second return nil } @@ -245,9 +247,12 @@ func (m *Multicast) announce() { break } } - m.announcer = time.AfterFunc(time.Second*15, func() { + m.announcer = time.AfterFunc(m.interval, func() { m.Act(m, m.announce) }) + if m.interval.Seconds() < 15 { + m.interval += time.Second + } } func (m *Multicast) listen() { From 8003ea0f3ef61ac7c60651a5cef361d3d21804e8 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 20 Sep 2019 17:42:42 -0500 Subject: [PATCH 0355/1109] use a separate multicast beacon interval per multicast interface --- src/multicast/multicast.go | 43 ++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index cc7b9af5..7044eaa2 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -26,24 +26,28 @@ type Multicast struct { log *log.Logger sock *ipv6.PacketConn groupAddr string - listeners map[string]*yggdrasil.TcpListener + listeners map[string]*listenerInfo listenPort uint16 isOpen bool - interval time.Duration announcer *time.Timer platformhandler *time.Timer } +type listenerInfo struct { + listener *yggdrasil.TcpListener + time time.Time + interval time.Duration +} + // Init prepares the multicast interface for use. func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) error { m.core = core m.config = state m.log = log - m.listeners = make(map[string]*yggdrasil.TcpListener) + m.listeners = make(map[string]*listenerInfo) current := m.config.GetCurrent() m.listenPort = current.LinkLocalTCPPort m.groupAddr = "[ff02::114]:9001" - m.interval = time.Second return nil } @@ -151,10 +155,10 @@ func (m *Multicast) announce() { interfaces := m.Interfaces() // There might be interfaces that we configured listeners for but are no // longer up - if that's the case then we should stop the listeners - for name, listener := range m.listeners { + for name, info := range m.listeners { // Prepare our stop function! stop := func() { - listener.Stop() + info.listener.Stop() delete(m.listeners, name) m.log.Debugln("No longer multicasting on", name) } @@ -167,7 +171,7 @@ func (m *Multicast) announce() { // It's possible that the link-local listener address has changed so if // that is the case then we should clean up the interface listener found := false - listenaddr, err := net.ResolveTCPAddr("tcp6", listener.Listener.Addr().String()) + listenaddr, err := net.ResolveTCPAddr("tcp6", info.listener.Listener.Addr().String()) if err != nil { stop() continue @@ -216,43 +220,46 @@ func (m *Multicast) announce() { // Join the multicast group m.sock.JoinGroup(&iface, groupAddr) // Try and see if we already have a TCP listener for this interface - var listener *yggdrasil.TcpListener - if l, ok := m.listeners[iface.Name]; !ok || l.Listener == nil { + var info *listenerInfo + if nfo, ok := m.listeners[iface.Name]; !ok || nfo.listener.Listener == nil { // No listener was found - let's create one listenaddr := fmt.Sprintf("[%s%%%s]:%d", addrIP, iface.Name, m.listenPort) if li, err := m.core.ListenTCP(listenaddr); err == nil { m.log.Debugln("Started multicasting on", iface.Name) // Store the listener so that we can stop it later if needed - m.listeners[iface.Name] = li - listener = li + info = &listenerInfo{listener: li, time: time.Now()} + m.listeners[iface.Name] = info } else { m.log.Warnln("Not multicasting on", iface.Name, "due to error:", err) } } else { // An existing listener was found - listener = m.listeners[iface.Name] + info = m.listeners[iface.Name] } // Make sure nothing above failed for some reason - if listener == nil { + if info == nil { + continue + } + if time.Since(info.time) < info.interval { continue } // Get the listener details and construct the multicast beacon - lladdr := listener.Listener.Addr().String() + lladdr := info.listener.Listener.Addr().String() if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil { a.Zone = "" destAddr.Zone = iface.Name msg := []byte(a.String()) m.sock.WriteTo(msg, nil, destAddr) } + if info.interval.Seconds() < 15 { + info.interval += time.Second + } break } } - m.announcer = time.AfterFunc(m.interval, func() { + m.announcer = time.AfterFunc(time.Second, func() { m.Act(m, m.announce) }) - if m.interval.Seconds() < 15 { - m.interval += time.Second - } } func (m *Multicast) listen() { From 87658f83e941fa574eaa7c0ea5a828480b6310b6 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 20 Sep 2019 23:09:12 -0500 Subject: [PATCH 0356/1109] Revert "force things to buffer in the switch if the best link is currently busy. note that other links can end up sending if they become non-idle for other reasons. this is a temporary workaround to packet reordering, until we can figure out a better solution" This reverts commit 80ba24d51255c3751e2b25aceee52b20d59ff746. --- src/yggdrasil/switch.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 4a1999a5..f4df60d5 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -677,10 +677,13 @@ func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]struct{}) boo ports := t.core.peers.getPorts() for _, cinfo := range closer { to := ports[cinfo.elem.port] + _, isIdle := idle[cinfo.elem.port] var update bool switch { case to == nil: // no port was found, ignore it + case !isIdle: + // the port is busy, ignore it case best == nil: // this is the first idle port we've found, so select it until we find a // better candidate port to use instead @@ -710,9 +713,6 @@ func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]struct{}) boo } if best != nil { // Send to the best idle next hop - if _, isIdle := idle[best.elem.port]; !isIdle { - return false - } delete(idle, best.elem.port) ports[best.elem.port].sendPacketsFrom(t, [][]byte{packet}) return true From 691192ff5ae5a3f012f5f616327645b01e4c1462 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 21 Sep 2019 14:33:45 -0500 Subject: [PATCH 0357/1109] weird scheduler hack, seems to tend to make things more stable without actually locking streams to any particular link --- src/yggdrasil/switch.go | 11 ++++++++--- src/yggdrasil/tcp.go | 5 +++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index f4df60d5..ece2fa26 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -712,10 +712,15 @@ func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]struct{}) boo } } if best != nil { - // Send to the best idle next hop delete(idle, best.elem.port) - ports[best.elem.port].sendPacketsFrom(t, [][]byte{packet}) - return true + // Tell ourselves to send to this node later + // If another (e.g. even better) hop becomes idle in the mean time, it'll take the packet instead + // FIXME this is just a hack, but seems to help with stability... + go t.Act(nil, func() { + t._idleIn(best.elem.port) + }) + //ports[best.elem.port].sendPacketsFrom(t, [][]byte{packet}) + //return true } // Didn't find anyone idle to send it to return false diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index ed8f7b9b..66f708c2 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -233,8 +233,9 @@ func (t *tcp) call(saddr string, options interface{}, sintf string) { } defer func() { // Block new calls for a little while, to mitigate livelock scenarios - time.Sleep(default_timeout) - time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond) + rand.Seed(time.Now().UnixNano()) + delay := default_timeout + time.Duration(rand.Intn(10000))*time.Millisecond + time.Sleep(delay) t.mutex.Lock() delete(t.calls, callname) t.mutex.Unlock() From 606d9ac97baec8018dd5e21289f32d03dad52202 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 24 Sep 2019 22:06:12 +0100 Subject: [PATCH 0358/1109] Build VyOS amd64/i386 Vyatta packages as well as EdgeRouter packages --- .circleci/config.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5778ffc9..1456332a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -68,13 +68,15 @@ jobs: find ~/rpmbuild/SRPMS/ -name '*.rpm' -exec mv {} /tmp/upload \; - run: - name: Build for EdgeRouter + name: Build for EdgeRouter and VyOS command: | rm -f {yggdrasil,yggdrasilctl} git clone https://github.com/neilalexander/vyatta-yggdrasil /tmp/vyatta-yggdrasil; cd /tmp/vyatta-yggdrasil; BUILDDIR_YGG=$CIRCLE_WORKING_DIRECTORY ./build-edgerouter-x $CIRCLE_BRANCH; BUILDDIR_YGG=$CIRCLE_WORKING_DIRECTORY ./build-edgerouter-lite $CIRCLE_BRANCH; + BUILDDIR_YGG=$CIRCLE_WORKING_DIRECTORY ./build-vyos-i386 $CIRCLE_BRANCH + BUILDDIR_YGG=$CIRCLE_WORKING_DIRECTORY ./build-vyos-amd64 $CIRCLE_BRANCH mv *.deb /tmp/upload; - persist_to_workspace: From 8c64e6fa093a06f166fd654450a8958191b9c6cd Mon Sep 17 00:00:00 2001 From: Arceliar Date: Tue, 24 Sep 2019 18:01:35 -0500 Subject: [PATCH 0359/1109] explicitly notify the switch when a link appears to be blocked in a send instead of assuming this is the case for all idle links. how we decide when it's really blocked still needs testing/optimizing --- src/yggdrasil/link.go | 17 +++++++++++++++++ src/yggdrasil/switch.go | 34 +++++++++++++++++++++++++--------- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index a4a41e7f..ece69caf 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -64,6 +64,8 @@ type linkInterface struct { closeTimer *time.Timer // Fires when the link has been idle so long we need to close it inSwitch bool // True if the switch is tracking this link stalled bool // True if we haven't been receiving any response traffic + sendSeqSent uint // Incremented each time we start sending + sendSeqRecv uint // Incremented each time we finish sending } func (l *link) init(c *Core) error { @@ -273,9 +275,23 @@ func (intf *linkInterface) notifySending(size int, isLinkTraffic bool) { } intf.sendTimer = time.AfterFunc(sendTime, intf.notifyBlockedSend) intf._cancelStallTimer() + intf.sendSeqSent++ + seq := intf.sendSeqSent + intf.Act(nil, func() { + intf._checkSending(seq) + }) }) } +// If check if we're still sending +func (intf *linkInterface) _checkSending(seq uint) { + if intf.sendSeqRecv != seq { + intf.link.core.switchTable.Act(intf, func() { + intf.link.core.switchTable._sendingIn(intf.peer.port) + }) + } +} + // we just sent something, so cancel any pending timer to send keep-alive traffic func (intf *linkInterface) _cancelStallTimer() { if intf.stallTimer != nil { @@ -305,6 +321,7 @@ func (intf *linkInterface) notifySent(size int, isLinkTraffic bool) { if size > 0 && intf.stallTimer == nil { intf.stallTimer = time.AfterFunc(stallTime, intf.notifyStalled) } + intf.sendSeqRecv++ }) } diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index ece2fa26..0150e173 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -177,6 +177,7 @@ type switchTable struct { phony.Inbox // Owns the below queues switch_buffers // Queues - not atomic so ONLY use through the actor idle map[switchPort]struct{} // idle peers - not atomic so ONLY use through the actor + sending map[switchPort]struct{} // peers known to be blocked in a send (somehow) } // Minimum allowed total size of switch queues. @@ -203,6 +204,7 @@ func (t *switchTable) init(core *Core) { core.config.Mutex.RUnlock() t.queues.bufs = make(map[string]switch_buffer) t.idle = make(map[switchPort]struct{}) + t.sending = make(map[switchPort]struct{}) }) } @@ -527,7 +529,7 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep t.parent = sender.port t.core.peers.sendSwitchMsgs(t) } - if doUpdate { + if true || doUpdate { t.updater.Store(&sync.Once{}) } return @@ -664,7 +666,7 @@ func (t *switchTable) bestPortForCoords(coords []byte) switchPort { // Handle an incoming packet // Either send it to ourself, or to the first idle peer that's free // Returns true if the packet has been handled somehow, false if it should be queued -func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]struct{}) bool { +func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]struct{}, sending map[switchPort]struct{}) bool { coords := switch_getPacketCoords(packet) closer := t.getCloser(coords) if len(closer) == 0 { @@ -677,12 +679,13 @@ func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]struct{}) boo ports := t.core.peers.getPorts() for _, cinfo := range closer { to := ports[cinfo.elem.port] - _, isIdle := idle[cinfo.elem.port] + //_, isIdle := idle[cinfo.elem.port] + _, isSending := sending[cinfo.elem.port] var update bool switch { case to == nil: // no port was found, ignore it - case !isIdle: + case isSending: // the port is busy, ignore it case best == nil: // this is the first idle port we've found, so select it until we find a @@ -702,6 +705,7 @@ func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]struct{}) boo // has a n older tstamp, so presumably a worse path case cinfo.elem.time.Before(best.elem.time): // same tstamp, but got it earlier, so presumably a better path + //t.core.log.Println("DEBUG new best:", best.elem.time, cinfo.elem.time) update = true default: // the search for a port has finished @@ -712,13 +716,18 @@ func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]struct{}) boo } } if best != nil { - delete(idle, best.elem.port) + if _, isIdle := idle[best.elem.port]; isIdle { + delete(idle, best.elem.port) + ports[best.elem.port].sendPacketsFrom(t, [][]byte{packet}) + return true + } + //delete(idle, best.elem.port) // Tell ourselves to send to this node later // If another (e.g. even better) hop becomes idle in the mean time, it'll take the packet instead // FIXME this is just a hack, but seems to help with stability... - go t.Act(nil, func() { - t._idleIn(best.elem.port) - }) + //go t.Act(nil, func() { + // t._idleIn(best.elem.port) + //}) //ports[best.elem.port].sendPacketsFrom(t, [][]byte{packet}) //return true } @@ -847,7 +856,7 @@ func (t *switchTable) packetInFrom(from phony.Actor, bytes []byte) { func (t *switchTable) _packetIn(bytes []byte) { // Try to send it somewhere (or drop it if it's corrupt or at a dead end) - if !t._handleIn(bytes, t.idle) { + if !t._handleIn(bytes, t.idle, t.sending) { // There's nobody free to take it right now, so queue it for later packet := switch_packetInfo{bytes, time.Now()} streamID := switch_getPacketStreamID(packet.bytes) @@ -874,8 +883,15 @@ func (t *switchTable) _packetIn(bytes []byte) { func (t *switchTable) _idleIn(port switchPort) { // Try to find something to send to this peer + delete(t.sending, port) if !t._handleIdle(port) { // Didn't find anything ready to send yet, so stay idle t.idle[port] = struct{}{} } } + +func (t *switchTable) _sendingIn(port switchPort) { + if _, isIn := t.idle[port]; !isIn { + t.sending[port] = struct{}{} + } +} From b9e74f34ec2663b043374d1e0163aa55338792f5 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Tue, 24 Sep 2019 18:28:13 -0500 Subject: [PATCH 0360/1109] replace the send-to-self with a timer and an arbitrary timeout; i don't really like this but it seems to work better (1 ms is fast by human standards but an eternity for a syscall or the scheduler, so i think that's reasonable) --- src/yggdrasil/link.go | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index ece69caf..875fde5e 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -64,8 +64,6 @@ type linkInterface struct { closeTimer *time.Timer // Fires when the link has been idle so long we need to close it inSwitch bool // True if the switch is tracking this link stalled bool // True if we haven't been receiving any response traffic - sendSeqSent uint // Incremented each time we start sending - sendSeqRecv uint // Incremented each time we finish sending } func (l *link) init(c *Core) error { @@ -275,21 +273,14 @@ func (intf *linkInterface) notifySending(size int, isLinkTraffic bool) { } intf.sendTimer = time.AfterFunc(sendTime, intf.notifyBlockedSend) intf._cancelStallTimer() - intf.sendSeqSent++ - seq := intf.sendSeqSent - intf.Act(nil, func() { - intf._checkSending(seq) - }) }) } -// If check if we're still sending -func (intf *linkInterface) _checkSending(seq uint) { - if intf.sendSeqRecv != seq { - intf.link.core.switchTable.Act(intf, func() { - intf.link.core.switchTable._sendingIn(intf.peer.port) - }) - } +// called by an AfterFunc if we seem to be blocked in a send syscall for a long time +func (intf *linkInterface) _notifySyscall() { + intf.link.core.switchTable.Act(intf, func() { + intf.link.core.switchTable._sendingIn(intf.peer.port) + }) } // we just sent something, so cancel any pending timer to send keep-alive traffic @@ -321,7 +312,6 @@ func (intf *linkInterface) notifySent(size int, isLinkTraffic bool) { if size > 0 && intf.stallTimer == nil { intf.stallTimer = time.AfterFunc(stallTime, intf.notifyStalled) } - intf.sendSeqRecv++ }) } @@ -397,7 +387,15 @@ func (w *linkWriter) sendFrom(from phony.Actor, bss [][]byte, isLinkTraffic bool size += len(bs) } w.intf.notifySending(size, isLinkTraffic) + var once sync.Once + timer := time.AfterFunc(time.Millisecond, func() { + once.Do(func() { + w.intf.Act(nil, w.intf._notifySyscall) + }) + }) w.intf.msgIO.writeMsgs(bss) + // Make sure we either stop the timer from doing anything or wait until it's done + once.Do(func() { timer.Stop() }) w.intf.notifySent(size, isLinkTraffic) // Cleanup for _, bs := range bss { From ac58c3586eacda562b3e71477a0f8353de460be5 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 25 Sep 2019 17:53:25 -0500 Subject: [PATCH 0361/1109] cleanup/comments --- src/yggdrasil/link.go | 10 ++++++++-- src/yggdrasil/switch.go | 10 +--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 875fde5e..98c080c7 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -291,9 +291,11 @@ func (intf *linkInterface) _cancelStallTimer() { } } -// called by an AfterFunc if we appear to have timed out +// This gets called from a time.AfterFunc, and notifies the switch that we appear +// to have gotten blocked on a write, so the switch should start routing traffic +// through other links, if alternatives exist func (intf *linkInterface) notifyBlockedSend() { - intf.Act(nil, func() { // Sent from a time.AfterFunc + intf.Act(nil, func() { if intf.sendTimer != nil { //As far as we know, we're still trying to send, and the timer fired. intf.link.core.switchTable.blockPeer(intf.peer.port) @@ -387,8 +389,12 @@ func (w *linkWriter) sendFrom(from phony.Actor, bss [][]byte, isLinkTraffic bool size += len(bs) } w.intf.notifySending(size, isLinkTraffic) + // start a timer that will fire if we get stuck in writeMsgs for an oddly long time var once sync.Once timer := time.AfterFunc(time.Millisecond, func() { + // 1 ms is kind of arbitrary + // the rationale is that this should be very long compared to a syscall + // but it's still short compared to end-to-end latency or human perception once.Do(func() { w.intf.Act(nil, w.intf._notifySyscall) }) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 0150e173..ba30758c 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -721,15 +721,6 @@ func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]struct{}, sen ports[best.elem.port].sendPacketsFrom(t, [][]byte{packet}) return true } - //delete(idle, best.elem.port) - // Tell ourselves to send to this node later - // If another (e.g. even better) hop becomes idle in the mean time, it'll take the packet instead - // FIXME this is just a hack, but seems to help with stability... - //go t.Act(nil, func() { - // t._idleIn(best.elem.port) - //}) - //ports[best.elem.port].sendPacketsFrom(t, [][]byte{packet}) - //return true } // Didn't find anyone idle to send it to return false @@ -799,6 +790,7 @@ func (b *switch_buffers) _cleanup(t *switchTable) { // 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) bool { + // TODO? only send packets for which this is the best next hop that isn't currently blocked sending to := t.core.peers.getPorts()[port] if to == nil { return true From 19c2a573aab101a38a8f978fbbface47abda98b0 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 26 Sep 2019 22:56:45 +0100 Subject: [PATCH 0362/1109] Update changelog for v0.3.9 --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8133a29..3945bf01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,33 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> +## [0.3.9] - 2019-09-27 +### Added +- Yggdrasil will now complain more verbosely when a peer URI is incorrectly formatted +- Soft-shutdown methods have been added, allowing a node to shut down gracefully when terminated +- New multicast interval logic which sends multicast beacons more often when Yggdrasil is first started to increase the chance of finding nearby nodes quickly after startup + +### Changed +- The switch now buffers packets more eagerly in an attempt to give the best link a chance to send, which appears to reduce packet reordering when crossing aggregate sets of peerings +- Substantial amounts of the codebase have been refactored to use the actor model, which should substantially reduce the chance of deadlocks +- Nonce tracking in sessions has been modified so that memory usage is reduced whilst still only allowing duplicate packets within a small window +- Soft-reconfiguration support has been simplified using new actor functions +- The garbage collector threshold has been adjusted for mobile builds +- The maximum queue size is now managed exclusively by the switch rather than by the core + +### Fixed +- The broken `hjson-go` dependency which affected builds of the previous version has now been resolved in the module manifest +- Some minor memory leaks in the switch have been fixed, which improves memory usage on mobile builds +- A memory leak in the add-peer loop has been fixed +- The admin socket now reports the correct URI strings for SOCKS peers in `getPeers` +- A race condition when dialling a remote node by both the node address and routed prefix simultaneously has been fixed +- A race condition between the router and the dial code resulting in a panic has been fixed +- A panic which could occur when the TUN/TAP interface disappears (e.g. during soft-shutdown) has been fixed +- A bug in the semantic versioning script which accompanies Yggdrasil for builds has been fixed + +### Removed +- A number of legacy debug functions have now been removed and a number of exported API functions are now better documented + ## [0.3.8] - 2019-08-21 ### Changed - Yggdrasil can now send multiple packets from the switch at once, which results in improved throughput with smaller packets or lower MTUs @@ -39,10 +66,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - New nonce tracking should help to reduce the number of packets dropped as a result of multiple/aggregate paths or congestion control in the switch ### Fixed -- **Security vulnerability**: Address verification was not strict enough, which could result in a malicious session sending traffic with unexpected or spoofed source or destination addresses which Yggdrasil could fail to reject +- A deadlock was fixed in the session code which could result in Yggdrasil failing to pass traffic after some time + +### Security +- Address verification was not strict enough, which could result in a malicious session sending traffic with unexpected or spoofed source or destination addresses which Yggdrasil could fail to reject - Versions `0.3.6` and `0.3.7` are vulnerable - users of these versions should upgrade as soon as possible - Versions `0.3.5` and earlier are not affected -- A deadlock was fixed in the session code which could result in Yggdrasil failing to pass traffic after some time ## [0.3.7] - 2019-08-14 ### Changed From e16d3efb0aa973c7a5591ebbf6baeba6d7bb55d6 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 26 Sep 2019 18:11:58 -0500 Subject: [PATCH 0363/1109] check packet length before checking if it's an ipv6 packet, and add some trace level logging whenever a packet is rejected for being too short to parse --- src/tuntap/iface.go | 7 +++++++ src/util/cancellation.go | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 753a8d02..5db9f1ca 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -148,6 +148,11 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { // Offset the buffer from now on so that we can ignore ethernet frames if // they are present bs := recvd[offset:] + // Check if the packet is long enough to detect if it's an ICMP packet or not + if len(bs) < 7 { + tun.log.Traceln("TUN/TAP iface read undersized unknown packet, length:", len(bs)) + return + } // If we detect an ICMP packet then hand it to the ICMPv6 module if bs[6] == 58 { // Found an ICMPv6 packet - we need to make sure to give ICMPv6 the full @@ -175,6 +180,7 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { if bs[0]&0xf0 == 0x60 { // Check if we have a fully-sized IPv6 header if len(bs) < 40 { + tun.log.Traceln("TUN/TAP iface read undersized ipv6 packet, length:", len(bs)) return } // Check the packet size @@ -188,6 +194,7 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { } else if bs[0]&0xf0 == 0x40 { // Check if we have a fully-sized IPv4 header if len(bs) < 20 { + tun.log.Traceln("TUN/TAP iface read undersized ipv6 packet, length:", len(bs)) return } // Check the packet size diff --git a/src/util/cancellation.go b/src/util/cancellation.go index 6b2002c8..1f6d1658 100644 --- a/src/util/cancellation.go +++ b/src/util/cancellation.go @@ -11,8 +11,8 @@ import ( // This is and is similar to a context, but with an error to specify the reason for the cancellation. type Cancellation interface { Finished() <-chan struct{} // Finished returns a channel which will be closed when Cancellation.Cancel is first called. - Cancel(error) error // Cancel closes the channel returned by Finished and sets the error returned by error, or else returns the existing error if the Cancellation has already run. - Error() error // Error returns the error provided to Cancel, or nil if no error has been provided. + Cancel(error) error // Cancel closes the channel returned by Finished and sets the error returned by error, or else returns the existing error if the Cancellation has already run. + Error() error // Error returns the error provided to Cancel, or nil if no error has been provided. } // CancellationFinalized is an error returned if a cancellation object was garbage collected and the finalizer was run. From 0f99d590a1c2592e333a51a71b4536aebaea5cc0 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 26 Sep 2019 18:15:26 -0500 Subject: [PATCH 0364/1109] typo, ipv6->ipv4 --- src/tuntap/iface.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 5db9f1ca..0da99631 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -194,7 +194,7 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { } else if bs[0]&0xf0 == 0x40 { // Check if we have a fully-sized IPv4 header if len(bs) < 20 { - tun.log.Traceln("TUN/TAP iface read undersized ipv6 packet, length:", len(bs)) + tun.log.Traceln("TUN/TAP iface read undersized ipv4 packet, length:", len(bs)) return } // Check the packet size From 94f4d6e2866aa49e1752e5fbcec67db2456b8816 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 26 Sep 2019 18:21:35 -0500 Subject: [PATCH 0365/1109] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3945bf01..fdac254f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - A race condition between the router and the dial code resulting in a panic has been fixed - A panic which could occur when the TUN/TAP interface disappears (e.g. during soft-shutdown) has been fixed - A bug in the semantic versioning script which accompanies Yggdrasil for builds has been fixed +- A panic which could occur when the TUN/TAP interface reads an undersized/corrupted packet has been fixed ### Removed - A number of legacy debug functions have now been removed and a number of exported API functions are now better documented From d6ee20580de03e8f44d993ab274dbf9f962bc25d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 27 Sep 2019 09:37:34 +0100 Subject: [PATCH 0366/1109] Set TimeoutStopSec for systemd service --- contrib/systemd/yggdrasil.service | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/systemd/yggdrasil.service b/contrib/systemd/yggdrasil.service index bd52ec9f..37859e79 100644 --- a/contrib/systemd/yggdrasil.service +++ b/contrib/systemd/yggdrasil.service @@ -16,6 +16,7 @@ ExecStartPre=/bin/sh -ec "if ! test -s /etc/yggdrasil.conf; \ ExecStart=/usr/bin/yggdrasil -useconffile /etc/yggdrasil.conf ExecReload=/bin/kill -HUP $MAINPID Restart=always +TimeoutStopSec=5 [Install] WantedBy=multi-user.target From 6ead31fb87d38cfe26568b997aea3ae0e1d22220 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 27 Sep 2019 09:44:55 +0100 Subject: [PATCH 0367/1109] Remove RPM spec from contrib as it is now in yggdrasil-network/yggdrasil-package-rpm --- contrib/rpm/yggdrasil.spec | 47 -------------------------------------- 1 file changed, 47 deletions(-) delete mode 100644 contrib/rpm/yggdrasil.spec diff --git a/contrib/rpm/yggdrasil.spec b/contrib/rpm/yggdrasil.spec deleted file mode 100644 index bab50906..00000000 --- a/contrib/rpm/yggdrasil.spec +++ /dev/null @@ -1,47 +0,0 @@ -Name: yggdrasil -Version: 0.3.0 -Release: 1%{?dist} -Summary: End-to-end encrypted IPv6 networking - -License: GPLv3 -URL: https://yggdrasil-network.github.io -Source0: https://codeload.github.com/yggdrasil-network/yggdrasil-go/tar.gz/v0.3.0 - -%{?systemd_requires} -BuildRequires: systemd golang >= 1.11 - -%description -Yggdrasil is a proof-of-concept to explore a wholly different approach to -network routing. Whereas current computer networks depend heavily on very -centralised design and configuration, Yggdrasil breaks this mould by making -use of a global spanning tree to form a scalable IPv6 encrypted mesh network. - -%prep -%setup -qn yggdrasil-go-%{version} - -%build -./build -t -l "-linkmode=external" - -%install -rm -rf %{buildroot} -mkdir -p %{buildroot}/%{_bindir} -mkdir -p %{buildroot}/%{_sysconfdir}/systemd/system -install -m 0755 yggdrasil %{buildroot}/%{_bindir}/yggdrasil -install -m 0755 yggdrasilctl %{buildroot}/%{_bindir}/yggdrasilctl -install -m 0755 contrib/systemd/yggdrasil.service %{buildroot}/%{_sysconfdir}/systemd/system/yggdrasil.service -install -m 0755 contrib/systemd/yggdrasil-resume.service %{buildroot}/%{_sysconfdir}/systemd/system/yggdrasil-resume.service - -%files -%{_bindir}/yggdrasil -%{_bindir}/yggdrasilctl -%{_sysconfdir}/systemd/system/yggdrasil.service -%{_sysconfdir}/systemd/system/yggdrasil-resume.service - -%post -%systemd_post yggdrasil.service - -%preun -%systemd_preun yggdrasil.service - -%postun -%systemd_postun_with_restart yggdrasil.service From 5c3f7df77c9265f3e33cd24bf3c5286d7f5c4ed7 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 27 Sep 2019 09:49:19 +0100 Subject: [PATCH 0368/1109] Update submodule doc/yggdrasil-network.github.io --- doc/yggdrasil-network.github.io | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/yggdrasil-network.github.io b/doc/yggdrasil-network.github.io index 10672210..c876890a 160000 --- a/doc/yggdrasil-network.github.io +++ b/doc/yggdrasil-network.github.io @@ -1 +1 @@ -Subproject commit 10672210f2fdce97dd5c301dfeed47284d4a28f2 +Subproject commit c876890a51d9140e68d5cec7fbeb2146c2562792 From d96fb27ab8adef666c5b0a1d2ce94e5f9bc58922 Mon Sep 17 00:00:00 2001 From: Aleksander Mistewicz Date: Wed, 25 Sep 2019 15:58:19 +0200 Subject: [PATCH 0369/1109] Add simple connection test --- src/yggdrasil/core_test.go | 47 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/yggdrasil/core_test.go diff --git a/src/yggdrasil/core_test.go b/src/yggdrasil/core_test.go new file mode 100644 index 00000000..4b283d4d --- /dev/null +++ b/src/yggdrasil/core_test.go @@ -0,0 +1,47 @@ +package yggdrasil + +import ( + "os" + "testing" + + "github.com/gologme/log" + + "github.com/yggdrasil-network/yggdrasil-go/src/config" +) + +// GenerateConfig is modification +func GenerateConfig() *config.NodeConfig { + cfg := config.GenerateConfig() + cfg.AdminListen = "none" + cfg.Listen = []string{"tcp://127.0.0.1:0"} + cfg.IfName = "none" + + return cfg +} + +func GetLoggerWithPrefix(prefix string) *log.Logger { + l := log.New(os.Stderr, prefix, log.Flags()) + l.EnableLevel("info") + l.EnableLevel("warn") + l.EnableLevel("error") + return l +} + +func TestCore_Start(t *testing.T) { + nodeA := Core{} + _, err := nodeA.Start(GenerateConfig(), GetLoggerWithPrefix("A: ")) + if err != nil { + t.Fatal(err) + } + + nodeB := Core{} + _, err = nodeB.Start(GenerateConfig(), GetLoggerWithPrefix("B: ")) + if err != nil { + t.Fatal(err) + } + + err = nodeB.AddPeer("tcp://"+nodeA.link.tcp.getAddr().String(), "") + if err != nil { + t.Fatal(err) + } +} From fffbbbcbd32923dc1219af4f7dfaeb664ca223de Mon Sep 17 00:00:00 2001 From: Aleksander Mistewicz Date: Wed, 25 Sep 2019 20:07:36 +0200 Subject: [PATCH 0370/1109] Pass message between nodes --- src/yggdrasil/core_test.go | 85 +++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/core_test.go b/src/yggdrasil/core_test.go index 4b283d4d..5bca94ff 100644 --- a/src/yggdrasil/core_test.go +++ b/src/yggdrasil/core_test.go @@ -1,8 +1,11 @@ package yggdrasil import ( + "bytes" + "math/rand" "os" "testing" + "time" "github.com/gologme/log" @@ -27,7 +30,7 @@ func GetLoggerWithPrefix(prefix string) *log.Logger { return l } -func TestCore_Start(t *testing.T) { +func CreateAndConnectTwo(t *testing.T) (*Core, *Core) { nodeA := Core{} _, err := nodeA.Start(GenerateConfig(), GetLoggerWithPrefix("A: ")) if err != nil { @@ -44,4 +47,84 @@ func TestCore_Start(t *testing.T) { 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 +} + +func TestCore_Start_Connect(t *testing.T) { + CreateAndConnectTwo(t) +} + +func TestCore_Start_Transfer(t *testing.T) { + nodeA, nodeB := CreateAndConnectTwo(t) + + // Listen + listener, err := nodeA.ConnListen() + if err != nil { + t.Fatal(err) + } + defer listener.Close() + + done := make(chan struct{}) + go func() { + conn, err := listener.Accept() + if err != nil { + t.Error(err) + return + } + defer conn.Close() + buf := make([]byte, 64) + n, err := conn.Read(buf) + if err != nil { + t.Error(err) + return + } + if n != 64 { + t.Error("missing data") + return + } + _, err = conn.Write(buf) + if err != nil { + t.Error(err) + } + done <- struct{}{} + }() + + time.Sleep(3 * time.Second) // FIXME + // Dial + dialer, err := nodeB.ConnDialer() + if err != nil { + t.Fatal(err) + } + t.Log(nodeA.GetSwitchPeers()) + t.Log(nodeB.GetSwitchPeers()) + t.Log(nodeA.GetSessions()) + t.Log(nodeB.GetSessions()) + conn, err := dialer.Dial("nodeid", nodeA.NodeID().String()) + if err != nil { + t.Fatal(err) + } + defer conn.Close() + msg := make([]byte, 64) + rand.Read(msg) + conn.Write(msg) + if err != nil { + t.Fatal(err) + } + buf := make([]byte, 64) + _, err = conn.Read(buf) + if err != nil { + t.Fatal(err) + } + if bytes.Compare(msg, buf) != 0 { + t.Fatal("expected echo") + } + <-done } From 8677a042cf7d079a41c25f2d85e610c3b76f14cc Mon Sep 17 00:00:00 2001 From: Aleksander Mistewicz Date: Sat, 28 Sep 2019 14:10:17 +0200 Subject: [PATCH 0371/1109] Wait for nodes to negotiate --- src/yggdrasil/core_test.go | 44 +++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/src/yggdrasil/core_test.go b/src/yggdrasil/core_test.go index 5bca94ff..0c70aaf8 100644 --- a/src/yggdrasil/core_test.go +++ b/src/yggdrasil/core_test.go @@ -58,35 +58,45 @@ func CreateAndConnectTwo(t *testing.T) (*Core, *Core) { 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 +} + func TestCore_Start_Connect(t *testing.T) { CreateAndConnectTwo(t) } -func TestCore_Start_Transfer(t *testing.T) { - nodeA, nodeB := CreateAndConnectTwo(t) - +func CreateEchoListener(t *testing.T, nodeA *Core, bufLen int) chan struct{} { // Listen listener, err := nodeA.ConnListen() if err != nil { t.Fatal(err) } - defer listener.Close() 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, 64) + buf := make([]byte, bufLen) n, err := conn.Read(buf) if err != nil { t.Error(err) return } - if n != 64 { + if n != bufLen { t.Error("missing data") return } @@ -97,28 +107,36 @@ func TestCore_Start_Transfer(t *testing.T) { done <- struct{}{} }() - time.Sleep(3 * time.Second) // FIXME + return done +} + +func TestCore_Start_Transfer(t *testing.T) { + nodeA, nodeB := CreateAndConnectTwo(t) + + msgLen := 1500 + done := CreateEchoListener(t, nodeA, msgLen) + + if !WaitConnected(nodeA, nodeB) { + t.Fatal("nodes did not connect") + } + // Dial dialer, err := nodeB.ConnDialer() if err != nil { t.Fatal(err) } - t.Log(nodeA.GetSwitchPeers()) - t.Log(nodeB.GetSwitchPeers()) - t.Log(nodeA.GetSessions()) - t.Log(nodeB.GetSessions()) conn, err := dialer.Dial("nodeid", nodeA.NodeID().String()) if err != nil { t.Fatal(err) } defer conn.Close() - msg := make([]byte, 64) + msg := make([]byte, msgLen) rand.Read(msg) conn.Write(msg) if err != nil { t.Fatal(err) } - buf := make([]byte, 64) + buf := make([]byte, msgLen) _, err = conn.Read(buf) if err != nil { t.Fatal(err) From 21b236771b31499590ed704b93902e87b88b33a0 Mon Sep 17 00:00:00 2001 From: Aleksander Mistewicz Date: Sat, 28 Sep 2019 14:20:57 +0200 Subject: [PATCH 0372/1109] Add a simple transfer benchmark --- src/yggdrasil/core_test.go | 73 ++++++++++++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 15 deletions(-) diff --git a/src/yggdrasil/core_test.go b/src/yggdrasil/core_test.go index 0c70aaf8..aaf600af 100644 --- a/src/yggdrasil/core_test.go +++ b/src/yggdrasil/core_test.go @@ -30,7 +30,7 @@ func GetLoggerWithPrefix(prefix string) *log.Logger { return l } -func CreateAndConnectTwo(t *testing.T) (*Core, *Core) { +func CreateAndConnectTwo(t testing.TB) (*Core, *Core) { nodeA := Core{} _, err := nodeA.Start(GenerateConfig(), GetLoggerWithPrefix("A: ")) if err != nil { @@ -74,7 +74,7 @@ func TestCore_Start_Connect(t *testing.T) { CreateAndConnectTwo(t) } -func CreateEchoListener(t *testing.T, nodeA *Core, bufLen int) chan struct{} { +func CreateEchoListener(t testing.TB, nodeA *Core, bufLen int, repeats int) chan struct{} { // Listen listener, err := nodeA.ConnListen() if err != nil { @@ -91,18 +91,21 @@ func CreateEchoListener(t *testing.T, nodeA *Core, bufLen int) chan struct{} { } defer conn.Close() buf := make([]byte, bufLen) - 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) + + 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{}{} }() @@ -114,7 +117,7 @@ func TestCore_Start_Transfer(t *testing.T) { nodeA, nodeB := CreateAndConnectTwo(t) msgLen := 1500 - done := CreateEchoListener(t, nodeA, msgLen) + done := CreateEchoListener(t, nodeA, msgLen, 1) if !WaitConnected(nodeA, nodeB) { t.Fatal("nodes did not connect") @@ -146,3 +149,43 @@ func TestCore_Start_Transfer(t *testing.T) { } <-done } + +func BenchmarkCore_Start_Transfer(b *testing.B) { + nodeA, nodeB := CreateAndConnectTwo(b) + + 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 +} From 805376609209d543f92b2bf165f5f668f230c16e Mon Sep 17 00:00:00 2001 From: Aleksander Mistewicz Date: Sat, 28 Sep 2019 14:24:54 +0200 Subject: [PATCH 0373/1109] Add verbosity setting --- src/yggdrasil/core_test.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/yggdrasil/core_test.go b/src/yggdrasil/core_test.go index aaf600af..2823495f 100644 --- a/src/yggdrasil/core_test.go +++ b/src/yggdrasil/core_test.go @@ -22,23 +22,26 @@ func GenerateConfig() *config.NodeConfig { return cfg } -func GetLoggerWithPrefix(prefix string) *log.Logger { +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 } -func CreateAndConnectTwo(t testing.TB) (*Core, *Core) { +func CreateAndConnectTwo(t testing.TB, verbose bool) (*Core, *Core) { nodeA := Core{} - _, err := nodeA.Start(GenerateConfig(), GetLoggerWithPrefix("A: ")) + _, err := nodeA.Start(GenerateConfig(), GetLoggerWithPrefix("A: ", verbose)) if err != nil { t.Fatal(err) } nodeB := Core{} - _, err = nodeB.Start(GenerateConfig(), GetLoggerWithPrefix("B: ")) + _, err = nodeB.Start(GenerateConfig(), GetLoggerWithPrefix("B: ", verbose)) if err != nil { t.Fatal(err) } @@ -71,7 +74,7 @@ func WaitConnected(nodeA, nodeB *Core) bool { } func TestCore_Start_Connect(t *testing.T) { - CreateAndConnectTwo(t) + CreateAndConnectTwo(t, true) } func CreateEchoListener(t testing.TB, nodeA *Core, bufLen int, repeats int) chan struct{} { @@ -114,7 +117,7 @@ func CreateEchoListener(t testing.TB, nodeA *Core, bufLen int, repeats int) chan } func TestCore_Start_Transfer(t *testing.T) { - nodeA, nodeB := CreateAndConnectTwo(t) + nodeA, nodeB := CreateAndConnectTwo(t, true) msgLen := 1500 done := CreateEchoListener(t, nodeA, msgLen, 1) @@ -151,7 +154,7 @@ func TestCore_Start_Transfer(t *testing.T) { } func BenchmarkCore_Start_Transfer(b *testing.B) { - nodeA, nodeB := CreateAndConnectTwo(b) + nodeA, nodeB := CreateAndConnectTwo(b, false) msgLen := 1500 // typical MTU done := CreateEchoListener(b, nodeA, msgLen, b.N) From 783959208cf879a1ab0f5cf8c3566aeecc911954 Mon Sep 17 00:00:00 2001 From: Aleksander Mistewicz Date: Sat, 28 Sep 2019 14:41:53 +0200 Subject: [PATCH 0374/1109] Add more comments to explain helper functions --- src/yggdrasil/core_test.go | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/yggdrasil/core_test.go b/src/yggdrasil/core_test.go index 2823495f..364ed0b0 100644 --- a/src/yggdrasil/core_test.go +++ b/src/yggdrasil/core_test.go @@ -12,7 +12,7 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/config" ) -// GenerateConfig is modification +// GenerateConfig produces default configuration with suitable modifications for tests. func GenerateConfig() *config.NodeConfig { cfg := config.GenerateConfig() cfg.AdminListen = "none" @@ -22,6 +22,8 @@ func GenerateConfig() *config.NodeConfig { 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 { @@ -33,14 +35,16 @@ func GetLoggerWithPrefix(prefix string, verbose bool) *log.Logger { return l } -func CreateAndConnectTwo(t testing.TB, verbose bool) (*Core, *Core) { - nodeA := Core{} +// 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 := Core{} + nodeB = new(Core) _, err = nodeB.Start(GenerateConfig(), GetLoggerWithPrefix("B: ", verbose)) if err != nil { t.Fatal(err) @@ -58,7 +62,7 @@ func CreateAndConnectTwo(t testing.TB, verbose bool) (*Core, *Core) { t.Fatal("unexpected number of peers", l) } - return &nodeA, &nodeB + return nodeA, nodeB } // WaitConnected blocks until either nodes negotiated DHT or 5 seconds passed. @@ -73,17 +77,16 @@ func WaitConnected(nodeA, nodeB *Core) bool { return false } -func TestCore_Start_Connect(t *testing.T) { - CreateAndConnectTwo(t, true) -} - +// 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 + // 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() @@ -116,6 +119,12 @@ func CreateEchoListener(t testing.TB, nodeA *Core, bufLen int, repeats int) chan 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) @@ -153,6 +162,7 @@ func TestCore_Start_Transfer(t *testing.T) { <-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) From 045a24d74ed06f6aa658a131f3980fb8e60938b5 Mon Sep 17 00:00:00 2001 From: Arano-kai Date: Wed, 2 Oct 2019 00:36:33 +0300 Subject: [PATCH 0375/1109] Systemd: tun module and capabilities - Enable (and limit to) capabilities that require to setup tun/tap interface. - Ensure that tun module is active. --- contrib/systemd/yggdrasil.service | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contrib/systemd/yggdrasil.service b/contrib/systemd/yggdrasil.service index 37859e79..0223dd90 100644 --- a/contrib/systemd/yggdrasil.service +++ b/contrib/systemd/yggdrasil.service @@ -8,6 +8,8 @@ Group=yggdrasil ProtectHome=true ProtectSystem=true SyslogIdentifier=yggdrasil +CapabilityBoundSet=CAP_NET_ADMIN +ExecStartPre=+/sbin/modprobe tun ExecStartPre=/bin/sh -ec "if ! test -s /etc/yggdrasil.conf; \ then umask 077; \ yggdrasil -genconf > /etc/yggdrasil.conf; \ From b2922189b83f2a335aa02b70467f20908da06fd6 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 3 Oct 2019 18:44:47 -0500 Subject: [PATCH 0376/1109] fix deadlock from use of phony.Block by actors when ckr is enabled --- src/tuntap/conn.go | 2 +- src/yggdrasil/conn.go | 20 ++++++++------------ 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index 31875d94..6db46b23 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -31,7 +31,7 @@ func (s *tunConn) close() { } 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.subnetToConn, s.snet) func() { diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index eb2bb74b..9da2ad83 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -96,7 +96,7 @@ func (c *Conn) setMTU(from phony.Actor, mtu uint16) { 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 { var err error done := make(chan struct{}) @@ -118,6 +118,10 @@ func (c *Conn) search() error { sinfo.setConn(nil, c) } c.session = sinfo + c.nodeID = crypto.GetNodeID(&c.session.theirPermPub) + for i := range c.nodeMask { + c.nodeMask[i] = 0xFF + } err = e close(done) } @@ -133,12 +137,6 @@ func (c *Conn) search() error { if c.session == nil && err == nil { panic("search failed but returned no error") } - if c.session != nil { - c.nodeID = crypto.GetNodeID(&c.session.theirPermPub) - for i := range c.nodeMask { - c.nodeMask[i] = 0xFF - } - } return err } @@ -262,7 +260,7 @@ func (c *Conn) _write(msg FlowKeyMessage) error { c.session.Act(c, func() { // Send the packet c.session._send(msg) - // Session keep-alive, while we wait for the crypto workers from send + // Session keep-alive, while we wait for the crypto workers from sefnd switch { case time.Since(c.session.time) > 6*time.Second: if c.session.time.Before(c.session.pingTime) && time.Since(c.session.pingTime) > 6*time.Second { @@ -353,10 +351,8 @@ func (c *Conn) LocalAddr() crypto.NodeID { // RemoteAddr returns the complete node ID of the remote side of the connection. 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... - var n crypto.NodeID - phony.Block(c, func() { n = *c.nodeID }) - return n + // RemoteAddr is set during the dial or accept, and isn't changed, so it's safe to access directly + return *c.nodeID } // SetDeadline is equivalent to calling both SetReadDeadline and From f22eac497b9c8673a9a13b6d4480ff5b0fa43303 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 3 Oct 2019 18:50:33 -0500 Subject: [PATCH 0377/1109] typo --- src/yggdrasil/conn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 9da2ad83..0a0fc83d 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -260,7 +260,7 @@ func (c *Conn) _write(msg FlowKeyMessage) error { c.session.Act(c, func() { // Send the packet c.session._send(msg) - // Session keep-alive, while we wait for the crypto workers from sefnd + // Session keep-alive, while we wait for the crypto workers from send switch { case time.Since(c.session.time) > 6*time.Second: if c.session.time.Before(c.session.pingTime) && time.Since(c.session.pingTime) > 6*time.Second { From 8e22d7137a9d46ce48ede098414b425c842e78b3 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 5 Oct 2019 10:47:15 -0500 Subject: [PATCH 0378/1109] use bbr congestion control on linux, note that we're not doing anything intelligent with the errors right now if setting it fails --- src/yggdrasil/tcp_linux.go | 28 ++++++++++++++++++++++++++++ src/yggdrasil/tcp_other.go | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 src/yggdrasil/tcp_linux.go diff --git a/src/yggdrasil/tcp_linux.go b/src/yggdrasil/tcp_linux.go new file mode 100644 index 00000000..d957ceba --- /dev/null +++ b/src/yggdrasil/tcp_linux.go @@ -0,0 +1,28 @@ +// +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) { + // sys/socket.h: #define SO_RECV_ANYIF 0x1104 + bbr = unix.SetsockoptString(int(fd), unix.IPPROTO_TCP, unix.TCP_CONGESTION, "bbr") + }) + + switch { + case bbr != nil: + return bbr + default: + return control + } +} diff --git a/src/yggdrasil/tcp_other.go b/src/yggdrasil/tcp_other.go index 47bd772c..44c3d76d 100644 --- a/src/yggdrasil/tcp_other.go +++ b/src/yggdrasil/tcp_other.go @@ -1,4 +1,4 @@ -// +build !darwin +// +build !darwin,!linux package yggdrasil From fb3430207c686fa71e903e70dba36ae278b7331d Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 5 Oct 2019 11:03:38 -0500 Subject: [PATCH 0379/1109] don't fail if there's an error setting bbr, just log it and continue --- src/yggdrasil/tcp_linux.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/yggdrasil/tcp_linux.go b/src/yggdrasil/tcp_linux.go index d957ceba..90a55d7e 100644 --- a/src/yggdrasil/tcp_linux.go +++ b/src/yggdrasil/tcp_linux.go @@ -19,10 +19,14 @@ func (t *tcp) tcpContext(network, address string, c syscall.RawConn) error { bbr = unix.SetsockoptString(int(fd), unix.IPPROTO_TCP, unix.TCP_CONGESTION, "bbr") }) - switch { - case bbr != nil: - return bbr - default: - return control + // 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 } From b519802fcb5959983932376309f8f2000b84489d Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 5 Oct 2019 12:16:22 -0500 Subject: [PATCH 0380/1109] update phony dependency --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d86101bf..185a9e3d 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/yggdrasil-network/yggdrasil-go require ( - github.com/Arceliar/phony v0.0.0-20190907031509-af5bdbeecab6 + github.com/Arceliar/phony v0.0.0-20191004004458-c7ba8368bafa github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 github.com/hashicorp/go-syslog v1.0.0 github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible diff --git a/go.sum b/go.sum index cdabc402..8a2b52f3 100644 --- a/go.sum +++ b/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-20190907031509-af5bdbeecab6/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= +github.com/Arceliar/phony v0.0.0-20191004004458-c7ba8368bafa h1:bFoWgQ17NKP4FBB2Tnt1QZ8fZqrxLYORIlHUa5ioDjc= +github.com/Arceliar/phony v0.0.0-20191004004458-c7ba8368bafa/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/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= From f474869ad9ab094fcd625d6c6ee3634bbc07bc52 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 5 Oct 2019 12:17:40 -0500 Subject: [PATCH 0381/1109] cleanup bad comment --- src/yggdrasil/tcp_linux.go | 1 - 1 file changed, 1 deletion(-) diff --git a/src/yggdrasil/tcp_linux.go b/src/yggdrasil/tcp_linux.go index 90a55d7e..7eda3b5e 100644 --- a/src/yggdrasil/tcp_linux.go +++ b/src/yggdrasil/tcp_linux.go @@ -15,7 +15,6 @@ func (t *tcp) tcpContext(network, address string, c syscall.RawConn) error { var bbr error control = c.Control(func(fd uintptr) { - // sys/socket.h: #define SO_RECV_ANYIF 0x1104 bbr = unix.SetsockoptString(int(fd), unix.IPPROTO_TCP, unix.TCP_CONGESTION, "bbr") }) From c38e40e8e3930dae7bf29fabceaf46fb4aeba5f7 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 5 Oct 2019 12:23:21 -0500 Subject: [PATCH 0382/1109] actually use doCancel in writeNoCopy --- src/yggdrasil/conn.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 0a0fc83d..4daf6a4a 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -292,6 +292,9 @@ func (c *Conn) writeNoCopy(msg FlowKeyMessage) error { var cancel util.Cancellation var doCancel bool phony.Block(c, func() { cancel, doCancel = c._getDeadlineCancellation(c.writeDeadline) }) + if doCancel { + defer cancel.Cancel(nil) + } var err error select { case <-cancel.Finished(): From 7f8dfe84cf461ca307bf8ccd5aef404003819d03 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 5 Oct 2019 13:19:17 -0500 Subject: [PATCH 0383/1109] fix race in phony --- go.mod | 2 +- go.sum | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 185a9e3d..83c22924 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/yggdrasil-network/yggdrasil-go require ( - github.com/Arceliar/phony v0.0.0-20191004004458-c7ba8368bafa + github.com/Arceliar/phony v0.0.0-20191005181740-21679e75e3f0 github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 github.com/hashicorp/go-syslog v1.0.0 github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible diff --git a/go.sum b/go.sum index 8a2b52f3..bca7c231 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/phony v0.0.0-20191004004458-c7ba8368bafa h1:bFoWgQ17NKP4FBB2Tnt1QZ8fZqrxLYORIlHUa5ioDjc= -github.com/Arceliar/phony v0.0.0-20191004004458-c7ba8368bafa/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= +github.com/Arceliar/phony v0.0.0-20191005181740-21679e75e3f0 h1:IOFsvAMFkgnKfSQHxXTeqb1+ODFeR5px1HCHU86KF30= +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/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= 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.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From a1c413f76944ab76977bc650a1de0ee254c9174f Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 6 Oct 2019 11:53:14 -0500 Subject: [PATCH 0384/1109] fix nil pointer dereference in yggdrasil.Conn.search --- src/yggdrasil/conn.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 4daf6a4a..bb5964b6 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -115,12 +115,12 @@ func (c *Conn) search() error { default: if sinfo != nil { // Finish initializing the session - sinfo.setConn(nil, c) - } - c.session = sinfo - c.nodeID = crypto.GetNodeID(&c.session.theirPermPub) - for i := range c.nodeMask { - c.nodeMask[i] = 0xFF + 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 close(done) From 92b1bbf08d2ea64959cf24be7aeb40313c4a956c Mon Sep 17 00:00:00 2001 From: Arceliar Date: Tue, 8 Oct 2019 20:32:41 -0500 Subject: [PATCH 0385/1109] draft of changelog --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fdac254f..c926bfa8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - 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 init configuration now attemps to load the `tun` module, in case tun/tap support is available but not enabled, and it limit Yggdrasil to `CAP_NET_ADMIN` to access the tun/tap, 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 ### Added - Yggdrasil will now complain more verbosely when a peer URI is incorrectly formatted From 14245b88fe46e5d07dc8bdc1fed1ba19c097e4f5 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 9 Oct 2019 21:40:54 +0100 Subject: [PATCH 0386/1109] Pedantic grammar stuff in changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c926bfa8..bbf0bfa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,8 +30,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - 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 init configuration now attemps to load the `tun` module, in case tun/tap support is available but not enabled, and it limit Yggdrasil to `CAP_NET_ADMIN` to access the tun/tap, rather than letting it do whatever the (typically `root`) user can do +- 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 From 31ce8548353addd4be489aa171610144b7b87cd3 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 12 Oct 2019 15:37:40 -0500 Subject: [PATCH 0387/1109] update session when a search for an existing session finishes --- src/yggdrasil/search.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 322131ed..6c43cfef 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -205,6 +205,8 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { panic("This should never happen") } } else { + sess.coords = res.Coords // In case coords have updated + sess.ping(sinfo.searches.router) // In case the remote side needs updating sinfo.callback(nil, errors.New("session already exists")) // Cleanup delete(sinfo.searches.searches, res.Dest) From 3491292599fcd523060cebfb84e04a15d940904b Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 12 Oct 2019 15:46:56 -0500 Subject: [PATCH 0388/1109] code cleanup --- src/yggdrasil/search.go | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 6c43cfef..caa8df7c 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -189,34 +189,34 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { if themMasked != destMasked { 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 + var err error sess, isIn := sinfo.searches.router.sessions.getByTheirPerm(&res.Key) if !isIn { + // Don't already have a session sess = sinfo.searches.router.sessions.createSession(&res.Key) if sess == nil { - // nil if the DHT search finished but the session wasn't allowed - sinfo.callback(nil, errors.New("session not allowed")) - // Cleanup - delete(sinfo.searches.searches, res.Dest) - return true - } - _, isIn := sinfo.searches.router.sessions.getByTheirPerm(&res.Key) - if !isIn { + err = errors.New("session not allowed") + } else if _, isIn := sinfo.searches.router.sessions.getByTheirPerm(&res.Key); !isIn { panic("This should never happen") } } else { - sess.coords = res.Coords // In case coords have updated - sess.ping(sinfo.searches.router) // In case the remote side needs updating - sinfo.callback(nil, errors.New("session already exists")) - // Cleanup - delete(sinfo.searches.searches, res.Dest) - return true + err = errors.New("session already exists") } - // FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)? - sess.coords = res.Coords - sess.ping(sinfo.searches.router) - sinfo.callback(sess, nil) - // Cleanup - delete(sinfo.searches.searches, res.Dest) + finishSearch(sess, err) return true } From cb40874f97fb5d076f9c5e0bf1cf26e315a7792c Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 19 Oct 2019 15:10:28 -0500 Subject: [PATCH 0389/1109] have listener return a net.Conn, adjust yggdrasil.Conn to match this interface --- src/tuntap/conn.go | 4 ++-- src/tuntap/tun.go | 8 ++++---- src/yggdrasil/conn.go | 9 +++++---- src/yggdrasil/listener.go | 2 +- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index 6db46b23..207cd14f 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -93,7 +93,7 @@ func (s *tunConn) _read(bs []byte) (err error) { skip = true } else if key, err := s.tun.ckr.getPublicKeyForAddress(srcAddr, addrlen); err == nil { 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 } else { // 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 } else if key, err := s.tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { 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 } else { // The CKR key associated with this address doesn't match the sender's NodeID diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 74d055ee..0bef9091 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -219,7 +219,7 @@ func (tun *TunAdapter) handler() error { return err } 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 already exists for this address or subnet tun.log.Debugln("TUN/TAP handler wrap:", err) @@ -237,9 +237,9 @@ func (tun *TunAdapter) _wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { } c = &s // Get the remote address and subnet of the other side - remoteNodeID := conn.RemoteAddr() - s.addr = *address.AddrForNodeID(&remoteNodeID) - s.snet = *address.SubnetForNodeID(&remoteNodeID) + remoteNodeID := conn.RemoteAddr().(*crypto.NodeID) + s.addr = *address.AddrForNodeID(remoteNodeID) + s.snet = *address.SubnetForNodeID(remoteNodeID) // Work out if this is already a destination we already know about atc, aok := tun.addrToConn[s.addr] stc, sok := tun.subnetToConn[s.snet] diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index bb5964b6..67426f4f 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -3,6 +3,7 @@ package yggdrasil import ( "errors" "fmt" + "net" "time" "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. // This is always going to return your own node's node ID. -func (c *Conn) LocalAddr() crypto.NodeID { - return *crypto.GetNodeID(&c.core.boxPub) +func (c *Conn) LocalAddr() net.Addr { + return crypto.GetNodeID(&c.core.boxPub) } // 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 - return *c.nodeID + return c.nodeID } // SetDeadline is equivalent to calling both SetReadDeadline and diff --git a/src/yggdrasil/listener.go b/src/yggdrasil/listener.go index fec543f4..63830970 100644 --- a/src/yggdrasil/listener.go +++ b/src/yggdrasil/listener.go @@ -13,7 +13,7 @@ type Listener struct { } // Accept blocks until a new incoming session is received -func (l *Listener) Accept() (*Conn, error) { +func (l *Listener) Accept() (net.Conn, error) { select { case c, ok := <-l.conn: if !ok { From a81476f4894cc96baa2b124f1a9f6572374fed44 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 20 Oct 2019 20:00:55 -0500 Subject: [PATCH 0390/1109] fix incorrectly held mutex in ckr getPublicKeyForAddress --- src/tuntap/ckr.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tuntap/ckr.go b/src/tuntap/ckr.go index 9af3564a..fdb2bbdc 100644 --- a/src/tuntap/ckr.go +++ b/src/tuntap/ckr.go @@ -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 // returned if the address is not suitable or no route was found. func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (crypto.BoxPubKey, error) { - c.mutexcaches.RLock() // Check if the address is a valid Yggdrasil address - if so it // is exempt from all CKR checking @@ -285,11 +284,11 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c } // Check if there's a cache entry for this addr + c.mutexcaches.RLock() if route, ok := (*routingcache)[addr]; ok { c.mutexcaches.RUnlock() return route.destination, nil } - c.mutexcaches.RUnlock() c.mutexremotes.RLock() From eccd9a348fe707f40bc3dec31603acb0d121861e Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 21 Oct 2019 19:44:06 -0500 Subject: [PATCH 0391/1109] give yggdrasil.Dialer the same interface as a net.Dialer, so the only differences are what fields exist in the struct --- src/address/address.go | 20 +++++++++++++++++++- src/tuntap/iface.go | 29 +++++++++++++++-------------- src/tuntap/tun.go | 4 ++-- src/yggdrasil/dialer.go | 29 ++++++++++++++++++----------- 4 files changed, 54 insertions(+), 28 deletions(-) diff --git a/src/address/address.go b/src/address/address.go index 3960b783..eba61708 100644 --- a/src/address/address.go +++ b/src/address/address.go @@ -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. 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. type Address [16]byte @@ -128,6 +132,13 @@ func (a *Address) GetNodeIDandMask() (*crypto.NodeID, *crypto.NodeID) { 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. // 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. @@ -156,3 +167,10 @@ func (s *Subnet) GetNodeIDandMask() (*crypto.NodeID, *crypto.NodeID) { } 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) +} diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 0da99631..3d788b1a 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -9,6 +9,7 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/util" + "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" "github.com/Arceliar/phony" ) @@ -225,7 +226,7 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { return } // Do we have an active connection for this node address? - var dstNodeID, dstNodeIDMask *crypto.NodeID + var dstString string session, isIn := tun.addrToConn[dstAddr] if !isIn || session == nil { 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 // the node ID and mask to commence a search if dstAddr.IsValid() { - dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask() + dstString = dstAddr.GetNodeIDLengthString() } else { - dstNodeID, dstNodeIDMask = dstSnet.GetNodeIDandMask() + dstString = dstSnet.GetNodeIDLengthString() } } } @@ -243,27 +244,27 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { if !isIn || session == nil { // Check we haven't been given empty node ID, really this shouldn't ever // happen but just to be sure... - if dstNodeID == nil || dstNodeIDMask == nil { - panic("Given empty dstNodeID and dstNodeIDMask - this shouldn't happen") + if dstString == "" { + panic("Given empty dstString - this shouldn't happen") } - _, known := tun.dials[*dstNodeID] - tun.dials[*dstNodeID] = append(tun.dials[*dstNodeID], bs) - for len(tun.dials[*dstNodeID]) > 32 { - util.PutBytes(tun.dials[*dstNodeID][0]) - tun.dials[*dstNodeID] = tun.dials[*dstNodeID][1:] + _, known := tun.dials[dstString] + tun.dials[dstString] = append(tun.dials[dstString], bs) + for len(tun.dials[dstString]) > 32 { + util.PutBytes(tun.dials[dstString][0]) + tun.dials[dstString] = tun.dials[dstString][1:] } if !known { go func() { - conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask) + conn, err := tun.dialer.Dial("nodeid", dstString) tun.Act(nil, func() { - packets := tun.dials[*dstNodeID] - delete(tun.dials, *dstNodeID) + packets := tun.dials[dstString] + delete(tun.dials, dstString) if err != nil { return } // We've been given a connection so prepare the session wrapper 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 already exists for this address or subnet tun.log.Debugln("TUN/TAP iface wrap:", err) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 0bef9091..5d77ecab 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -52,7 +52,7 @@ type TunAdapter struct { //mutex sync.RWMutex // Protects the below addrToConn map[address.Address]*tunConn subnetToConn map[address.Subnet]*tunConn - dials map[crypto.NodeID][][]byte // Buffer of packets to send after dialing finishes + dials map[string][][]byte // Buffer of packets to send after dialing finishes isOpen bool } @@ -117,7 +117,7 @@ func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener tun.dialer = dialer tun.addrToConn = make(map[address.Address]*tunConn) tun.subnetToConn = make(map[address.Subnet]*tunConn) - tun.dials = make(map[crypto.NodeID][][]byte) + tun.dials = make(map[string][][]byte) tun.writer.tun = tun tun.reader.tun = tun } diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index 04410855..e3f24e94 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -1,8 +1,10 @@ package yggdrasil import ( + "context" "encoding/hex" "errors" + "net" "strconv" "strings" "time" @@ -15,12 +17,18 @@ type Dialer struct { core *Core } -// TODO DialContext that allows timeouts/cancellation, Dial should just call this with no timeout set in the context - // Dial opens a session to the given node. The first paramter should be "nodeid" // and the second parameter should contain a hexadecimal representation of the -// target node ID. -func (d *Dialer) Dial(network, address string) (*Conn, error) { +// target node ID. Internally, it uses DialContext with a 6-second timeout. +func (d *Dialer) Dial(network, address string) (net.Conn, error) { + const timeout = 6 * time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + return d.DialContext(ctx, network, address) +} + +// DialContext is used internally by Dial, and should only be used with a context that includes a timeout. +func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { var nodeID crypto.NodeID var nodeMask crypto.NodeID // Process @@ -28,7 +36,7 @@ func (d *Dialer) Dial(network, address string) (*Conn, error) { case "nodeid": // A node ID was provided - we don't need to do anything special with it if tokens := strings.Split(address, "/"); len(tokens) == 2 { - len, err := strconv.Atoi(tokens[1]) + l, err := strconv.Atoi(tokens[1]) if err != nil { return nil, err } @@ -37,7 +45,7 @@ func (d *Dialer) Dial(network, address string) (*Conn, error) { return nil, err } copy(nodeID[:], dest) - for idx := 0; idx < len; idx++ { + for idx := 0; idx < l; idx++ { nodeMask[idx/8] |= 0x80 >> byte(idx%8) } } else { @@ -50,7 +58,7 @@ func (d *Dialer) Dial(network, address string) (*Conn, error) { nodeMask[i] = 0xFF } } - return d.DialByNodeIDandMask(&nodeID, &nodeMask) + return d.DialByNodeIDandMask(ctx, &nodeID, &nodeMask) default: // An unexpected address type was given, so give up return nil, errors.New("unexpected address type") @@ -59,19 +67,18 @@ func (d *Dialer) Dial(network, address string) (*Conn, error) { // DialByNodeIDandMask opens a session to the given node based on raw // NodeID parameters. -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) if err := conn.search(); err != nil { + // TODO: make searches take a context, so they can be cancelled early conn.Close() return nil, err } conn.session.setConn(nil, conn) - t := time.NewTimer(6 * time.Second) // TODO use a context instead - defer t.Stop() select { case <-conn.session.init: return conn, nil - case <-t.C: + case <-ctx.Done(): conn.Close() return nil, errors.New("session handshake timeout") } From 681c8ca6f930afe4b8f3690363cdafe0c3c0e5a0 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 21 Oct 2019 20:47:50 -0500 Subject: [PATCH 0392/1109] safer dial timeout handling, in case it was used with a nil context or a context that had no timeout set --- src/yggdrasil/dialer.go | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index e3f24e94..8ef0582f 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -19,15 +19,12 @@ type Dialer struct { // Dial opens a session to the given node. The first paramter should be "nodeid" // and the second parameter should contain a hexadecimal representation of the -// target node ID. Internally, it uses DialContext with a 6-second timeout. +// target node ID. It uses DialContext internally. func (d *Dialer) Dial(network, address string) (net.Conn, error) { - const timeout = 6 * time.Second - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - return d.DialContext(ctx, network, address) + return d.DialContext(nil, network, address) } -// DialContext is used internally by Dial, and should only be used with a context that includes a timeout. +// 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 nodeMask crypto.NodeID @@ -66,7 +63,7 @@ func (d *Dialer) DialContext(ctx context.Context, network, address string) (net. } // 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(ctx context.Context, nodeID, nodeMask *crypto.NodeID) (net.Conn, error) { conn := newConn(d.core, nodeID, nodeMask, nil) if err := conn.search(); err != nil { @@ -75,10 +72,19 @@ func (d *Dialer) DialByNodeIDandMask(ctx context.Context, nodeID, nodeMask *cryp return nil, err } conn.session.setConn(nil, conn) + var c context.Context + var cancel context.CancelFunc + const timeout = 6 * time.Second + if ctx != nil { + c, cancel = context.WithTimeout(ctx, timeout) + } else { + c, cancel = context.WithTimeout(context.Background(), timeout) + } + defer cancel() select { case <-conn.session.init: return conn, nil - case <-ctx.Done(): + case <-c.Done(): conn.Close() return nil, errors.New("session handshake timeout") } From ea085663ea75ee2c84364244da6b0d195d0b76e0 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 21 Oct 2019 20:52:16 -0500 Subject: [PATCH 0393/1109] slight cleanup of dial's timeout --- src/yggdrasil/dialer.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index 8ef0582f..47a68813 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -72,19 +72,16 @@ func (d *Dialer) DialByNodeIDandMask(ctx context.Context, nodeID, nodeMask *cryp return nil, err } conn.session.setConn(nil, conn) - var c context.Context var cancel context.CancelFunc - const timeout = 6 * time.Second - if ctx != nil { - c, cancel = context.WithTimeout(ctx, timeout) - } else { - c, cancel = context.WithTimeout(context.Background(), timeout) + if ctx == nil { + ctx = context.Background() } + ctx, cancel = context.WithTimeout(ctx, 6*time.Second) defer cancel() select { case <-conn.session.init: return conn, nil - case <-c.Done(): + case <-ctx.Done(): conn.Close() return nil, errors.New("session handshake timeout") } From a072e063d8864caf24c9c6a41600756990eabb5f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 23 Oct 2019 10:44:58 +0100 Subject: [PATCH 0394/1109] Define module.Module interface, update admin/tuntap/multicast modules to comply with it, fix #581 --- cmd/yggdrasil/main.go | 18 +++++++---- go.mod | 2 ++ src/admin/admin.go | 61 ++++++++++++++++++++++---------------- src/module/module.go | 20 +++++++++++++ src/multicast/multicast.go | 21 +++++++++++++ src/tuntap/tun.go | 28 ++++++++++------- 6 files changed, 108 insertions(+), 42 deletions(-) create mode 100644 src/module/module.go diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 33a8769d..91cea9a7 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -23,6 +23,7 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/admin" "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/module" "github.com/yggdrasil-network/yggdrasil-go/src/multicast" "github.com/yggdrasil-network/yggdrasil-go/src/tuntap" "github.com/yggdrasil-network/yggdrasil-go/src/version" @@ -32,9 +33,9 @@ import ( type node struct { core yggdrasil.Core state *config.NodeState - tuntap tuntap.TunAdapter - multicast multicast.Multicast - admin admin.AdminSocket + tuntap module.Module // tuntap.TunAdapter + multicast module.Module // multicast.Multicast + admin module.Module // admin.AdminSocket } func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *config.NodeConfig { @@ -231,25 +232,30 @@ func main() { } // Register the session firewall gatekeeper function n.core.SetSessionGatekeeper(n.sessionFirewall) + // Allocate our modules + n.admin = &admin.AdminSocket{} + n.multicast = &multicast.Multicast{} + n.tuntap = &tuntap.TunAdapter{} // Start the admin socket n.admin.Init(&n.core, n.state, logger, nil) if err := n.admin.Start(); err != nil { logger.Errorln("An error occurred starting admin socket:", err) } + n.admin.SetupAdminHandlers(n.admin.(*admin.AdminSocket)) // Start the multicast interface n.multicast.Init(&n.core, n.state, logger, nil) if err := n.multicast.Start(); err != nil { logger.Errorln("An error occurred starting multicast:", err) } - n.multicast.SetupAdminHandlers(&n.admin) + n.multicast.SetupAdminHandlers(n.admin.(*admin.AdminSocket)) // Start the TUN/TAP interface if listener, err := n.core.ConnListen(); err == nil { if dialer, err := n.core.ConnDialer(); err == nil { - n.tuntap.Init(n.state, logger, listener, dialer) + n.tuntap.Init(&n.core, n.state, logger, tuntap.TunOptions{Listener: listener, Dialer: dialer}) if err := n.tuntap.Start(); err != nil { logger.Errorln("An error occurred starting TUN/TAP:", err) } - n.tuntap.SetupAdminHandlers(&n.admin) + n.tuntap.SetupAdminHandlers(n.admin.(*admin.AdminSocket)) } else { logger.Errorln("Unable to get Dialer:", err) } diff --git a/go.mod b/go.mod index 83c22924..d1a3587f 100644 --- a/go.mod +++ b/go.mod @@ -17,3 +17,5 @@ require ( golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a golang.org/x/text v0.3.2 ) + +go 1.13 diff --git a/src/admin/admin.go b/src/admin/admin.go index c7fc151d..f7637998 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -25,12 +25,12 @@ import ( // TODO: Add authentication type AdminSocket struct { - core *yggdrasil.Core - log *log.Logger - reconfigure chan chan error - listenaddr string - listener net.Listener - handlers map[string]handler + core *yggdrasil.Core + log *log.Logger + listenaddr string + listener net.Listener + handlers map[string]handler + started bool } // Info refers to information that is returned to the admin socket handler. @@ -54,25 +54,27 @@ func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc func(In } // 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.log = log - a.reconfigure = make(chan chan error, 1) a.handlers = make(map[string]handler) - go func() { - for { - e := <-a.reconfigure - current, previous := state.GetCurrent(), state.GetPrevious() - if current.AdminListen != previous.AdminListen { - a.listenaddr = current.AdminListen - a.Stop() - a.Start() - } - e <- nil - } - }() current := state.GetCurrent() a.listenaddr = current.AdminListen + 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("list", []string{}, func(in Info) (Info, error) { handlers := make(map[string]interface{}) for handlername, handler := range a.handlers { @@ -81,15 +83,15 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. return Info{"list": handlers}, nil }) a.AddHandler("getSelf", []string{}, func(in Info) (Info, error) { - ip := c.Address().String() - subnet := c.Subnet() + ip := a.core.Address().String() + subnet := a.core.Subnet() return Info{ "self": Info{ ip: Info{ - "box_pub_key": c.EncryptionPublicKey(), + "box_pub_key": a.core.EncryptionPublicKey(), "build_name": version.BuildName(), "build_version": version.BuildVersion(), - "coords": fmt.Sprintf("%v", c.Coords()), + "coords": fmt.Sprintf("%v", a.core.Coords()), "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 { if a.listenaddr != "none" && a.listenaddr != "" { go a.listen() + a.started = true } 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 { if a.listener != nil { + a.started = false return a.listener.Close() } else { return nil diff --git a/src/module/module.go b/src/module/module.go new file mode 100644 index 00000000..ab704e70 --- /dev/null +++ b/src/module/module.go @@ -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 +} diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 7044eaa2..6c1af1c4 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -55,6 +55,12 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log // listen for multicast beacons from other hosts and will advertise multicast // beacons out to the network. func (m *Multicast) Start() error { + if len(m.config.GetCurrent().MulticastInterfaces) == 0 { + return fmt.Errorf("no MulticastInterfaces configured") + } + + m.log.Infoln("Starting multicast module") + addr, err := net.ResolveUDPAddr("udp", m.groupAddr) if err != nil { return err @@ -80,8 +86,14 @@ func (m *Multicast) Start() error { return nil } +// IsStarted returns true if the module has been started. +func (m *Multicast) IsStarted() bool { + return m.isOpen +} + // Stop is not implemented for multicast yet. func (m *Multicast) Stop() error { + m.log.Infoln("Stopping multicast module") m.isOpen = false if m.announcer != nil { m.announcer.Stop() @@ -98,7 +110,16 @@ func (m *Multicast) Stop() error { // needed. func (m *Multicast) UpdateConfig(config *config.NodeConfig) { m.log.Debugln("Reloading multicast configuration...") + if m.IsStarted() { + if len(config.MulticastInterfaces) == 0 || config.LinkLocalTCPPort != m.listenPort { + m.Stop() + } + } m.config.Replace(*config) + m.listenPort = config.LinkLocalTCPPort + if !m.IsStarted() && len(config.MulticastInterfaces) > 0 { + m.Start() + } } // GetInterfaces returns the currently known/enabled multicast interfaces. It is diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 5d77ecab..06feede0 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -56,6 +56,11 @@ type TunAdapter struct { isOpen bool } +type TunOptions struct { + Listener *yggdrasil.Listener + Dialer *yggdrasil.Dialer +} + // Gets the maximum supported MTU for the platform based on the defaults in // defaults.GetDefaults(). 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 // 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.log = log - tun.listener = listener - tun.dialer = dialer + tun.listener = tunoptions.Listener + tun.dialer = tunoptions.Dialer tun.addrToConn = make(map[address.Address]*tunConn) tun.subnetToConn = make(map[address.Subnet]*tunConn) tun.dials = make(map[string][][]byte) tun.writer.tun = tun tun.reader.tun = tun + return nil } // Start the setup process for the TUN/TAP adapter. If successful, starts the @@ -160,13 +170,6 @@ func (tun *TunAdapter) _start() error { return nil } tun.isOpen = true - tun.reconfigure = make(chan chan error) - go func() { - for { - e := <-tun.reconfigure - e <- nil - } - }() go tun.handler() tun.reader.Act(nil, tun.reader._read) // Start the reader tun.icmpv6.Init(tun) @@ -177,6 +180,11 @@ func (tun *TunAdapter) _start() error { return nil } +// IsStarted returns true if the module has been started. +func (tun *TunAdapter) IsStarted() bool { + return tun.isOpen +} + // Start the setup process for the TUN/TAP adapter. If successful, starts the // read/write goroutines to handle packets on that interface. func (tun *TunAdapter) Stop() error { From 337626a32cc38c13e6809e63aff60b2c4be1b0ae Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 23 Oct 2019 11:12:51 +0100 Subject: [PATCH 0395/1109] Act multicast updates for safety --- go.mod | 2 -- src/admin/admin.go | 14 +++++++------- src/multicast/multicast.go | 4 ++++ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index d1a3587f..83c22924 100644 --- a/go.mod +++ b/go.mod @@ -17,5 +17,3 @@ require ( golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a golang.org/x/text v0.3.2 ) - -go 1.13 diff --git a/src/admin/admin.go b/src/admin/admin.go index f7637998..8028bcc6 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -60,6 +60,13 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. a.handlers = make(map[string]handler) current := state.GetCurrent() a.listenaddr = current.AdminListen + a.AddHandler("list", []string{}, func(in Info) (Info, error) { + handlers := make(map[string]interface{}) + for handlername, handler := range a.handlers { + handlers[handlername] = Info{"fields": handler.args} + } + return Info{"list": handlers}, nil + }) return nil } @@ -75,13 +82,6 @@ func (a *AdminSocket) UpdateConfig(config *config.NodeConfig) { } func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { - a.AddHandler("list", []string{}, func(in Info) (Info, error) { - handlers := make(map[string]interface{}) - for handlername, handler := range a.handlers { - handlers[handlername] = Info{"fields": handler.args} - } - return Info{"list": handlers}, nil - }) a.AddHandler("getSelf", []string{}, func(in Info) (Info, error) { ip := a.core.Address().String() subnet := a.core.Subnet() diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 6c1af1c4..deffecd2 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -109,6 +109,10 @@ func (m *Multicast) Stop() error { // and then signals the various module goroutines to reconfigure themselves if // needed. func (m *Multicast) UpdateConfig(config *config.NodeConfig) { + m.Act(m, func() { m._updateConfig(config) }) +} + +func (m *Multicast) _updateConfig(config *config.NodeConfig) { m.log.Debugln("Reloading multicast configuration...") if m.IsStarted() { if len(config.MulticastInterfaces) == 0 || config.LinkLocalTCPPort != m.listenPort { From b0bcf29d27f2464cc7d4a16848ac4cc0ab713aa0 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 23 Oct 2019 11:15:57 +0100 Subject: [PATCH 0396/1109] Allow ExecStartPre to fail for containers (#573) --- contrib/systemd/yggdrasil.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/systemd/yggdrasil.service b/contrib/systemd/yggdrasil.service index 0223dd90..b48ff78b 100644 --- a/contrib/systemd/yggdrasil.service +++ b/contrib/systemd/yggdrasil.service @@ -9,7 +9,7 @@ ProtectHome=true ProtectSystem=true SyslogIdentifier=yggdrasil CapabilityBoundSet=CAP_NET_ADMIN -ExecStartPre=+/sbin/modprobe tun +ExecStartPre=+-/sbin/modprobe tun ExecStartPre=/bin/sh -ec "if ! test -s /etc/yggdrasil.conf; \ then umask 077; \ yggdrasil -genconf > /etc/yggdrasil.conf; \ From f6c7c1b8db69b08f17d55310cb2ef967c9da297c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 23 Oct 2019 11:24:00 +0100 Subject: [PATCH 0397/1109] Produce armel build (closes #577) --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1456332a..5a9e1e31 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -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=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=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; mv *.deb /tmp/upload/ From 6a22e6c9de3f2046c65aaea88587d2493933da5f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 23 Oct 2019 17:26:35 +0100 Subject: [PATCH 0398/1109] Initial connection upgrade/TLS peering support --- src/yggdrasil/api.go | 9 ++++- src/yggdrasil/link.go | 11 ++++-- src/yggdrasil/tcp.go | 82 +++++++++++++++++++++++++++----------- src/yggdrasil/tls.go | 92 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 167 insertions(+), 27 deletions(-) create mode 100644 src/yggdrasil/tls.go diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 4245f439..80f669b6 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -280,7 +280,14 @@ func (c *Core) ConnDialer() (*Dialer, error) { // "Listen" configuration item, e.g. // tcp://a.b.c.d:e func (c *Core) ListenTCP(uri string) (*TcpListener, error) { - return c.link.tcp.listen(uri) + 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. diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 98c080c7..1710e202 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -93,9 +93,11 @@ func (l *link) call(uri string, sintf string) error { pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/") switch u.Scheme { case "tcp": - l.tcp.call(u.Host, nil, sintf) + l.tcp.call(u.Host, nil, sintf, nil) 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: return errors.New("unknown call scheme: " + u.Scheme) } @@ -109,7 +111,10 @@ func (l *link) listen(uri string) error { } switch u.Scheme { 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 default: return errors.New("unknown listen scheme: " + u.Scheme) diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 66f708c2..c4569591 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -39,6 +39,7 @@ type tcp struct { listeners map[string]*TcpListener calls map[string]struct{} conns map[linkInfo](chan struct{}) + tls tcptls } // TcpListener is a stoppable TCP listener interface. These are typically @@ -47,9 +48,15 @@ type tcp struct { // multicast interfaces. type TcpListener struct { Listener net.Listener + upgrade *TcpUpgrade stop chan struct{} } +type TcpUpgrade struct { + upgrade func(c net.Conn) (net.Conn, error) + name string +} + func (l *TcpListener) Stop() { defer func() { recover() }() close(l.stop) @@ -81,6 +88,7 @@ func (t *tcp) getAddr() *net.TCPAddr { // Initializes the struct. func (t *tcp) init(l *link) error { t.link = l + t.tls.init(t) t.mutex.Lock() t.calls = make(map[string]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() defer t.link.core.config.Mutex.RUnlock() 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") - 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() if len(added) > 0 || len(deleted) > 0 { 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") - 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 { - 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") 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 ctx := context.Background() @@ -157,6 +173,7 @@ func (t *tcp) listen(listenaddr string) (*TcpListener, error) { if err == nil { l := TcpListener{ Listener: listener, + upgrade: upgrade, stop: make(chan struct{}), } t.waitgroup.Add(1) @@ -204,7 +221,7 @@ func (t *tcp) listener(l *TcpListener, listenaddr string) { return } 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. // When finished, it removes the outgoing call, so reconnection attempts can be made later. // 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() { callname := saddr + callproto := "TCP" + if upgrade != nil { + callproto = strings.ToUpper(upgrade.name) + } if sintf != "" { - callname = fmt.Sprintf("%s/%s", saddr, sintf) + callname = fmt.Sprintf("%s/%s/%s", callproto, saddr, sintf) } if !t.startCalling(callname) { return @@ -261,7 +282,7 @@ func (t *tcp) call(saddr string, options interface{}, sintf string) { return } t.waitgroup.Add(1) - t.handler(conn, false, saddr) + t.handler(conn, false, saddr, nil) } else { dst, err := net.ResolveTCPAddr("tcp", saddr) if err != nil { @@ -322,18 +343,28 @@ func (t *tcp) call(saddr string, options interface{}, sintf string) { } conn, err = dialer.Dial("tcp", dst.String()) 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 } 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 sock.Close() + 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 + } + } t.setExtraOptions(sock) stream := stream{} stream.init(sock) @@ -344,8 +375,13 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}) { local, _, _ = net.SplitHostPort(sock.LocalAddr().String()) remote, _, _ = net.SplitHostPort(socksaddr) } else { - name = "tcp://" + sock.RemoteAddr().String() - proto = "tcp" + if upgraded { + proto = upgrade.name + name = proto + "://" + sock.RemoteAddr().String() + } else { + proto = "tcp" + name = proto + "://" + sock.RemoteAddr().String() + } local, _, _ = net.SplitHostPort(sock.LocalAddr().String()) remote, _, _ = net.SplitHostPort(sock.RemoteAddr().String()) } diff --git a/src/yggdrasil/tls.go b/src/yggdrasil/tls.go new file mode 100644 index 00000000..78fe3a9d --- /dev/null +++ b/src/yggdrasil/tls.go @@ -0,0 +1,92 @@ +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{} + + 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{ + 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 +} From cd77727c1e63a21d3acfacad6a22edb680d4123b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 23 Oct 2019 18:24:08 +0100 Subject: [PATCH 0399/1109] Set TCP socket options before upgrading connection --- src/yggdrasil/tcp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index c4569591..7d5b80b2 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -355,6 +355,7 @@ func (t *tcp) call(saddr string, options interface{}, sintf string, upgrade *Tcp func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}, upgrade *TcpUpgrade) { defer t.waitgroup.Done() // Happens after sock.close defer sock.Close() + t.setExtraOptions(sock) var upgraded bool if upgrade != nil { var err error @@ -365,7 +366,6 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}, upgrade upgraded = true } } - t.setExtraOptions(sock) stream := stream{} stream.init(sock) var name, proto, local, remote string From 996c6b4f474bba3e80ba0b9c3c737a7a4909cdd8 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 23 Oct 2019 20:28:11 -0500 Subject: [PATCH 0400/1109] add one TODO comment and run gofmt --- src/yggdrasil/tls.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/tls.go b/src/yggdrasil/tls.go index 78fe3a9d..7212c4df 100644 --- a/src/yggdrasil/tls.go +++ b/src/yggdrasil/tls.go @@ -38,6 +38,7 @@ func (t *tcptls) init(tcp *tcp) { 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{ @@ -65,7 +66,7 @@ func (t *tcptls) init(tcp *tcp) { t.config = &tls.Config{ RootCAs: cpool, Certificates: []tls.Certificate{ - tls.Certificate{ + { Certificate: [][]byte{derbytes}, PrivateKey: edpriv, }, From c3dee478f5b6e45827ed46614a0386921eba85b3 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 23 Oct 2019 20:38:09 -0500 Subject: [PATCH 0401/1109] fix ed25519 dependency for golang 1.12 and earlier, though we may want to update builds to 1.13 anyway... --- src/yggdrasil/tls.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/tls.go b/src/yggdrasil/tls.go index 7212c4df..26ecf1d7 100644 --- a/src/yggdrasil/tls.go +++ b/src/yggdrasil/tls.go @@ -2,7 +2,6 @@ package yggdrasil import ( "bytes" - "crypto/ed25519" "crypto/rand" "crypto/tls" "crypto/x509" @@ -13,6 +12,8 @@ import ( "math/big" "net" "time" + + "golang.org/x/crypto/ed25519" ) type tcptls struct { From f784f33c2d2670cb73bc5c1fcbf1e2c01901cc94 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 24 Oct 2019 09:25:31 +0100 Subject: [PATCH 0402/1109] Backport fix for #581 from #583 --- src/multicast/multicast.go | 47 +++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 7044eaa2..ed8efafd 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -55,6 +55,21 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log // listen for multicast beacons from other hosts and will advertise multicast // beacons out to the network. func (m *Multicast) Start() error { + var err error + phony.Block(m, func() { + err = m._start() + }) + 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 fmt.Errorf("no MulticastInterfaces configured") + } + m.log.Infoln("Starting multicast module") addr, err := net.ResolveUDPAddr("udp", m.groupAddr) if err != nil { return err @@ -80,8 +95,22 @@ func (m *Multicast) Start() error { return nil } -// Stop is not implemented for multicast yet. -func (m *Multicast) Stop() error { +// 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() { + m.Act(m, m._stop) +} + +func (m *Multicast) _stop() { + m.log.Infoln("Stopping multicast module") m.isOpen = false if m.announcer != nil { m.announcer.Stop() @@ -90,15 +119,27 @@ func (m *Multicast) Stop() error { m.platformhandler.Stop() } m.sock.Close() - return nil } // UpdateConfig updates the multicast module with the provided config.NodeConfig // and then signals the various module goroutines to reconfigure themselves if // needed. func (m *Multicast) UpdateConfig(config *config.NodeConfig) { + m.Act(m, func() { m._updateConfig(config) }) +} + +func (m *Multicast) _updateConfig(config *config.NodeConfig) { m.log.Debugln("Reloading multicast configuration...") + if m.IsStarted() { + if len(config.MulticastInterfaces) == 0 || config.LinkLocalTCPPort != m.listenPort { + m.Stop() + } + } m.config.Replace(*config) + m.listenPort = config.LinkLocalTCPPort + if !m.IsStarted() && len(config.MulticastInterfaces) > 0 { + m.Start() + } } // GetInterfaces returns the currently known/enabled multicast interfaces. It is From d58f88d29aeccfaa7f7de59e4e41a6be78a8882d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 24 Oct 2019 09:28:09 +0100 Subject: [PATCH 0403/1109] Update builds to Go 1.13 as this is required for TLS (apparently golang.org/x/crypto/ed25519 is not acceptable to the crypto/tls module and this prevents Yggdrasil from starting) --- .circleci/config.yml | 2 +- README.md | 2 +- go.mod | 2 ++ src/yggdrasil/tls.go | 3 +-- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5a9e1e31..146d5e53 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ version: 2.1 jobs: build-linux: docker: - - image: circleci/golang:1.12.7 + - image: circleci/golang:1.13.3 steps: - checkout diff --git a/README.md b/README.md index 07b202f1..c02151f4 100644 --- a/README.md +++ b/README.md @@ -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 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. Run `./build` diff --git a/go.mod b/go.mod index 83c22924..d1a3587f 100644 --- a/go.mod +++ b/go.mod @@ -17,3 +17,5 @@ require ( golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a golang.org/x/text v0.3.2 ) + +go 1.13 diff --git a/src/yggdrasil/tls.go b/src/yggdrasil/tls.go index 26ecf1d7..7212c4df 100644 --- a/src/yggdrasil/tls.go +++ b/src/yggdrasil/tls.go @@ -2,6 +2,7 @@ package yggdrasil import ( "bytes" + "crypto/ed25519" "crypto/rand" "crypto/tls" "crypto/x509" @@ -12,8 +13,6 @@ import ( "math/big" "net" "time" - - "golang.org/x/crypto/ed25519" ) type tcptls struct { From 5ca81f916ef1952e58d8699976eb9cb01a15cde8 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 24 Oct 2019 09:54:57 +0100 Subject: [PATCH 0404/1109] Fix deadlocks --- src/multicast/multicast.go | 47 ++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index ed8efafd..82d47ed0 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -5,6 +5,7 @@ import ( "fmt" "net" "regexp" + "sync/atomic" "time" "github.com/Arceliar/phony" @@ -28,7 +29,7 @@ type Multicast struct { groupAddr string listeners map[string]*listenerInfo listenPort uint16 - isOpen bool + isOpen atomic.Value // bool announcer *time.Timer platformhandler *time.Timer } @@ -48,6 +49,7 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log current := m.config.GetCurrent() m.listenPort = current.LinkLocalTCPPort m.groupAddr = "[ff02::114]:9001" + m.isOpen.Store(false) return nil } @@ -59,15 +61,16 @@ func (m *Multicast) Start() error { phony.Block(m, func() { err = m._start() }) + m.log.Debugln("Started multicast module") return err } func (m *Multicast) _start() error { - if m.isOpen { + if m.IsStarted() { return fmt.Errorf("multicast module is already started") } if len(m.config.GetCurrent().MulticastInterfaces) == 0 { - return fmt.Errorf("no MulticastInterfaces configured") + return nil } m.log.Infoln("Starting multicast module") addr, err := net.ResolveUDPAddr("udp", m.groupAddr) @@ -87,7 +90,7 @@ func (m *Multicast) _start() error { // Windows can't set this flag, so we need to handle it in other ways } - m.isOpen = true + m.isOpen.Store(true) go m.listen() m.Act(m, m.multicastStarted) m.Act(m, m.announce) @@ -97,21 +100,25 @@ func (m *Multicast) _start() error { // 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 + if m.isOpen.Load() == nil { + return false + } + return m.isOpen.Load().(bool) } // Stop stops the multicast module. -func (m *Multicast) Stop() { - m.Act(m, m._stop) +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() { +func (m *Multicast) _stop() error { m.log.Infoln("Stopping multicast module") - m.isOpen = false + m.isOpen.Store(false) if m.announcer != nil { m.announcer.Stop() } @@ -119,6 +126,7 @@ func (m *Multicast) _stop() { m.platformhandler.Stop() } m.sock.Close() + return nil } // UpdateConfig updates the multicast module with the provided config.NodeConfig @@ -129,17 +137,22 @@ func (m *Multicast) UpdateConfig(config *config.NodeConfig) { } func (m *Multicast) _updateConfig(config *config.NodeConfig) { - m.log.Debugln("Reloading multicast configuration...") + m.log.Infoln("Reloading multicast configuration...") if m.IsStarted() { if len(config.MulticastInterfaces) == 0 || config.LinkLocalTCPPort != m.listenPort { - m.Stop() + if err := m._stop(); err != nil { + m.log.Errorln("Error stopping multicast module:", err) + } } } m.config.Replace(*config) m.listenPort = config.LinkLocalTCPPort if !m.IsStarted() && len(config.MulticastInterfaces) > 0 { - m.Start() + 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 @@ -312,7 +325,7 @@ func (m *Multicast) listen() { for { nBytes, rcm, fromAddr, err := m.sock.ReadFrom(bs) if err != nil { - if !m.isOpen { + if !m.IsStarted() { return } panic(err) From 51fe1940c593c2a97ecdf4118f0ffa452cc9cfc9 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 24 Oct 2019 10:13:59 +0100 Subject: [PATCH 0405/1109] Try go 1.13 to see if this fixes failing builds --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 146d5e53..33290840 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ version: 2.1 jobs: build-linux: docker: - - image: circleci/golang:1.13.3 + - image: circleci/golang:1.13 steps: - checkout From ee644c47e8be8d2fa41ddb614d48972a8044cbad Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 24 Oct 2019 10:16:52 +0100 Subject: [PATCH 0406/1109] Update go.mod/go.sum, go back to 1.13.3 circleci image again --- .circleci/config.yml | 2 +- go.mod | 17 +++++++---------- go.sum | 29 ++++++++++++----------------- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 33290840..146d5e53 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ version: 2.1 jobs: build-linux: docker: - - image: circleci/golang:1.13 + - image: circleci/golang:1.13.3 steps: - checkout diff --git a/go.mod b/go.mod index d1a3587f..6d27e6ed 100644 --- a/go.mod +++ b/go.mod @@ -1,21 +1,18 @@ module github.com/yggdrasil-network/yggdrasil-go +go 1.13 + 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/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.0+incompatible github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/mitchellh/mapstructure v1.1.2 github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 - github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b // indirect - github.com/vishvananda/netlink v1.0.0 - github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8 - golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 - golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 - golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a + golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 + golang.org/x/net v0.0.0-20191021144547-ec77196f6094 + golang.org/x/sys v0.0.0-20191024073052-e66fe6eb8e0c golang.org/x/text v0.3.2 ) - -go 1.13 diff --git a/go.sum b/go.sum index bca7c231..61fc7242 100644 --- a/go.sum +++ b/go.sum @@ -1,35 +1,30 @@ -github.com/Arceliar/phony v0.0.0-20191005181740-21679e75e3f0 h1:IOFsvAMFkgnKfSQHxXTeqb1+ODFeR5px1HCHU86KF30= -github.com/Arceliar/phony v0.0.0-20191005181740-21679e75e3f0/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= +github.com/Arceliar/phony v0.0.0-20191006174943-d0c68492aca0 h1:p3puK8Sl2xK+2FnnIvY/C0N1aqJo2kbEsdAzU+Tnv48= +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/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible h1:bLQ2Ve+eW65id3b8xEMQiAwJT4qGZeywAEMLvXjznvw= -github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= +github.com/hjson/hjson-go v3.0.0+incompatible h1:mc8olpIxqF8mrEx6ePJOD6wCCOkX7+JcrKgINcJES10= +github.com/hjson/hjson-go v3.0.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk8851WTVWlqMC1ecJH07Ctz+Ezxx4u54g= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0/go.mod h1:rUi0/YffDo1oXBOGn1KRq7Fr07LX48XEBecQnmwjsAo= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 h1:1zN6ImoqhSJhN8hGXFaJlSC8msLmIbX8bFqOfWLKw0w= github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091/go.mod h1:N20Z5Y8oye9a7HmytmZ+tr8Q2vlP0tAHP13kTHzwvQY= -github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b h1:+y4hCMc/WKsDbAPsOQZgBSaSZ26uh2afyaWeVg/3s/c= -github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= -github.com/vishvananda/netlink v1.0.0 h1:bqNY2lgheFIu1meHUFSH3d7vG93AFyqg3oGbJCOJgSM= -github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= -github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f h1:nBX3nTcmxEtHSERBJaIo1Qa26VwRaopnZmfDQUXsF4I= -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/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/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= -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 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= +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-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191024073052-e66fe6eb8e0c h1:usSYQsGq37L8RjJc5eznJ/AbwBxn3QFFEVkWNPAejLs= +golang.org/x/sys v0.0.0-20191024073052-e66fe6eb8e0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 0e7ed4c99771785b7817d5c5aa39f69be07124e2 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 24 Oct 2019 10:18:08 +0100 Subject: [PATCH 0407/1109] Actually really use 1.13.3 for all the builds this time --- .circleci/config.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 146d5e53..17cbfade 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -106,11 +106,11 @@ jobs: echo -e "Host *\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config - run: - name: Install Go 1.12.7 + name: Install Go 1.13.3 command: | cd /tmp - curl -LO https://dl.google.com/go/go1.12.7.darwin-amd64.pkg - sudo installer -pkg /tmp/go1.12.7.darwin-amd64.pkg -target / + curl -LO https://dl.google.com/go/go1.13.3.darwin-amd64.pkg + sudo installer -pkg /tmp/go1.13.3.darwin-amd64.pkg -target / #- run: # name: Install Gomobile @@ -146,7 +146,7 @@ jobs: build-other: docker: - - image: circleci/golang:1.12.7 + - image: circleci/golang:1.13.3 steps: - checkout From 77ffb5efc4f325bf8e252e60308d9ff44feac93f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 24 Oct 2019 10:47:44 +0100 Subject: [PATCH 0408/1109] Fix HJSON references in go.mod/go.sum, again... --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6d27e6ed..34fdf2e6 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Arceliar/phony v0.0.0-20191006174943-d0c68492aca0 github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 github.com/hashicorp/go-syslog v1.0.0 - github.com/hjson/hjson-go v3.0.0+incompatible + github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/mitchellh/mapstructure v1.1.2 github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 diff --git a/go.sum b/go.sum index 61fc7242..7b94caa8 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 h1:WD8iJ37bRNwvETMfVTu github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hjson/hjson-go v3.0.0+incompatible h1:mc8olpIxqF8mrEx6ePJOD6wCCOkX7+JcrKgINcJES10= -github.com/hjson/hjson-go v3.0.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= +github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible h1:bLQ2Ve+eW65id3b8xEMQiAwJT4qGZeywAEMLvXjznvw= +github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk8851WTVWlqMC1ecJH07Ctz+Ezxx4u54g= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0/go.mod h1:rUi0/YffDo1oXBOGn1KRq7Fr07LX48XEBecQnmwjsAo= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= From de3bdfa524acbf1c3d2273ea551db723e7b00517 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 24 Oct 2019 23:31:47 +0100 Subject: [PATCH 0409/1109] No longer use atomic for isOpen in multicast --- src/multicast/multicast.go | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 82d47ed0..206edab6 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -5,7 +5,6 @@ import ( "fmt" "net" "regexp" - "sync/atomic" "time" "github.com/Arceliar/phony" @@ -29,7 +28,7 @@ type Multicast struct { groupAddr string listeners map[string]*listenerInfo listenPort uint16 - isOpen atomic.Value // bool + isOpen bool announcer *time.Timer platformhandler *time.Timer } @@ -49,7 +48,6 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log current := m.config.GetCurrent() m.listenPort = current.LinkLocalTCPPort m.groupAddr = "[ff02::114]:9001" - m.isOpen.Store(false) return nil } @@ -66,7 +64,7 @@ func (m *Multicast) Start() error { } func (m *Multicast) _start() error { - if m.IsStarted() { + if m.isOpen { return fmt.Errorf("multicast module is already started") } if len(m.config.GetCurrent().MulticastInterfaces) == 0 { @@ -90,7 +88,7 @@ func (m *Multicast) _start() error { // Windows can't set this flag, so we need to handle it in other ways } - m.isOpen.Store(true) + m.isOpen = true go m.listen() m.Act(m, m.multicastStarted) m.Act(m, m.announce) @@ -100,10 +98,11 @@ func (m *Multicast) _start() error { // IsStarted returns true if the module has been started. func (m *Multicast) IsStarted() bool { - if m.isOpen.Load() == nil { - return false - } - return m.isOpen.Load().(bool) + var isOpen bool + phony.Block(m, func() { + isOpen = m.isOpen + }) + return isOpen } // Stop stops the multicast module. @@ -118,7 +117,7 @@ func (m *Multicast) Stop() error { func (m *Multicast) _stop() error { m.log.Infoln("Stopping multicast module") - m.isOpen.Store(false) + m.isOpen = false if m.announcer != nil { m.announcer.Stop() } @@ -138,7 +137,7 @@ func (m *Multicast) UpdateConfig(config *config.NodeConfig) { func (m *Multicast) _updateConfig(config *config.NodeConfig) { m.log.Infoln("Reloading multicast configuration...") - if m.IsStarted() { + 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) @@ -147,7 +146,7 @@ func (m *Multicast) _updateConfig(config *config.NodeConfig) { } m.config.Replace(*config) m.listenPort = config.LinkLocalTCPPort - if !m.IsStarted() && len(config.MulticastInterfaces) > 0 { + if !m.isOpen && len(config.MulticastInterfaces) > 0 { if err := m._start(); err != nil { m.log.Errorln("Error starting multicast module:", err) } From cd9396993057ba25ef904a890f2e01e5b73b1286 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 24 Oct 2019 23:37:39 +0100 Subject: [PATCH 0410/1109] Fix isOpen for TUN/TAP actor --- src/tuntap/tun.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 06feede0..f0250c93 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -143,9 +143,12 @@ 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() 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 boxPubHex, err := hex.DecodeString(current.EncryptionPublicKey) @@ -182,7 +185,11 @@ func (tun *TunAdapter) _start() error { // IsStarted returns true if the module has been started. func (tun *TunAdapter) IsStarted() bool { - return tun.isOpen + 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 From ba43c1d874879ee9d2a6ac395992c1b6925657a3 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 24 Oct 2019 23:59:58 +0100 Subject: [PATCH 0411/1109] Changelog for v0.3.11 --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbf0bfa5..0557012e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,25 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - 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` 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 +- Dial timeouts are now handled more safely in the event of a nil context +- Multicast module no longer opens a listener socket if no multicast interfaces are configured + ## [0.3.10] - 2019-10-10 ### Added - The core library now includes several unit tests for peering and `yggdrasil.Conn` connections From aea41f464eb06a076e4c23a3f2678b131a747fb8 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 24 Oct 2019 21:47:02 -0500 Subject: [PATCH 0412/1109] Update CHANGELOG.md --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0557012e..83c4bb0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,14 +34,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - 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` instead of `yggdrasil.Dialer` + - `net.Dialer` (or the interface that type would satisfy, if one was defined) 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 -- Dial timeouts are now handled more safely in the event of a nil context - Multicast module no longer opens a listener socket if no multicast interfaces are configured ## [0.3.10] - 2019-10-10 From 9337b17cffb3a733c16bf9bc1542c7767deb98e5 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 24 Oct 2019 21:50:10 -0500 Subject: [PATCH 0413/1109] update a few deps and run 'go mod tidy' --- go.mod | 5 ++++- go.sum | 10 ++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 34fdf2e6..a45600a3 100644 --- a/go.mod +++ b/go.mod @@ -10,9 +10,12 @@ require ( github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/mitchellh/mapstructure v1.1.2 github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 + github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b // indirect + github.com/vishvananda/netlink v1.0.0 + github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 golang.org/x/net v0.0.0-20191021144547-ec77196f6094 - golang.org/x/sys v0.0.0-20191024073052-e66fe6eb8e0c + golang.org/x/sys v0.0.0-20191024172528-b4ff53e7a1cb golang.org/x/text v0.3.2 ) diff --git a/go.sum b/go.sum index 7b94caa8..ecfa4626 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,12 @@ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQz github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 h1:1zN6ImoqhSJhN8hGXFaJlSC8msLmIbX8bFqOfWLKw0w= github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091/go.mod h1:N20Z5Y8oye9a7HmytmZ+tr8Q2vlP0tAHP13kTHzwvQY= +github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b h1:+y4hCMc/WKsDbAPsOQZgBSaSZ26uh2afyaWeVg/3s/c= +github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= +github.com/vishvananda/netlink v1.0.0 h1:bqNY2lgheFIu1meHUFSH3d7vG93AFyqg3oGbJCOJgSM= +github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= +github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f h1:nBX3nTcmxEtHSERBJaIo1Qa26VwRaopnZmfDQUXsF4I= +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/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -22,8 +28,8 @@ golang.org/x/net v0.0.0-20191021144547-ec77196f6094 h1:5O4U9trLjNpuhpynaDsqwCk+T 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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191024073052-e66fe6eb8e0c h1:usSYQsGq37L8RjJc5eznJ/AbwBxn3QFFEVkWNPAejLs= -golang.org/x/sys v0.0.0-20191024073052-e66fe6eb8e0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191024172528-b4ff53e7a1cb h1:ZxSglHghKPYD8WDeRUzRJrUJtDF0PxsTUSxyqr9/5BI= +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.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= From 7f758b7bf7b1beeb6ae26e1f54530c15499041e5 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 24 Oct 2019 21:55:25 -0500 Subject: [PATCH 0414/1109] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83c4bb0e..903ffc7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - 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` (or the interface that type would satisfy, if one was defined) instead of `yggdrasil.Dialer` + - `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 From bcacfb06385ab4177ffabe4e3aa979d3540b6428 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 25 Oct 2019 18:33:23 -0500 Subject: [PATCH 0415/1109] test adding BindToDevice to linux. if it works then we'll want to rethink slightly how we get the tcpContext on every platform, to make this compile everywhere and look a little cleaner --- src/yggdrasil/tcp.go | 1 + src/yggdrasil/tcp_linux.go | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 7d5b80b2..0733b58a 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -299,6 +299,7 @@ func (t *tcp) call(saddr string, options interface{}, sintf string, upgrade *Tcp Timeout: time.Second * 5, } if sintf != "" { + dialer.Control = t.getContextWithBindToDevice(sintf) ief, err := net.InterfaceByName(sintf) if err != nil { return diff --git a/src/yggdrasil/tcp_linux.go b/src/yggdrasil/tcp_linux.go index 7eda3b5e..8d36f04c 100644 --- a/src/yggdrasil/tcp_linux.go +++ b/src/yggdrasil/tcp_linux.go @@ -29,3 +29,17 @@ func (t *tcp) tcpContext(network, address string, c syscall.RawConn) error { // Return nil because errors here are not considered fatal for the connection, it just means congestion control is suboptimal return nil } + +func (t *tcp) getContextWithBindToDevice(sintf string) func(string, string, syscall.RawConn) error { + return func(network, address string, c syscall.RawConn) error { + var err error + btd := func(fd uintptr) { + err = unix.BindToDevice(int(fd), sintf) + } + c.Control(btd) + if err != nil { + t.link.core.log.Debugln("Failed to set SO_BINDTODEVICE:", sintf) + } + return t.tcpContext(network, address, c) + } +} From cfc1e6b83d56585bd984fd71ed4f077bdf079d9a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 25 Oct 2019 18:40:09 -0500 Subject: [PATCH 0416/1109] fix a crash when shutting down if no multicast interfaces are configured --- src/multicast/multicast.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 206edab6..4e0b4f35 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -124,7 +124,9 @@ func (m *Multicast) _stop() error { if m.platformhandler != nil { m.platformhandler.Stop() } - m.sock.Close() + if m.sock != nil { + m.sock.Close() + } return nil } From 710815fed595af7abeac82ca088577e287d231d4 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 25 Oct 2019 19:32:53 -0500 Subject: [PATCH 0417/1109] add dummy functions for other platforms --- src/yggdrasil/tcp.go | 2 +- src/yggdrasil/tcp_darwin.go | 4 ++++ src/yggdrasil/tcp_linux.go | 2 +- src/yggdrasil/tcp_other.go | 4 ++++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 0733b58a..9cca4193 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -299,7 +299,7 @@ func (t *tcp) call(saddr string, options interface{}, sintf string, upgrade *Tcp Timeout: time.Second * 5, } if sintf != "" { - dialer.Control = t.getContextWithBindToDevice(sintf) + dialer.Control = t.getControl(sintf) ief, err := net.InterfaceByName(sintf) if err != nil { return diff --git a/src/yggdrasil/tcp_darwin.go b/src/yggdrasil/tcp_darwin.go index 9d55a1db..3d0626c0 100644 --- a/src/yggdrasil/tcp_darwin.go +++ b/src/yggdrasil/tcp_darwin.go @@ -26,3 +26,7 @@ func (t *tcp) tcpContext(network, address string, c syscall.RawConn) error { return control } } + +func (t *tcp) getControl(sintf string) func(string, string, syscall.RawConn) error { + return t.tcpContext +} diff --git a/src/yggdrasil/tcp_linux.go b/src/yggdrasil/tcp_linux.go index 8d36f04c..9ec3c10f 100644 --- a/src/yggdrasil/tcp_linux.go +++ b/src/yggdrasil/tcp_linux.go @@ -30,7 +30,7 @@ func (t *tcp) tcpContext(network, address string, c syscall.RawConn) error { return nil } -func (t *tcp) getContextWithBindToDevice(sintf string) func(string, string, syscall.RawConn) error { +func (t *tcp) getControl(sintf string) func(string, string, syscall.RawConn) error { return func(network, address string, c syscall.RawConn) error { var err error btd := func(fd uintptr) { diff --git a/src/yggdrasil/tcp_other.go b/src/yggdrasil/tcp_other.go index 44c3d76d..7ee41971 100644 --- a/src/yggdrasil/tcp_other.go +++ b/src/yggdrasil/tcp_other.go @@ -11,3 +11,7 @@ import ( func (t *tcp) tcpContext(network, address string, c syscall.RawConn) error { return nil } + +func (t *tcp) getControl(sintf string) func(string, string, syscall.RawConn) error { + return t.tcpContext +} From 6d3aefb8257f52f5e2d075464efcc5f39ec75244 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 27 Oct 2019 19:55:35 -0500 Subject: [PATCH 0418/1109] fix a data race when an existing session's coords are updated in response to a successful search --- src/yggdrasil/search.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index caa8df7c..f52dcbe8 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -192,7 +192,7 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { 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.Act(sinfo.searches.router, func() { sess.coords = res.Coords }) sess.ping(sinfo.searches.router) } if err != nil { From 74d824302b2dacd04b3d41ef41e0ab0902b504ae Mon Sep 17 00:00:00 2001 From: Arano-kai Date: Tue, 29 Oct 2019 16:36:03 +0200 Subject: [PATCH 0419/1109] FIX: Systemd: typo in directive --- contrib/systemd/yggdrasil.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/systemd/yggdrasil.service b/contrib/systemd/yggdrasil.service index b48ff78b..bdf365c8 100644 --- a/contrib/systemd/yggdrasil.service +++ b/contrib/systemd/yggdrasil.service @@ -8,7 +8,7 @@ Group=yggdrasil ProtectHome=true ProtectSystem=true SyslogIdentifier=yggdrasil -CapabilityBoundSet=CAP_NET_ADMIN +CapabilityBoundingSet=CAP_NET_ADMIN ExecStartPre=+-/sbin/modprobe tun ExecStartPre=/bin/sh -ec "if ! test -s /etc/yggdrasil.conf; \ then umask 077; \ From e3a5e4f3b79c934ab20b2aebe40e839a172c60af Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 10 Nov 2019 19:38:35 +0000 Subject: [PATCH 0420/1109] Add -address and -subnet flag for getting address/subnet out of config --- cmd/yggdrasil/main.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 91cea9a7..6158fd9d 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -2,11 +2,13 @@ package main import ( "bytes" + "crypto/sha512" "encoding/hex" "encoding/json" "flag" "fmt" "io/ioutil" + "net" "os" "os/signal" "strings" @@ -20,6 +22,7 @@ import ( "github.com/kardianos/minwinsvc" "github.com/mitchellh/mapstructure" + "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/admin" "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" @@ -142,6 +145,8 @@ func main() { ver := flag.Bool("version", false, "prints the version of this build") logging := flag.String("logging", "info,warn,error", "comma-separated list of logging levels to enable") logto := flag.String("logto", "stdout", "file path to log to, \"syslog\" or \"stdout\"") + getaddr := flag.Bool("address", false, "returns the IPv6 address as derived from the supplied configuration") + getsnet := flag.Bool("subnet", false, "returns the IPv6 subnet as derived from the supplied configuration") flag.Parse() var cfg *config.NodeConfig @@ -188,6 +193,26 @@ func main() { if cfg == nil { return } + // Have we been asked for the node address yet? If so, print it and then stop. + switch { + case *getaddr: + if pubkey, err := hex.DecodeString(cfg.EncryptionPublicKey); err == nil { + nodeid := sha512.Sum512(pubkey) + fromnodeid := address.AddrForNodeID((*crypto.NodeID)(&nodeid)) + fmt.Println(net.IP(fromnodeid[:]).String()) + } + return + case *getsnet: + if pubkey, err := hex.DecodeString(cfg.EncryptionPublicKey); err == nil { + nodeid := sha512.Sum512(pubkey) + fromnodeid := address.SubnetForNodeID((*crypto.NodeID)(&nodeid)) + subnet := append(fromnodeid[:], 0, 0, 0, 0, 0, 0, 0, 0) + ipnet := net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)} + fmt.Println(ipnet.String()) + } + return + default: + } // Create a new logger that logs output to stdout. var logger *log.Logger switch *logto { From 49ba5bae171d8d7078979e261832fccea669d784 Mon Sep 17 00:00:00 2001 From: William Fleurant Date: Mon, 11 Nov 2019 00:24:50 -0500 Subject: [PATCH 0421/1109] yggdrasil: buildName should report unknown --- src/version/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version/version.go b/src/version/version.go index 4cc7a8f2..09fc04e8 100644 --- a/src/version/version.go +++ b/src/version/version.go @@ -7,7 +7,7 @@ var buildVersion string // from git, or returns "unknown" otherwise. func BuildName() string { if buildName == "" { - return "yggdrasilctl" + return "unknown" } return buildName } From e310a25e5987a2400bbdbb765b14bb1967ca02e2 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 11 Nov 2019 09:40:25 +0000 Subject: [PATCH 0422/1109] Use crypto.GetNodeID instead of sha512 directly --- cmd/yggdrasil/main.go | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 6158fd9d..981c6efc 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -2,7 +2,6 @@ package main import ( "bytes" - "crypto/sha512" "encoding/hex" "encoding/json" "flag" @@ -194,20 +193,29 @@ func main() { return } // Have we been asked for the node address yet? If so, print it and then stop. + getNodeID := func() *crypto.NodeID { + if pubkey, err := hex.DecodeString(cfg.EncryptionPublicKey); err == nil { + var box crypto.BoxPubKey + copy(box[:], pubkey[:]) + return crypto.GetNodeID(&box) + } + return nil + } switch { case *getaddr: - if pubkey, err := hex.DecodeString(cfg.EncryptionPublicKey); err == nil { - nodeid := sha512.Sum512(pubkey) - fromnodeid := address.AddrForNodeID((*crypto.NodeID)(&nodeid)) - fmt.Println(net.IP(fromnodeid[:]).String()) + if nodeid := getNodeID(); nodeid != nil { + addr := *address.AddrForNodeID(nodeid) + ip := net.IP(addr[:]) + fmt.Println(ip.String()) } return case *getsnet: - if pubkey, err := hex.DecodeString(cfg.EncryptionPublicKey); err == nil { - nodeid := sha512.Sum512(pubkey) - fromnodeid := address.SubnetForNodeID((*crypto.NodeID)(&nodeid)) - subnet := append(fromnodeid[:], 0, 0, 0, 0, 0, 0, 0, 0) - ipnet := net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)} + if nodeid := getNodeID(); nodeid != nil { + snet := *address.SubnetForNodeID(nodeid) + ipnet := net.IPNet{ + IP: append(snet[:], 0, 0, 0, 0, 0, 0, 0, 0), + Mask: net.CIDRMask(len(snet)*8, 128), + } fmt.Println(ipnet.String()) } return From 5f1aea3636003a282467d7fc01afe39dd9f018eb Mon Sep 17 00:00:00 2001 From: Arceliar Date: Tue, 12 Nov 2019 21:01:32 -0600 Subject: [PATCH 0423/1109] fix deadlock when AddPeer fails --- src/yggdrasil/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 80f669b6..6dd70b8e 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -424,6 +424,7 @@ func (c *Core) AddPeer(addr string, sintf string) error { return err } c.config.Mutex.Lock() + defer c.config.Mutex.Unlock() if sintf == "" { for _, peer := range c.config.Current.Peers { if peer == addr { @@ -445,7 +446,6 @@ func (c *Core) AddPeer(addr string, sintf string) error { c.config.Current.InterfacePeers[sintf] = append(c.config.Current.InterfacePeers[sintf], addr) } } - c.config.Mutex.Unlock() return nil } From 7068160b200d1d074f50f986edd3c1954fa96c73 Mon Sep 17 00:00:00 2001 From: Arano-kai Date: Thu, 14 Nov 2019 13:29:27 +0200 Subject: [PATCH 0424/1109] Systemd: move config generation to a separate unit - Modular unit composition: different tasks in separate units - Use systemd tool set to run checks - Avoid using inline shell in unit --- contrib/systemd/yggdrasil-default-config.service | 13 +++++++++++++ contrib/systemd/yggdrasil.service | 7 ++----- 2 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 contrib/systemd/yggdrasil-default-config.service diff --git a/contrib/systemd/yggdrasil-default-config.service b/contrib/systemd/yggdrasil-default-config.service new file mode 100644 index 00000000..e9fe45be --- /dev/null +++ b/contrib/systemd/yggdrasil-default-config.service @@ -0,0 +1,13 @@ +[Unit] +Description=yggdrasil default config generator +ConditionPathExists=|!/etc/yggdrasil.conf +ConditionFileNotEmpty=|!/etc/yggdrasil.conf +Wants=local-fs.target +After=local-fs.target + +[Service] +Type=oneshot +Group=yggdrasil +StandardOutput=file:/etc/yggdrasil.conf +ExecStart=/usr/bin/yggdrasil -genconf +ExecStartPost=/usr/bin/chmod 0640 /etc/yggdrasil.conf diff --git a/contrib/systemd/yggdrasil.service b/contrib/systemd/yggdrasil.service index b48ff78b..075c0bd1 100644 --- a/contrib/systemd/yggdrasil.service +++ b/contrib/systemd/yggdrasil.service @@ -1,7 +1,9 @@ [Unit] Description=yggdrasil Wants=network.target +Wants=yggdrasil-default-config.service After=network.target +After=yggdrasil-default-config.service [Service] Group=yggdrasil @@ -10,11 +12,6 @@ ProtectSystem=true SyslogIdentifier=yggdrasil CapabilityBoundSet=CAP_NET_ADMIN ExecStartPre=+-/sbin/modprobe tun -ExecStartPre=/bin/sh -ec "if ! test -s /etc/yggdrasil.conf; \ - then umask 077; \ - yggdrasil -genconf > /etc/yggdrasil.conf; \ - echo 'WARNING: A new /etc/yggdrasil.conf file has been generated.'; \ - fi" ExecStart=/usr/bin/yggdrasil -useconffile /etc/yggdrasil.conf ExecReload=/bin/kill -HUP $MAINPID Restart=always From f49d9de421e98bed3eedeabacf2055ba65ed8ae9 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 19 Nov 2019 14:20:11 +0000 Subject: [PATCH 0425/1109] Fix setting up of MTU when value is outside of acceptable bounds, also account for ethernet headers in calculations, notify about clipping to stdout --- src/tuntap/tun.go | 30 ++++++++++++++++++++++-------- src/tuntap/tun_bsd.go | 2 +- src/tuntap/tun_darwin.go | 5 +++-- src/tuntap/tun_linux.go | 2 +- src/tuntap/tun_other.go | 4 ++-- src/tuntap/tun_windows.go | 5 +++-- 6 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index f0250c93..ffee63b6 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -63,9 +63,12 @@ type TunOptions struct { // Gets the maximum supported MTU for the platform based on the defaults in // defaults.GetDefaults(). -func getSupportedMTU(mtu int) int { - if mtu > defaults.GetDefaults().MaximumIfMTU { - return defaults.GetDefaults().MaximumIfMTU +func getSupportedMTU(mtu int, istapmode bool) int { + if mtu < 1280 { + return 1280 + } + if mtu > MaximumMTU(istapmode) { + return MaximumMTU(istapmode) } return mtu } @@ -80,7 +83,7 @@ func (tun *TunAdapter) Name() string { // the maximum value is determined by your platform. The returned value will // never exceed that of MaximumMTU(). func (tun *TunAdapter) MTU() int { - return getSupportedMTU(tun.mtu) + return getSupportedMTU(tun.mtu, tun.IsTAP()) } // IsTAP returns true if the adapter is a TAP adapter (Layer 2) or false if it @@ -97,7 +100,11 @@ func DefaultName() string { // DefaultMTU gets the default TUN/TAP interface MTU for your platform. This can // be as high as MaximumMTU(), depending on platform, but is never lower than 1280. func DefaultMTU() int { - return defaults.GetDefaults().DefaultIfMTU + ehbytes := 0 + if DefaultIsTAP() { + ehbytes = 14 + } + return defaults.GetDefaults().DefaultIfMTU - ehbytes } // DefaultIsTAP returns true if the default adapter mode for the current @@ -109,8 +116,12 @@ func DefaultIsTAP() bool { // MaximumMTU returns the maximum supported TUN/TAP interface MTU for your // platform. This can be as high as 65535, depending on platform, but is never // lower than 1280. -func MaximumMTU() int { - return defaults.GetDefaults().MaximumIfMTU +func MaximumMTU(iftapmode bool) int { + ehbytes := 0 + if iftapmode { + ehbytes = 14 + } + return defaults.GetDefaults().MaximumIfMTU - ehbytes } // Init initialises the TUN/TAP module. You must have acquired a Listener from @@ -167,6 +178,9 @@ func (tun *TunAdapter) _start() error { if err := tun.setup(ifname, iftapmode, addr, tun.mtu); err != nil { return err } + if tun.MTU() != current.IfMTU { + tun.log.Warnf("Warning: Interface MTU %d automatically adjusted to %d (supported range is 1280-%d)", current.IfMTU, tun.MTU(), MaximumMTU(tun.IsTAP())) + } } if ifname == "none" || ifname == "dummy" { tun.log.Debugln("Not starting TUN/TAP as ifname is none or dummy") @@ -176,7 +190,7 @@ func (tun *TunAdapter) _start() error { go tun.handler() tun.reader.Act(nil, tun.reader._read) // Start the reader tun.icmpv6.Init(tun) - if iftapmode { + if tun.IsTAP() { go tun.icmpv6.Solicit(tun.addr) } tun.ckr.init(tun) diff --git a/src/tuntap/tun_bsd.go b/src/tuntap/tun_bsd.go index 996f3140..78a4adab 100644 --- a/src/tuntap/tun_bsd.go +++ b/src/tuntap/tun_bsd.go @@ -99,7 +99,7 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int panic(err) } tun.iface = iface - tun.mtu = getSupportedMTU(mtu) + tun.mtu = getSupportedMTU(mtu, iftapmode) return tun.setupAddress(addr) } diff --git a/src/tuntap/tun_darwin.go b/src/tuntap/tun_darwin.go index d7b46537..ab6f34e3 100644 --- a/src/tuntap/tun_darwin.go +++ b/src/tuntap/tun_darwin.go @@ -18,7 +18,8 @@ import ( // Configures the "utun" adapter with the correct IPv6 address and MTU. func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { if iftapmode { - tun.log.Warnln("TAP mode is not supported on this platform, defaulting to TUN") + tun.log.Warnln("Warning: TAP mode is not supported on this platform, defaulting to TUN") + iftapmode = false } config := water.Config{DeviceType: water.TUN} iface, err := water.New(config) @@ -26,7 +27,7 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int panic(err) } tun.iface = iface - tun.mtu = getSupportedMTU(mtu) + tun.mtu = getSupportedMTU(mtu, iftapmode) return tun.setupAddress(addr) } diff --git a/src/tuntap/tun_linux.go b/src/tuntap/tun_linux.go index 764e56b9..d3e9c859 100644 --- a/src/tuntap/tun_linux.go +++ b/src/tuntap/tun_linux.go @@ -26,7 +26,7 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int panic(err) } tun.iface = iface - tun.mtu = getSupportedMTU(mtu) + tun.mtu = getSupportedMTU(mtu, iftapmode) // The following check is specific to Linux, as the TAP driver only supports // an MTU of 65535-14 to make room for the ethernet headers. This makes sure // that the MTU gets rounded down to 65521 instead of causing a panic. diff --git a/src/tuntap/tun_other.go b/src/tuntap/tun_other.go index 48276b49..7d4f0643 100644 --- a/src/tuntap/tun_other.go +++ b/src/tuntap/tun_other.go @@ -21,13 +21,13 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int panic(err) } tun.iface = iface - tun.mtu = getSupportedMTU(mtu) + tun.mtu = getSupportedMTU(mtu, iftapmode) return tun.setupAddress(addr) } // We don't know how to set the IPv6 address on an unknown platform, therefore // write about it to stdout and don't try to do anything further. func (tun *TunAdapter) setupAddress(addr string) error { - tun.log.Warnln("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr) + tun.log.Warnln("Warning: Platform not supported, you must set the address of", tun.iface.Name(), "to", addr) return nil } diff --git a/src/tuntap/tun_windows.go b/src/tuntap/tun_windows.go index ea1515ff..d4fd1c3d 100644 --- a/src/tuntap/tun_windows.go +++ b/src/tuntap/tun_windows.go @@ -17,7 +17,8 @@ import ( // delegate the hard work to "netsh". func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { if !iftapmode { - tun.log.Warnln("TUN mode is not supported on this platform, defaulting to TAP") + tun.log.Warnln("Warning: TUN mode is not supported on this platform, defaulting to TAP") + iftapmode = true } config := water.Config{DeviceType: water.TAP} config.PlatformSpecificParams.ComponentID = "tap0901" @@ -60,7 +61,7 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int panic(err) } tun.iface = iface - tun.mtu = getSupportedMTU(mtu) + tun.mtu = getSupportedMTU(mtu, iftapmode) err = tun.setupMTU(tun.mtu) if err != nil { panic(err) From 16a487cb1dbb327b8477fdb422a095a667e25d68 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 19 Nov 2019 14:34:10 +0000 Subject: [PATCH 0426/1109] Move genkeys into cmd/ as this allows 'go run github.com/yggdrasil-network/yggdrasil-go/cmd/genkeys' --- misc/genkeys.go => cmd/genkeys/main.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename misc/genkeys.go => cmd/genkeys/main.go (100%) diff --git a/misc/genkeys.go b/cmd/genkeys/main.go similarity index 100% rename from misc/genkeys.go rename to cmd/genkeys/main.go From 4b9bce855eeb6af6b53e6927dc394c65af8bd157 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 19 Nov 2019 14:37:16 +0000 Subject: [PATCH 0427/1109] Only build yggdrasil/yggdrasilctl when running ./build --- build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build b/build index 7ca5f4fa..68dac037 100755 --- a/build +++ b/build @@ -44,7 +44,7 @@ elif [ $ANDROID ]; then github.com/yggdrasil-network/yggdrasil-extras/src/mobile \ github.com/yggdrasil-network/yggdrasil-extras/src/dummy else - for CMD in `ls cmd/` ; do + for CMD in yggdrasil yggdrasilctl ; do echo "Building: $CMD" go build $ARGS -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" ./cmd/$CMD From b70fbfa0f1e38a85d9805558fc6f735eb1938a93 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 20 Nov 2019 11:54:09 +0000 Subject: [PATCH 0428/1109] Update changelog for v0.3.12 --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 903ffc7c..32490d83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,22 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> +## [0.3.12] - 2019-11-22 +### Added +- New command line parameters `-address` and `-subnet` for getting the address/subnet from the config file +- A warning is now produced in the Yggdrasil output when the MTU has been adjusted for some reason + +### Changed +- Outgoing `InterfacePeers` connections now use `SO_BINDTODEVICE` to prefer an outgoing interface +- The `genkeys` utility is now in `cmd` rather than `contrib` + +### Fixed +- A data race has been fixed when updating session coordinates +- A crash when shutting down when no multicast interfaces are configured has been fixed +- A deadlock when calling `AddPeer` multiple times has been fixed +- A typo in the systemd unit file (for some Linux packages) has been fixed +- The nodeinfo now reports `unknown` correctly when no build name/version is available in the environment +- The MTU calculation now correctly accounts for ethernet headers when running in TAP mode ## [0.3.11] - 2019-10-25 ### Added From ec46b217da1434f59c7e992e0428f6316f1e9f29 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 20 Nov 2019 13:41:18 +0000 Subject: [PATCH 0429/1109] Update CHANGELOG.md --- CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32490d83..3babf8f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,19 +27,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [0.3.12] - 2019-11-22 ### Added -- New command line parameters `-address` and `-subnet` for getting the address/subnet from the config file -- A warning is now produced in the Yggdrasil output when the MTU has been adjusted for some reason +- New command line parameters `-address` and `-subnet` for getting the address/subnet from the config file, for use with `-useconffile` or `-useconf` +- A warning is now produced in the Yggdrasil output at startup when the MTU in the config is invalid or has been adjusted for some reason ### Changed -- Outgoing `InterfacePeers` connections now use `SO_BINDTODEVICE` to prefer an outgoing interface +- On Linux, outgoing `InterfacePeers` connections now use `SO_BINDTODEVICE` to prefer an outgoing interface - The `genkeys` utility is now in `cmd` rather than `contrib` ### Fixed -- A data race has been fixed when updating session coordinates +- A data race condition has been fixed when updating session coordinates - A crash when shutting down when no multicast interfaces are configured has been fixed - A deadlock when calling `AddPeer` multiple times has been fixed - A typo in the systemd unit file (for some Linux packages) has been fixed -- The nodeinfo now reports `unknown` correctly when no build name/version is available in the environment +- The NodeInfo and admin socket now report `unknown` correctly when no build name/version is available in the environment at build time - The MTU calculation now correctly accounts for ethernet headers when running in TAP mode ## [0.3.11] - 2019-10-25 From 9fca3640f9743862c4fa1f25e3cce5b026e7c2a5 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 20 Nov 2019 22:11:52 +0000 Subject: [PATCH 0430/1109] Fix couple of issues with MTU calculations --- src/tuntap/tun.go | 19 +++++++------------ src/tuntap/tun_linux.go | 8 -------- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index ffee63b6..c9a627a0 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -170,22 +170,17 @@ func (tun *TunAdapter) _start() error { nodeID := crypto.GetNodeID(&boxPub) tun.addr = *address.AddrForNodeID(nodeID) tun.subnet = *address.SubnetForNodeID(nodeID) - tun.mtu = current.IfMTU - ifname := current.IfName - iftapmode := current.IfTAPMode addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1) - if ifname != "none" { - if err := tun.setup(ifname, iftapmode, addr, tun.mtu); err != nil { - return err - } - if tun.MTU() != current.IfMTU { - tun.log.Warnf("Warning: Interface MTU %d automatically adjusted to %d (supported range is 1280-%d)", current.IfMTU, tun.MTU(), MaximumMTU(tun.IsTAP())) - } - } - if ifname == "none" || ifname == "dummy" { + if current.IfName == "none" || current.IfName == "dummy" { tun.log.Debugln("Not starting TUN/TAP as ifname is none or dummy") return nil } + if err := tun.setup(current.IfName, current.IfTAPMode, addr, current.IfMTU); err != nil { + return err + } + if tun.MTU() != current.IfMTU { + tun.log.Warnf("Warning: Interface MTU %d automatically adjusted to %d (supported range is 1280-%d)", current.IfMTU, tun.MTU(), MaximumMTU(tun.IsTAP())) + } tun.isOpen = true go tun.handler() tun.reader.Act(nil, tun.reader._read) // Start the reader diff --git a/src/tuntap/tun_linux.go b/src/tuntap/tun_linux.go index d3e9c859..b5918328 100644 --- a/src/tuntap/tun_linux.go +++ b/src/tuntap/tun_linux.go @@ -27,14 +27,6 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int } tun.iface = iface tun.mtu = getSupportedMTU(mtu, iftapmode) - // The following check is specific to Linux, as the TAP driver only supports - // an MTU of 65535-14 to make room for the ethernet headers. This makes sure - // that the MTU gets rounded down to 65521 instead of causing a panic. - if iftapmode { - if tun.mtu > 65535-tun_ETHER_HEADER_LENGTH { - tun.mtu = 65535 - tun_ETHER_HEADER_LENGTH - } - } // Friendly output tun.log.Infof("Interface name: %s", tun.iface.Name()) tun.log.Infof("Interface IPv6: %s", addr) From d06c40ad19dca10fa0c106be3cffe88ec1f0ea41 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 20 Nov 2019 22:40:48 +0000 Subject: [PATCH 0431/1109] Use existing constant --- src/tuntap/tun.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index c9a627a0..d5bed523 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -102,7 +102,7 @@ func DefaultName() string { func DefaultMTU() int { ehbytes := 0 if DefaultIsTAP() { - ehbytes = 14 + ehbytes = tun_ETHER_HEADER_LENGTH } return defaults.GetDefaults().DefaultIfMTU - ehbytes } @@ -119,7 +119,7 @@ func DefaultIsTAP() bool { func MaximumMTU(iftapmode bool) int { ehbytes := 0 if iftapmode { - ehbytes = 14 + ehbytes = tun_ETHER_HEADER_LENGTH } return defaults.GetDefaults().MaximumIfMTU - ehbytes } From e90be6f5695ebc38a096796e049e774ec475d48e Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 21 Nov 2019 00:02:39 +0000 Subject: [PATCH 0432/1109] Add API functions for manipulating maximum session MTU, fix TUN/TAP to use that --- src/tuntap/tun.go | 3 +++ src/yggdrasil/api.go | 16 ++++++++++++++++ src/yggdrasil/session.go | 8 +++++--- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index d5bed523..6feb533b 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -35,6 +35,7 @@ const tun_ETHER_HEADER_LENGTH = 14 // you should pass this object to the yggdrasil.SetRouterAdapter() function // before calling yggdrasil.Start(). type TunAdapter struct { + core *yggdrasil.Core writer tunWriter reader tunReader config *config.NodeState @@ -131,6 +132,7 @@ func (tun *TunAdapter) Init(core *yggdrasil.Core, config *config.NodeState, log if !ok { return fmt.Errorf("invalid options supplied to TunAdapter module") } + tun.core = core tun.config = config tun.log = log tun.listener = tunoptions.Listener @@ -181,6 +183,7 @@ func (tun *TunAdapter) _start() error { if tun.MTU() != current.IfMTU { tun.log.Warnf("Warning: Interface MTU %d automatically adjusted to %d (supported range is 1280-%d)", current.IfMTU, tun.MTU(), MaximumMTU(tun.IsTAP())) } + tun.core.SetMaximumSessionMTU(uint16(tun.MTU())) tun.isOpen = true go tun.handler() tun.reader.Act(nil, tun.reader._read) // Start the reader diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 6dd70b8e..59cc8310 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -363,6 +363,22 @@ func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) { c.router.nodeinfo.setNodeInfo(nodeinfo, nodeinfoprivacy) } +// GetMaximumSessionMTU returns the maximum allowed session MTU size. +func (c *Core) GetMaximumSessionMTU(mtu uint16) uint16 { + return c.router.sessions.myMaximumMTU +} + +// SetMaximumSessionMTU sets the maximum allowed session MTU size. The return +// value contains the actual set value, since Yggdrasil will not accept MTUs +// below 1280 bytes. The default value is 65535 bytes. +func (c *Core) SetMaximumSessionMTU(mtu uint16) uint16 { + if mtu < 1280 { + mtu = 1280 + } + c.router.sessions.myMaximumMTU = mtu + return mtu +} + // GetNodeInfo requests nodeinfo from a remote node, as specified by the public // key and coordinates specified. The third parameter specifies whether a cached // result is acceptable - this results in less traffic being generated than is diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 2f5e0af3..b287377a 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -121,6 +121,7 @@ type sessions struct { lastCleanup time.Time isAllowedHandler func(pubkey *crypto.BoxPubKey, initiator bool) bool // Returns true or false if session setup is allowed isAllowedMutex sync.RWMutex // Protects the above + myMaximumMTU uint16 // Maximum allowed session MTU permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey // Maps known permanent keys to their shared key, used by DHT a lot sinfos map[crypto.Handle]*sessionInfo // Maps handle onto session info byTheirPerm map[crypto.BoxPubKey]*crypto.Handle // Maps theirPermPub onto handle @@ -133,6 +134,7 @@ func (ss *sessions) init(r *router) { ss.sinfos = make(map[crypto.Handle]*sessionInfo) ss.byTheirPerm = make(map[crypto.BoxPubKey]*crypto.Handle) ss.lastCleanup = time.Now() + ss.myMaximumMTU = 65535 } func (ss *sessions) reconfigure() { @@ -187,9 +189,9 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.mySesPriv = *priv sinfo.myNonce = *crypto.NewBoxNonce() sinfo.theirMTU = 1280 - ss.router.core.config.Mutex.RLock() - sinfo.myMTU = uint16(ss.router.core.config.Current.IfMTU) - ss.router.core.config.Mutex.RUnlock() + // TODO: sinfo.myMTU becomes unnecessary if we always have a reference to the + // sessions struct so let's check if that is the case + sinfo.myMTU = ss.myMaximumMTU now := time.Now() sinfo.timeOpened = now sinfo.time = now From d1c445dc41837d5f3c85d37acc8d693a9f4df279 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 21 Nov 2019 09:28:36 +0000 Subject: [PATCH 0433/1109] Thread safety for MTU API functions --- src/yggdrasil/api.go | 21 +++++++++++---------- src/yggdrasil/session.go | 2 -- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 59cc8310..e6b32e68 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -365,18 +365,19 @@ func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) { // GetMaximumSessionMTU returns the maximum allowed session MTU size. func (c *Core) GetMaximumSessionMTU(mtu uint16) uint16 { - return c.router.sessions.myMaximumMTU + mtu := 0 + phony.Block(c.router, func() { + mtu = c.router.sessions.myMaximumMTU + }) + return mtu } -// SetMaximumSessionMTU sets the maximum allowed session MTU size. The return -// value contains the actual set value, since Yggdrasil will not accept MTUs -// below 1280 bytes. The default value is 65535 bytes. -func (c *Core) SetMaximumSessionMTU(mtu uint16) uint16 { - if mtu < 1280 { - mtu = 1280 - } - c.router.sessions.myMaximumMTU = mtu - return mtu +// SetMaximumSessionMTU sets the maximum allowed session MTU size. The default +// value is 65535 bytes. +func (c *Core) SetMaximumSessionMTU(mtu uint16) { + phony.Block(c.router, func() { + c.router.sessions.myMaximumMTU = mtu + }) } // GetNodeInfo requests nodeinfo from a remote node, as specified by the public diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index b287377a..78b0a3b2 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -189,8 +189,6 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.mySesPriv = *priv sinfo.myNonce = *crypto.NewBoxNonce() sinfo.theirMTU = 1280 - // TODO: sinfo.myMTU becomes unnecessary if we always have a reference to the - // sessions struct so let's check if that is the case sinfo.myMTU = ss.myMaximumMTU now := time.Now() sinfo.timeOpened = now From 7c18c6806d0d0a61b4eb7895471339c1e65e5f99 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 21 Nov 2019 09:54:36 +0000 Subject: [PATCH 0434/1109] Further updates, notify sessions about updated MTU from API call --- src/tuntap/tun.go | 6 ++++++ src/yggdrasil/api.go | 16 ++++++++++------ src/yggdrasil/session.go | 13 ++++++------- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 6feb533b..1e994ea5 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -233,6 +233,12 @@ func (tun *TunAdapter) UpdateConfig(config *config.NodeConfig) { // Replace the active configuration with the supplied one tun.config.Replace(*config) + // If the MTU has changed in the TUN/TAP module then this is where we would + // tell the router so that updated session pings can be sent. However, we + // don't currently update the MTU of the adapter once it has been created so + // this doesn't actually happen in the real world yet. + // tun.core.SetMaximumSessionMTU(...) + // Notify children about the configuration change tun.Act(nil, tun.ckr.configure) } diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index e6b32e68..7f82c260 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -364,19 +364,23 @@ func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) { } // GetMaximumSessionMTU returns the maximum allowed session MTU size. -func (c *Core) GetMaximumSessionMTU(mtu uint16) uint16 { - mtu := 0 - phony.Block(c.router, func() { +func (c *Core) GetMaximumSessionMTU() uint16 { + var mtu uint16 + phony.Block(&c.router, func() { mtu = c.router.sessions.myMaximumMTU }) return mtu } // SetMaximumSessionMTU sets the maximum allowed session MTU size. The default -// value is 65535 bytes. +// value is 65535 bytes. Session pings will be sent to update all open sessions +// if the MTU has changed. func (c *Core) SetMaximumSessionMTU(mtu uint16) { - phony.Block(c.router, func() { - c.router.sessions.myMaximumMTU = mtu + phony.Block(&c.router, func() { + if c.router.sessions.myMaximumMTU != mtu { + c.router.sessions.myMaximumMTU = mtu + c.router.sessions.reconfigure() + } }) } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 78b0a3b2..c203fe2f 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -55,10 +55,6 @@ type sessionInfo struct { callbacks []chan func() // Finished work from crypto workers } -func (sinfo *sessionInfo) reconfigure() { - // This is where reconfiguration would go, if we had anything to do -} - // Represents a session ping/pong packet, andincludes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU. type sessionPing struct { SendPermPub crypto.BoxPubKey // Sender's permanent key @@ -138,9 +134,12 @@ func (ss *sessions) init(r *router) { } func (ss *sessions) reconfigure() { - for _, session := range ss.sinfos { - session.reconfigure() - } + ss.router.Act(nil, func() { + for _, session := range ss.sinfos { + session.myMTU = ss.myMaximumMTU + session.ping(ss.router) + } + }) } // Determines whether the session with a given publickey is allowed based on From d3a2087e0fc0c449b8f5b37fca537c5b67ab83cd Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 21 Nov 2019 10:02:18 +0000 Subject: [PATCH 0435/1109] Update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3babf8f5..eb5c4394 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [0.3.12] - 2019-11-22 ### Added -- New command line parameters `-address` and `-subnet` for getting the address/subnet from the config file, for use with `-useconffile` or `-useconf` +- New API functions `SetMaximumSessionMTU` and `GetMaximumSessionMTU` +- New command line parameters `-address` and `-subnet` for getting the address/subnet from the config file, for use with `-useconffile` or `-useconf` - A warning is now produced in the Yggdrasil output at startup when the MTU in the config is invalid or has been adjusted for some reason ### Changed From 248a08b2f1362389e48901c6725796c4eabd5986 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 21 Nov 2019 19:23:44 -0600 Subject: [PATCH 0436/1109] send a message to the sessions to update mtu instead of trying to update it directly --- src/yggdrasil/session.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index c203fe2f..743dd7a4 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -136,7 +136,10 @@ func (ss *sessions) init(r *router) { func (ss *sessions) reconfigure() { ss.router.Act(nil, func() { for _, session := range ss.sinfos { - session.myMTU = ss.myMaximumMTU + sinfo, mtu := session, ss.myMaximumMTU + sinfo.Act(ss.router, func() { + sinfo.myMTU = mtu + }) session.ping(ss.router) } }) From f5517acc81911cf4477d939fe91fff97ecae3f56 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 22 Nov 2019 16:43:50 +0000 Subject: [PATCH 0437/1109] Drop Water, use Wireguard tun library, drop TAP support --- go.mod | 1 + go.sum | 5 + src/config/config.go | 2 - src/defaults/defaults.go | 7 +- src/defaults/defaults_darwin.go | 7 +- src/defaults/defaults_freebsd.go | 7 +- src/defaults/defaults_linux.go | 7 +- src/defaults/defaults_netbsd.go | 7 +- src/defaults/defaults_openbsd.go | 7 +- src/defaults/defaults_other.go | 7 +- src/defaults/defaults_windows.go | 7 +- src/tuntap/admin.go | 5 +- src/tuntap/icmpv6.go | 297 +------------------------------ src/tuntap/iface.go | 128 +++---------- src/tuntap/tun.go | 90 ++++------ src/tuntap/tun_bsd.go | 36 ++-- src/tuntap/tun_darwin.go | 21 ++- src/tuntap/tun_linux.go | 32 ++-- src/tuntap/tun_other.go | 27 ++- src/tuntap/tun_windows.go | 62 +------ 20 files changed, 146 insertions(+), 616 deletions(-) diff --git a/go.mod b/go.mod index a45600a3..417e6fa3 100644 --- a/go.mod +++ b/go.mod @@ -18,4 +18,5 @@ require ( golang.org/x/net v0.0.0-20191021144547-ec77196f6094 golang.org/x/sys v0.0.0-20191024172528-b4ff53e7a1cb golang.org/x/text v0.3.2 + golang.zx2c4.com/wireguard v0.0.20191012 ) diff --git a/go.sum b/go.sum index ecfa4626..222728a1 100644 --- a/go.sum +++ b/go.sum @@ -21,16 +21,21 @@ github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f/go.mod h1:ZjcWmF 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= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= 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-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191024172528-b4ff53e7a1cb h1:ZxSglHghKPYD8WDeRUzRJrUJtDF0PxsTUSxyqr9/5BI= 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.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.zx2c4.com/wireguard v0.0.20191012 h1:sdX+y3hrHkW8KJkjY7ZgzpT5Tqo8XnBkH55U1klphko= +golang.zx2c4.com/wireguard v0.0.20191012/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4= diff --git a/src/config/config.go b/src/config/config.go index ac88bfc5..bacf9003 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -71,7 +71,6 @@ type NodeConfig struct { SigningPrivateKey string `comment:"Your private signing key. DO NOT share this with anyone!"` LinkLocalTCPPort uint16 `comment:"The port number to be used for the link-local TCP listeners for the\nconfigured MulticastInterfaces. This option does not affect listeners\nspecified in the Listen option. Unless you plan to firewall link-local\ntraffic, it is best to leave this as the default value of 0. This\noption cannot currently be changed by reloading config during runtime."` IfName string `comment:"Local network interface name for TUN/TAP adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN/TAP."` - IfTAPMode bool `comment:"Set local network interface to TAP mode rather than TUN mode if\nsupported by your platform - option will be ignored if not."` IfMTU int `comment:"Maximux Transmission Unit (MTU) size for your local TUN/TAP interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` SessionFirewall SessionFirewall `comment:"The session firewall controls who can send/receive network traffic\nto/from. This is useful if you want to protect this node without\nresorting to using a real firewall. This does not affect traffic\nbeing routed via this node to somewhere else. Rules are prioritised as\nfollows: blacklist, whitelist, always allow outgoing, direct, remote."` TunnelRouting TunnelRouting `comment:"Allow tunneling non-Yggdrasil traffic over Yggdrasil. This effectively\nallows you to use Yggdrasil to route to, or to bridge other networks,\nsimilar to a VPN tunnel. Tunnelling works between any two nodes and\ndoes not require them to be directly peered."` @@ -127,7 +126,6 @@ func GenerateConfig() *NodeConfig { cfg.MulticastInterfaces = defaults.GetDefaults().DefaultMulticastInterfaces cfg.IfName = defaults.GetDefaults().DefaultIfName cfg.IfMTU = defaults.GetDefaults().DefaultIfMTU - cfg.IfTAPMode = defaults.GetDefaults().DefaultIfTAPMode cfg.SessionFirewall.Enable = false cfg.SessionFirewall.AllowFromDirect = true cfg.SessionFirewall.AllowFromRemote = true diff --git a/src/defaults/defaults.go b/src/defaults/defaults.go index a5784198..ceb58ba8 100644 --- a/src/defaults/defaults.go +++ b/src/defaults/defaults.go @@ -14,8 +14,7 @@ type platformDefaultParameters struct { DefaultMulticastInterfaces []string // TUN/TAP - MaximumIfMTU int - DefaultIfMTU int - DefaultIfName string - DefaultIfTAPMode bool + MaximumIfMTU int + DefaultIfMTU int + DefaultIfName string } diff --git a/src/defaults/defaults_darwin.go b/src/defaults/defaults_darwin.go index 47683bf9..9fbe6d8e 100644 --- a/src/defaults/defaults_darwin.go +++ b/src/defaults/defaults_darwin.go @@ -19,9 +19,8 @@ func GetDefaults() platformDefaultParameters { }, // TUN/TAP - MaximumIfMTU: 65535, - DefaultIfMTU: 65535, - DefaultIfName: "auto", - DefaultIfTAPMode: false, + MaximumIfMTU: 65535, + DefaultIfMTU: 65535, + DefaultIfName: "auto", } } diff --git a/src/defaults/defaults_freebsd.go b/src/defaults/defaults_freebsd.go index 0e523483..d7dc43b9 100644 --- a/src/defaults/defaults_freebsd.go +++ b/src/defaults/defaults_freebsd.go @@ -18,9 +18,8 @@ func GetDefaults() platformDefaultParameters { }, // TUN/TAP - MaximumIfMTU: 32767, - DefaultIfMTU: 32767, - DefaultIfName: "/dev/tap0", - DefaultIfTAPMode: true, + MaximumIfMTU: 32767, + DefaultIfMTU: 32767, + DefaultIfName: "/dev/tap0", } } diff --git a/src/defaults/defaults_linux.go b/src/defaults/defaults_linux.go index b0aaf855..5f3f12a8 100644 --- a/src/defaults/defaults_linux.go +++ b/src/defaults/defaults_linux.go @@ -18,9 +18,8 @@ func GetDefaults() platformDefaultParameters { }, // TUN/TAP - MaximumIfMTU: 65535, - DefaultIfMTU: 65535, - DefaultIfName: "auto", - DefaultIfTAPMode: false, + MaximumIfMTU: 65535, + DefaultIfMTU: 65535, + DefaultIfName: "auto", } } diff --git a/src/defaults/defaults_netbsd.go b/src/defaults/defaults_netbsd.go index 52a487b7..bc9d7a39 100644 --- a/src/defaults/defaults_netbsd.go +++ b/src/defaults/defaults_netbsd.go @@ -18,9 +18,8 @@ func GetDefaults() platformDefaultParameters { }, // TUN/TAP - MaximumIfMTU: 9000, - DefaultIfMTU: 9000, - DefaultIfName: "/dev/tap0", - DefaultIfTAPMode: true, + MaximumIfMTU: 9000, + DefaultIfMTU: 9000, + DefaultIfName: "/dev/tun0", } } diff --git a/src/defaults/defaults_openbsd.go b/src/defaults/defaults_openbsd.go index d44e5714..20741ee8 100644 --- a/src/defaults/defaults_openbsd.go +++ b/src/defaults/defaults_openbsd.go @@ -18,9 +18,8 @@ func GetDefaults() platformDefaultParameters { }, // TUN/TAP - MaximumIfMTU: 16384, - DefaultIfMTU: 16384, - DefaultIfName: "/dev/tap0", - DefaultIfTAPMode: true, + MaximumIfMTU: 16384, + DefaultIfMTU: 16384, + DefaultIfName: "/dev/tun0", } } diff --git a/src/defaults/defaults_other.go b/src/defaults/defaults_other.go index 0ba825c5..763a7f89 100644 --- a/src/defaults/defaults_other.go +++ b/src/defaults/defaults_other.go @@ -18,9 +18,8 @@ func GetDefaults() platformDefaultParameters { }, // TUN/TAP - MaximumIfMTU: 65535, - DefaultIfMTU: 65535, - DefaultIfName: "none", - DefaultIfTAPMode: false, + MaximumIfMTU: 65535, + DefaultIfMTU: 65535, + DefaultIfName: "none", } } diff --git a/src/defaults/defaults_windows.go b/src/defaults/defaults_windows.go index 6d53225a..ade833d4 100644 --- a/src/defaults/defaults_windows.go +++ b/src/defaults/defaults_windows.go @@ -18,9 +18,8 @@ func GetDefaults() platformDefaultParameters { }, // TUN/TAP - MaximumIfMTU: 65535, - DefaultIfMTU: 65535, - DefaultIfName: "auto", - DefaultIfTAPMode: true, + MaximumIfMTU: 65535, + DefaultIfMTU: 65535, + DefaultIfName: "auto", } } diff --git a/src/tuntap/admin.go b/src/tuntap/admin.go index 778c03ae..c7fc20b0 100644 --- a/src/tuntap/admin.go +++ b/src/tuntap/admin.go @@ -19,9 +19,8 @@ func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { }() return admin.Info{ - t.iface.Name(): admin.Info{ - "tap_mode": t.iface.IsTAP(), - "mtu": t.mtu, + t.Name(): admin.Info{ + "mtu": t.mtu, }, }, nil }) diff --git a/src/tuntap/icmpv6.go b/src/tuntap/icmpv6.go index e601acb5..67c10e54 100644 --- a/src/tuntap/icmpv6.go +++ b/src/tuntap/icmpv6.go @@ -11,32 +11,16 @@ package tuntap import ( "encoding/binary" - "errors" "net" - "sync" - "time" "golang.org/x/net/icmp" "golang.org/x/net/ipv6" - - "github.com/yggdrasil-network/yggdrasil-go/src/address" ) const len_ETHER = 14 type ICMPv6 struct { - tun *TunAdapter - mylladdr net.IP - mymac net.HardwareAddr - peermacs map[address.Address]neighbor - peermacsmutex sync.RWMutex -} - -type neighbor struct { - mac net.HardwareAddr - learned bool - lastadvertisement time.Time - lastsolicitation time.Time + tun *TunAdapter } // Marshal returns the binary encoding of h. @@ -61,182 +45,6 @@ func ipv6Header_Marshal(h *ipv6.Header) ([]byte, error) { // addresses. func (i *ICMPv6) Init(t *TunAdapter) { i.tun = t - i.peermacsmutex.Lock() - i.peermacs = make(map[address.Address]neighbor) - i.peermacsmutex.Unlock() - - // Our MAC address and link-local address - i.mymac = net.HardwareAddr{ - 0x02, 0x00, 0x00, 0x00, 0x00, 0x02} - i.mylladdr = net.IP{ - 0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFE} - copy(i.mymac[:], i.tun.addr[:]) - copy(i.mylladdr[9:], i.tun.addr[1:]) -} - -// Parses an incoming ICMPv6 packet. The packet provided may be either an -// ethernet frame containing an IP packet, or the IP packet alone. This is -// determined by whether the TUN/TAP adapter is running in TUN (layer 3) or -// TAP (layer 2) mode. Returns an error condition which is nil if the ICMPv6 -// module handled the packet or contains the error if not. -func (i *ICMPv6) ParsePacket(datain []byte) error { - var response []byte - var err error - - // Parse the frame/packet - if i.tun.IsTAP() { - response, err = i.UnmarshalPacketL2(datain) - } else { - response, err = i.UnmarshalPacket(datain, nil) - } - - if err != nil { - return err - } - - // Write the packet to TUN/TAP - i.tun.iface.Write(response) - return nil -} - -// Unwraps the ethernet headers of an incoming ICMPv6 packet and hands off -// the IP packet to the ParsePacket function for further processing. -// A response buffer is also created for the response message, also complete -// with ethernet headers. -func (i *ICMPv6) UnmarshalPacketL2(datain []byte) ([]byte, error) { - // Ignore non-IPv6 frames - if binary.BigEndian.Uint16(datain[12:14]) != uint16(0x86DD) { - return nil, errors.New("Ignoring non-IPv6 frame") - } - - // Hand over to ParsePacket to interpret the IPv6 packet - mac := datain[6:12] - ipv6packet, err := i.UnmarshalPacket(datain[len_ETHER:], &mac) - if err != nil { - return nil, err - } - - // Create the response buffer - dataout := make([]byte, len_ETHER+ipv6.HeaderLen+32) - - // Populate the response ethernet headers - copy(dataout[:6], datain[6:12]) - copy(dataout[6:12], i.mymac[:]) - binary.BigEndian.PutUint16(dataout[12:14], uint16(0x86DD)) - - // Copy the returned packet to our response ethernet frame - copy(dataout[len_ETHER:], ipv6packet) - return dataout, nil -} - -// Unwraps the IP headers of an incoming IPv6 packet and performs various -// sanity checks on the packet - i.e. is the packet an ICMPv6 packet, does the -// ICMPv6 message match a known expected type. The relevant handler function -// is then called and a response packet may be returned. -func (i *ICMPv6) UnmarshalPacket(datain []byte, datamac *[]byte) ([]byte, error) { - // Parse the IPv6 packet headers - ipv6Header, err := ipv6.ParseHeader(datain[:ipv6.HeaderLen]) - if err != nil { - return nil, err - } - - // Check if the packet is IPv6 - if ipv6Header.Version != ipv6.Version { - return nil, errors.New("Ignoring non-IPv6 packet") - } - - // Check if the packet is ICMPv6 - if ipv6Header.NextHeader != 58 { - return nil, errors.New("Ignoring non-ICMPv6 packet") - } - - // Parse the ICMPv6 message contents - icmpv6Header, err := icmp.ParseMessage(58, datain[ipv6.HeaderLen:]) - if err != nil { - return nil, err - } - - // Check for a supported message type - switch icmpv6Header.Type { - case ipv6.ICMPTypeNeighborSolicitation: - if !i.tun.IsTAP() { - return nil, errors.New("Ignoring Neighbor Solicitation in TUN mode") - } - response, err := i.HandleNDP(datain[ipv6.HeaderLen:]) - if err == nil { - // Create our ICMPv6 response - responsePacket, err := CreateICMPv6( - ipv6Header.Src, i.mylladdr, - ipv6.ICMPTypeNeighborAdvertisement, 0, - &icmp.DefaultMessageBody{Data: response}) - if err != nil { - return nil, err - } - // Send it back - return responsePacket, nil - } else { - return nil, err - } - case ipv6.ICMPTypeNeighborAdvertisement: - if !i.tun.IsTAP() { - return nil, errors.New("Ignoring Neighbor Advertisement in TUN mode") - } - if datamac != nil { - var addr address.Address - var target address.Address - mac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00} - copy(addr[:], ipv6Header.Src[:]) - copy(target[:], datain[48:64]) - copy(mac[:], (*datamac)[:]) - i.peermacsmutex.Lock() - neighbor := i.peermacs[target] - neighbor.mac = mac - neighbor.learned = true - neighbor.lastadvertisement = time.Now() - i.peermacs[target] = neighbor - i.peermacsmutex.Unlock() - i.tun.log.Debugln("Learned peer MAC", mac.String(), "for", net.IP(target[:]).String()) - /* - i.tun.log.Debugln("Peer MAC table:") - i.peermacsmutex.RLock() - for t, n := range i.peermacs { - if n.learned { - i.tun.log.Debugln("- Target", net.IP(t[:]).String(), "has MAC", n.mac.String()) - } else { - i.tun.log.Debugln("- Target", net.IP(t[:]).String(), "is not learned yet") - } - } - i.peermacsmutex.RUnlock() - */ - } - return nil, errors.New("No response needed") - } - - return nil, errors.New("ICMPv6 type not matched") -} - -// Creates an ICMPv6 packet based on the given icmp.MessageBody and other -// parameters, complete with ethernet and IP headers, which can be written -// directly to a TAP adapter. -func (i *ICMPv6) CreateICMPv6L2(dstmac net.HardwareAddr, dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) { - // Pass through to CreateICMPv6 - ipv6packet, err := CreateICMPv6(dst, src, mtype, mcode, mbody) - if err != nil { - return nil, err - } - - // Create the response buffer - dataout := make([]byte, len_ETHER+len(ipv6packet)) - - // Populate the response ethernet headers - copy(dataout[:6], dstmac[:6]) - copy(dataout[6:12], i.mymac[:]) - binary.BigEndian.PutUint16(dataout[12:14], uint16(0x86DD)) - - // Copy the returned packet to our response ethernet frame - copy(dataout[len_ETHER:], ipv6packet) - return dataout, nil } // Creates an ICMPv6 packet based on the given icmp.MessageBody and other @@ -281,106 +89,3 @@ func CreateICMPv6(dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody // Send it back return responsePacket, nil } - -func (i *ICMPv6) Solicit(addr address.Address) { - retries := 5 - for retries > 0 { - retries-- - i.peermacsmutex.RLock() - if n, ok := i.peermacs[addr]; ok && n.learned { - i.tun.log.Debugln("MAC learned for", net.IP(addr[:]).String()) - i.peermacsmutex.RUnlock() - return - } - i.peermacsmutex.RUnlock() - i.tun.log.Debugln("Sending neighbor solicitation for", net.IP(addr[:]).String()) - i.peermacsmutex.Lock() - if n, ok := i.peermacs[addr]; !ok { - i.peermacs[addr] = neighbor{ - lastsolicitation: time.Now(), - } - } else { - n.lastsolicitation = time.Now() - } - i.peermacsmutex.Unlock() - request, err := i.createNDPL2(addr) - if err != nil { - panic(err) - } - if _, err := i.tun.iface.Write(request); err != nil { - panic(err) - } - i.tun.log.Debugln("Sent neighbor solicitation for", net.IP(addr[:]).String()) - time.Sleep(time.Second) - } -} - -func (i *ICMPv6) getNeighbor(addr address.Address) (neighbor, bool) { - i.peermacsmutex.RLock() - defer i.peermacsmutex.RUnlock() - - n, ok := i.peermacs[addr] - return n, ok -} - -func (i *ICMPv6) createNDPL2(dst address.Address) ([]byte, error) { - // Create the ND payload - var payload [28]byte - copy(payload[:4], []byte{0x00, 0x00, 0x00, 0x00}) // Flags - copy(payload[4:20], dst[:]) // Destination - copy(payload[20:22], []byte{0x01, 0x01}) // Type & length - copy(payload[22:28], i.mymac[:6]) // Link layer address - - // Create the ICMPv6 solicited-node address - var dstaddr address.Address - copy(dstaddr[:13], []byte{ - 0xFF, 0x02, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0xFF}) - copy(dstaddr[13:], dst[13:16]) - - // Create the multicast MAC - dstmac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00} - copy(dstmac[:2], []byte{0x33, 0x33}) - copy(dstmac[2:6], dstaddr[12:16]) - - // Create the ND request - requestPacket, err := i.CreateICMPv6L2( - dstmac, dstaddr[:], i.mylladdr, - ipv6.ICMPTypeNeighborSolicitation, 0, - &icmp.DefaultMessageBody{Data: payload[:]}) - if err != nil { - return nil, err - } - - return requestPacket, nil -} - -// Generates a response to an NDP discovery packet. This is effectively called -// when the host operating system generates an NDP request for any address in -// the fd00::/8 range, so that the operating system knows to route that traffic -// to the Yggdrasil TAP adapter. -func (i *ICMPv6) HandleNDP(in []byte) ([]byte, error) { - // Ignore NDP requests for anything outside of fd00::/8 - var source address.Address - copy(source[:], in[8:]) - var snet address.Subnet - copy(snet[:], in[8:]) - switch { - case source.IsValid(): - case snet.IsValid(): - default: - return nil, errors.New("Not an NDP for 0200::/7") - } - - // Create our NDP message body response - body := make([]byte, 28) - binary.BigEndian.PutUint32(body[:4], uint32(0x40000000)) // Flags - copy(body[4:20], in[8:24]) // Target address - body[20] = uint8(2) // Type: Target link-layer address - body[21] = uint8(1) // Length: 1x address (8 bytes) - copy(body[22:28], i.mymac[:6]) - - // Send it back - return body, nil -} diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 3d788b1a..9cb5e370 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -1,11 +1,6 @@ package tuntap import ( - "bytes" - "net" - "time" - - "github.com/songgao/packets/ethernet" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/util" @@ -14,6 +9,8 @@ import ( "github.com/Arceliar/phony" ) +const TUN_OFFSET_BYTES = 4 + type tunWriter struct { phony.Inbox tun *TunAdapter @@ -25,7 +22,8 @@ func (w *tunWriter) writeFrom(from phony.Actor, b []byte) { }) } -// write is pretty loose with the memory safety rules, e.g. it assumes it can read w.tun.iface.IsTap() safely +// write is pretty loose with the memory safety rules, e.g. it assumes it can +// read w.tun.iface.IsTap() safely func (w *tunWriter) _write(b []byte) { var written int var err error @@ -33,72 +31,17 @@ func (w *tunWriter) _write(b []byte) { if n == 0 { return } - if w.tun.iface.IsTAP() { - sendndp := func(dstAddr address.Address) { - neigh, known := w.tun.icmpv6.getNeighbor(dstAddr) - known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30) - if !known { - w.tun.icmpv6.Solicit(dstAddr) - } - } - peermac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00} - var dstAddr address.Address - var peerknown bool - if b[0]&0xf0 == 0x40 { - dstAddr = w.tun.addr - } else if b[0]&0xf0 == 0x60 { - if !bytes.Equal(w.tun.addr[:16], dstAddr[:16]) && !bytes.Equal(w.tun.subnet[:8], dstAddr[:8]) { - dstAddr = w.tun.addr - } - } - if neighbor, ok := w.tun.icmpv6.getNeighbor(dstAddr); ok && neighbor.learned { - // If we've learned the MAC of a 300::/7 address, for example, or a CKR - // address, use the MAC address of that - peermac = neighbor.mac - peerknown = true - } else if neighbor, ok := w.tun.icmpv6.getNeighbor(w.tun.addr); ok && neighbor.learned { - // Otherwise send directly to the MAC address of the host if that's - // known instead - peermac = neighbor.mac - peerknown = true - } else { - // Nothing has been discovered, try to discover the destination - sendndp(w.tun.addr) - } - if peerknown { - var proto ethernet.Ethertype - switch { - case b[0]&0xf0 == 0x60: - proto = ethernet.IPv6 - case b[0]&0xf0 == 0x40: - proto = ethernet.IPv4 - } - var frame ethernet.Frame - frame.Prepare( - peermac[:6], // Destination MAC address - w.tun.icmpv6.mymac[:6], // Source MAC address - ethernet.NotTagged, // VLAN tagging - proto, // Ethertype - len(b)) // Payload length - copy(frame[tun_ETHER_HEADER_LENGTH:], b[:n]) - n += tun_ETHER_HEADER_LENGTH - written, err = w.tun.iface.Write(frame[:n]) - } else { - w.tun.log.Errorln("TUN/TAP iface write error: no peer MAC known for", net.IP(dstAddr[:]).String(), "- dropping packet") - } - } else { - written, err = w.tun.iface.Write(b[:n]) - util.PutBytes(b) - } + written, err = w.tun.iface.Write(append([]byte{0, 0, 0, 0}, b[:n]...), TUN_OFFSET_BYTES) + util.PutBytes(b) if err != nil { w.tun.Act(w, func() { if !w.tun.isOpen { - w.tun.log.Errorln("TUN/TAP iface write error:", err) + w.tun.log.Errorln("TUN iface write error:", err) } }) } - if written != n { - w.tun.log.Errorln("TUN/TAP iface write mismatch:", written, "bytes written vs", n, "bytes given") + if written-TUN_OFFSET_BYTES != n { + w.tun.log.Errorln("TUN iface write mismatch:", written-TUN_OFFSET_BYTES, "bytes written vs", n, "bytes given") } } @@ -109,13 +52,18 @@ type tunReader struct { func (r *tunReader) _read() { // Get a slice to store the packet in - recvd := util.ResizeBytes(util.GetBytes(), 65535+tun_ETHER_HEADER_LENGTH) - // Wait for a packet to be delivered to us through the TUN/TAP adapter - n, err := r.tun.iface.Read(recvd) - if n <= 0 { + recvd := util.ResizeBytes(util.GetBytes(), r.tun.mtu+TUN_OFFSET_BYTES) + // Wait for a packet to be delivered to us through the TUN adapter + n, err := r.tun.iface.Read(recvd, TUN_OFFSET_BYTES) + if n <= TUN_OFFSET_BYTES || err != nil { + r.tun.log.Errorln("Error reading TUN:", err) + err = r.tun.iface.Flush() + if err != nil { + r.tun.log.Errorln("Unable to flush packets:", err) + } util.PutBytes(recvd) } else { - r.tun.handlePacketFrom(r, recvd[:n], err) + r.tun.handlePacketFrom(r, recvd[TUN_OFFSET_BYTES:n+TUN_OFFSET_BYTES], err) } if err == nil { // Now read again @@ -132,43 +80,17 @@ func (tun *TunAdapter) handlePacketFrom(from phony.Actor, packet []byte, err err // does the work of reading a packet and sending it to the correct tunConn func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { if err != nil { - tun.log.Errorln("TUN/TAP iface read error:", err) + tun.log.Errorln("TUN iface read error:", err) return } - // If it's a TAP adapter, update the buffer slice so that we no longer - // include the ethernet headers - offset := 0 - if tun.iface.IsTAP() { - // Set our offset to beyond the ethernet headers - offset = tun_ETHER_HEADER_LENGTH - // Check first of all that we can go beyond the ethernet headers - if len(recvd) <= offset { - return - } - } // Offset the buffer from now on so that we can ignore ethernet frames if // they are present - bs := recvd[offset:] + bs := recvd[:] // Check if the packet is long enough to detect if it's an ICMP packet or not if len(bs) < 7 { - tun.log.Traceln("TUN/TAP iface read undersized unknown packet, length:", len(bs)) + tun.log.Traceln("TUN iface read undersized unknown packet, length:", len(bs)) return } - // If we detect an ICMP packet then hand it to the ICMPv6 module - if bs[6] == 58 { - // Found an ICMPv6 packet - we need to make sure to give ICMPv6 the full - // Ethernet frame rather than just the IPv6 packet as this is needed for - // NDP to work correctly - if err := tun.icmpv6.ParsePacket(recvd); err == nil { - // We acted on the packet in the ICMPv6 module so don't forward or do - // anything else with it - return - } - } - if offset != 0 { - // Shift forward to avoid leaking bytes off the front of the slice when we eventually store it - bs = append(recvd[:0], bs...) - } // From the IP header, work out what our source and destination addresses // and node IDs are. We will need these in order to work out where to send // the packet @@ -181,7 +103,7 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { if bs[0]&0xf0 == 0x60 { // Check if we have a fully-sized IPv6 header if len(bs) < 40 { - tun.log.Traceln("TUN/TAP iface read undersized ipv6 packet, length:", len(bs)) + tun.log.Traceln("TUN iface read undersized ipv6 packet, length:", len(bs)) return } // Check the packet size @@ -195,7 +117,7 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { } else if bs[0]&0xf0 == 0x40 { // Check if we have a fully-sized IPv4 header if len(bs) < 20 { - tun.log.Traceln("TUN/TAP iface read undersized ipv4 packet, length:", len(bs)) + tun.log.Traceln("TUN iface read undersized ipv4 packet, length:", len(bs)) return } // Check the packet size @@ -267,7 +189,7 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { if tc, err = tun._wrap(conn.(*yggdrasil.Conn)); err != nil { // Something went wrong when storing the connection, typically that // something already exists for this address or subnet - tun.log.Debugln("TUN/TAP iface wrap:", err) + tun.log.Debugln("TUN iface wrap:", err) return } for _, packet := range packets { diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 1e994ea5..807ce7fb 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -18,7 +18,7 @@ import ( "github.com/Arceliar/phony" "github.com/gologme/log" - "github.com/yggdrasil-network/water" + "golang.zx2c4.com/wireguard/tun" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/config" @@ -28,12 +28,11 @@ import ( ) const tun_IPv6_HEADER_LENGTH = 40 -const tun_ETHER_HEADER_LENGTH = 14 -// TunAdapter represents a running TUN/TAP interface and extends the -// yggdrasil.Adapter type. In order to use the TUN/TAP adapter with Yggdrasil, -// you should pass this object to the yggdrasil.SetRouterAdapter() function -// before calling yggdrasil.Start(). +// TunAdapter represents a running TUN interface and extends the +// yggdrasil.Adapter type. In order to use the TUN adapter with Yggdrasil, you +// should pass this object to the yggdrasil.SetRouterAdapter() function before +// calling yggdrasil.Start(). type TunAdapter struct { core *yggdrasil.Core writer tunWriter @@ -48,7 +47,7 @@ type TunAdapter struct { ckr cryptokey icmpv6 ICMPv6 mtu int - iface *water.Interface + iface tun.Device phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below //mutex sync.RWMutex // Protects the below addrToConn map[address.Address]*tunConn @@ -64,12 +63,12 @@ type TunOptions struct { // Gets the maximum supported MTU for the platform based on the defaults in // defaults.GetDefaults(). -func getSupportedMTU(mtu int, istapmode bool) int { +func getSupportedMTU(mtu int) int { if mtu < 1280 { return 1280 } - if mtu > MaximumMTU(istapmode) { - return MaximumMTU(istapmode) + if mtu > MaximumMTU() { + return MaximumMTU() } return mtu } @@ -77,55 +76,38 @@ func getSupportedMTU(mtu int, istapmode bool) int { // Name returns the name of the adapter, e.g. "tun0". On Windows, this may // return a canonical adapter name instead. func (tun *TunAdapter) Name() string { - return tun.iface.Name() + if name, err := tun.iface.Name(); err == nil { + return name + } + return "" } // MTU gets the adapter's MTU. This can range between 1280 and 65535, although // the maximum value is determined by your platform. The returned value will // never exceed that of MaximumMTU(). func (tun *TunAdapter) MTU() int { - return getSupportedMTU(tun.mtu, tun.IsTAP()) + return getSupportedMTU(tun.mtu) } -// IsTAP returns true if the adapter is a TAP adapter (Layer 2) or false if it -// is a TUN adapter (Layer 3). -func (tun *TunAdapter) IsTAP() bool { - return tun.iface.IsTAP() -} - -// DefaultName gets the default TUN/TAP interface name for your platform. +// DefaultName gets the default TUN interface name for your platform. func DefaultName() string { return defaults.GetDefaults().DefaultIfName } -// DefaultMTU gets the default TUN/TAP interface MTU for your platform. This can +// DefaultMTU gets the default TUN interface MTU for your platform. This can // be as high as MaximumMTU(), depending on platform, but is never lower than 1280. func DefaultMTU() int { - ehbytes := 0 - if DefaultIsTAP() { - ehbytes = tun_ETHER_HEADER_LENGTH - } - return defaults.GetDefaults().DefaultIfMTU - ehbytes + return defaults.GetDefaults().DefaultIfMTU } -// DefaultIsTAP returns true if the default adapter mode for the current -// platform is TAP (Layer 2) and returns false for TUN (Layer 3). -func DefaultIsTAP() bool { - return defaults.GetDefaults().DefaultIfTAPMode -} - -// MaximumMTU returns the maximum supported TUN/TAP interface MTU for your +// MaximumMTU returns the maximum supported TUN interface MTU for your // platform. This can be as high as 65535, depending on platform, but is never // lower than 1280. -func MaximumMTU(iftapmode bool) int { - ehbytes := 0 - if iftapmode { - ehbytes = tun_ETHER_HEADER_LENGTH - } - return defaults.GetDefaults().MaximumIfMTU - ehbytes +func MaximumMTU() int { + return defaults.GetDefaults().MaximumIfMTU } -// Init initialises the TUN/TAP module. You must have acquired a Listener from +// Init initialises the TUN module. You must have acquired a Listener from // the Yggdrasil core before this point and it must not be in use elsewhere. func (tun *TunAdapter) Init(core *yggdrasil.Core, config *config.NodeState, log *log.Logger, options interface{}) error { tunoptions, ok := options.(TunOptions) @@ -145,7 +127,7 @@ func (tun *TunAdapter) Init(core *yggdrasil.Core, config *config.NodeState, log return nil } -// Start the setup process for the TUN/TAP adapter. If successful, starts the +// Start the setup process for the TUN adapter. If successful, starts the // reader actor to handle packets on that interface. func (tun *TunAdapter) Start() error { var err error @@ -157,11 +139,11 @@ func (tun *TunAdapter) Start() error { func (tun *TunAdapter) _start() error { if tun.isOpen { - return errors.New("TUN/TAP module is already started") + return errors.New("TUN module is already started") } current := tun.config.GetCurrent() 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") } var boxPub crypto.BoxPubKey boxPubHex, err := hex.DecodeString(current.EncryptionPublicKey) @@ -174,23 +156,19 @@ func (tun *TunAdapter) _start() error { tun.subnet = *address.SubnetForNodeID(nodeID) addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1) if current.IfName == "none" || current.IfName == "dummy" { - tun.log.Debugln("Not starting TUN/TAP as ifname is none or dummy") + tun.log.Debugln("Not starting TUN as ifname is none or dummy") return nil } - if err := tun.setup(current.IfName, current.IfTAPMode, addr, current.IfMTU); err != nil { + if err := tun.setup(current.IfName, addr, current.IfMTU); err != nil { return err } if tun.MTU() != current.IfMTU { - tun.log.Warnf("Warning: Interface MTU %d automatically adjusted to %d (supported range is 1280-%d)", current.IfMTU, tun.MTU(), MaximumMTU(tun.IsTAP())) + tun.log.Warnf("Warning: Interface MTU %d automatically adjusted to %d (supported range is 1280-%d)", current.IfMTU, tun.MTU(), MaximumMTU()) } tun.core.SetMaximumSessionMTU(uint16(tun.MTU())) tun.isOpen = true go tun.handler() tun.reader.Act(nil, tun.reader._read) // Start the reader - tun.icmpv6.Init(tun) - if tun.IsTAP() { - go tun.icmpv6.Solicit(tun.addr) - } tun.ckr.init(tun) return nil } @@ -204,7 +182,7 @@ func (tun *TunAdapter) IsStarted() bool { return isOpen } -// Start the setup process for the TUN/TAP adapter. If successful, starts the +// Start the setup process for the TUN adapter. If successful, starts the // read/write goroutines to handle packets on that interface. func (tun *TunAdapter) Stop() error { var err error @@ -216,7 +194,7 @@ func (tun *TunAdapter) Stop() error { func (tun *TunAdapter) _stop() error { tun.isOpen = false - // by TUN/TAP, e.g. readers/writers, sessions + // by TUN, e.g. readers/writers, sessions if tun.iface != nil { // Just in case we failed to start up the iface for some reason, this can apparently happen on Windows tun.iface.Close() @@ -224,16 +202,16 @@ func (tun *TunAdapter) _stop() error { return nil } -// UpdateConfig updates the TUN/TAP module with the provided config.NodeConfig +// UpdateConfig updates the TUN module with the provided config.NodeConfig // and then signals the various module goroutines to reconfigure themselves if // needed. func (tun *TunAdapter) UpdateConfig(config *config.NodeConfig) { - tun.log.Debugln("Reloading TUN/TAP configuration...") + tun.log.Debugln("Reloading TUN configuration...") // Replace the active configuration with the supplied one tun.config.Replace(*config) - // If the MTU has changed in the TUN/TAP module then this is where we would + // If the MTU has changed in the TUN module then this is where we would // tell the router so that updated session pings can be sent. However, we // don't currently update the MTU of the adapter once it has been created so // this doesn't actually happen in the real world yet. @@ -248,14 +226,14 @@ func (tun *TunAdapter) handler() error { // Accept the incoming connection conn, err := tun.listener.Accept() if err != nil { - tun.log.Errorln("TUN/TAP connection accept error:", err) + tun.log.Errorln("TUN connection accept error:", err) return err } phony.Block(tun, func() { if _, err := tun._wrap(conn.(*yggdrasil.Conn)); err != nil { // Something went wrong when storing the connection, typically that // something already exists for this address or subnet - tun.log.Debugln("TUN/TAP handler wrap:", err) + tun.log.Debugln("TUN handler wrap:", err) } }) } diff --git a/src/tuntap/tun_bsd.go b/src/tuntap/tun_bsd.go index 78a4adab..7ad89edc 100644 --- a/src/tuntap/tun_bsd.go +++ b/src/tuntap/tun_bsd.go @@ -1,4 +1,4 @@ -// +build openbsd freebsd netbsd +// +build openbsd freebsd package tuntap @@ -12,7 +12,7 @@ import ( "golang.org/x/sys/unix" - "github.com/yggdrasil-network/water" + wgtun "golang.zx2c4.com/wireguard/tun" ) const SIOCSIFADDR_IN6 = (0x80000000) | ((288 & 0x1fff) << 16) | uint32(byte('i'))<<8 | 12 @@ -72,34 +72,18 @@ type in6_ifreq_lifetime struct { ifru_addrlifetime in6_addrlifetime } -// Sets the IPv6 address of the utun adapter. On all BSD platforms (FreeBSD, -// OpenBSD, NetBSD) an attempt is made to set the adapter properties by using -// a system socket and making syscalls to the kernel. This is not refined though -// and often doesn't work (if at all), therefore if a call fails, it resorts -// to calling "ifconfig" instead. -func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { - var config water.Config - if ifname[:4] == "auto" { - ifname = "/dev/tap0" - } - if len(ifname) < 9 { - panic("TUN/TAP name must be in format /dev/tunX or /dev/tapX") - } - switch { - case iftapmode || ifname[:8] == "/dev/tap": - config = water.Config{DeviceType: water.TAP} - case !iftapmode || ifname[:8] == "/dev/tun": - panic("TUN mode is not currently supported on this platform, please use TAP instead") - default: - panic("TUN/TAP name must be in format /dev/tunX or /dev/tapX") - } - config.Name = ifname - iface, err := water.New(config) +// Configures the TUN adapter with the correct IPv6 address and MTU. +func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { + iface, err := wgtun.CreateTUN(ifname, mtu) if err != nil { panic(err) } tun.iface = iface - tun.mtu = getSupportedMTU(mtu, iftapmode) + if mtu, err := iface.MTU(); err == nil { + tun.mtu = getSupportedMTU(mtu) + } else { + tun.mtu = 0 + } return tun.setupAddress(addr) } diff --git a/src/tuntap/tun_darwin.go b/src/tuntap/tun_darwin.go index ab6f34e3..c9b8bceb 100644 --- a/src/tuntap/tun_darwin.go +++ b/src/tuntap/tun_darwin.go @@ -12,22 +12,21 @@ import ( "golang.org/x/sys/unix" - water "github.com/yggdrasil-network/water" + wgtun "golang.zx2c4.com/wireguard/tun" ) // Configures the "utun" adapter with the correct IPv6 address and MTU. -func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { - if iftapmode { - tun.log.Warnln("Warning: TAP mode is not supported on this platform, defaulting to TUN") - iftapmode = false - } - config := water.Config{DeviceType: water.TUN} - iface, err := water.New(config) +func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { + iface, err := wgtun.CreateTUN(ifname, mtu) if err != nil { panic(err) } tun.iface = iface - tun.mtu = getSupportedMTU(mtu, iftapmode) + if mtu, err := iface.MTU(); err == nil { + tun.mtu = getSupportedMTU(mtu) + } else { + tun.mtu = 0 + } return tun.setupAddress(addr) } @@ -80,7 +79,7 @@ func (tun *TunAdapter) setupAddress(addr string) error { } var ar in6_aliasreq - copy(ar.ifra_name[:], tun.iface.Name()) + copy(ar.ifra_name[:], tun.Name()) ar.ifra_prefixmask.sin6_len = uint8(unsafe.Sizeof(ar.ifra_prefixmask)) b := make([]byte, 16) @@ -104,7 +103,7 @@ func (tun *TunAdapter) setupAddress(addr string) error { ar.ifra_lifetime.ia6t_pltime = darwin_ND6_INFINITE_LIFETIME var ir ifreq - copy(ir.ifr_name[:], tun.iface.Name()) + copy(ir.ifr_name[:], tun.Name()) ir.ifru_mtu = uint32(tun.mtu) tun.log.Infof("Interface name: %s", ar.ifra_name) diff --git a/src/tuntap/tun_linux.go b/src/tuntap/tun_linux.go index b5918328..1129cfad 100644 --- a/src/tuntap/tun_linux.go +++ b/src/tuntap/tun_linux.go @@ -6,31 +6,21 @@ package tuntap import ( "github.com/vishvananda/netlink" - - water "github.com/yggdrasil-network/water" + wgtun "golang.zx2c4.com/wireguard/tun" ) -// Configures the TAP adapter with the correct IPv6 address and MTU. -func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { - var config water.Config - if iftapmode { - config = water.Config{DeviceType: water.TAP} - } else { - config = water.Config{DeviceType: water.TUN} - } - if ifname != "" && ifname != "auto" { - config.Name = ifname - } - iface, err := water.New(config) +// Configures the TUN adapter with the correct IPv6 address and MTU. +func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { + iface, err := wgtun.CreateTUN(ifname, mtu) if err != nil { panic(err) } tun.iface = iface - tun.mtu = getSupportedMTU(mtu, iftapmode) - // Friendly output - tun.log.Infof("Interface name: %s", tun.iface.Name()) - tun.log.Infof("Interface IPv6: %s", addr) - tun.log.Infof("Interface MTU: %d", tun.mtu) + if mtu, err := iface.MTU(); err == nil { + tun.mtu = getSupportedMTU(mtu) + } else { + tun.mtu = 0 + } return tun.setupAddress(addr) } @@ -56,5 +46,9 @@ func (tun *TunAdapter) setupAddress(addr string) error { if err := netlink.LinkSetUp(nlintf); err != nil { return err } + // Friendly output + tun.log.Infof("Interface name: %s", tun.iface.Name()) + tun.log.Infof("Interface IPv6: %s", addr) + tun.log.Infof("Interface MTU: %d", tun.mtu) return nil } diff --git a/src/tuntap/tun_other.go b/src/tuntap/tun_other.go index 7d4f0643..c5ff58fb 100644 --- a/src/tuntap/tun_other.go +++ b/src/tuntap/tun_other.go @@ -1,27 +1,26 @@ -// +build !linux,!darwin,!windows,!openbsd,!freebsd,!netbsd,!mobile +// +build !linux,!darwin,!windows,!openbsd,!freebsd,!mobile package tuntap -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 -// Creates the TUN/TAP adapter, if supported by the Water library. Note that -// no guarantees are made at this point on an unsupported platform. -func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { - var config water.Config - if iftapmode { - config = water.Config{DeviceType: water.TAP} - } else { - config = water.Config{DeviceType: water.TUN} - } - iface, err := water.New(config) +import ( + wgtun "golang.zx2c4.com/wireguard/tun" +) + +// Configures the TUN adapter with the correct IPv6 address and MTU. +func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { + iface, err := wgtun.CreateTUN(ifname, mtu) if err != nil { panic(err) } tun.iface = iface - tun.mtu = getSupportedMTU(mtu, iftapmode) + if mtu, err := iface.MTU(); err == nil { + tun.mtu = getSupportedMTU(mtu) + } else { + tun.mtu = 0 + } return tun.setupAddress(addr) } diff --git a/src/tuntap/tun_windows.go b/src/tuntap/tun_windows.go index d4fd1c3d..65417546 100644 --- a/src/tuntap/tun_windows.go +++ b/src/tuntap/tun_windows.go @@ -7,69 +7,23 @@ import ( "strings" "time" - water "github.com/yggdrasil-network/water" + wgtun "golang.zx2c4.com/wireguard/tun" ) // This is to catch Windows platforms -// Configures the TAP adapter with the correct IPv6 address and MTU. On Windows -// we don't make use of a direct operating system API to do this - we instead -// delegate the hard work to "netsh". -func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { - if !iftapmode { - tun.log.Warnln("Warning: TUN mode is not supported on this platform, defaulting to TAP") - iftapmode = true - } - config := water.Config{DeviceType: water.TAP} - config.PlatformSpecificParams.ComponentID = "tap0901" - config.PlatformSpecificParams.Network = "169.254.0.1/32" - if ifname == "auto" { - config.PlatformSpecificParams.InterfaceName = "" - } else { - config.PlatformSpecificParams.InterfaceName = ifname - } - iface, err := water.New(config) - if err != nil { - return err - } - if iface.Name() == "" { - return errors.New("unable to find TAP adapter with component ID " + config.PlatformSpecificParams.ComponentID) - } - // Reset the adapter - this invalidates iface so we'll need to get a new one - cmd := exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=DISABLED") - tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " ")) - output, err := cmd.CombinedOutput() - if err != nil { - tun.log.Errorln("Windows netsh failed:", err) - tun.log.Traceln(string(output)) - return err - } - time.Sleep(time.Second) // FIXME artifical delay to give netsh time to take effect - // Bring the interface back up - cmd = exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=ENABLED") - tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " ")) - output, err = cmd.CombinedOutput() - if err != nil { - tun.log.Errorln("Windows netsh failed:", err) - tun.log.Traceln(string(output)) - return err - } - time.Sleep(time.Second) // FIXME artifical delay to give netsh time to take effect - // Get a new iface - iface, err = water.New(config) +// Configures the TUN adapter with the correct IPv6 address and MTU. +func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { + iface, err := wgtun.CreateTUN(ifname, mtu) if err != nil { panic(err) } tun.iface = iface - tun.mtu = getSupportedMTU(mtu, iftapmode) - err = tun.setupMTU(tun.mtu) - if err != nil { - panic(err) + if mtu, err := iface.MTU(); err == nil { + tun.mtu = getSupportedMTU(mtu) + } else { + tun.mtu = 0 } - // Friendly output - tun.log.Infof("Interface name: %s", tun.iface.Name()) - tun.log.Infof("Interface IPv6: %s", addr) - tun.log.Infof("Interface MTU: %d", tun.mtu) return tun.setupAddress(addr) } From 235b64345e16d87ee173352aa44705a697911b89 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 22 Nov 2019 18:34:43 +0000 Subject: [PATCH 0438/1109] Configure addresses and MTUs, fix bugs --- src/tuntap/iface.go | 4 +- src/tuntap/tun_windows.go | 237 +++++++++++++++++++++++++++++++------- 2 files changed, 197 insertions(+), 44 deletions(-) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 9cb5e370..fbb7c86d 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -40,8 +40,8 @@ func (w *tunWriter) _write(b []byte) { } }) } - if written-TUN_OFFSET_BYTES != n { - w.tun.log.Errorln("TUN iface write mismatch:", written-TUN_OFFSET_BYTES, "bytes written vs", n, "bytes given") + if written != n { + w.tun.log.Errorln("TUN iface write mismatch:", written, "bytes written vs", n, "bytes given") } } diff --git a/src/tuntap/tun_windows.go b/src/tuntap/tun_windows.go index 65417546..8b4f92cf 100644 --- a/src/tuntap/tun_windows.go +++ b/src/tuntap/tun_windows.go @@ -1,70 +1,223 @@ package tuntap import ( + "bytes" "errors" - "fmt" - "os/exec" + "log" + "net" + "runtime" "strings" - "time" + "unsafe" + + "golang.org/x/sys/windows" wgtun "golang.zx2c4.com/wireguard/tun" + "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" ) // This is to catch Windows platforms // Configures the TUN adapter with the correct IPv6 address and MTU. func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { - iface, err := wgtun.CreateTUN(ifname, mtu) - if err != nil { - panic(err) - } - tun.iface = iface - if mtu, err := iface.MTU(); err == nil { - tun.mtu = getSupportedMTU(mtu) - } else { - tun.mtu = 0 - } - return tun.setupAddress(addr) + var err error + err = doAsSystem(func() { + iface, err := wgtun.CreateTUN(ifname, mtu) + if err != nil { + panic(err) + } + tun.iface = iface + + if err := tun.setupAddress(addr); err != nil { + tun.log.Errorln("Failed to set up TUN address:", err) + } + if err := tun.setupMTU(getSupportedMTU(mtu)); err != nil { + tun.log.Errorln("Failed to set up TUN MTU:", err) + } + + if mtu, err = iface.MTU(); err == nil { + tun.mtu = mtu + } + }) + return err } // Sets the MTU of the TAP adapter. func (tun *TunAdapter) setupMTU(mtu int) error { - if tun.iface == nil || tun.iface.Name() == "" { - return errors.New("Can't configure MTU as TAP adapter is not present") + if tun.iface == nil || tun.Name() == "" { + return errors.New("Can't configure MTU as TUN adapter is not present") } - // Set MTU - cmd := exec.Command("netsh", "interface", "ipv6", "set", "subinterface", - fmt.Sprintf("interface=%s", tun.iface.Name()), - fmt.Sprintf("mtu=%d", mtu), - "store=active") - tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " ")) - output, err := cmd.CombinedOutput() - if err != nil { - tun.log.Errorln("Windows netsh failed:", err) - tun.log.Traceln(string(output)) - return err + if intf, ok := tun.iface.(*wgtun.NativeTun); ok { + luid := winipcfg.LUID(intf.LUID()) + ipfamily, err := luid.IPInterface(windows.AF_INET6) + if err != nil { + return err + } + + ipfamily.NLMTU = uint32(mtu) + intf.ForceMTU(int(ipfamily.NLMTU)) + ipfamily.UseAutomaticMetric = false + ipfamily.Metric = 0 + ipfamily.DadTransmits = 0 + ipfamily.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled + + if err := ipfamily.Set(); err != nil { + return err + } } - time.Sleep(time.Second) // FIXME artifical delay to give netsh time to take effect + return nil } // Sets the IPv6 address of the TAP adapter. func (tun *TunAdapter) setupAddress(addr string) error { - if tun.iface == nil || tun.iface.Name() == "" { - return errors.New("Can't configure IPv6 address as TAP adapter is not present") + if tun.iface == nil || tun.Name() == "" { + return errors.New("Can't configure IPv6 address as TUN adapter is not present") } - // Set address - cmd := exec.Command("netsh", "interface", "ipv6", "add", "address", - fmt.Sprintf("interface=%s", tun.iface.Name()), - fmt.Sprintf("addr=%s", addr), - "store=active") - tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " ")) - output, err := cmd.CombinedOutput() - if err != nil { - tun.log.Errorln("Windows netsh failed:", err) - tun.log.Traceln(string(output)) - return err + if intf, ok := tun.iface.(*wgtun.NativeTun); ok { + if ipaddr, ipnet, err := net.ParseCIDR(addr); err == nil { + luid := winipcfg.LUID(intf.LUID()) + addresses := append([]net.IPNet{}, net.IPNet{ + IP: ipaddr, + Mask: ipnet.Mask, + }) + + err := luid.SetIPAddressesForFamily(windows.AF_INET6, addresses) + if err == windows.ERROR_OBJECT_ALREADY_EXISTS { + cleanupAddressesOnDisconnectedInterfaces(windows.AF_INET6, addresses) + err = luid.SetIPAddressesForFamily(windows.AF_INET6, addresses) + } + if err != nil { + return err + } + } else { + return err + } + } else { + return errors.New("unable to get NativeTUN") } - time.Sleep(time.Second) // FIXME artifical delay to give netsh time to take effect return nil } + +/* + * doAsSystem + * SPDX-License-Identifier: LGPL-3.0 + * Copyright (C) 2017-2019 Jason A. Donenfeld . All Rights Reserved. + */ +func doAsSystem(f func()) error { + runtime.LockOSThread() + defer func() { + windows.RevertToSelf() + runtime.UnlockOSThread() + }() + privileges := windows.Tokenprivileges{ + PrivilegeCount: 1, + Privileges: [1]windows.LUIDAndAttributes{ + { + Attributes: windows.SE_PRIVILEGE_ENABLED, + }, + }, + } + err := windows.LookupPrivilegeValue(nil, windows.StringToUTF16Ptr("SeDebugPrivilege"), &privileges.Privileges[0].Luid) + if err != nil { + return err + } + err = windows.ImpersonateSelf(windows.SecurityImpersonation) + if err != nil { + return err + } + var threadToken windows.Token + err = windows.OpenThreadToken(windows.CurrentThread(), windows.TOKEN_ADJUST_PRIVILEGES, false, &threadToken) + if err != nil { + return err + } + defer threadToken.Close() + err = windows.AdjustTokenPrivileges(threadToken, false, &privileges, uint32(unsafe.Sizeof(privileges)), nil, nil) + if err != nil { + return err + } + + processes, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0) + if err != nil { + return err + } + defer windows.CloseHandle(processes) + + processEntry := windows.ProcessEntry32{Size: uint32(unsafe.Sizeof(windows.ProcessEntry32{}))} + pid := uint32(0) + for err = windows.Process32First(processes, &processEntry); err == nil; err = windows.Process32Next(processes, &processEntry) { + if strings.ToLower(windows.UTF16ToString(processEntry.ExeFile[:])) == "winlogon.exe" { + pid = processEntry.ProcessID + break + } + } + if pid == 0 { + return errors.New("unable to find winlogon.exe process") + } + + winlogonProcess, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION, false, pid) + if err != nil { + return err + } + defer windows.CloseHandle(winlogonProcess) + var winlogonToken windows.Token + err = windows.OpenProcessToken(winlogonProcess, windows.TOKEN_IMPERSONATE|windows.TOKEN_DUPLICATE, &winlogonToken) + if err != nil { + return err + } + defer winlogonToken.Close() + var duplicatedToken windows.Token + err = windows.DuplicateTokenEx(winlogonToken, 0, nil, windows.SecurityImpersonation, windows.TokenImpersonation, &duplicatedToken) + if err != nil { + return err + } + defer duplicatedToken.Close() + err = windows.SetThreadToken(nil, duplicatedToken) + if err != nil { + return err + } + f() + return nil +} + +/* + * cleanupAddressesOnDisconnectedInterfaces + * SPDX-License-Identifier: MIT + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ +func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []net.IPNet) { + if len(addresses) == 0 { + return + } + includedInAddresses := func(a net.IPNet) bool { + // TODO: this makes the whole algorithm O(n^2). But we can't stick net.IPNet in a Go hashmap. Bummer! + for _, addr := range addresses { + ip := addr.IP + if ip4 := ip.To4(); ip4 != nil { + ip = ip4 + } + mA, _ := addr.Mask.Size() + mB, _ := a.Mask.Size() + if bytes.Equal(ip, a.IP) && mA == mB { + return true + } + } + return false + } + interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagDefault) + if err != nil { + return + } + for _, iface := range interfaces { + if iface.OperStatus == winipcfg.IfOperStatusUp { + continue + } + for address := iface.FirstUnicastAddress; address != nil; address = address.Next { + ip := address.Address.IP() + ipnet := net.IPNet{IP: ip, Mask: net.CIDRMask(int(address.OnLinkPrefixLength), 8*len(ip))} + if includedInAddresses(ipnet) { + log.Printf("Cleaning up stale address %s from interface ‘%s’", ipnet.String(), iface.FriendlyName()) + iface.LUID.DeleteIPAddress(ipnet) + } + } + } +} From b27ada91910f60baa39187b00b87f9891c267eb9 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 22 Nov 2019 18:39:27 +0000 Subject: [PATCH 0439/1109] Fix bad Name() calls --- src/tuntap/tun_bsd.go | 10 +++++----- src/tuntap/tun_linux.go | 4 ++-- src/tuntap/tun_other.go | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/tuntap/tun_bsd.go b/src/tuntap/tun_bsd.go index 7ad89edc..219e3485 100644 --- a/src/tuntap/tun_bsd.go +++ b/src/tuntap/tun_bsd.go @@ -98,13 +98,13 @@ func (tun *TunAdapter) setupAddress(addr string) error { } // Friendly output - tun.log.Infof("Interface name: %s", tun.iface.Name()) + tun.log.Infof("Interface name: %s", tun.Name()) tun.log.Infof("Interface IPv6: %s", addr) tun.log.Infof("Interface MTU: %d", tun.mtu) // Create the MTU request var ir in6_ifreq_mtu - copy(ir.ifr_name[:], tun.iface.Name()) + copy(ir.ifr_name[:], tun.Name()) ir.ifru_mtu = int(tun.mtu) // Set the MTU @@ -113,7 +113,7 @@ func (tun *TunAdapter) setupAddress(addr string) error { tun.log.Errorf("Error in SIOCSIFMTU: %v", errno) // Fall back to ifconfig to set the MTU - cmd := exec.Command("ifconfig", tun.iface.Name(), "mtu", string(tun.mtu)) + cmd := exec.Command("ifconfig", tun.Name(), "mtu", string(tun.mtu)) tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { @@ -125,7 +125,7 @@ func (tun *TunAdapter) setupAddress(addr string) error { // Create the address request // FIXME: I don't work! var ar in6_ifreq_addr - copy(ar.ifr_name[:], tun.iface.Name()) + copy(ar.ifr_name[:], tun.Name()) ar.ifru_addr.sin6_len = uint8(unsafe.Sizeof(ar.ifru_addr)) ar.ifru_addr.sin6_family = unix.AF_INET6 parts := strings.Split(strings.Split(addr, "/")[0], ":") @@ -142,7 +142,7 @@ func (tun *TunAdapter) setupAddress(addr string) error { tun.log.Errorf("Error in SIOCSIFADDR_IN6: %v", errno) // Fall back to ifconfig to set the address - cmd := exec.Command("ifconfig", tun.iface.Name(), "inet6", addr) + cmd := exec.Command("ifconfig", tun.Name(), "inet6", addr) tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { diff --git a/src/tuntap/tun_linux.go b/src/tuntap/tun_linux.go index 1129cfad..7935e7cf 100644 --- a/src/tuntap/tun_linux.go +++ b/src/tuntap/tun_linux.go @@ -33,7 +33,7 @@ func (tun *TunAdapter) setupAddress(addr string) error { if err != nil { return err } - nlintf, err := netlink.LinkByName(tun.iface.Name()) + nlintf, err := netlink.LinkByName(tun.Name()) if err != nil { return err } @@ -47,7 +47,7 @@ func (tun *TunAdapter) setupAddress(addr string) error { return err } // Friendly output - tun.log.Infof("Interface name: %s", tun.iface.Name()) + tun.log.Infof("Interface name: %s", tun.Name()) tun.log.Infof("Interface IPv6: %s", addr) tun.log.Infof("Interface MTU: %d", tun.mtu) return nil diff --git a/src/tuntap/tun_other.go b/src/tuntap/tun_other.go index c5ff58fb..8a27f57b 100644 --- a/src/tuntap/tun_other.go +++ b/src/tuntap/tun_other.go @@ -27,6 +27,6 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { // We don't know how to set the IPv6 address on an unknown platform, therefore // write about it to stdout and don't try to do anything further. func (tun *TunAdapter) setupAddress(addr string) error { - tun.log.Warnln("Warning: Platform not supported, you must set the address of", tun.iface.Name(), "to", addr) + tun.log.Warnln("Warning: Platform not supported, you must set the address of", tun.Name(), "to", addr) return nil } From 15726fe90d9f1cf71df30eaba3e7219f2ad4c1a4 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 22 Nov 2019 18:52:12 +0000 Subject: [PATCH 0440/1109] Don't build for NetBSD (not supported by the TUN package right now) --- .circleci/config.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 17cbfade..3e223e55 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -174,13 +174,6 @@ jobs: GOOS=freebsd GOARCH=amd64 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-freebsd-amd64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-freebsd-amd64; GOOS=freebsd GOARCH=386 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-freebsd-i386 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-freebsd-i386; - - run: - name: Build for NetBSD - command: | - rm -f {yggdrasil,yggdrasilctl} - GOOS=netbsd GOARCH=amd64 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-netbsd-amd64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-netbsd-amd64; - GOOS=netbsd GOARCH=386 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-netbsd-i386 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-netbsd-i386; - - run: name: Build for Windows command: | From 7d00206f4bde6de34dfd84ac4d9e092a98ce57de Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 22 Nov 2019 20:07:08 +0000 Subject: [PATCH 0441/1109] Update platform defaults, handling of 'auto' on Linux/Darwin --- src/defaults/defaults_freebsd.go | 2 +- src/defaults/defaults_netbsd.go | 25 ------------------------- src/defaults/defaults_other.go | 2 +- src/defaults/defaults_windows.go | 2 +- src/tuntap/tun_darwin.go | 3 +++ src/tuntap/tun_linux.go | 3 +++ 6 files changed, 9 insertions(+), 28 deletions(-) delete mode 100644 src/defaults/defaults_netbsd.go diff --git a/src/defaults/defaults_freebsd.go b/src/defaults/defaults_freebsd.go index d7dc43b9..b08d80d0 100644 --- a/src/defaults/defaults_freebsd.go +++ b/src/defaults/defaults_freebsd.go @@ -20,6 +20,6 @@ func GetDefaults() platformDefaultParameters { // TUN/TAP MaximumIfMTU: 32767, DefaultIfMTU: 32767, - DefaultIfName: "/dev/tap0", + DefaultIfName: "/dev/tun0", } } diff --git a/src/defaults/defaults_netbsd.go b/src/defaults/defaults_netbsd.go deleted file mode 100644 index bc9d7a39..00000000 --- a/src/defaults/defaults_netbsd.go +++ /dev/null @@ -1,25 +0,0 @@ -// +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: "unix:///var/run/yggdrasil.sock", - - // Configuration (used for yggdrasilctl) - DefaultConfigFile: "/etc/yggdrasil.conf", - - // Multicast interfaces - DefaultMulticastInterfaces: []string{ - ".*", - }, - - // TUN/TAP - MaximumIfMTU: 9000, - DefaultIfMTU: 9000, - DefaultIfName: "/dev/tun0", - } -} diff --git a/src/defaults/defaults_other.go b/src/defaults/defaults_other.go index 763a7f89..3b035537 100644 --- a/src/defaults/defaults_other.go +++ b/src/defaults/defaults_other.go @@ -1,4 +1,4 @@ -// +build !linux,!darwin,!windows,!openbsd,!freebsd,!netbsd +// +build !linux,!darwin,!windows,!openbsd,!freebsd package defaults diff --git a/src/defaults/defaults_windows.go b/src/defaults/defaults_windows.go index ade833d4..305a2ffe 100644 --- a/src/defaults/defaults_windows.go +++ b/src/defaults/defaults_windows.go @@ -20,6 +20,6 @@ func GetDefaults() platformDefaultParameters { // TUN/TAP MaximumIfMTU: 65535, DefaultIfMTU: 65535, - DefaultIfName: "auto", + DefaultIfName: "Yggdrasil", } } diff --git a/src/tuntap/tun_darwin.go b/src/tuntap/tun_darwin.go index c9b8bceb..cf2fbfb4 100644 --- a/src/tuntap/tun_darwin.go +++ b/src/tuntap/tun_darwin.go @@ -17,6 +17,9 @@ import ( // Configures the "utun" adapter with the correct IPv6 address and MTU. func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { + if ifname == "auto" { + ifname = "utun" + } iface, err := wgtun.CreateTUN(ifname, mtu) if err != nil { panic(err) diff --git a/src/tuntap/tun_linux.go b/src/tuntap/tun_linux.go index 7935e7cf..4206b26a 100644 --- a/src/tuntap/tun_linux.go +++ b/src/tuntap/tun_linux.go @@ -11,6 +11,9 @@ import ( // Configures the TUN adapter with the correct IPv6 address and MTU. func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { + if ifname == "auto" { + ifname = "\000" + } iface, err := wgtun.CreateTUN(ifname, mtu) if err != nil { panic(err) From f95ebeb8216748a5914d9f0fca9eb602f7b16cbe Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 22 Nov 2019 20:08:19 +0000 Subject: [PATCH 0442/1109] Remove references to TAP --- src/config/config.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/config/config.go b/src/config/config.go index bacf9003..3e1438b8 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -5,7 +5,7 @@ Yggdrasil node. The configuration contains, amongst other things, encryption keys which are used to derive a node's identity, information about peerings and node information that is shared with the network. There are also some module-specific options -related to TUN/TAP, multicast and the admin socket. +related to TUN, multicast and the admin socket. In order for a node to maintain the same identity across restarts, you should persist the configuration onto the filesystem or into some configuration storage @@ -70,8 +70,8 @@ type NodeConfig struct { SigningPublicKey string `comment:"Your public signing key. You should not ordinarily need to share\nthis with anyone."` SigningPrivateKey string `comment:"Your private signing key. DO NOT share this with anyone!"` LinkLocalTCPPort uint16 `comment:"The port number to be used for the link-local TCP listeners for the\nconfigured MulticastInterfaces. This option does not affect listeners\nspecified in the Listen option. Unless you plan to firewall link-local\ntraffic, it is best to leave this as the default value of 0. This\noption cannot currently be changed by reloading config during runtime."` - IfName string `comment:"Local network interface name for TUN/TAP adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN/TAP."` - IfMTU int `comment:"Maximux Transmission Unit (MTU) size for your local TUN/TAP interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` + IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."` + IfMTU int `comment:"Maximux Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` SessionFirewall SessionFirewall `comment:"The session firewall controls who can send/receive network traffic\nto/from. This is useful if you want to protect this node without\nresorting to using a real firewall. This does not affect traffic\nbeing routed via this node to somewhere else. Rules are prioritised as\nfollows: blacklist, whitelist, always allow outgoing, direct, remote."` TunnelRouting TunnelRouting `comment:"Allow tunneling non-Yggdrasil traffic over Yggdrasil. This effectively\nallows you to use Yggdrasil to route to, or to bridge other networks,\nsimilar to a VPN tunnel. Tunnelling works between any two nodes and\ndoes not require them to be directly peered."` SwitchOptions SwitchOptions `comment:"Advanced options for tuning the switch. Normally you will not need\nto edit these options."` From 3a0870a448f0c1b43a4438a3b2ec9a92d4a4aec4 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 22 Nov 2019 20:11:39 +0000 Subject: [PATCH 0443/1109] Fix IfName 'auto' behaviour on Windows --- src/tuntap/tun_windows.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tuntap/tun_windows.go b/src/tuntap/tun_windows.go index 8b4f92cf..2afe2da6 100644 --- a/src/tuntap/tun_windows.go +++ b/src/tuntap/tun_windows.go @@ -9,6 +9,7 @@ import ( "strings" "unsafe" + "github.com/yggdrasil-network/yggdrasil-go/src/defaults" "golang.org/x/sys/windows" wgtun "golang.zx2c4.com/wireguard/tun" @@ -19,6 +20,9 @@ import ( // Configures the TUN adapter with the correct IPv6 address and MTU. func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { + if ifname == "auto" { + ifname = defaults.GetDefaults().IfName + } var err error err = doAsSystem(func() { iface, err := wgtun.CreateTUN(ifname, mtu) From baebaabc437cbe697abae3fcc3d00c25fd48efa0 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 22 Nov 2019 20:16:24 +0000 Subject: [PATCH 0444/1109] Fix typo --- src/tuntap/tun_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tuntap/tun_windows.go b/src/tuntap/tun_windows.go index 2afe2da6..7cedfc85 100644 --- a/src/tuntap/tun_windows.go +++ b/src/tuntap/tun_windows.go @@ -21,7 +21,7 @@ import ( // Configures the TUN adapter with the correct IPv6 address and MTU. func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { if ifname == "auto" { - ifname = defaults.GetDefaults().IfName + ifname = defaults.GetDefaults().DefaultIfName } var err error err = doAsSystem(func() { From 0529910b01015332b0568342f82830baf52fe158 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 23 Nov 2019 13:34:27 +0000 Subject: [PATCH 0445/1109] Reuse GUID so Windows no longer keeps creating new networks each time Yggdrasil starts --- src/tuntap/tun_windows.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/tuntap/tun_windows.go b/src/tuntap/tun_windows.go index 7cedfc85..73ab398d 100644 --- a/src/tuntap/tun_windows.go +++ b/src/tuntap/tun_windows.go @@ -24,8 +24,13 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { ifname = defaults.GetDefaults().DefaultIfName } var err error + var iface wgtun.Device err = doAsSystem(func() { - iface, err := wgtun.CreateTUN(ifname, mtu) + if guid, gerr := windows.GUIDFromString("{8f59971a-7872-4aa6-b2eb-061fc4e9d0a7}"); gerr == nil { + iface, err = wgtun.CreateTUNWithRequestedGUID(ifname, &guid, mtu) + } else { + panic(gerr) + } if err != nil { panic(err) } From d0a307db97a7a6418844aa882cd9e52d25a575c2 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 23 Nov 2019 13:46:05 +0000 Subject: [PATCH 0446/1109] Use Wireguard's DoAsSystem, fix build tags and go.mod/go.sum --- go.mod | 8 ++-- go.sum | 21 ++++----- src/tuntap/tun_windows.go | 89 ++------------------------------------- 3 files changed, 18 insertions(+), 100 deletions(-) diff --git a/go.mod b/go.mod index 417e6fa3..6e57de50 100644 --- a/go.mod +++ b/go.mod @@ -9,14 +9,12 @@ require ( github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/mitchellh/mapstructure v1.1.2 - github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 - github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b // indirect github.com/vishvananda/netlink v1.0.0 github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect - github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 golang.org/x/net v0.0.0-20191021144547-ec77196f6094 - golang.org/x/sys v0.0.0-20191024172528-b4ff53e7a1cb + golang.org/x/sys v0.0.0-20191025090151-53bf42e6b339 golang.org/x/text v0.3.2 - golang.zx2c4.com/wireguard v0.0.20191012 + golang.zx2c4.com/wireguard v0.0.20191013-0.20191022095125-f7d0edd2ecf5 + golang.zx2c4.com/wireguard/windows v0.0.35 ) diff --git a/go.sum b/go.sum index 222728a1..c1dc2b78 100644 --- a/go.sum +++ b/go.sum @@ -8,18 +8,16 @@ github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible h1:b github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk8851WTVWlqMC1ecJH07Ctz+Ezxx4u54g= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0/go.mod h1:rUi0/YffDo1oXBOGn1KRq7Fr07LX48XEBecQnmwjsAo= +github.com/lxn/walk v0.0.0-20191024161928-0ee7d2cded97 h1:3zBUhgnxeLyaImKEtPfKWipiHyI5zYp/V2NN967zPFo= +github.com/lxn/walk v0.0.0-20191024161928-0ee7d2cded97/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= +github.com/lxn/win v0.0.0-20191024121223-cc00c7492fe1 h1:h0wbuSK8xUNmMwDdCxZx2OLdkVck6Bb31zj4CxCN5I4= +github.com/lxn/win v0.0.0-20191024121223-cc00c7492fe1/go.mod h1:ouWl4wViUNh8tPSIwxTVMuS014WakR1hqvBc2I0bMoA= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 h1:1zN6ImoqhSJhN8hGXFaJlSC8msLmIbX8bFqOfWLKw0w= -github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091/go.mod h1:N20Z5Y8oye9a7HmytmZ+tr8Q2vlP0tAHP13kTHzwvQY= -github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b h1:+y4hCMc/WKsDbAPsOQZgBSaSZ26uh2afyaWeVg/3s/c= -github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= github.com/vishvananda/netlink v1.0.0 h1:bqNY2lgheFIu1meHUFSH3d7vG93AFyqg3oGbJCOJgSM= github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f h1:nBX3nTcmxEtHSERBJaIo1Qa26VwRaopnZmfDQUXsF4I= 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/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= @@ -30,12 +28,15 @@ golang.org/x/net v0.0.0-20191021144547-ec77196f6094 h1:5O4U9trLjNpuhpynaDsqwCk+T 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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191024172528-b4ff53e7a1cb h1:ZxSglHghKPYD8WDeRUzRJrUJtDF0PxsTUSxyqr9/5BI= -golang.org/x/sys v0.0.0-20191024172528-b4ff53e7a1cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191025090151-53bf42e6b339 h1:zSqWKgm/o7HAnlAzBQ+aetp9fpuyytsXnKA8eiLHYQM= +golang.org/x/sys v0.0.0-20191025090151-53bf42e6b339/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.zx2c4.com/wireguard v0.0.20191012 h1:sdX+y3hrHkW8KJkjY7ZgzpT5Tqo8XnBkH55U1klphko= -golang.zx2c4.com/wireguard v0.0.20191012/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4= +golang.zx2c4.com/wireguard v0.0.20191013-0.20191022095125-f7d0edd2ecf5 h1:tijV0YMbg6JbCA9TNaNVjAK2+6KefpNwF4coc6UkNkQ= +golang.zx2c4.com/wireguard v0.0.20191013-0.20191022095125-f7d0edd2ecf5/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4= +golang.zx2c4.com/wireguard/windows v0.0.35 h1:egyV5IRbpKr78b+9gkB8AMQ32TtCo0NO//rfh10OSDg= +golang.zx2c4.com/wireguard/windows v0.0.35/go.mod h1:A3MmRXpatlao+Zty3Zxe2nqZ/sQCGYvnMVZ4z7TQY2w= diff --git a/src/tuntap/tun_windows.go b/src/tuntap/tun_windows.go index 73ab398d..0a14768d 100644 --- a/src/tuntap/tun_windows.go +++ b/src/tuntap/tun_windows.go @@ -1,3 +1,5 @@ +// +build windows + package tuntap import ( @@ -5,14 +7,12 @@ import ( "errors" "log" "net" - "runtime" - "strings" - "unsafe" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" "golang.org/x/sys/windows" wgtun "golang.zx2c4.com/wireguard/tun" + "golang.zx2c4.com/wireguard/windows/elevate" "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" ) @@ -25,7 +25,7 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { } var err error var iface wgtun.Device - err = doAsSystem(func() { + err = elevate.DoAsSystem(func() { if guid, gerr := windows.GUIDFromString("{8f59971a-7872-4aa6-b2eb-061fc4e9d0a7}"); gerr == nil { iface, err = wgtun.CreateTUNWithRequestedGUID(ifname, &guid, mtu) } else { @@ -107,87 +107,6 @@ func (tun *TunAdapter) setupAddress(addr string) error { return nil } -/* - * doAsSystem - * SPDX-License-Identifier: LGPL-3.0 - * Copyright (C) 2017-2019 Jason A. Donenfeld . All Rights Reserved. - */ -func doAsSystem(f func()) error { - runtime.LockOSThread() - defer func() { - windows.RevertToSelf() - runtime.UnlockOSThread() - }() - privileges := windows.Tokenprivileges{ - PrivilegeCount: 1, - Privileges: [1]windows.LUIDAndAttributes{ - { - Attributes: windows.SE_PRIVILEGE_ENABLED, - }, - }, - } - err := windows.LookupPrivilegeValue(nil, windows.StringToUTF16Ptr("SeDebugPrivilege"), &privileges.Privileges[0].Luid) - if err != nil { - return err - } - err = windows.ImpersonateSelf(windows.SecurityImpersonation) - if err != nil { - return err - } - var threadToken windows.Token - err = windows.OpenThreadToken(windows.CurrentThread(), windows.TOKEN_ADJUST_PRIVILEGES, false, &threadToken) - if err != nil { - return err - } - defer threadToken.Close() - err = windows.AdjustTokenPrivileges(threadToken, false, &privileges, uint32(unsafe.Sizeof(privileges)), nil, nil) - if err != nil { - return err - } - - processes, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0) - if err != nil { - return err - } - defer windows.CloseHandle(processes) - - processEntry := windows.ProcessEntry32{Size: uint32(unsafe.Sizeof(windows.ProcessEntry32{}))} - pid := uint32(0) - for err = windows.Process32First(processes, &processEntry); err == nil; err = windows.Process32Next(processes, &processEntry) { - if strings.ToLower(windows.UTF16ToString(processEntry.ExeFile[:])) == "winlogon.exe" { - pid = processEntry.ProcessID - break - } - } - if pid == 0 { - return errors.New("unable to find winlogon.exe process") - } - - winlogonProcess, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION, false, pid) - if err != nil { - return err - } - defer windows.CloseHandle(winlogonProcess) - var winlogonToken windows.Token - err = windows.OpenProcessToken(winlogonProcess, windows.TOKEN_IMPERSONATE|windows.TOKEN_DUPLICATE, &winlogonToken) - if err != nil { - return err - } - defer winlogonToken.Close() - var duplicatedToken windows.Token - err = windows.DuplicateTokenEx(winlogonToken, 0, nil, windows.SecurityImpersonation, windows.TokenImpersonation, &duplicatedToken) - if err != nil { - return err - } - defer duplicatedToken.Close() - err = windows.SetThreadToken(nil, duplicatedToken) - if err != nil { - return err - } - f() - return nil -} - /* * cleanupAddressesOnDisconnectedInterfaces * SPDX-License-Identifier: MIT From 746fac6594217d3a3cef7379e0fb8636b6498ad2 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 23 Nov 2019 13:56:48 +0000 Subject: [PATCH 0447/1109] Fix go.mod/go.sum again and update DoAsSystem call --- go.mod | 10 +++++----- go.sum | 26 ++++++++++++++------------ src/tuntap/tun_windows.go | 27 +++++++++++++-------------- 3 files changed, 32 insertions(+), 31 deletions(-) diff --git a/go.mod b/go.mod index 6e57de50..c11ccdf7 100644 --- a/go.mod +++ b/go.mod @@ -11,10 +11,10 @@ require ( github.com/mitchellh/mapstructure v1.1.2 github.com/vishvananda/netlink v1.0.0 github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect - golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 - golang.org/x/net v0.0.0-20191021144547-ec77196f6094 - golang.org/x/sys v0.0.0-20191025090151-53bf42e6b339 + golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c + golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914 + golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e golang.org/x/text v0.3.2 - golang.zx2c4.com/wireguard v0.0.20191013-0.20191022095125-f7d0edd2ecf5 - golang.zx2c4.com/wireguard/windows v0.0.35 + golang.zx2c4.com/wireguard v0.0.20191013-0.20191030132932-4cdf805b29b1 + golang.zx2c4.com/wireguard/windows v0.0.35-0.20191123133119-cb4a03094c25 ) diff --git a/go.sum b/go.sum index c1dc2b78..9a61fd2c 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,7 @@ github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible h1:b github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk8851WTVWlqMC1ecJH07Ctz+Ezxx4u54g= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0/go.mod h1:rUi0/YffDo1oXBOGn1KRq7Fr07LX48XEBecQnmwjsAo= -github.com/lxn/walk v0.0.0-20191024161928-0ee7d2cded97 h1:3zBUhgnxeLyaImKEtPfKWipiHyI5zYp/V2NN967zPFo= -github.com/lxn/walk v0.0.0-20191024161928-0ee7d2cded97/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= +github.com/lxn/walk v0.0.0-20191031081659-c0bb82ae46cb/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= github.com/lxn/win v0.0.0-20191024121223-cc00c7492fe1 h1:h0wbuSK8xUNmMwDdCxZx2OLdkVck6Bb31zj4CxCN5I4= github.com/lxn/win v0.0.0-20191024121223-cc00c7492fe1/go.mod h1:ouWl4wViUNh8tPSIwxTVMuS014WakR1hqvBc2I0bMoA= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= @@ -20,23 +19,26 @@ github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f h1:nBX3nTcmxEtHS github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c h1:/nJuwDLoL/zrqY6gf57vxC+Pi+pZ8bfhpPkicO5H7W4= +golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -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/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914 h1:MlY3mEfbnWGmUi4rtHOtNnnnN4UJRGSyLPx+DXA5Sq4= +golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191025090151-53bf42e6b339 h1:zSqWKgm/o7HAnlAzBQ+aetp9fpuyytsXnKA8eiLHYQM= -golang.org/x/sys v0.0.0-20191025090151-53bf42e6b339/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e h1:N7DeIrjYszNmSW409R3frPPwglRwMkXSBzwVbkOjLLA= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.zx2c4.com/wireguard v0.0.20191013-0.20191022095125-f7d0edd2ecf5 h1:tijV0YMbg6JbCA9TNaNVjAK2+6KefpNwF4coc6UkNkQ= -golang.zx2c4.com/wireguard v0.0.20191013-0.20191022095125-f7d0edd2ecf5/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4= -golang.zx2c4.com/wireguard/windows v0.0.35 h1:egyV5IRbpKr78b+9gkB8AMQ32TtCo0NO//rfh10OSDg= -golang.zx2c4.com/wireguard/windows v0.0.35/go.mod h1:A3MmRXpatlao+Zty3Zxe2nqZ/sQCGYvnMVZ4z7TQY2w= +golang.zx2c4.com/wireguard v0.0.20191013-0.20191030132932-4cdf805b29b1 h1:KxtBKNgJUQG8vwZzJKkwBGOcqp95xLu6A6KIMde1kl0= +golang.zx2c4.com/wireguard v0.0.20191013-0.20191030132932-4cdf805b29b1/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4= +golang.zx2c4.com/wireguard/windows v0.0.35-0.20191123133119-cb4a03094c25 h1:TreP+furSwdqoSToFrwb1S5cwxb7jhOsnwj2MsDeT+4= +golang.zx2c4.com/wireguard/windows v0.0.35-0.20191123133119-cb4a03094c25/go.mod h1:EO8KCpT944a9CnwHJLZ1sl84FfIrY42fP/fcXUuYhKM= diff --git a/src/tuntap/tun_windows.go b/src/tuntap/tun_windows.go index 0a14768d..9b5f7b0c 100644 --- a/src/tuntap/tun_windows.go +++ b/src/tuntap/tun_windows.go @@ -23,31 +23,30 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { if ifname == "auto" { ifname = defaults.GetDefaults().DefaultIfName } - var err error - var iface wgtun.Device - err = elevate.DoAsSystem(func() { - if guid, gerr := windows.GUIDFromString("{8f59971a-7872-4aa6-b2eb-061fc4e9d0a7}"); gerr == nil { - iface, err = wgtun.CreateTUNWithRequestedGUID(ifname, &guid, mtu) - } else { - panic(gerr) + return elevate.DoAsSystem(func() error { + var err error + var iface wgtun.Device + var guid windows.GUID + if guid, err = windows.GUIDFromString("{8f59971a-7872-4aa6-b2eb-061fc4e9d0a7}"); err != nil { + return err } - if err != nil { - panic(err) + if iface, err = wgtun.CreateTUNWithRequestedGUID(ifname, &guid, mtu); err != nil { + return err } tun.iface = iface - - if err := tun.setupAddress(addr); err != nil { + if err = tun.setupAddress(addr); err != nil { tun.log.Errorln("Failed to set up TUN address:", err) + return err } - if err := tun.setupMTU(getSupportedMTU(mtu)); err != nil { + if err = tun.setupMTU(getSupportedMTU(mtu)); err != nil { tun.log.Errorln("Failed to set up TUN MTU:", err) + return err } - if mtu, err = iface.MTU(); err == nil { tun.mtu = mtu } + return nil }) - return err } // Sets the MTU of the TAP adapter. From 117d44d008a7390939e66f40c86ad34fb6fef10f Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 23 Nov 2019 15:47:08 -0600 Subject: [PATCH 0448/1109] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb5c4394..51d67dc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,7 +33,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed - On Linux, outgoing `InterfacePeers` connections now use `SO_BINDTODEVICE` to prefer an outgoing interface -- The `genkeys` utility is now in `cmd` rather than `contrib` +- The `genkeys` utility is now in `cmd` rather than `misc` ### Fixed - A data race condition has been fixed when updating session coordinates From ebef3045e2395a56c8d9615fe338744e8233e2b5 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 24 Nov 2019 09:44:52 +0000 Subject: [PATCH 0449/1109] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51d67dc7..a511c523 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> -## [0.3.12] - 2019-11-22 +## [0.3.12] - 2019-11-24 ### Added - New API functions `SetMaximumSessionMTU` and `GetMaximumSessionMTU` - New command line parameters `-address` and `-subnet` for getting the address/subnet from the config file, for use with `-useconffile` or `-useconf` From 6560aac1e9eb3b3d3c1f0ef29ae8ecdf8a34a767 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 24 Nov 2019 13:42:56 -0600 Subject: [PATCH 0450/1109] fix error spam on shutdown --- src/tuntap/iface.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index fbb7c86d..f80ea4f4 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -57,9 +57,9 @@ func (r *tunReader) _read() { n, err := r.tun.iface.Read(recvd, TUN_OFFSET_BYTES) if n <= TUN_OFFSET_BYTES || err != nil { r.tun.log.Errorln("Error reading TUN:", err) - err = r.tun.iface.Flush() - if err != nil { - r.tun.log.Errorln("Unable to flush packets:", err) + ferr := r.tun.iface.Flush() + if ferr != nil { + r.tun.log.Errorln("Unable to flush packets:", ferr) } util.PutBytes(recvd) } else { From f6f9b3ef76bd967ac7ef7fa1a957eb6b3f0c2b82 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 24 Nov 2019 15:01:20 -0600 Subject: [PATCH 0451/1109] include offset in expected bytes written --- src/tuntap/iface.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index f80ea4f4..f1ca0add 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -40,7 +40,7 @@ func (w *tunWriter) _write(b []byte) { } }) } - if written != n { + if written != n+TUN_OFFSET_BYTES { w.tun.log.Errorln("TUN iface write mismatch:", written, "bytes written vs", n, "bytes given") } } From 85c5bc61ac39169f96b9c853d63e165823a014e1 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 24 Nov 2019 21:03:02 +0000 Subject: [PATCH 0452/1109] TUN_OFFSET_BYTES per platform --- src/tuntap/iface.go | 2 -- src/tuntap/tun_bsd.go | 2 ++ src/tuntap/tun_darwin.go | 2 ++ src/tuntap/tun_linux.go | 2 ++ src/tuntap/tun_other.go | 2 ++ src/tuntap/tun_windows.go | 4 +++- 6 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index f1ca0add..633903da 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -9,8 +9,6 @@ import ( "github.com/Arceliar/phony" ) -const TUN_OFFSET_BYTES = 4 - type tunWriter struct { phony.Inbox tun *TunAdapter diff --git a/src/tuntap/tun_bsd.go b/src/tuntap/tun_bsd.go index 219e3485..79184cb9 100644 --- a/src/tuntap/tun_bsd.go +++ b/src/tuntap/tun_bsd.go @@ -15,6 +15,8 @@ import ( wgtun "golang.zx2c4.com/wireguard/tun" ) +const TUN_OFFSET_BYTES = 0 + const SIOCSIFADDR_IN6 = (0x80000000) | ((288 & 0x1fff) << 16) | uint32(byte('i'))<<8 | 12 type in6_addrlifetime struct { diff --git a/src/tuntap/tun_darwin.go b/src/tuntap/tun_darwin.go index cf2fbfb4..4dab6f31 100644 --- a/src/tuntap/tun_darwin.go +++ b/src/tuntap/tun_darwin.go @@ -15,6 +15,8 @@ import ( wgtun "golang.zx2c4.com/wireguard/tun" ) +const TUN_OFFSET_BYTES = 0 + // Configures the "utun" adapter with the correct IPv6 address and MTU. func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { if ifname == "auto" { diff --git a/src/tuntap/tun_linux.go b/src/tuntap/tun_linux.go index 4206b26a..ca402522 100644 --- a/src/tuntap/tun_linux.go +++ b/src/tuntap/tun_linux.go @@ -9,6 +9,8 @@ import ( wgtun "golang.zx2c4.com/wireguard/tun" ) +const TUN_OFFSET_BYTES = 0 + // Configures the TUN adapter with the correct IPv6 address and MTU. func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { if ifname == "auto" { diff --git a/src/tuntap/tun_other.go b/src/tuntap/tun_other.go index 8a27f57b..85934f39 100644 --- a/src/tuntap/tun_other.go +++ b/src/tuntap/tun_other.go @@ -9,6 +9,8 @@ import ( wgtun "golang.zx2c4.com/wireguard/tun" ) +const TUN_OFFSET_BYTES = 0 + // Configures the TUN adapter with the correct IPv6 address and MTU. func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { iface, err := wgtun.CreateTUN(ifname, mtu) diff --git a/src/tuntap/tun_windows.go b/src/tuntap/tun_windows.go index 9b5f7b0c..e611af76 100644 --- a/src/tuntap/tun_windows.go +++ b/src/tuntap/tun_windows.go @@ -2,6 +2,8 @@ package tuntap +// This is to catch Windows platforms + import ( "bytes" "errors" @@ -16,7 +18,7 @@ import ( "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" ) -// This is to catch Windows platforms +const TUN_OFFSET_BYTES = 4 // Configures the TUN adapter with the correct IPv6 address and MTU. func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { From 2982b53555c54eb1952a52ad3788f9375ad9fe52 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 24 Nov 2019 15:09:28 -0600 Subject: [PATCH 0453/1109] make offset generic over TUN_OFFSET_BYTES so we can make this platform dependent --- src/tuntap/iface.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index f1ca0add..5eca3aed 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -31,7 +31,7 @@ func (w *tunWriter) _write(b []byte) { if n == 0 { return } - written, err = w.tun.iface.Write(append([]byte{0, 0, 0, 0}, b[:n]...), TUN_OFFSET_BYTES) + written, err = w.tun.iface.Write(append(make([]byte, TUN_OFFSET_BYTES), b[:n]...), TUN_OFFSET_BYTES) util.PutBytes(b) if err != nil { w.tun.Act(w, func() { From 8f323b740d7171120b6619b320a77973b2ea7d6b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 24 Nov 2019 21:09:29 +0000 Subject: [PATCH 0454/1109] Revert "TUN_OFFSET_BYTES per platform" This reverts commit 85c5bc61ac39169f96b9c853d63e165823a014e1. --- src/tuntap/iface.go | 2 ++ src/tuntap/tun_bsd.go | 2 -- src/tuntap/tun_darwin.go | 2 -- src/tuntap/tun_linux.go | 2 -- src/tuntap/tun_other.go | 2 -- src/tuntap/tun_windows.go | 4 +--- 6 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 633903da..f1ca0add 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -9,6 +9,8 @@ import ( "github.com/Arceliar/phony" ) +const TUN_OFFSET_BYTES = 4 + type tunWriter struct { phony.Inbox tun *TunAdapter diff --git a/src/tuntap/tun_bsd.go b/src/tuntap/tun_bsd.go index 79184cb9..219e3485 100644 --- a/src/tuntap/tun_bsd.go +++ b/src/tuntap/tun_bsd.go @@ -15,8 +15,6 @@ import ( wgtun "golang.zx2c4.com/wireguard/tun" ) -const TUN_OFFSET_BYTES = 0 - const SIOCSIFADDR_IN6 = (0x80000000) | ((288 & 0x1fff) << 16) | uint32(byte('i'))<<8 | 12 type in6_addrlifetime struct { diff --git a/src/tuntap/tun_darwin.go b/src/tuntap/tun_darwin.go index 4dab6f31..cf2fbfb4 100644 --- a/src/tuntap/tun_darwin.go +++ b/src/tuntap/tun_darwin.go @@ -15,8 +15,6 @@ import ( wgtun "golang.zx2c4.com/wireguard/tun" ) -const TUN_OFFSET_BYTES = 0 - // Configures the "utun" adapter with the correct IPv6 address and MTU. func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { if ifname == "auto" { diff --git a/src/tuntap/tun_linux.go b/src/tuntap/tun_linux.go index ca402522..4206b26a 100644 --- a/src/tuntap/tun_linux.go +++ b/src/tuntap/tun_linux.go @@ -9,8 +9,6 @@ import ( wgtun "golang.zx2c4.com/wireguard/tun" ) -const TUN_OFFSET_BYTES = 0 - // Configures the TUN adapter with the correct IPv6 address and MTU. func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { if ifname == "auto" { diff --git a/src/tuntap/tun_other.go b/src/tuntap/tun_other.go index 85934f39..8a27f57b 100644 --- a/src/tuntap/tun_other.go +++ b/src/tuntap/tun_other.go @@ -9,8 +9,6 @@ import ( wgtun "golang.zx2c4.com/wireguard/tun" ) -const TUN_OFFSET_BYTES = 0 - // Configures the TUN adapter with the correct IPv6 address and MTU. func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { iface, err := wgtun.CreateTUN(ifname, mtu) diff --git a/src/tuntap/tun_windows.go b/src/tuntap/tun_windows.go index e611af76..9b5f7b0c 100644 --- a/src/tuntap/tun_windows.go +++ b/src/tuntap/tun_windows.go @@ -2,8 +2,6 @@ package tuntap -// This is to catch Windows platforms - import ( "bytes" "errors" @@ -18,7 +16,7 @@ import ( "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" ) -const TUN_OFFSET_BYTES = 4 +// This is to catch Windows platforms // Configures the TUN adapter with the correct IPv6 address and MTU. func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { From 2e95a3131c86389ff2e49c1d5e2c534a50d4eb97 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 24 Nov 2019 15:37:37 -0600 Subject: [PATCH 0455/1109] comment out pointless error that prints on some platforms and not others --- src/tuntap/iface.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 5eca3aed..463f48f0 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -31,7 +31,7 @@ func (w *tunWriter) _write(b []byte) { if n == 0 { return } - written, err = w.tun.iface.Write(append(make([]byte, TUN_OFFSET_BYTES), b[:n]...), TUN_OFFSET_BYTES) + written, err = w.tun.iface.Write(append(make([]byte, TUN_OFFSET_BYTES), b...), TUN_OFFSET_BYTES) util.PutBytes(b) if err != nil { w.tun.Act(w, func() { @@ -41,7 +41,8 @@ func (w *tunWriter) _write(b []byte) { }) } if written != n+TUN_OFFSET_BYTES { - w.tun.log.Errorln("TUN iface write mismatch:", written, "bytes written vs", n, "bytes given") + // FIXME some platforms return the wrong number of bytes written, causing error spam + //w.tun.log.Errorln("TUN iface write mismatch:", written, "bytes written vs", n+TUN_OFFSET_BYTES, "bytes given") } } From 27cc57dbbc91220c982155e53826f7ca524e0ca8 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 24 Nov 2019 18:24:17 -0600 Subject: [PATCH 0456/1109] attempt to prevent incorrect idle notification in switch, needs testing --- src/yggdrasil/link.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 1710e202..10a31457 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -64,6 +64,7 @@ type linkInterface struct { closeTimer *time.Timer // Fires when the link has been idle so long we need to close it inSwitch bool // True if the switch is tracking this link stalled bool // True if we haven't been receiving any response traffic + unstalled bool // False if an idle notification to the switch hasn't been sent because we stalled (or are first starting up) } func (l *link) init(c *Core) error { @@ -324,11 +325,15 @@ func (intf *linkInterface) notifySent(size int, isLinkTraffic bool) { // Notify the switch that we're ready for more traffic, assuming we're not in a stalled state func (intf *linkInterface) _notifySwitch() { - if !intf.inSwitch && !intf.stalled { - intf.inSwitch = true - intf.link.core.switchTable.Act(intf, func() { - intf.link.core.switchTable._idleIn(intf.peer.port) - }) + if !intf.inSwitch { + if intf.stalled { + intf.unstalled = false + } else { + intf.inSwitch = true + intf.link.core.switchTable.Act(intf, func() { + intf.link.core.switchTable._idleIn(intf.peer.port) + }) + } } } @@ -362,7 +367,9 @@ func (intf *linkInterface) notifyRead(size int) { intf.stallTimer = nil } intf.stalled = false - intf._notifySwitch() + if !intf.unstalled { + intf._notifySwitch() + } if size > 0 && intf.stallTimer == nil { intf.stallTimer = time.AfterFunc(keepAliveTime, intf.notifyDoKeepAlive) } From 3e0799551835c7b1846605bd88ae8a133c00c4ee Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 24 Nov 2019 18:53:58 -0600 Subject: [PATCH 0457/1109] it helps to actually set the flag... --- src/yggdrasil/link.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 10a31457..cd40a9ea 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -369,6 +369,7 @@ func (intf *linkInterface) notifyRead(size int) { intf.stalled = false if !intf.unstalled { intf._notifySwitch() + intf.unstalled = true } if size > 0 && intf.stallTimer == nil { intf.stallTimer = time.AfterFunc(keepAliveTime, intf.notifyDoKeepAlive) From 38c54efd73f27db2265fbe467593bac22b0a3780 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 24 Nov 2019 22:54:30 -0600 Subject: [PATCH 0458/1109] Update yggdrasil.service --- contrib/systemd/yggdrasil.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/systemd/yggdrasil.service b/contrib/systemd/yggdrasil.service index bdf365c8..b90d09d1 100644 --- a/contrib/systemd/yggdrasil.service +++ b/contrib/systemd/yggdrasil.service @@ -8,7 +8,7 @@ Group=yggdrasil ProtectHome=true ProtectSystem=true SyslogIdentifier=yggdrasil -CapabilityBoundingSet=CAP_NET_ADMIN +CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW ExecStartPre=+-/sbin/modprobe tun ExecStartPre=/bin/sh -ec "if ! test -s /etc/yggdrasil.conf; \ then umask 077; \ From 837e7da7921d78aa58f6e1a10220184d99d3f6e7 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 25 Nov 2019 20:13:41 +0000 Subject: [PATCH 0459/1109] Force packets through the switch to be buffered (seems to help the reordering problem on Windows) --- src/yggdrasil/switch.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index ba30758c..5d355041 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -718,8 +718,14 @@ func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]struct{}, sen if best != nil { if _, isIdle := idle[best.elem.port]; isIdle { delete(idle, best.elem.port) - ports[best.elem.port].sendPacketsFrom(t, [][]byte{packet}) - return true + + // FIXME: This was causing the out-of-order packets on Windows but forcing + // all packets to buffer might have a mild performance penalty + //ports[best.elem.port].sendPacketsFrom(t, [][]byte{packet}) + //return true + t.Act(nil, func() { + t._idleIn(best.elem.port) + }) } } // Didn't find anyone idle to send it to From 98339cdc3f89cc4342c5e7ef0624dc440ff8a4ac Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 25 Nov 2019 17:40:58 -0600 Subject: [PATCH 0460/1109] possible fix if monotonic time resolution is related to packet reordering --- src/yggdrasil/switch.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index ba30758c..ab888a76 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -808,12 +808,12 @@ func (t *switchTable) _handleIdle(port switchPort) bool { packet := buf.packets[0] coords := switch_getPacketCoords(packet.bytes) priority := float64(now.Sub(packet.time)) / float64(buf.size) - if priority > bestPriority && t.portIsCloser(coords, port) { + if priority >= bestPriority && t.portIsCloser(coords, port) { best = streamID bestPriority = priority } } - if bestPriority != 0 { + if best != "" { buf := t.queues.bufs[best] var packet switch_packetInfo // TODO decide if this should be LIFO or FIFO From ad8d30ce74bc90eb9363bf5a341933a2972b892a Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 26 Nov 2019 09:44:35 +0000 Subject: [PATCH 0461/1109] Revert "Force packets through the switch to be buffered (seems to help the reordering problem on Windows)" This reverts commit 837e7da7921d78aa58f6e1a10220184d99d3f6e7. --- src/yggdrasil/switch.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index d0a9f4e0..ab888a76 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -718,14 +718,8 @@ func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]struct{}, sen if best != nil { if _, isIdle := idle[best.elem.port]; isIdle { delete(idle, best.elem.port) - - // FIXME: This was causing the out-of-order packets on Windows but forcing - // all packets to buffer might have a mild performance penalty - //ports[best.elem.port].sendPacketsFrom(t, [][]byte{packet}) - //return true - t.Act(nil, func() { - t._idleIn(best.elem.port) - }) + ports[best.elem.port].sendPacketsFrom(t, [][]byte{packet}) + return true } } // Didn't find anyone idle to send it to From e1b0d0f20c546a905a701bbfef181fe109791a2a Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 28 Nov 2019 00:35:29 +0000 Subject: [PATCH 0462/1109] Appveyor MSI builds for Windows (#621) * Try appveyor for MSI (not finished) * build-msi.sh * Don't shallow clone * Don't set clone depth * Build Yggdrasil for each arch * Try to get rest of branches * Allow upgrades (hopefully) * Try using MajorUpgrade * AllowDowngrades * Try harder to build x86 :-) * Bugfix * Bugfix * AllowSameVersionUpgrades * AllowSameVersionUpgrades * Generate new GUID for each build (might fix upgrades) --- appveyor.yml | 23 +++++ contrib/msi/build-msi.sh | 189 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 appveyor.yml create mode 100644 contrib/msi/build-msi.sh diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..1389bebe --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,23 @@ +version: '{build}' +pull_requests: + do_not_increment_build_number: true +os: Visual Studio 2017 +shallow_clone: false + +environment: + MSYS2_PATH_TYPE: inherit + CHERE_INVOKING: enabled_from_arguments + +install: +- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -S curl unzip" + +build_script: +- cmd: >- + cd \projects\yggdrasil-go +- c:\msys64\usr\bin\bash -lc "./contrib/msi/build-msi.sh x64" +- c:\msys64\usr\bin\bash -lc "./contrib/msi/build-msi.sh x86" + +test: off + +artifacts: +- path: '*.msi' diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh new file mode 100644 index 00000000..d314b0c6 --- /dev/null +++ b/contrib/msi/build-msi.sh @@ -0,0 +1,189 @@ +#!/bin/sh + +# Get arch from command line if given +PKGARCH=$1 +if [ "${PKGARCH}" == "" ]; +then + echo "tell me the architecture: x86 or x64" + exit 1 +fi + +# Get the rest of the repository history +if [ "${APPVEYOR_REPO_BRANCH}" != "" ]; +then + git fetch --all + git checkout ${APPVEYOR_REPO_BRANCH} +fi + +# Install prerequisites +pacman -S --needed --noconfirm unzip git curl +# export PATH=$PATH:/c/go/bin/ + +# Download the wix tools! +if [ ! -d wixbin ]; +then + curl -LO https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip + if [ `md5sum wix311-binaries.zip | cut -f 1 -d " "` != "47a506f8ab6666ee3cc502fb07d0ee2a" ]; + then + echo "wix package didn't match expected checksum" + exit 1 + fi + mkdir -p wixbin + unzip -o wix311-binaries.zip -d wixbin || ( + echo "failed to unzip WiX" + exit 1 + ) +fi + +# Check the prerequisite files are in place +[ "${PKGARCH}" == "x64" ] && GOOS=windows GOARCH=amd64 CGO_ENABLED=0 ./build +[ "${PKGARCH}" == "x86" ] && GOOS=windows GOARCH=386 CGO_ENABLED=0 ./build + +# Create the postinstall script +cat > config.bat << EOF +if exist yggdrasil.conf ( + move yggdrasil.conf yggdrasil.conf.backup + yggdrasil.exe -useconffile yggdrasil.conf.backup -normaliseconf > yggdrasil.conf +) else ( + yggdrasil.exe -genconf > yggdrasil.conf +) +EOF + +# Work out metadata for the package info +PKGNAME=$(sh contrib/semver/name.sh) +PKGVERSION=$(sh contrib/semver/version.sh --bare) +PKGVERSIONMS=$(echo $PKGVERSION | tr - .) +[ "${PKGARCH}" == "x64" ] && \ + PKGGUID="77757838-1a23-40a5-a720-c3b43e0260cc" PKGINSTFOLDER="ProgramFiles64Folder" || \ + PKGGUID="54a3294e-a441-4322-aefb-3bb40dd022bb" PKGINSTFOLDER="ProgramFilesFolder" + +# Download the Wintun driver +if [ $PKGARCH = "x64" ]; then + PKGMSMNAME=wintun-x64.msm + curl -o ${PKGMSMNAME} https://www.wintun.net/builds/wintun-amd64-0.7.msm || (echo "couldn't get wintun"; exit 1) +elif [ $PKGARCH = "x86" ]; then + PKGMSMNAME=wintun-x86.msm + curl -o ${PKGMSMNAME} https://www.wintun.net/builds/wintun-x86-0.7.msm || (echo "couldn't get wintun"; exit 1) +else + echo "wasn't sure which architecture to get wintun for" + exit 1 +fi + +# Generate the wix.xml file +cat > wix.xml << EOF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +EOF + +# Generate the MSI +CANDLEFLAGS="-nologo" +LIGHTFLAGS="-nologo -spdb -sice:ICE71 -sice:ICE61" +wixbin/candle $CANDLEFLAGS -out ${PKGNAME}-${PKGVERSION}-${PKGARCH}.wixobj -arch ${PKGARCH} wix.xml && \ +wixbin/light $LIGHTFLAGS -out ${PKGNAME}-${PKGVERSION}-${PKGARCH}.msi ${PKGNAME}-${PKGVERSION}-${PKGARCH}.wixobj From 41a2e731eb61a274b16b16f4a764fc2665d460d7 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 28 Nov 2019 09:52:14 +0000 Subject: [PATCH 0463/1109] More MSI updates (#622) * Try embedding config script * Update config when installing * Don't update config on uninstall --- contrib/msi/build-msi.sh | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index d314b0c6..de459e54 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -147,7 +147,7 @@ cat > wix.xml << EOF @@ -168,14 +168,16 @@ cat > wix.xml << EOF + ExeCommand="cmd.exe /c updateconfig.bat" + Execute="immediate" + Return="asyncWait" /> + After="InstallFiles"> + NOT Installed AND NOT REMOVE + From b88a623a9f79414e8375e813d50ce6c92f9c1f8c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 28 Nov 2019 09:56:14 +0000 Subject: [PATCH 0464/1109] Handle pull request branch --- contrib/msi/build-msi.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index de459e54..aecd034f 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -9,7 +9,11 @@ then fi # Get the rest of the repository history -if [ "${APPVEYOR_REPO_BRANCH}" != "" ]; +if [ "${APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH}" != "" ]; +then + git fetch --all + git checkout ${APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH} +elif [ "${APPVEYOR_REPO_BRANCH}" != "" ]; then git fetch --all git checkout ${APPVEYOR_REPO_BRANCH} From a673625e82516c13e7514a9fd607b9a2c42d0c09 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 28 Nov 2019 10:08:01 +0000 Subject: [PATCH 0465/1109] Configure service with -useconffile --- contrib/msi/build-msi.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index aecd034f..22eab223 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -128,7 +128,7 @@ cat > wix.xml << EOF Name="yggdrasil" Start="auto" Type="ownProcess" - Arguments="-autoconf" + Arguments='-useconffile "[YggdrasilInstallFolder]yggdrasil.conf"' Vital="yes" /> Date: Thu, 28 Nov 2019 10:19:47 +0000 Subject: [PATCH 0466/1109] Fix update action --- contrib/msi/build-msi.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index 22eab223..ed2b6815 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -173,7 +173,7 @@ cat > wix.xml << EOF Id="UpdateGenerateConfig" Directory="YggdrasilInstallFolder" ExeCommand="cmd.exe /c updateconfig.bat" - Execute="immediate" + Execute="commit" Return="asyncWait" /> From 724446bb04f487455d69f08a0abe1b59814dd444 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 28 Nov 2019 10:42:57 +0000 Subject: [PATCH 0467/1109] Defer updateconfig --- contrib/msi/build-msi.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index ed2b6815..20f9e9b8 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -45,6 +45,7 @@ fi # Create the postinstall script cat > config.bat << EOF + if exist yggdrasil.conf ( move yggdrasil.conf yggdrasil.conf.backup yggdrasil.exe -useconffile yggdrasil.conf.backup -normaliseconf > yggdrasil.conf @@ -173,8 +174,8 @@ cat > wix.xml << EOF Id="UpdateGenerateConfig" Directory="YggdrasilInstallFolder" ExeCommand="cmd.exe /c updateconfig.bat" - Execute="commit" - Return="asyncWait" /> + Execute="deferred" + Return="check" /> Date: Thu, 28 Nov 2019 10:56:22 +0000 Subject: [PATCH 0468/1109] Set output logging --- contrib/msi/build-msi.sh | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index 20f9e9b8..3f2501d5 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -129,7 +129,7 @@ cat > wix.xml << EOF Name="yggdrasil" Start="auto" Type="ownProcess" - Arguments='-useconffile "[YggdrasilInstallFolder]yggdrasil.conf"' + Arguments='-useconffile "[YggdrasilInstallFolder]yggdrasil.conf" -logto "[YggdrasilInstallFolder]yggdrasil.log"' Vital="yes" /> wix.xml << EOF - + + - Date: Thu, 28 Nov 2019 11:16:36 +0000 Subject: [PATCH 0469/1109] Don't impersonate user for updateconfig.bat --- contrib/msi/build-msi.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index 3f2501d5..c4646ae5 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -179,12 +179,13 @@ cat > wix.xml << EOF Directory="YggdrasilInstallFolder" ExeCommand="cmd.exe /c updateconfig.bat" Execute="deferred" + Impersonate="no" Return="check" /> + Before="InstallServices"> NOT Installed AND NOT REMOVE From 9c113c05bf462b96e4d69ea0b569cb4da6a57e3c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 28 Nov 2019 12:57:19 +0000 Subject: [PATCH 0470/1109] Use appveyor build folder in case slugs are different --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 1389bebe..bd0948de 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -13,7 +13,7 @@ install: build_script: - cmd: >- - cd \projects\yggdrasil-go + cd %APPVEYOR_BUILD_FOLDER% - c:\msys64\usr\bin\bash -lc "./contrib/msi/build-msi.sh x64" - c:\msys64\usr\bin\bash -lc "./contrib/msi/build-msi.sh x86" From 3f29a2ff0566879e421c93a0c879a094ff9b29d8 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 28 Nov 2019 13:00:52 +0000 Subject: [PATCH 0471/1109] Some comments --- contrib/msi/build-msi.sh | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index c4646ae5..5daa003b 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -1,5 +1,12 @@ #!/bin/sh +# This script generates an MSI file for Yggdrasil for a given architecture. It +# needs to run on Windows within MSYS2 and Go 1.13 or later must be installed on +# the system and within the PATH. This is ran currently by Appveyor (see +# appveyor.yml in the repository root) for both x86 and x64. +# +# Author: Neil Alexander + # Get arch from command line if given PKGARCH=$1 if [ "${PKGARCH}" == "" ]; @@ -8,7 +15,9 @@ then exit 1 fi -# Get the rest of the repository history +# Get the rest of the repository history. This is needed within Appveyor because +# otherwise we don't get all of the branch histories and therefore the semver +# scripts don't work properly. if [ "${APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH}" != "" ]; then git fetch --all @@ -19,9 +28,8 @@ then git checkout ${APPVEYOR_REPO_BRANCH} fi -# Install prerequisites +# Install prerequisites within MSYS2 pacman -S --needed --noconfirm unzip git curl -# export PATH=$PATH:/c/go/bin/ # Download the wix tools! if [ ! -d wixbin ]; @@ -39,13 +47,12 @@ then ) fi -# Check the prerequisite files are in place +# Build Yggdrasil! [ "${PKGARCH}" == "x64" ] && GOOS=windows GOARCH=amd64 CGO_ENABLED=0 ./build [ "${PKGARCH}" == "x86" ] && GOOS=windows GOARCH=386 CGO_ENABLED=0 ./build # Create the postinstall script cat > config.bat << EOF - if exist yggdrasil.conf ( move yggdrasil.conf yggdrasil.conf.backup yggdrasil.exe -useconffile yggdrasil.conf.backup -normaliseconf > yggdrasil.conf From c17c4af26d9c0a41ce7a6dd23aa35d4eb762ce09 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 28 Nov 2019 13:08:56 +0000 Subject: [PATCH 0472/1109] Don't normalise on upgrade --- contrib/msi/build-msi.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index 5daa003b..5d8eb7db 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -53,10 +53,7 @@ fi # Create the postinstall script cat > config.bat << EOF -if exist yggdrasil.conf ( - move yggdrasil.conf yggdrasil.conf.backup - yggdrasil.exe -useconffile yggdrasil.conf.backup -normaliseconf > yggdrasil.conf -) else ( +if not exist yggdrasil.conf ( yggdrasil.exe -genconf > yggdrasil.conf ) EOF From 71404f5270174d157aa624235713033ef2926f3d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 28 Nov 2019 15:17:49 +0000 Subject: [PATCH 0473/1109] Clean up appveyor.yml --- appveyor.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index bd0948de..58724a24 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,9 +8,6 @@ environment: MSYS2_PATH_TYPE: inherit CHERE_INVOKING: enabled_from_arguments -install: -- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -S curl unzip" - build_script: - cmd: >- cd %APPVEYOR_BUILD_FOLDER% From c2a8b4bb5767b35974d45dad6777187616553131 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 28 Nov 2019 12:00:00 -0600 Subject: [PATCH 0474/1109] get rid of an allocation in tunWriter's _write --- src/tuntap/iface.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 463f48f0..5efbc8a7 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -25,14 +25,16 @@ func (w *tunWriter) writeFrom(from phony.Actor, b []byte) { // write is pretty loose with the memory safety rules, e.g. it assumes it can // read w.tun.iface.IsTap() safely func (w *tunWriter) _write(b []byte) { + defer util.PutBytes(b) var written int var err error n := len(b) if n == 0 { return } - written, err = w.tun.iface.Write(append(make([]byte, TUN_OFFSET_BYTES), b...), TUN_OFFSET_BYTES) - util.PutBytes(b) + temp := append(util.ResizeBytes(util.GetBytes(), TUN_OFFSET_BYTES), b...) + defer util.PutBytes(temp) + written, err = w.tun.iface.Write(temp, TUN_OFFSET_BYTES) if err != nil { w.tun.Act(w, func() { if !w.tun.isOpen { From 73f50af3b73e864ac0d18d5f995b3f9c3adb5d4f Mon Sep 17 00:00:00 2001 From: Dimitris Apostolou Date: Fri, 29 Nov 2019 11:45:02 +0200 Subject: [PATCH 0475/1109] Fix typos --- CHANGELOG.md | 8 ++++---- cmd/genkeys/main.go | 2 +- doc/Whitepaper.md | 4 ++-- misc/sim/treesim-forward.py | 2 +- misc/sim/treesim.go | 2 +- src/address/address.go | 4 ++-- src/config/config.go | 2 +- src/crypto/crypto.go | 2 +- src/tuntap/conn.go | 2 +- src/yggdrasil/conn.go | 4 ++-- src/yggdrasil/core_test.go | 2 +- src/yggdrasil/dht.go | 2 +- src/yggdrasil/dialer.go | 2 +- src/yggdrasil/link.go | 2 +- src/yggdrasil/peer.go | 6 +++--- src/yggdrasil/search.go | 2 +- src/yggdrasil/session.go | 8 ++++---- src/yggdrasil/switch.go | 4 ++-- src/yggdrasil/version.go | 6 +++--- 19 files changed, 33 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a511c523..d3950f7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,7 +66,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### 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 +- The systemd service configuration in `contrib` (and, by extension, some of our packages) now attempts 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 @@ -90,7 +90,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Some minor memory leaks in the switch have been fixed, which improves memory usage on mobile builds - A memory leak in the add-peer loop has been fixed - The admin socket now reports the correct URI strings for SOCKS peers in `getPeers` -- A race condition when dialling a remote node by both the node address and routed prefix simultaneously has been fixed +- A race condition when dialing a remote node by both the node address and routed prefix simultaneously has been fixed - A race condition between the router and the dial code resulting in a panic has been fixed - A panic which could occur when the TUN/TAP interface disappears (e.g. during soft-shutdown) has been fixed - A bug in the semantic versioning script which accompanies Yggdrasil for builds has been fixed @@ -180,7 +180,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [0.3.4] - 2019-03-12 ### Added - Support for multiple listeners (although currently only TCP listeners are supported) -- New multicast behaviour where each multicast interface is given it's own link-local listener and does not depend on the `Listen` configuration +- New multicast behaviour where each multicast interface is given its own link-local listener and does not depend on the `Listen` configuration - Blocking detection in the switch to avoid parenting a blocked peer - Support for adding and removing listeners and multicast interfaces when reloading configuration during runtime - Yggdrasil will now attempt to clean up UNIX admin sockets on startup if left behind by a previous crash @@ -374,7 +374,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Wire format changes (backwards incompatible). - Less maintenance traffic per peer. - Exponential back-off for DHT maintenance traffic (less maintenance traffic for known good peers). -- Iterative DHT (added some time between v0.1.0 and here). +- Iterative DHT (added sometime between v0.1.0 and here). - Use local queue sizes for a sort of local-only backpressure routing, instead of the removed bandwidth estimates, when deciding where to send a packet. ### Removed diff --git a/cmd/genkeys/main.go b/cmd/genkeys/main.go index 7a0d316a..7b48cdc7 100644 --- a/cmd/genkeys/main.go +++ b/cmd/genkeys/main.go @@ -3,7 +3,7 @@ This file generates crypto keys. It prints out a new set of keys each time if finds a "better" one. By default, "better" means a higher NodeID (-> higher IP address). -This is because the IP address format can compress leading 1s in the address, to incrase the number of ID bits in the address. +This is because the IP address format can compress leading 1s in the address, to increase the number of ID bits in the address. If run with the "-sig" flag, it generates signing keys instead. A "better" signing key means one with a higher TreeID. diff --git a/doc/Whitepaper.md b/doc/Whitepaper.md index d7f13279..b65963d1 100644 --- a/doc/Whitepaper.md +++ b/doc/Whitepaper.md @@ -65,12 +65,12 @@ Given the coordinates of any two nodes, it is possible to calculate the length o Traffic is forwarded using a [greedy routing](https://en.wikipedia.org/wiki/Small-world_routing#Greedy_routing) scheme, where each node forwards the packet to a one-hop neighbor that is closer to the destination (according to this distance metric) than the current node. In particular, when a packet needs to be forwarded, a node will forward it to whatever peer is closest to the destination in the greedy [metric space](https://en.wikipedia.org/wiki/Metric_space) used by the network, provided that the peer is closer to the destination than the current node. -If no closer peers are idle, then the packet is queued in FIFO order, with separate queues per destination coords (currently, as a bit of a hack, IPv6 flow labels are embedeed after the end of the significant part of the coords, so queues distinguish between different traffic streams with the same destination). +If no closer peers are idle, then the packet is queued in FIFO order, with separate queues per destination coords (currently, as a bit of a hack, IPv6 flow labels are embedded after the end of the significant part of the coords, so queues distinguish between different traffic streams with the same destination). Whenever the node finishes forwarding a packet to a peer, it checks the queues, and will forward the first packet from the queue with the maximum `/`, i.e. the bandwidth the queue is attempting to use, subject to the constraint that the peer is a valid next hop (i.e. closer to the destination than the current node). If no non-empty queue is available, then the peer is added to the idle set, forward packets when the need arises. This acts as a crude approximation of backpressure routing, where the remote queue sizes are assumed to be equal to the distance of a node from a destination (rather than communicating queue size information), and packets are never forwarded "backwards" through the network, but congestion on a local link is routed around when possible. -The queue selection strategy behaves similar to shortest-queue-first, in that a larger fration of available bandwith to sessions that attempt to use less bandwidth, and is loosely based on the rationale behind some proposed solutions to the [cake-cutting](https://en.wikipedia.org/wiki/Fair_cake-cutting) problem. +The queue selection strategy behaves similar to shortest-queue-first, in that a larger fraction of available bandwidth to sessions that attempt to use less bandwidth, and is loosely based on the rationale behind some proposed solutions to the [cake-cutting](https://en.wikipedia.org/wiki/Fair_cake-cutting) problem. The queue size is limited to 4 MB. If a packet is added to a queue and the total size of all queues is larger than this threshold, then a random queue is selected (with odds proportional to relative queue sizes), and the first packet from that queue is dropped, with the process repeated until the total queue size drops below the allowed threshold. diff --git a/misc/sim/treesim-forward.py b/misc/sim/treesim-forward.py index b717b29d..f7ca509d 100644 --- a/misc/sim/treesim-forward.py +++ b/misc/sim/treesim-forward.py @@ -793,7 +793,7 @@ def timelineDimesTest(): store = makeStoreDimesEdges(path, bestRoot) rootID = "R" + bestRoot[1:] assert rootID in store - # Don't forget to set random seed before setitng times + # Don't forget to set random seed before setting times # To make results reproducible nodeIDs = sorted(store.keys()) random.seed(12345) diff --git a/misc/sim/treesim.go b/misc/sim/treesim.go index a62f9ff4..2b155151 100644 --- a/misc/sim/treesim.go +++ b/misc/sim/treesim.go @@ -424,7 +424,7 @@ func main() { //*/ startNetwork(kstore) //time.Sleep(10*time.Second) - // Note that testPaths only works if pressure is turend off + // Note that testPaths only works if pressure is turned off // Otherwise congestion can lead to routing loops? for finished := false; !finished; { finished = testPaths(kstore) diff --git a/src/address/address.go b/src/address/address.go index eba61708..0569af00 100644 --- a/src/address/address.go +++ b/src/address/address.go @@ -15,9 +15,9 @@ type Address [16]byte type Subnet [8]byte // GetPrefix returns the address prefix used by yggdrasil. -// The current implementation requires this to be a muliple of 8 bits + 7 bits. +// The current implementation requires this to be a multiple of 8 bits + 7 bits. // The 8th bit of the last byte is used to signal nodes (0) or /64 prefixes (1). -// Nodes that configure this differently will be unable to communicate with eachother using IP packets, though routing and the DHT machinery *should* still work. +// Nodes that configure this differently will be unable to communicate with each other using IP packets, though routing and the DHT machinery *should* still work. func GetPrefix() [1]byte { return [...]byte{0x02} } diff --git a/src/config/config.go b/src/config/config.go index 3e1438b8..93b77eed 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -71,7 +71,7 @@ type NodeConfig struct { SigningPrivateKey string `comment:"Your private signing key. DO NOT share this with anyone!"` LinkLocalTCPPort uint16 `comment:"The port number to be used for the link-local TCP listeners for the\nconfigured MulticastInterfaces. This option does not affect listeners\nspecified in the Listen option. Unless you plan to firewall link-local\ntraffic, it is best to leave this as the default value of 0. This\noption cannot currently be changed by reloading config during runtime."` IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."` - IfMTU int `comment:"Maximux Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` + IfMTU int `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` SessionFirewall SessionFirewall `comment:"The session firewall controls who can send/receive network traffic\nto/from. This is useful if you want to protect this node without\nresorting to using a real firewall. This does not affect traffic\nbeing routed via this node to somewhere else. Rules are prioritised as\nfollows: blacklist, whitelist, always allow outgoing, direct, remote."` TunnelRouting TunnelRouting `comment:"Allow tunneling non-Yggdrasil traffic over Yggdrasil. This effectively\nallows you to use Yggdrasil to route to, or to bridge other networks,\nsimilar to a VPN tunnel. Tunnelling works between any two nodes and\ndoes not require them to be directly peered."` SwitchOptions SwitchOptions `comment:"Advanced options for tuning the switch. Normally you will not need\nto edit these options."` diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go index 6c10a2ef..e084d713 100644 --- a/src/crypto/crypto.go +++ b/src/crypto/crypto.go @@ -215,7 +215,7 @@ func GetSharedKey(myPrivKey *BoxPrivKey, return (*BoxSharedKey)(&shared) } -// BoxOpen returns a message and true if it successfull opens a crypto box using the provided shared key and nonce. +// BoxOpen returns a message and true if it successful opens a crypto box using the provided shared key and nonce. func BoxOpen(shared *BoxSharedKey, boxed []byte, nonce *BoxNonce) ([]byte, bool) { diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index 207cd14f..aa149063 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -63,7 +63,7 @@ func (s *tunConn) _read(bs []byte) (err error) { default: isCGA = false } - // Check destiantion addresses + // Check destination addresses switch { case ipv6 && bs[24] == 0x02 && bytes.Equal(s.tun.addr[:16], bs[24:40]): // destination case ipv6 && bs[24] == 0x03 && bytes.Equal(s.tun.subnet[:8], bs[24:32]): // destination diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 67426f4f..9a26432d 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -164,7 +164,7 @@ func (c *Conn) _getDeadlineCancellation(t *time.Time) (util.Cancellation, bool) c := util.CancellationWithDeadline(c.session.cancel, *t) return c, true } else { - // No deadline was set, so just return the existinc cancellation and a dummy value + // No deadline was set, so just return the existing cancellation and a dummy value return c.session.cancel, false } } @@ -279,7 +279,7 @@ func (c *Conn) _write(msg FlowKeyMessage) error { } // WriteFrom should be called by a phony.Actor, and tells the Conn to send a -// message. This is used internaly by Write. If the callback is called with a +// message. This is used internally by Write. If the callback is called with a // non-nil value, then it is safe to reuse the argument FlowKeyMessage. func (c *Conn) WriteFrom(from phony.Actor, msg FlowKeyMessage, callback func(error)) { c.Act(from, func() { diff --git a/src/yggdrasil/core_test.go b/src/yggdrasil/core_test.go index 364ed0b0..5f302c23 100644 --- a/src/yggdrasil/core_test.go +++ b/src/yggdrasil/core_test.go @@ -22,7 +22,7 @@ func GenerateConfig() *config.NodeConfig { return cfg } -// GetLoggerWithPrefix creates a new logger instance wih prefix. +// GetLoggerWithPrefix creates a new logger instance with 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()) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 575c8b1a..72165948 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -20,7 +20,7 @@ const ( ) // dhtInfo represents everything we know about a node in the DHT. -// This includes its key, a cache of it's NodeID, coords, and timing/ping related info for deciding who/when to ping nodes for maintenance. +// This includes its key, a cache of its NodeID, coords, and timing/ping related info for deciding who/when to ping nodes for maintenance. type dhtInfo struct { nodeID_hidden *crypto.NodeID key crypto.BoxPubKey diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index 47a68813..e9da97a2 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -17,7 +17,7 @@ type Dialer struct { core *Core } -// Dial opens a session to the given node. The first paramter should be "nodeid" +// Dial opens a session to the given node. The first parameter should be "nodeid" // and the second parameter should contain a hexadecimal representation of the // target node ID. It uses DialContext internally. func (d *Dialer) Dial(network, address string) (net.Conn, error) { diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index cd40a9ea..157ea525 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -123,7 +123,7 @@ func (l *link) listen(uri string) error { } func (l *link) create(msgIO linkInterfaceMsgIO, name, linkType, local, remote string, incoming, force bool) (*linkInterface, error) { - // Technically anything unique would work for names, but lets pick something human readable, just for debugging + // Technically anything unique would work for names, but let's pick something human readable, just for debugging intf := linkInterface{ name: name, link: l, diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 381e6917..4206857e 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -19,7 +19,7 @@ import ( // The peers struct represents peers with an active connection. // Incoming packets are passed to the corresponding peer, which handles them somehow. // In most cases, this involves passing the packet to the handler for outgoing traffic to another peer. -// In other cases, it's link protocol traffic used to build the spanning tree, in which case this checks signatures and passes the message along to the switch. +// In other cases, its link protocol traffic is used to build the spanning tree, in which case this checks signatures and passes the message along to the switch. type peers struct { core *Core mutex sync.Mutex // Synchronize writes to atomic @@ -90,7 +90,7 @@ func (ps *peers) putPorts(ports map[switchPort]*peer) { ps.ports.Store(ports) } -// Information known about a peer, including thier box/sig keys, precomputed shared keys (static and ephemeral) and a handler for their outgoing traffic +// Information known about a peer, including their box/sig keys, precomputed shared keys (static and ephemeral) and a handler for their outgoing traffic type peer struct { phony.Inbox core *Core @@ -356,7 +356,7 @@ func (p *peer) _handleSwitchMsg(packet []byte) { p.dinfo = nil return } - // Pass a mesage to the dht informing it that this peer (still) exists + // Pass a message to the dht informing it that this peer (still) exists loc.coords = loc.coords[:len(loc.coords)-1] p.dinfo = &dhtInfo{ key: p.box, diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index f52dcbe8..b0fdbb51 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -144,7 +144,7 @@ func (sinfo *searchInfo) doSearchStep() { sinfo.time = time.Now() } -// If we've recenty sent a ping for this search, do nothing. +// If we've recently sent a ping for this search, do nothing. // Otherwise, doSearchStep and schedule another continueSearch to happen after search_RETRY_TIME. func (sinfo *searchInfo) continueSearch() { sinfo.doSearchStep() diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 743dd7a4..ecc7d499 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -39,7 +39,7 @@ type sessionInfo struct { theirMTU uint16 // myMTU uint16 // wasMTUFixed bool // Was the MTU fixed by a receive error? - timeOpened time.Time // Time the sessino was opened + timeOpened time.Time // Time the session was opened time time.Time // Time we last received a packet mtuTime time.Time // time myMTU was last changed pingTime time.Time // time the first ping was sent since the last received packet @@ -55,7 +55,7 @@ type sessionInfo struct { callbacks []chan func() // Finished work from crypto workers } -// Represents a session ping/pong packet, andincludes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU. +// Represents a session ping/pong packet, and includes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU. type sessionPing struct { SendPermPub crypto.BoxPubKey // Sender's permanent key Handle crypto.Handle // Random number to ID session @@ -175,7 +175,7 @@ func (ss *sessions) getByTheirPerm(key *crypto.BoxPubKey) (*sessionInfo, bool) { } // Creates a new session and lazily cleans up old existing sessions. This -// includse initializing session info to sane defaults (e.g. lowest supported +// includes initializing session info to sane defaults (e.g. lowest supported // MTU). func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { // TODO: this check definitely needs to be moved @@ -415,7 +415,7 @@ func (sinfo *sessionInfo) _updateNonce(theirNonce *crypto.BoxNonce) { } // Resets all sessions to an uninitialized state. -// Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change. +// Called after coord changes, so attempts to use a session will trigger a new ping and notify the remote end of the coord change. // Only call this from the router actor. func (ss *sessions) reset() { for _, _sinfo := range ss.sinfos { diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index ab888a76..653b12f1 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -107,7 +107,7 @@ func (l *switchLocator) getCoords() []byte { return bs } -// Returns true if the this locator represents an ancestor of the locator given as an argument. +// Returns true if this locator represents an ancestor of the locator given as an argument. // Ancestor means that it's the parent node, or the parent of parent, and so on... func (x *switchLocator) isAncestorOf(y *switchLocator) bool { if x.root != y.root { @@ -381,7 +381,7 @@ func (t *switchTable) handleMsg(msg *switchMsg, fromPort switchPort) { // Then the tricky part, it decides if it should update our own locator as a result. // That happens if this node is already our parent, or is advertising a better root, or is advertising a better path to the same root, etc... // There are a lot of very delicate order sensitive checks here, so its' best to just read the code if you need to understand what it's doing. -// It's very important to not change the order of the statements in the case function unless you're absolutely sure that it's safe, including safe if used along side nodes that used the previous order. +// It's very important to not change the order of the statements in the case function unless you're absolutely sure that it's safe, including safe if used alongside nodes that used the previous order. // Set the third arg to true if you're reprocessing an old message, e.g. to find a new parent after one disconnects, to avoid updating some timing related things. func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, reprocessing bool) { // TODO directly use a switchMsg instead of switchMessage + sigs diff --git a/src/yggdrasil/version.go b/src/yggdrasil/version.go index f71780e0..91fcc825 100644 --- a/src/yggdrasil/version.go +++ b/src/yggdrasil/version.go @@ -1,14 +1,14 @@ package yggdrasil // This file contains the version metadata struct -// Used in the inital connection setup and key exchange +// Used in the initial connection setup and key exchange // Some of this could arguably go in wire.go instead import "github.com/yggdrasil-network/yggdrasil-go/src/crypto" // This is the version-specific metadata exchanged at the start of a connection. -// It must always beign with the 4 bytes "meta" and a wire formatted uint64 major version number. -// The current version also includes a minor version number, and the box/sig/link keys that need to be exchanged to open an connection. +// It must always begin with the 4 bytes "meta" and a wire formatted uint64 major version number. +// The current version also includes a minor version number, and the box/sig/link keys that need to be exchanged to open a connection. type version_metadata struct { meta [4]byte ver uint64 // 1 byte in this version From 16e55992b6d1a3e278d6faa2b0c64def2470b2ab Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 29 Nov 2019 11:06:08 +0000 Subject: [PATCH 0476/1109] Move yggdrasil.conf to ALLUSERSPROFILE --- contrib/msi/build-msi.sh | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index 5d8eb7db..4e68b406 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -52,9 +52,14 @@ fi [ "${PKGARCH}" == "x86" ] && GOOS=windows GOARCH=386 CGO_ENABLED=0 ./build # Create the postinstall script -cat > config.bat << EOF -if not exist yggdrasil.conf ( - yggdrasil.exe -genconf > yggdrasil.conf +cat > updateconfig.bat << EOF +if not exist %ALLUSERSPROFILE%\\Yggdrasil ( + mkdir %ALLUSERSPROFILE%\\Yggdrasil +) +if not exist %ALLUSERSPROFILE%\\Yggdrasil\\yggdrasil.conf ( + if exist yggdrasil.exe ( + yggdrasil.exe -genconf > %ALLUSERSPROFILE%\\Yggdrasil\\yggdrasil.conf + ) ) EOF @@ -95,9 +100,10 @@ cat > wix.xml << EOF Id="*" Keywords="Installer" Description="Yggdrasil Network Installer" - Comments="This is the Yggdrasil Network binary." + Comments="This is the Yggdrasil Network router for Windows." Manufacturer="github.com/yggdrasil-network" InstallerVersion="200" + InstallScope="perMachine" Languages="1033" Compressed="yes" Platform="${PKGARCH}" @@ -133,7 +139,7 @@ cat > wix.xml << EOF Name="yggdrasil" Start="auto" Type="ownProcess" - Arguments='-useconffile "[YggdrasilInstallFolder]yggdrasil.conf" -logto "[YggdrasilInstallFolder]yggdrasil.log"' + Arguments='-useconffile "%ALLUSERSPROFILE%\\Yggdrasil\\yggdrasil.conf" -logto "%ALLUSERSPROFILE%\\Yggdrasil\\yggdrasil.log"' Vital="yes" /> wix.xml << EOF Id="Configbat" Name="updateconfig.bat" DiskId="1" - Source="config.bat" + Source="updateconfig.bat" KeyPath="yes"/> @@ -183,13 +189,13 @@ cat > wix.xml << EOF Directory="YggdrasilInstallFolder" ExeCommand="cmd.exe /c updateconfig.bat" Execute="deferred" - Impersonate="no" - Return="check" /> + Return="check" + Impersonate="yes" /> + Before="StartServices"> NOT Installed AND NOT REMOVE @@ -202,4 +208,4 @@ EOF CANDLEFLAGS="-nologo" LIGHTFLAGS="-nologo -spdb -sice:ICE71 -sice:ICE61" wixbin/candle $CANDLEFLAGS -out ${PKGNAME}-${PKGVERSION}-${PKGARCH}.wixobj -arch ${PKGARCH} wix.xml && \ -wixbin/light $LIGHTFLAGS -out ${PKGNAME}-${PKGVERSION}-${PKGARCH}.msi ${PKGNAME}-${PKGVERSION}-${PKGARCH}.wixobj +wixbin/light $LIGHTFLAGS -ext WixUtilExtension.dll -out ${PKGNAME}-${PKGVERSION}-${PKGARCH}.msi ${PKGNAME}-${PKGVERSION}-${PKGARCH}.wixobj From 729d2ca2ba991b0a26d9a58acff1664b34b1002c Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 29 Nov 2019 17:14:27 -0600 Subject: [PATCH 0477/1109] Update crypto.go --- src/crypto/crypto.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go index e084d713..3a6e02d1 100644 --- a/src/crypto/crypto.go +++ b/src/crypto/crypto.go @@ -215,7 +215,7 @@ func GetSharedKey(myPrivKey *BoxPrivKey, return (*BoxSharedKey)(&shared) } -// BoxOpen returns a message and true if it successful opens a crypto box using the provided shared key and nonce. +// BoxOpen returns a message and true if it successfully opens a crypto box using the provided shared key and nonce. func BoxOpen(shared *BoxSharedKey, boxed []byte, nonce *BoxNonce) ([]byte, bool) { From 4159ccb893205f532a5f83d26072ef7a46d8129b Mon Sep 17 00:00:00 2001 From: Anatolii Kurotych Date: Sat, 30 Nov 2019 16:05:44 +0200 Subject: [PATCH 0478/1109] Fix return value in Multicast.Stop() --- src/multicast/multicast.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 4e0b4f35..93bf94bb 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -112,7 +112,7 @@ func (m *Multicast) Stop() error { err = m._stop() }) m.log.Debugln("Stopped multicast module") - return nil + return err } func (m *Multicast) _stop() error { From 468e3661684dfe11dbc4dcc5856a166b87e21009 Mon Sep 17 00:00:00 2001 From: Anatolii Kurotych Date: Sat, 30 Nov 2019 20:46:29 +0200 Subject: [PATCH 0479/1109] Use loglevel instead comma-separated list of logging --- cmd/yggdrasil/main.go | 45 ++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 981c6efc..813e950b 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -132,6 +132,32 @@ func doGenconf(isjson bool) string { return string(bs) } +func setLogLevel(loglevel string, logger *log.Logger) { + levels := [...]string{"error", "warn", "info", "debug", "trace"} + loglevel = strings.ToLower(loglevel) + + contains := func() bool { + for _, l := range levels { + if l == loglevel { + return true + } + } + return false + } + + if !contains() { // set default log level + logger.Infoln("Loglevel parse failed. Set default level(info)") + loglevel = "info" + } + + for _, l := range levels { + logger.EnableLevel(l) + if l == loglevel { + break + } + } +} + // The main function is responsible for configuring and starting Yggdrasil. func main() { // Configure the command line parameters. @@ -142,10 +168,10 @@ func main() { confjson := flag.Bool("json", false, "print configuration from -genconf or -normaliseconf as JSON instead of HJSON") autoconf := flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)") ver := flag.Bool("version", false, "prints the version of this build") - logging := flag.String("logging", "info,warn,error", "comma-separated list of logging levels to enable") logto := flag.String("logto", "stdout", "file path to log to, \"syslog\" or \"stdout\"") getaddr := flag.Bool("address", false, "returns the IPv6 address as derived from the supplied configuration") getsnet := flag.Bool("subnet", false, "returns the IPv6 subnet as derived from the supplied configuration") + loglevel := flag.String("loglevel", "info", "loglevel to enable") flag.Parse() var cfg *config.NodeConfig @@ -239,20 +265,9 @@ func main() { logger = log.New(os.Stdout, "", log.Flags()) logger.Warnln("Logging defaulting to stdout") } - //logger.EnableLevel("error") - //logger.EnableLevel("warn") - //logger.EnableLevel("info") - if levels := strings.Split(*logging, ","); len(levels) > 0 { - for _, level := range levels { - l := strings.TrimSpace(level) - switch l { - case "error", "warn", "info", "trace", "debug": - logger.EnableLevel(l) - default: - continue - } - } - } + + setLogLevel(*loglevel, logger) + // Setup the Yggdrasil node itself. The node{} type includes a Core, so we // don't need to create this manually. n := node{} From bf5d5b2269329906512e887701a9d27a382b7057 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 4 Dec 2019 09:29:30 +0000 Subject: [PATCH 0480/1109] Rename service from 'yggdrasil' to 'Yggdrasil' --- contrib/msi/build-msi.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index 4e68b406..cfa2728d 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -136,7 +136,7 @@ cat > wix.xml << EOF DisplayName="Yggdrasil Service" ErrorControl="normal" LoadOrderGroup="NetworkProvider" - Name="yggdrasil" + Name="Yggdrasil" Start="auto" Type="ownProcess" Arguments='-useconffile "%ALLUSERSPROFILE%\\Yggdrasil\\yggdrasil.conf" -logto "%ALLUSERSPROFILE%\\Yggdrasil\\yggdrasil.log"' From 1d4119950172d8d65ba24e3c7ae146a89fdf5ec9 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 10 Dec 2019 10:55:20 +0000 Subject: [PATCH 0481/1109] Move Wintun to separate feature --- contrib/msi/build-msi.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index cfa2728d..cf69d6bf 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -177,13 +177,16 @@ cat > wix.xml << EOF SourceFile="${PKGMSMNAME}" /> - - + + + + + Date: Tue, 10 Dec 2019 11:17:15 +0000 Subject: [PATCH 0482/1109] Update metadata --- contrib/msi/build-msi.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index cf69d6bf..3f80487d 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -100,7 +100,7 @@ cat > wix.xml << EOF Id="*" Keywords="Installer" Description="Yggdrasil Network Installer" - Comments="This is the Yggdrasil Network router for Windows." + Comments="Yggdrasil Network standalone router for Windows." Manufacturer="github.com/yggdrasil-network" InstallerVersion="200" InstallScope="perMachine" @@ -115,7 +115,8 @@ cat > wix.xml << EOF + EmbedCab="yes" + CompressionLevel="high" /> From 3e388cd7f9b6adedf768fea512a7c8e3801d2c49 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 10 Dec 2019 11:27:49 +0000 Subject: [PATCH 0483/1109] Try to avoid breaking Wintun during upgrades --- contrib/msi/build-msi.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index 3f80487d..358fb775 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -185,6 +185,9 @@ cat > wix.xml << EOF + + NOT UPGRADINGPRODUCTCODE + From 6f927b06133b5a2b5d01cdd8757e5509e45e5403 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 10 Dec 2019 11:33:52 +0000 Subject: [PATCH 0484/1109] Reverse upgrade condition --- contrib/msi/build-msi.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index 358fb775..b6601968 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -186,7 +186,7 @@ cat > wix.xml << EOF - NOT UPGRADINGPRODUCTCODE + UPGRADINGPRODUCTCODE From 4762edc2b3067884c696755d51907c5e467371c0 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 10 Dec 2019 11:38:58 +0000 Subject: [PATCH 0485/1109] Package display name --- contrib/msi/build-msi.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index b6601968..e291074e 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -83,12 +83,18 @@ else exit 1 fi +if [ $PKGNAME != "master" ]; then + PKGDISPLAYNAME="Yggdrasil Network (${PKGNAME} branch)" +elif + PKGDISPLAYNAME="Yggdrasil Network" +fi + # Generate the wix.xml file cat > wix.xml << EOF Date: Tue, 10 Dec 2019 11:40:16 +0000 Subject: [PATCH 0486/1109] Fix syntax error in build-msi.sh --- contrib/msi/build-msi.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index e291074e..421481cd 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -85,7 +85,7 @@ fi if [ $PKGNAME != "master" ]; then PKGDISPLAYNAME="Yggdrasil Network (${PKGNAME} branch)" -elif +else PKGDISPLAYNAME="Yggdrasil Network" fi From ff5de89762bdda544e52e6d2256e4a92e008fc5a Mon Sep 17 00:00:00 2001 From: cathugger Date: Wed, 11 Dec 2019 15:24:43 +0200 Subject: [PATCH 0487/1109] util: fix possible OOB in IPv4 flowkey calc, use switch there ihl may grow upto 15*4=60 so extract and check it before using it as offset in flowkey calculation. also replace IFs with switches for protocol matching as it's less redundant and nicer to document. --- src/util/util.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/util/util.go b/src/util/util.go index a7a54562..c20421d5 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -103,11 +103,11 @@ func GetFlowKey(bs []byte) uint64 { // Get the IP protocol version from the packet switch bs[0] & 0xf0 { case 0x40: // IPv4 packet - // Check the packet meets minimum UDP packet length - if len(bs) >= 24 { - // Is the protocol TCP, UDP or SCTP? - if bs[9] == 0x06 || bs[9] == 0x11 || bs[9] == 0x84 { - ihl := bs[0] & 0x0f * 4 // Header length + ihl := (bs[0] & 0x0f) * 4 // whole IPv4 header length (min 20) + // 8 is minimum UDP packet length + if ihl >= 20 && len(bs)-int(ihl) >= 8 { + switch bs[9] /* protocol */ { + case 0x06 /* TCP */, 0x11 /* UDP */, 0x84 /* SCTP */ : flowkey = uint64(bs[9])<<32 /* proto */ | uint64(bs[ihl+0])<<24 | uint64(bs[ihl+1])<<16 /* sport */ | uint64(bs[ihl+2])<<8 | uint64(bs[ihl+3]) /* dport */ @@ -119,8 +119,8 @@ func GetFlowKey(bs []byte) uint64 { // If the flowlabel isn't present, make protokey from proto | sport | dport // if the packet meets minimum UDP packet length if flowkey == 0 && len(bs) >= 48 { - // Is the protocol TCP, UDP or SCTP? - if bs[6] == 0x06 || bs[6] == 0x11 || bs[6] == 0x84 { + switch bs[9] /* protocol */ { + case 0x06 /* TCP */, 0x11 /* UDP */, 0x84 /* SCTP */ : flowkey = uint64(bs[6])<<32 /* proto */ | uint64(bs[40])<<24 | uint64(bs[41])<<16 /* sport */ | uint64(bs[42])<<8 | uint64(bs[43]) /* dport */ From 5bd9391c615327379b885ed07d003918486dec5a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 25 Dec 2019 17:45:24 -0600 Subject: [PATCH 0488/1109] slightly cleaner way for yggdrasilctl to os.exit, making sure defers are called --- cmd/yggdrasilctl/main.go | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index 94d90842..697a3faa 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -25,14 +25,20 @@ import ( type admin_info map[string]interface{} func main() { + // makes sure we can use defer and still return an error code to the OS + os.Exit(run()) +} + +func run() int { logbuffer := &bytes.Buffer{} logger := log.New(logbuffer, "", log.Flags()) - defer func() { + defer func() int { if r := recover(); r != nil { logger.Println("Fatal error:", r) fmt.Print(logbuffer) - os.Exit(1) + return 1 } + return 0 }() endpoint := defaults.GetDefaults().DefaultAdminListen @@ -62,12 +68,12 @@ func main() { fmt.Println("Build name:", version.BuildName()) fmt.Println("Build version:", version.BuildVersion()) fmt.Println("To get the version number of the running Yggdrasil node, run", os.Args[0], "getSelf") - return + return 0 } if len(args) == 0 { flag.Usage() - return + return 0 } if *server == endpoint { @@ -176,15 +182,15 @@ func main() { } else { fmt.Println("Admin socket returned an error but didn't specify any error text") } - os.Exit(1) + return 1 } if _, ok := recv["request"]; !ok { fmt.Println("Missing request in response (malformed response?)") - os.Exit(1) + return 1 } if _, ok := recv["response"]; !ok { fmt.Println("Missing response body (malformed response?)") - os.Exit(1) + return 1 } req := recv["request"].(map[string]interface{}) res := recv["response"].(map[string]interface{}) @@ -193,7 +199,7 @@ func main() { if json, err := json.MarshalIndent(res, "", " "); err == nil { fmt.Println(string(json)) } - os.Exit(0) + return 0 } switch strings.ToLower(req["request"].(string)) { @@ -434,7 +440,7 @@ func main() { } if v, ok := recv["status"]; ok && v != "success" { - os.Exit(1) + return 1 } - os.Exit(0) + return 0 } From 9fac5355eb15a03d1eed04365ccc475c35e98294 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 25 Dec 2019 18:55:29 -0600 Subject: [PATCH 0489/1109] make searches more parallel --- src/yggdrasil/search.go | 32 +++++++------------------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index b0fdbb51..903f9c30 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -16,7 +16,6 @@ package yggdrasil import ( "errors" - "sort" "time" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" @@ -104,24 +103,6 @@ func (sinfo *searchInfo) addToSearch(res *dhtRes) { sinfo.toVisit = append(sinfo.toVisit, info) } } - // Deduplicate - vMap := make(map[crypto.NodeID]*dhtInfo) - for _, info := range sinfo.toVisit { - vMap[*info.getNodeID()] = info - } - sinfo.toVisit = sinfo.toVisit[:0] - for _, info := range vMap { - sinfo.toVisit = append(sinfo.toVisit, info) - } - // Sort - sort.SliceStable(sinfo.toVisit, func(i, j int) bool { - // Should return true if i is closer to the destination than j - return dht_ordered(&res.Dest, sinfo.toVisit[i].getNodeID(), sinfo.toVisit[j].getNodeID()) - }) - // Truncate to some maximum size - if len(sinfo.toVisit) > search_MAX_SEARCH_SIZE { - sinfo.toVisit = sinfo.toVisit[:search_MAX_SEARCH_SIZE] - } } // If there are no nodes left toVisit, then this cleans up the search. @@ -136,12 +117,13 @@ func (sinfo *searchInfo) doSearchStep() { return } // Send to the next search target - var next *dhtInfo - next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:] - rq := dhtReqKey{next.key, sinfo.dest} - sinfo.searches.router.dht.addCallback(&rq, sinfo.handleDHTRes) - sinfo.searches.router.dht.ping(next, &sinfo.dest) - sinfo.time = time.Now() + for _, next := range sinfo.toVisit { + rq := dhtReqKey{next.key, sinfo.dest} + sinfo.searches.router.dht.addCallback(&rq, sinfo.handleDHTRes) + sinfo.searches.router.dht.ping(next, &sinfo.dest) + sinfo.time = time.Now() + } + sinfo.toVisit = sinfo.toVisit[:0] } // If we've recently sent a ping for this search, do nothing. From 201dbec63d2bc629bb26ed7515112d223f5147cb Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 25 Dec 2019 19:01:20 -0600 Subject: [PATCH 0490/1109] always keep the 2 closest nodes in each direction around the dht ring, possibly helps things recover faster after joins/leaves --- src/yggdrasil/dht.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 72165948..013fd1e4 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -387,6 +387,8 @@ func (t *dht) getImportant() []*dhtInfo { if dist < minDist { minDist = dist important = append(important, info) + } else if len(important) < 2 { + important = append(important, info) } } var temp []*dhtInfo @@ -397,6 +399,8 @@ func (t *dht) getImportant() []*dhtInfo { if dist < minDist { minDist = dist temp = append(temp, info) + } else if len(temp) < 2 { + temp = append(temp, info) } } for idx := len(temp) - 1; idx >= 0; idx-- { From 8513f8f4dc019ad1f589e8db5d3d18b3ef290a90 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 4 Jan 2020 16:08:48 -0600 Subject: [PATCH 0491/1109] constant space searches that should play nicer if searching for an unreachable destination --- src/yggdrasil/search.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 903f9c30..4708a705 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -37,7 +37,7 @@ type searchInfo struct { mask crypto.NodeID time time.Time toVisit []*dhtInfo - visited map[crypto.NodeID]bool + visited crypto.NodeID // Closest address visited so far callback func(*sessionInfo, error) // TODO context.Context for timeout and cancellation } @@ -93,12 +93,16 @@ func (sinfo *searchInfo) handleDHTRes(res *dhtRes) { func (sinfo *searchInfo) addToSearch(res *dhtRes) { // Add responses to toVisit if closer to dest than the res node from := dhtInfo{key: res.Key, coords: res.Coords} - sinfo.visited[*from.getNodeID()] = true + if dht_ordered(&sinfo.dest, from.getNodeID(), &sinfo.visited) { + // Closer to the destination, so update visited + sinfo.visited = *from.getNodeID() + } for _, info := range res.Infos { - if *info.getNodeID() == sinfo.searches.router.dht.nodeID || sinfo.visited[*info.getNodeID()] { + if *info.getNodeID() == sinfo.visited { + // dht_ordered could return true here, but we want to skip it in this case continue } - if dht_ordered(&sinfo.dest, info.getNodeID(), from.getNodeID()) { + if dht_ordered(&sinfo.dest, info.getNodeID(), &sinfo.visited) { // Response is closer to the destination sinfo.toVisit = append(sinfo.toVisit, info) } @@ -148,7 +152,7 @@ func (sinfo *searchInfo) continueSearch() { // Calls create search, and initializes the iterative search parts of the struct before returning it. func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo { sinfo := s.createSearch(dest, mask, callback) - sinfo.visited = make(map[crypto.NodeID]bool) + sinfo.visited = s.router.dht.nodeID loc := s.router.core.switchTable.getLocator() sinfo.toVisit = append(sinfo.toVisit, &dhtInfo{ key: s.router.core.boxPub, From 8358fe5c5cc4fb66c987abb449c7f59c7e6b0ece Mon Sep 17 00:00:00 2001 From: Adam Ruzicka Date: Sun, 5 Jan 2020 17:27:54 +0000 Subject: [PATCH 0492/1109] Unify MTU datatypes across the codebase The codebase uses int and unit16 to represent MTU randomly. This change unifies it to a MTU type from types package, which is currently uint16. --- src/config/config.go | 5 ++++- src/defaults/defaults.go | 6 ++++-- src/tuntap/iface.go | 2 +- src/tuntap/tun.go | 15 +++++++++------ src/tuntap/tun_bsd.go | 6 +++--- src/tuntap/tun_darwin.go | 6 +++--- src/tuntap/tun_linux.go | 8 ++++---- src/tuntap/tun_windows.go | 10 +++++----- src/types/types.go | 3 +++ src/yggdrasil/api.go | 8 ++++---- src/yggdrasil/conn.go | 7 +++++-- src/yggdrasil/session.go | 10 +++++----- src/yggdrasil/wire.go | 2 +- 13 files changed, 51 insertions(+), 37 deletions(-) create mode 100644 src/types/types.go diff --git a/src/config/config.go b/src/config/config.go index 93b77eed..95d9bbd1 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -22,8 +22,11 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" + "github.com/yggdrasil-network/yggdrasil-go/src/types" ) +type MTU = types.MTU + // NodeState represents the active and previous configuration of an Yggdrasil // node. A NodeState object is returned when starting an Yggdrasil node. Note // that this structure and related functions are likely to disappear soon. @@ -71,7 +74,7 @@ type NodeConfig struct { SigningPrivateKey string `comment:"Your private signing key. DO NOT share this with anyone!"` LinkLocalTCPPort uint16 `comment:"The port number to be used for the link-local TCP listeners for the\nconfigured MulticastInterfaces. This option does not affect listeners\nspecified in the Listen option. Unless you plan to firewall link-local\ntraffic, it is best to leave this as the default value of 0. This\noption cannot currently be changed by reloading config during runtime."` IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."` - IfMTU int `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` + IfMTU MTU `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` SessionFirewall SessionFirewall `comment:"The session firewall controls who can send/receive network traffic\nto/from. This is useful if you want to protect this node without\nresorting to using a real firewall. This does not affect traffic\nbeing routed via this node to somewhere else. Rules are prioritised as\nfollows: blacklist, whitelist, always allow outgoing, direct, remote."` TunnelRouting TunnelRouting `comment:"Allow tunneling non-Yggdrasil traffic over Yggdrasil. This effectively\nallows you to use Yggdrasil to route to, or to bridge other networks,\nsimilar to a VPN tunnel. Tunnelling works between any two nodes and\ndoes not require them to be directly peered."` SwitchOptions SwitchOptions `comment:"Advanced options for tuning the switch. Normally you will not need\nto edit these options."` diff --git a/src/defaults/defaults.go b/src/defaults/defaults.go index ceb58ba8..a0d372e4 100644 --- a/src/defaults/defaults.go +++ b/src/defaults/defaults.go @@ -1,5 +1,7 @@ package defaults +import "github.com/yggdrasil-network/yggdrasil-go/src/types" + // 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. @@ -14,7 +16,7 @@ type platformDefaultParameters struct { DefaultMulticastInterfaces []string // TUN/TAP - MaximumIfMTU int - DefaultIfMTU int + MaximumIfMTU types.MTU + DefaultIfMTU types.MTU DefaultIfName string } diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 5efbc8a7..1e5902e8 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -55,7 +55,7 @@ type tunReader struct { func (r *tunReader) _read() { // Get a slice to store the packet in - recvd := util.ResizeBytes(util.GetBytes(), r.tun.mtu+TUN_OFFSET_BYTES) + recvd := util.ResizeBytes(util.GetBytes(), int(r.tun.mtu)+TUN_OFFSET_BYTES) // Wait for a packet to be delivered to us through the TUN adapter n, err := r.tun.iface.Read(recvd, TUN_OFFSET_BYTES) if n <= TUN_OFFSET_BYTES || err != nil { diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 807ce7fb..be0ea708 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -25,8 +25,11 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" + "github.com/yggdrasil-network/yggdrasil-go/src/types" ) +type MTU = types.MTU + const tun_IPv6_HEADER_LENGTH = 40 // TunAdapter represents a running TUN interface and extends the @@ -46,7 +49,7 @@ type TunAdapter struct { subnet address.Subnet ckr cryptokey icmpv6 ICMPv6 - mtu int + mtu MTU iface tun.Device phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below //mutex sync.RWMutex // Protects the below @@ -63,7 +66,7 @@ type TunOptions struct { // Gets the maximum supported MTU for the platform based on the defaults in // defaults.GetDefaults(). -func getSupportedMTU(mtu int) int { +func getSupportedMTU(mtu MTU) MTU { if mtu < 1280 { return 1280 } @@ -85,7 +88,7 @@ func (tun *TunAdapter) Name() string { // MTU gets the adapter's MTU. This can range between 1280 and 65535, although // the maximum value is determined by your platform. The returned value will // never exceed that of MaximumMTU(). -func (tun *TunAdapter) MTU() int { +func (tun *TunAdapter) MTU() MTU { return getSupportedMTU(tun.mtu) } @@ -96,14 +99,14 @@ func DefaultName() string { // DefaultMTU gets the default TUN interface MTU for your platform. This can // be as high as MaximumMTU(), depending on platform, but is never lower than 1280. -func DefaultMTU() int { +func DefaultMTU() MTU { return defaults.GetDefaults().DefaultIfMTU } // MaximumMTU returns the maximum supported TUN interface MTU for your // platform. This can be as high as 65535, depending on platform, but is never // lower than 1280. -func MaximumMTU() int { +func MaximumMTU() MTU { return defaults.GetDefaults().MaximumIfMTU } @@ -165,7 +168,7 @@ func (tun *TunAdapter) _start() error { if tun.MTU() != current.IfMTU { tun.log.Warnf("Warning: Interface MTU %d automatically adjusted to %d (supported range is 1280-%d)", current.IfMTU, tun.MTU(), MaximumMTU()) } - tun.core.SetMaximumSessionMTU(uint16(tun.MTU())) + tun.core.SetMaximumSessionMTU(tun.MTU()) tun.isOpen = true go tun.handler() tun.reader.Act(nil, tun.reader._read) // Start the reader diff --git a/src/tuntap/tun_bsd.go b/src/tuntap/tun_bsd.go index 219e3485..4710e8cd 100644 --- a/src/tuntap/tun_bsd.go +++ b/src/tuntap/tun_bsd.go @@ -73,14 +73,14 @@ type in6_ifreq_lifetime struct { } // Configures the TUN adapter with the correct IPv6 address and MTU. -func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { - iface, err := wgtun.CreateTUN(ifname, mtu) +func (tun *TunAdapter) setup(ifname string, addr string, mtu MTU) error { + iface, err := wgtun.CreateTUN(ifname, int(mtu)) if err != nil { panic(err) } tun.iface = iface if mtu, err := iface.MTU(); err == nil { - tun.mtu = getSupportedMTU(mtu) + tun.mtu = getSupportedMTU(MTU(mtu)) } else { tun.mtu = 0 } diff --git a/src/tuntap/tun_darwin.go b/src/tuntap/tun_darwin.go index cf2fbfb4..b984e164 100644 --- a/src/tuntap/tun_darwin.go +++ b/src/tuntap/tun_darwin.go @@ -16,17 +16,17 @@ import ( ) // Configures the "utun" adapter with the correct IPv6 address and MTU. -func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { +func (tun *TunAdapter) setup(ifname string, addr string, mtu MTU) error { if ifname == "auto" { ifname = "utun" } - iface, err := wgtun.CreateTUN(ifname, mtu) + iface, err := wgtun.CreateTUN(ifname, int(mtu)) if err != nil { panic(err) } tun.iface = iface if mtu, err := iface.MTU(); err == nil { - tun.mtu = getSupportedMTU(mtu) + tun.mtu = getSupportedMTU(MTU(mtu)) } else { tun.mtu = 0 } diff --git a/src/tuntap/tun_linux.go b/src/tuntap/tun_linux.go index 4206b26a..ad7b64ef 100644 --- a/src/tuntap/tun_linux.go +++ b/src/tuntap/tun_linux.go @@ -10,17 +10,17 @@ import ( ) // Configures the TUN adapter with the correct IPv6 address and MTU. -func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { +func (tun *TunAdapter) setup(ifname string, addr string, mtu MTU) error { if ifname == "auto" { ifname = "\000" } - iface, err := wgtun.CreateTUN(ifname, mtu) + iface, err := wgtun.CreateTUN(ifname, int(mtu)) if err != nil { panic(err) } tun.iface = iface if mtu, err := iface.MTU(); err == nil { - tun.mtu = getSupportedMTU(mtu) + tun.mtu = getSupportedMTU(MTU(mtu)) } else { tun.mtu = 0 } @@ -43,7 +43,7 @@ func (tun *TunAdapter) setupAddress(addr string) error { if err := netlink.AddrAdd(nlintf, nladdr); err != nil { return err } - if err := netlink.LinkSetMTU(nlintf, tun.mtu); err != nil { + if err := netlink.LinkSetMTU(nlintf, int(tun.mtu)); err != nil { return err } if err := netlink.LinkSetUp(nlintf); err != nil { diff --git a/src/tuntap/tun_windows.go b/src/tuntap/tun_windows.go index 9b5f7b0c..8eb21658 100644 --- a/src/tuntap/tun_windows.go +++ b/src/tuntap/tun_windows.go @@ -19,7 +19,7 @@ import ( // This is to catch Windows platforms // Configures the TUN adapter with the correct IPv6 address and MTU. -func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { +func (tun *TunAdapter) setup(ifname string, addr string, mtu MTU) error { if ifname == "auto" { ifname = defaults.GetDefaults().DefaultIfName } @@ -30,7 +30,7 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { if guid, err = windows.GUIDFromString("{8f59971a-7872-4aa6-b2eb-061fc4e9d0a7}"); err != nil { return err } - if iface, err = wgtun.CreateTUNWithRequestedGUID(ifname, &guid, mtu); err != nil { + if iface, err = wgtun.CreateTUNWithRequestedGUID(ifname, &guid, int(mtu)); err != nil { return err } tun.iface = iface @@ -42,15 +42,15 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { tun.log.Errorln("Failed to set up TUN MTU:", err) return err } - if mtu, err = iface.MTU(); err == nil { - tun.mtu = mtu + if mtu, err := iface.MTU(); err == nil { + tun.mtu = MTU(mtu) } return nil }) } // Sets the MTU of the TAP adapter. -func (tun *TunAdapter) setupMTU(mtu int) error { +func (tun *TunAdapter) setupMTU(mtu MTU) error { if tun.iface == nil || tun.Name() == "" { return errors.New("Can't configure MTU as TUN adapter is not present") } diff --git a/src/types/types.go b/src/types/types.go new file mode 100644 index 00000000..5d1c024f --- /dev/null +++ b/src/types/types.go @@ -0,0 +1,3 @@ +package types + +type MTU uint16 diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 7f82c260..1a98e0d8 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -99,7 +99,7 @@ type Session struct { Coords []uint64 // The coordinates of the remote node BytesSent uint64 // Bytes sent to the session BytesRecvd uint64 // Bytes received from the session - MTU uint16 // The maximum supported message size of the session + MTU MTU // The maximum supported message size of the session Uptime time.Duration // How long this session has been active for WasMTUFixed bool // This field is no longer used } @@ -364,8 +364,8 @@ func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) { } // GetMaximumSessionMTU returns the maximum allowed session MTU size. -func (c *Core) GetMaximumSessionMTU() uint16 { - var mtu uint16 +func (c *Core) GetMaximumSessionMTU() MTU { + var mtu MTU phony.Block(&c.router, func() { mtu = c.router.sessions.myMaximumMTU }) @@ -375,7 +375,7 @@ func (c *Core) GetMaximumSessionMTU() uint16 { // SetMaximumSessionMTU sets the maximum allowed session MTU size. The default // value is 65535 bytes. Session pings will be sent to update all open sessions // if the MTU has changed. -func (c *Core) SetMaximumSessionMTU(mtu uint16) { +func (c *Core) SetMaximumSessionMTU(mtu MTU) { phony.Block(&c.router, func() { if c.router.sessions.myMaximumMTU != mtu { c.router.sessions.myMaximumMTU = mtu diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 9a26432d..dab38d3d 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -8,10 +8,13 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/util" + "github.com/yggdrasil-network/yggdrasil-go/src/types" "github.com/Arceliar/phony" ) +type MTU = types.MTU + // ConnError implements the net.Error interface type ConnError struct { error @@ -65,7 +68,7 @@ type Conn struct { nodeID *crypto.NodeID nodeMask *crypto.NodeID session *sessionInfo - mtu uint16 + mtu MTU readCallback func([]byte) readBuffer chan []byte } @@ -93,7 +96,7 @@ func (c *Conn) String() string { return s } -func (c *Conn) setMTU(from phony.Actor, mtu uint16) { +func (c *Conn) setMTU(from phony.Actor, mtu MTU) { c.Act(from, func() { c.mtu = mtu }) } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index ecc7d499..91c530d4 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -36,8 +36,8 @@ type sessionInfo struct { myHandle crypto.Handle // theirNonce crypto.BoxNonce // myNonce crypto.BoxNonce // - theirMTU uint16 // - myMTU uint16 // + theirMTU MTU // + myMTU MTU // wasMTUFixed bool // Was the MTU fixed by a receive error? timeOpened time.Time // Time the session was opened time time.Time // Time we last received a packet @@ -63,7 +63,7 @@ type sessionPing struct { Coords []byte // Tstamp int64 // unix time, but the only real requirement is that it increases IsPong bool // - MTU uint16 // + MTU MTU // } // Updates session info in response to a ping, after checking that the ping is OK. @@ -117,7 +117,7 @@ type sessions struct { lastCleanup time.Time isAllowedHandler func(pubkey *crypto.BoxPubKey, initiator bool) bool // Returns true or false if session setup is allowed isAllowedMutex sync.RWMutex // Protects the above - myMaximumMTU uint16 // Maximum allowed session MTU + myMaximumMTU MTU // Maximum allowed session MTU permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey // Maps known permanent keys to their shared key, used by DHT a lot sinfos map[crypto.Handle]*sessionInfo // Maps handle onto session info byTheirPerm map[crypto.BoxPubKey]*crypto.Handle // Maps theirPermPub onto handle @@ -385,7 +385,7 @@ func (ss *sessions) handlePing(ping *sessionPing) { // Get the MTU of the session. // Will be equal to the smaller of this node's MTU or the remote node's MTU. // If sending over links with a maximum message size (this was a thing with the old UDP code), it could be further lowered, to a minimum of 1280. -func (sinfo *sessionInfo) _getMTU() uint16 { +func (sinfo *sessionInfo) _getMTU() MTU { if sinfo.theirMTU == 0 || sinfo.myMTU == 0 { return 0 } diff --git a/src/yggdrasil/wire.go b/src/yggdrasil/wire.go index 4ff19e2d..18a647d8 100644 --- a/src/yggdrasil/wire.go +++ b/src/yggdrasil/wire.go @@ -380,7 +380,7 @@ func (p *sessionPing) decode(bs []byte) bool { if pType == wire_SessionPong { p.IsPong = true } - p.MTU = uint16(mtu) + p.MTU = MTU(mtu) return true } From a2adcbd7e43e584396919ad7063ab0fde5e3ece3 Mon Sep 17 00:00:00 2001 From: William Fleurant Date: Sun, 5 Jan 2020 15:26:08 -0500 Subject: [PATCH 0493/1109] docker: build and copy genkeys --- contrib/docker/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile index fb4fbfaa..fd2ae981 100644 --- a/contrib/docker/Dockerfile +++ b/contrib/docker/Dockerfile @@ -5,13 +5,14 @@ WORKDIR /src ENV CGO_ENABLED=0 -RUN apk add git && ./build +RUN apk add git && ./build && go build -o /src/genkeys cmd/genkeys/main.go FROM docker.io/alpine LABEL maintainer="Christer Waren/CWINFO " COPY --from=builder /src/yggdrasil /usr/bin/yggdrasil COPY --from=builder /src/yggdrasilctl /usr/bin/yggdrasilctl +COPY --from=builder /src/genkeys /usr/bin/genkeys COPY contrib/docker/entrypoint.sh /usr/bin/entrypoint.sh # RUN addgroup -g 1000 -S yggdrasil-network \ From 9304873047a6f3be84fef00c4be528f65e8ee2d7 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 5 Jan 2020 22:15:52 +0000 Subject: [PATCH 0494/1109] Convert nodeinfo to actor --- src/yggdrasil/api.go | 19 +++---- src/yggdrasil/nodeinfo.go | 114 +++++++++++++++++++++++--------------- src/yggdrasil/router.go | 1 + 3 files changed, 77 insertions(+), 57 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 7f82c260..693dbd06 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -390,17 +390,14 @@ func (c *Core) SetMaximumSessionMTU(mtu uint16) { // necessary when, e.g. crawling the network. func (c *Core) GetNodeInfo(key crypto.BoxPubKey, coords []uint64, nocache bool) (NodeInfoPayload, error) { response := make(chan *NodeInfoPayload, 1) - sendNodeInfoRequest := func() { - c.router.nodeinfo.addCallback(key, func(nodeinfo *NodeInfoPayload) { - defer func() { recover() }() - select { - case response <- nodeinfo: - default: - } - }) - c.router.nodeinfo.sendNodeInfo(key, wire_coordsUint64stoBytes(coords), false) - } - phony.Block(&c.router, sendNodeInfoRequest) + c.router.nodeinfo.addCallback(key, func(nodeinfo *NodeInfoPayload) { + defer func() { recover() }() + select { + case response <- nodeinfo: + default: + } + }) + c.router.nodeinfo.sendNodeInfo(key, wire_coordsUint64stoBytes(coords), false) timer := time.AfterFunc(6*time.Second, func() { close(response) }) defer timer.Stop() for res := range response { diff --git a/src/yggdrasil/nodeinfo.go b/src/yggdrasil/nodeinfo.go index 8a5d7872..c3e9a274 100644 --- a/src/yggdrasil/nodeinfo.go +++ b/src/yggdrasil/nodeinfo.go @@ -5,21 +5,19 @@ import ( "errors" "runtime" "strings" - "sync" "time" + "github.com/Arceliar/phony" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/version" ) type nodeinfo struct { - core *Core - myNodeInfo NodeInfoPayload - myNodeInfoMutex sync.RWMutex - callbacks map[crypto.BoxPubKey]nodeinfoCallback - callbacksMutex sync.Mutex - cache map[crypto.BoxPubKey]nodeinfoCached - cacheMutex sync.RWMutex + phony.Inbox + core *Core + myNodeInfo NodeInfoPayload + callbacks map[crypto.BoxPubKey]nodeinfoCallback + cache map[crypto.BoxPubKey]nodeinfoCached } type nodeinfoCached struct { @@ -43,35 +41,43 @@ type nodeinfoReqRes struct { // Initialises the nodeinfo cache/callback maps, and starts a goroutine to keep // the cache/callback maps clean of stale entries func (m *nodeinfo) init(core *Core) { + m.Act(m, func() { + m._init(core) + }) +} + +func (m *nodeinfo) _init(core *Core) { m.core = core m.callbacks = make(map[crypto.BoxPubKey]nodeinfoCallback) m.cache = make(map[crypto.BoxPubKey]nodeinfoCached) - var f func() - f = func() { - m.callbacksMutex.Lock() - for boxPubKey, callback := range m.callbacks { - if time.Since(callback.created) > time.Minute { - delete(m.callbacks, boxPubKey) - } + m._cleanup() +} + +func (m *nodeinfo) _cleanup() { + for boxPubKey, callback := range m.callbacks { + if time.Since(callback.created) > time.Minute { + delete(m.callbacks, boxPubKey) } - m.callbacksMutex.Unlock() - m.cacheMutex.Lock() - for boxPubKey, cache := range m.cache { - if time.Since(cache.created) > time.Hour { - delete(m.cache, boxPubKey) - } - } - m.cacheMutex.Unlock() - time.AfterFunc(time.Second*30, f) } - go f() + for boxPubKey, cache := range m.cache { + if time.Since(cache.created) > time.Hour { + delete(m.cache, boxPubKey) + } + } + time.AfterFunc(time.Second*30, func() { + m.Act(m, m._cleanup) + }) } // Add a callback for a nodeinfo lookup func (m *nodeinfo) addCallback(sender crypto.BoxPubKey, call func(nodeinfo *NodeInfoPayload)) { - m.callbacksMutex.Lock() - defer m.callbacksMutex.Unlock() + m.Act(m, func() { + m._addCallback(sender, call) + }) +} + +func (m *nodeinfo) _addCallback(sender crypto.BoxPubKey, call func(nodeinfo *NodeInfoPayload)) { m.callbacks[sender] = nodeinfoCallback{ created: time.Now(), call: call, @@ -79,9 +85,7 @@ func (m *nodeinfo) addCallback(sender crypto.BoxPubKey, call func(nodeinfo *Node } // Handles the callback, if there is one -func (m *nodeinfo) callback(sender crypto.BoxPubKey, nodeinfo NodeInfoPayload) { - m.callbacksMutex.Lock() - defer m.callbacksMutex.Unlock() +func (m *nodeinfo) _callback(sender crypto.BoxPubKey, nodeinfo NodeInfoPayload) { if callback, ok := m.callbacks[sender]; ok { callback.call(&nodeinfo) delete(m.callbacks, sender) @@ -89,16 +93,26 @@ func (m *nodeinfo) callback(sender crypto.BoxPubKey, nodeinfo NodeInfoPayload) { } // Get the current node's nodeinfo -func (m *nodeinfo) getNodeInfo() NodeInfoPayload { - m.myNodeInfoMutex.RLock() - defer m.myNodeInfoMutex.RUnlock() +func (m *nodeinfo) getNodeInfo() (p NodeInfoPayload) { + phony.Block(m, func() { + p = m._getNodeInfo() + }) + return +} + +func (m *nodeinfo) _getNodeInfo() NodeInfoPayload { return m.myNodeInfo } // Set the current node's nodeinfo -func (m *nodeinfo) setNodeInfo(given interface{}, privacy bool) error { - m.myNodeInfoMutex.Lock() - defer m.myNodeInfoMutex.Unlock() +func (m *nodeinfo) setNodeInfo(given interface{}, privacy bool) (err error) { + phony.Block(m, func() { + err = m._setNodeInfo(given, privacy) + }) + return +} + +func (m *nodeinfo) _setNodeInfo(given interface{}, privacy bool) error { defaults := map[string]interface{}{ "buildname": version.BuildName(), "buildversion": version.BuildVersion(), @@ -134,9 +148,7 @@ func (m *nodeinfo) setNodeInfo(given interface{}, privacy bool) error { } // Add nodeinfo into the cache for a node -func (m *nodeinfo) addCachedNodeInfo(key crypto.BoxPubKey, payload NodeInfoPayload) { - m.cacheMutex.Lock() - defer m.cacheMutex.Unlock() +func (m *nodeinfo) _addCachedNodeInfo(key crypto.BoxPubKey, payload NodeInfoPayload) { m.cache[key] = nodeinfoCached{ created: time.Now(), payload: payload, @@ -144,9 +156,7 @@ func (m *nodeinfo) addCachedNodeInfo(key crypto.BoxPubKey, payload NodeInfoPaylo } // Get a nodeinfo entry from the cache -func (m *nodeinfo) getCachedNodeInfo(key crypto.BoxPubKey) (NodeInfoPayload, error) { - m.cacheMutex.RLock() - defer m.cacheMutex.RUnlock() +func (m *nodeinfo) _getCachedNodeInfo(key crypto.BoxPubKey) (NodeInfoPayload, error) { if nodeinfo, ok := m.cache[key]; ok { return nodeinfo.payload, nil } @@ -155,21 +165,33 @@ func (m *nodeinfo) getCachedNodeInfo(key crypto.BoxPubKey) (NodeInfoPayload, err // Handles a nodeinfo request/response - called from the router func (m *nodeinfo) handleNodeInfo(nodeinfo *nodeinfoReqRes) { + m.Act(m, func() { + m._handleNodeInfo(nodeinfo) + }) +} + +func (m *nodeinfo) _handleNodeInfo(nodeinfo *nodeinfoReqRes) { if nodeinfo.IsResponse { - m.callback(nodeinfo.SendPermPub, nodeinfo.NodeInfo) - m.addCachedNodeInfo(nodeinfo.SendPermPub, nodeinfo.NodeInfo) + m._callback(nodeinfo.SendPermPub, nodeinfo.NodeInfo) + m._addCachedNodeInfo(nodeinfo.SendPermPub, nodeinfo.NodeInfo) } else { - m.sendNodeInfo(nodeinfo.SendPermPub, nodeinfo.SendCoords, true) + m._sendNodeInfo(nodeinfo.SendPermPub, nodeinfo.SendCoords, true) } } // Send nodeinfo request or response - called from the router func (m *nodeinfo) sendNodeInfo(key crypto.BoxPubKey, coords []byte, isResponse bool) { + m.Act(m, func() { + m._sendNodeInfo(key, coords, isResponse) + }) +} + +func (m *nodeinfo) _sendNodeInfo(key crypto.BoxPubKey, coords []byte, isResponse bool) { table := m.core.switchTable.table.Load().(lookupTable) nodeinfo := nodeinfoReqRes{ SendCoords: table.self.getCoords(), IsResponse: isResponse, - NodeInfo: m.getNodeInfo(), + NodeInfo: m._getNodeInfo(), } bs := nodeinfo.encode() shared := m.core.router.sessions.getSharedKey(&m.core.boxPriv, &key) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 64c81701..bd6eefdf 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -78,6 +78,7 @@ func (r *router) init(core *Core) { func (r *router) reconfigure() { // Reconfigure the router current := r.core.config.GetCurrent() + r.core.log.Println("Reloading NodeInfo...") if err := r.nodeinfo.setNodeInfo(current.NodeInfo, current.NodeInfoPrivacy); err != nil { r.core.log.Errorln("Error reloading NodeInfo:", err) } else { From a5bcc227cae08c6e6d3d7a27466cf1d13f605595 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 5 Jan 2020 23:43:27 +0000 Subject: [PATCH 0495/1109] Update go.mod/go.sum for golang.org/x dependencies (may resolve #635 possibly?) --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index c11ccdf7..4e084db5 100644 --- a/go.mod +++ b/go.mod @@ -11,9 +11,9 @@ require ( github.com/mitchellh/mapstructure v1.1.2 github.com/vishvananda/netlink v1.0.0 github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect - golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c - golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914 - golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e + golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 + golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 + golang.org/x/sys v0.0.0-20200103143344-a1369afcdac7 golang.org/x/text v0.3.2 golang.zx2c4.com/wireguard v0.0.20191013-0.20191030132932-4cdf805b29b1 golang.zx2c4.com/wireguard/windows v0.0.35-0.20191123133119-cb4a03094c25 diff --git a/go.sum b/go.sum index 9a61fd2c..9c4082f9 100644 --- a/go.sum +++ b/go.sum @@ -20,20 +20,20 @@ github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f/go.mod h1:ZjcWmF golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c h1:/nJuwDLoL/zrqY6gf57vxC+Pi+pZ8bfhpPkicO5H7W4= -golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 h1:sKJQZMuxjOAR/Uo2LBfU90onWEf1dF4C+0hPJCc9Mpc= +golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914 h1:MlY3mEfbnWGmUi4rtHOtNnnnN4UJRGSyLPx+DXA5Sq4= -golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e h1:N7DeIrjYszNmSW409R3frPPwglRwMkXSBzwVbkOjLLA= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200103143344-a1369afcdac7 h1:/W9OPMnnpmFXHYkcp2rQsbFUbRlRzfECQjmAFiOyHE8= +golang.org/x/sys v0.0.0-20200103143344-a1369afcdac7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= From da9f02a381cdf35c9d801175909c4d6ac1b87abb Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 6 Jan 2020 19:34:03 +0000 Subject: [PATCH 0496/1109] Add -p for PIE builds, preserve environment LDFLAGS --- build | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/build b/build index 68dac037..6b355c00 100755 --- a/build +++ b/build @@ -6,10 +6,10 @@ PKGSRC=${PKGSRC:-github.com/yggdrasil-network/yggdrasil-go/src/version} PKGNAME=${PKGNAME:-$(sh contrib/semver/name.sh)} PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)} -LDFLAGS="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER" +LDFLAGS="$LDFLAGS -X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER" ARGS="-v" -while getopts "uaitc:l:dro:" option +while getopts "uaitc:l:dro:p" option do case "$option" in @@ -22,6 +22,7 @@ do d) ARGS="$ARGS -tags debug" DEBUG=true;; r) ARGS="$ARGS -race";; o) ARGS="$ARGS -o $OPTARG";; + p) ARGS="$ARGS -buildmode=pie";; esac done From 507c95efa9350a10e2d8a14db16536d04b34c1e9 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 6 Jan 2020 19:37:24 +0000 Subject: [PATCH 0497/1109] Don't preserve LDFLAGS from environment after all since they are probably go-specific --- build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build b/build index 6b355c00..66f94403 100755 --- a/build +++ b/build @@ -6,7 +6,7 @@ PKGSRC=${PKGSRC:-github.com/yggdrasil-network/yggdrasil-go/src/version} PKGNAME=${PKGNAME:-$(sh contrib/semver/name.sh)} PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)} -LDFLAGS="$LDFLAGS -X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER" +LDFLAGS="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER" ARGS="-v" while getopts "uaitc:l:dro:p" option From c3b1a6af657e70a0c12407cc1b89ffdc17a17869 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 6 Jan 2020 18:37:43 -0600 Subject: [PATCH 0498/1109] some nodeinfo actor fixes and adjust search timeout --- src/yggdrasil/api.go | 1 + src/yggdrasil/nodeinfo.go | 12 ++++++------ src/yggdrasil/router.go | 2 +- src/yggdrasil/search.go | 6 ++---- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 693dbd06..e2c3b97a 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -398,6 +398,7 @@ func (c *Core) GetNodeInfo(key crypto.BoxPubKey, coords []uint64, nocache bool) } }) c.router.nodeinfo.sendNodeInfo(key, wire_coordsUint64stoBytes(coords), false) + phony.Block(&c.router.nodeinfo, func() {}) // Wait for sendNodeInfo before starting timer timer := time.AfterFunc(6*time.Second, func() { close(response) }) defer timer.Stop() for res := range response { diff --git a/src/yggdrasil/nodeinfo.go b/src/yggdrasil/nodeinfo.go index c3e9a274..fc6250d6 100644 --- a/src/yggdrasil/nodeinfo.go +++ b/src/yggdrasil/nodeinfo.go @@ -41,7 +41,7 @@ type nodeinfoReqRes struct { // Initialises the nodeinfo cache/callback maps, and starts a goroutine to keep // the cache/callback maps clean of stale entries func (m *nodeinfo) init(core *Core) { - m.Act(m, func() { + m.Act(nil, func() { m._init(core) }) } @@ -66,13 +66,13 @@ func (m *nodeinfo) _cleanup() { } } time.AfterFunc(time.Second*30, func() { - m.Act(m, m._cleanup) + m.Act(nil, m._cleanup) }) } // Add a callback for a nodeinfo lookup func (m *nodeinfo) addCallback(sender crypto.BoxPubKey, call func(nodeinfo *NodeInfoPayload)) { - m.Act(m, func() { + m.Act(nil, func() { m._addCallback(sender, call) }) } @@ -164,8 +164,8 @@ func (m *nodeinfo) _getCachedNodeInfo(key crypto.BoxPubKey) (NodeInfoPayload, er } // Handles a nodeinfo request/response - called from the router -func (m *nodeinfo) handleNodeInfo(nodeinfo *nodeinfoReqRes) { - m.Act(m, func() { +func (m *nodeinfo) handleNodeInfo(from phony.Actor, nodeinfo *nodeinfoReqRes) { + m.Act(from, func() { m._handleNodeInfo(nodeinfo) }) } @@ -181,7 +181,7 @@ func (m *nodeinfo) _handleNodeInfo(nodeinfo *nodeinfoReqRes) { // Send nodeinfo request or response - called from the router func (m *nodeinfo) sendNodeInfo(key crypto.BoxPubKey, coords []byte, isResponse bool) { - m.Act(m, func() { + m.Act(nil, func() { m._sendNodeInfo(key, coords, isResponse) }) } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index bd6eefdf..b08a12d3 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -250,5 +250,5 @@ func (r *router) _handleNodeInfo(bs []byte, fromKey *crypto.BoxPubKey) { return } req.SendPermPub = *fromKey - r.nodeinfo.handleNodeInfo(&req) + r.nodeinfo.handleNodeInfo(r, &req) } diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 4708a705..92fc68b6 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -24,10 +24,8 @@ import ( // This defines the maximum number of dhtInfo that we keep track of for nodes to query in an ongoing search. const search_MAX_SEARCH_SIZE = 16 -// This defines the time after which we send a new search packet. -// Search packets are sent automatically immediately after a response is received. -// So this allows for timeouts and for long searches to become increasingly parallel. -const search_RETRY_TIME = time.Second +// This defines the time after which we time out a search (so it can restart). +const search_RETRY_TIME = 3 * time.Second // Information about an ongoing search. // Includes the target NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited. From 819cf234ae149b28717ae04676c0963d095123f2 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 1 Feb 2020 16:32:22 +0000 Subject: [PATCH 0499/1109] update Wireguard library --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 4e084db5..ea1f309b 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,6 @@ require ( golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 golang.org/x/sys v0.0.0-20200103143344-a1369afcdac7 golang.org/x/text v0.3.2 - golang.zx2c4.com/wireguard v0.0.20191013-0.20191030132932-4cdf805b29b1 + golang.zx2c4.com/wireguard v0.0.20200121 golang.zx2c4.com/wireguard/windows v0.0.35-0.20191123133119-cb4a03094c25 ) diff --git a/go.sum b/go.sum index 9c4082f9..63638837 100644 --- a/go.sum +++ b/go.sum @@ -40,5 +40,7 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.zx2c4.com/wireguard v0.0.20191013-0.20191030132932-4cdf805b29b1 h1:KxtBKNgJUQG8vwZzJKkwBGOcqp95xLu6A6KIMde1kl0= golang.zx2c4.com/wireguard v0.0.20191013-0.20191030132932-4cdf805b29b1/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4= +golang.zx2c4.com/wireguard v0.0.20200121 h1:vcswa5Q6f+sylDfjqyrVNNrjsFUUbPsgAQTBCAg/Qf8= +golang.zx2c4.com/wireguard v0.0.20200121/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4= golang.zx2c4.com/wireguard/windows v0.0.35-0.20191123133119-cb4a03094c25 h1:TreP+furSwdqoSToFrwb1S5cwxb7jhOsnwj2MsDeT+4= golang.zx2c4.com/wireguard/windows v0.0.35-0.20191123133119-cb4a03094c25/go.mod h1:EO8KCpT944a9CnwHJLZ1sl84FfIrY42fP/fcXUuYhKM= From 7e64f54c1ff810b538f40c5e9b5b617b4d1394fd Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 1 Feb 2020 13:58:08 -0600 Subject: [PATCH 0500/1109] log some info about searches and reduce search traffic (especially when things dead-end) --- src/tuntap/tun.go | 2 +- src/yggdrasil/conn.go | 2 +- src/yggdrasil/search.go | 21 ++++++++++++--------- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index be0ea708..53a17466 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -24,8 +24,8 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" - "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" "github.com/yggdrasil-network/yggdrasil-go/src/types" + "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) type MTU = types.MTU diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index dab38d3d..25605cd1 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -7,8 +7,8 @@ import ( "time" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" - "github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/yggdrasil-network/yggdrasil-go/src/types" + "github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/Arceliar/phony" ) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 92fc68b6..913b40a1 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -38,6 +38,8 @@ type searchInfo struct { visited crypto.NodeID // Closest address visited so far callback func(*sessionInfo, error) // TODO context.Context for timeout and cancellation + send uint64 // log number of requests sent + recv uint64 // log number of responses received } // This stores a map of active searches. @@ -75,6 +77,7 @@ func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callba // If there is, it adds the response info to the search and triggers a new search step. // If there's no ongoing search, or we if the dhtRes finished the search (it was from the target node), then don't do anything more. func (sinfo *searchInfo) handleDHTRes(res *dhtRes) { + sinfo.recv++ if res == nil || sinfo.checkDHTRes(res) { // Either we don't recognize this search, or we just finished it return @@ -87,20 +90,13 @@ func (sinfo *searchInfo) handleDHTRes(res *dhtRes) { // Adds the information from a dhtRes to an ongoing search. // Info about a node that has already been visited is not re-added to the search. // Duplicate information about nodes toVisit is deduplicated (the newest information is kept). -// The toVisit list is sorted in ascending order of keyspace distance from the destination. func (sinfo *searchInfo) addToSearch(res *dhtRes) { - // Add responses to toVisit if closer to dest than the res node - from := dhtInfo{key: res.Key, coords: res.Coords} - if dht_ordered(&sinfo.dest, from.getNodeID(), &sinfo.visited) { - // Closer to the destination, so update visited - sinfo.visited = *from.getNodeID() - } for _, info := range res.Infos { if *info.getNodeID() == sinfo.visited { // dht_ordered could return true here, but we want to skip it in this case continue } - if dht_ordered(&sinfo.dest, info.getNodeID(), &sinfo.visited) { + if dht_ordered(&sinfo.dest, info.getNodeID(), &sinfo.visited) && *info.getNodeID() != sinfo.visited { // Response is closer to the destination sinfo.toVisit = append(sinfo.toVisit, info) } @@ -124,6 +120,7 @@ func (sinfo *searchInfo) doSearchStep() { sinfo.searches.router.dht.addCallback(&rq, sinfo.handleDHTRes) sinfo.searches.router.dht.ping(next, &sinfo.dest) sinfo.time = time.Now() + sinfo.send++ } sinfo.toVisit = sinfo.toVisit[:0] } @@ -163,7 +160,13 @@ func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callb // If the response is from the target, get/create a session, trigger a session ping, and return true. // Otherwise return false. func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { - them := crypto.GetNodeID(&res.Key) + from := dhtInfo{key: res.Key, coords: res.Coords} + if dht_ordered(&sinfo.dest, from.getNodeID(), &sinfo.visited) { + // Closer to the destination, so update visited + sinfo.searches.router.core.log.Debugln("Updating search:", sinfo.dest, *from.getNodeID(), sinfo.send, sinfo.recv) + sinfo.visited = *from.getNodeID() + } + them := from.getNodeID() var destMasked crypto.NodeID var themMasked crypto.NodeID for idx := 0; idx < crypto.NodeIDLen; idx++ { From 70659bfb91032de2c9647677800dbd32dc307f1e Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 6 Feb 2020 17:38:42 -0600 Subject: [PATCH 0501/1109] sort search response results before sending requests --- src/yggdrasil/search.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 913b40a1..6bd53c63 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -16,6 +16,7 @@ package yggdrasil import ( "errors" + "sort" "time" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" @@ -100,6 +101,11 @@ func (sinfo *searchInfo) addToSearch(res *dhtRes) { // Response is closer to the destination sinfo.toVisit = append(sinfo.toVisit, info) } + // Sort + sort.SliceStable(sinfo.toVisit, func(i, j int) bool { + // Should return true if i is closer to the destination than j + return dht_ordered(&res.Dest, sinfo.toVisit[i].getNodeID(), sinfo.toVisit[j].getNodeID()) + }) } } From cd856426e5b8c4e7098cab0aeb087e48df163796 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 6 Feb 2020 18:37:58 -0600 Subject: [PATCH 0502/1109] search timing changes --- src/yggdrasil/search.go | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 6bd53c63..bd55e174 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -27,6 +27,7 @@ const search_MAX_SEARCH_SIZE = 16 // This defines the time after which we time out a search (so it can restart). const search_RETRY_TIME = 3 * time.Second +const search_STEP_TIME = 100 * time.Millisecond // Information about an ongoing search. // Includes the target NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited. @@ -78,14 +79,20 @@ func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callba // If there is, it adds the response info to the search and triggers a new search step. // If there's no ongoing search, or we if the dhtRes finished the search (it was from the target node), then don't do anything more. func (sinfo *searchInfo) handleDHTRes(res *dhtRes) { - sinfo.recv++ - if res == nil || sinfo.checkDHTRes(res) { - // Either we don't recognize this search, or we just finished it - return + old := sinfo.visited + if res != nil { + sinfo.recv++ + if sinfo.checkDHTRes(res) { + sinfo.searches.router.core.log.Debugln("Finished search:", sinfo.dest, sinfo.send, sinfo.recv) + return // Search finished successfully + } + // Add results to the search + sinfo.addToSearch(res) + } + if res == nil || sinfo.visited != old { + // Continue the search + sinfo.doSearchStep() } - // Add to the search and continue - sinfo.addToSearch(res) - sinfo.doSearchStep() } // Adds the information from a dhtRes to an ongoing search. @@ -104,7 +111,7 @@ func (sinfo *searchInfo) addToSearch(res *dhtRes) { // Sort sort.SliceStable(sinfo.toVisit, func(i, j int) bool { // Should return true if i is closer to the destination than j - return dht_ordered(&res.Dest, sinfo.toVisit[i].getNodeID(), sinfo.toVisit[j].getNodeID()) + return dht_ordered(&sinfo.dest, sinfo.toVisit[i].getNodeID(), sinfo.toVisit[j].getNodeID()) }) } } @@ -121,14 +128,14 @@ func (sinfo *searchInfo) doSearchStep() { return } // Send to the next search target - for _, next := range sinfo.toVisit { + if len(sinfo.toVisit) > 0 { + next := sinfo.toVisit[0] + sinfo.toVisit = sinfo.toVisit[1:] rq := dhtReqKey{next.key, sinfo.dest} sinfo.searches.router.dht.addCallback(&rq, sinfo.handleDHTRes) sinfo.searches.router.dht.ping(next, &sinfo.dest) - sinfo.time = time.Now() sinfo.send++ } - sinfo.toVisit = sinfo.toVisit[:0] } // If we've recently sent a ping for this search, do nothing. @@ -138,7 +145,7 @@ func (sinfo *searchInfo) continueSearch() { // In case the search dies, try to spawn another thread later // Note that this will spawn multiple parallel searches as time passes // Any that die aren't restarted, but a new one will start later - time.AfterFunc(search_RETRY_TIME, func() { + time.AfterFunc(search_STEP_TIME, func() { sinfo.searches.router.Act(nil, func() { // FIXME this keeps the search alive forever if not for the searches map, fix that newSearchInfo := sinfo.searches.searches[sinfo.dest] @@ -171,6 +178,7 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { // Closer to the destination, so update visited sinfo.searches.router.core.log.Debugln("Updating search:", sinfo.dest, *from.getNodeID(), sinfo.send, sinfo.recv) sinfo.visited = *from.getNodeID() + sinfo.time = time.Now() } them := from.getNodeID() var destMasked crypto.NodeID From 7c2cb9a02d406241bb92ce20702e9bca29ed5698 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 6 Feb 2020 20:21:17 -0600 Subject: [PATCH 0503/1109] more search fixes/updates --- src/yggdrasil/search.go | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index bd55e174..ede4547c 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -83,7 +83,6 @@ func (sinfo *searchInfo) handleDHTRes(res *dhtRes) { if res != nil { sinfo.recv++ if sinfo.checkDHTRes(res) { - sinfo.searches.router.core.log.Debugln("Finished search:", sinfo.dest, sinfo.send, sinfo.recv) return // Search finished successfully } // Add results to the search @@ -97,22 +96,22 @@ func (sinfo *searchInfo) handleDHTRes(res *dhtRes) { // Adds the information from a dhtRes to an ongoing search. // Info about a node that has already been visited is not re-added to the search. -// Duplicate information about nodes toVisit is deduplicated (the newest information is kept). func (sinfo *searchInfo) addToSearch(res *dhtRes) { + // Add to search for _, info := range res.Infos { - if *info.getNodeID() == sinfo.visited { - // dht_ordered could return true here, but we want to skip it in this case - continue + sinfo.toVisit = append(sinfo.toVisit, info) + } + // Sort + sort.SliceStable(sinfo.toVisit, func(i, j int) bool { + // Should return true if i is closer to the destination than j + return dht_ordered(&sinfo.dest, sinfo.toVisit[i].getNodeID(), sinfo.toVisit[j].getNodeID()) + }) + // Remove anything too far away + for idx, info := range sinfo.toVisit { + if *info.getNodeID() == sinfo.visited || !dht_ordered(&sinfo.dest, info.getNodeID(), &sinfo.visited) { + sinfo.toVisit = sinfo.toVisit[:idx] + break } - if dht_ordered(&sinfo.dest, info.getNodeID(), &sinfo.visited) && *info.getNodeID() != sinfo.visited { - // Response is closer to the destination - sinfo.toVisit = append(sinfo.toVisit, info) - } - // Sort - sort.SliceStable(sinfo.toVisit, func(i, j int) bool { - // Should return true if i is closer to the destination than j - return dht_ordered(&sinfo.dest, sinfo.toVisit[i].getNodeID(), sinfo.toVisit[j].getNodeID()) - }) } } @@ -174,7 +173,7 @@ func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callb // Otherwise return false. func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { from := dhtInfo{key: res.Key, coords: res.Coords} - if dht_ordered(&sinfo.dest, from.getNodeID(), &sinfo.visited) { + if *from.getNodeID() != sinfo.visited && dht_ordered(&sinfo.dest, from.getNodeID(), &sinfo.visited) { // Closer to the destination, so update visited sinfo.searches.router.core.log.Debugln("Updating search:", sinfo.dest, *from.getNodeID(), sinfo.send, sinfo.recv) sinfo.visited = *from.getNodeID() @@ -202,7 +201,10 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { sinfo.callback(sess, nil) } // Cleanup - delete(sinfo.searches.searches, res.Dest) + if _, isIn := sinfo.searches.searches[sinfo.dest]; isIn { + sinfo.searches.router.core.log.Debugln("Finished search:", sinfo.dest, sinfo.send, sinfo.recv) + delete(sinfo.searches.searches, res.Dest) + } } // They match, so create a session and send a sessionRequest var err error From 3faa0b28545bdf5fbc384a18ad23e79230288695 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 6 Feb 2020 20:47:53 -0600 Subject: [PATCH 0504/1109] deduplicate the list of nodes to visit in a search (keeping newest rumors) --- src/yggdrasil/search.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index ede4547c..7df3bdd3 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -97,8 +97,19 @@ func (sinfo *searchInfo) handleDHTRes(res *dhtRes) { // Adds the information from a dhtRes to an ongoing search. // Info about a node that has already been visited is not re-added to the search. func (sinfo *searchInfo) addToSearch(res *dhtRes) { - // Add to search + // Get a (deduplicated) list of known nodes to check + temp := make(map[crypto.NodeID]*dhtInfo, len(sinfo.toVisit)+len(res.Infos)) + for _, info := range sinfo.toVisit { + temp[*info.getNodeID()] = info + } + // Add new results to the list for _, info := range res.Infos { + temp[*info.getNodeID()] = info + } + // Move list to toVisit + delete(temp, sinfo.visited) + sinfo.toVisit = sinfo.toVisit[:0] + for _, info := range temp { sinfo.toVisit = append(sinfo.toVisit, info) } // Sort @@ -106,9 +117,9 @@ func (sinfo *searchInfo) addToSearch(res *dhtRes) { // Should return true if i is closer to the destination than j return dht_ordered(&sinfo.dest, sinfo.toVisit[i].getNodeID(), sinfo.toVisit[j].getNodeID()) }) - // Remove anything too far away + // Remove anything too far away to be useful for idx, info := range sinfo.toVisit { - if *info.getNodeID() == sinfo.visited || !dht_ordered(&sinfo.dest, info.getNodeID(), &sinfo.visited) { + if !dht_ordered(&sinfo.dest, info.getNodeID(), &sinfo.visited) { sinfo.toVisit = sinfo.toVisit[:idx] break } From cd9613fddc141a0d31df4975d0fb097418a15d3a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 7 Feb 2020 22:34:54 -0600 Subject: [PATCH 0505/1109] add some additional debug timing info and logging to dials, and fix an unnecessary delay in search startup --- src/yggdrasil/dialer.go | 6 ++++++ src/yggdrasil/search.go | 16 +++++++++------- src/yggdrasil/session.go | 2 -- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index e9da97a2..490502b4 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -65,12 +65,15 @@ func (d *Dialer) DialContext(ctx context.Context, network, address string) (net. // DialByNodeIDandMask opens a session to the given node based on raw // 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(ctx context.Context, nodeID, nodeMask *crypto.NodeID) (net.Conn, error) { + startDial := time.Now() conn := newConn(d.core, nodeID, nodeMask, nil) if err := conn.search(); err != nil { // TODO: make searches take a context, so they can be cancelled early conn.Close() return nil, err } + endSearch := time.Now() + d.core.log.Debugln("Dial searched for:", nodeID, "in time:", endSearch.Sub(startDial)) conn.session.setConn(nil, conn) var cancel context.CancelFunc if ctx == nil { @@ -80,6 +83,9 @@ func (d *Dialer) DialByNodeIDandMask(ctx context.Context, nodeID, nodeMask *cryp defer cancel() select { case <-conn.session.init: + endInit := time.Now() + d.core.log.Debugln("Dial initialized session for:", nodeID, "in time:", endInit.Sub(endSearch)) + d.core.log.Debugln("Finished dial for:", nodeID, "in time:", endInit.Sub(startDial)) return conn, nil case <-ctx.Done(): conn.Close() diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 7df3bdd3..5e1967c7 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -22,9 +22,6 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/crypto" ) -// This defines the maximum number of dhtInfo that we keep track of for nodes to query in an ongoing search. -const search_MAX_SEARCH_SIZE = 16 - // This defines the time after which we time out a search (so it can restart). const search_RETRY_TIME = 3 * time.Second const search_STEP_TIME = 100 * time.Millisecond @@ -79,7 +76,7 @@ func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callba // If there is, it adds the response info to the search and triggers a new search step. // If there's no ongoing search, or we if the dhtRes finished the search (it was from the target node), then don't do anything more. func (sinfo *searchInfo) handleDHTRes(res *dhtRes) { - old := sinfo.visited + var doStep bool if res != nil { sinfo.recv++ if sinfo.checkDHTRes(res) { @@ -87,8 +84,13 @@ func (sinfo *searchInfo) handleDHTRes(res *dhtRes) { } // Add results to the search sinfo.addToSearch(res) + // FIXME check this elsewhere so we don't need to create a from struct + from := dhtInfo{key: res.Key, coords: res.Coords} + doStep = sinfo.visited == *from.getNodeID() + } else { + doStep = true } - if res == nil || sinfo.visited != old { + if doStep { // Continue the search sinfo.doSearchStep() } @@ -186,7 +188,7 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { from := dhtInfo{key: res.Key, coords: res.Coords} if *from.getNodeID() != sinfo.visited && dht_ordered(&sinfo.dest, from.getNodeID(), &sinfo.visited) { // Closer to the destination, so update visited - sinfo.searches.router.core.log.Debugln("Updating search:", sinfo.dest, *from.getNodeID(), sinfo.send, sinfo.recv) + sinfo.searches.router.core.log.Debugln("Updating search:", &sinfo.dest, from.getNodeID(), sinfo.send, sinfo.recv) sinfo.visited = *from.getNodeID() sinfo.time = time.Now() } @@ -213,7 +215,7 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { } // Cleanup if _, isIn := sinfo.searches.searches[sinfo.dest]; isIn { - sinfo.searches.router.core.log.Debugln("Finished search:", sinfo.dest, sinfo.send, sinfo.recv) + sinfo.searches.router.core.log.Debugln("Finished search:", &sinfo.dest, sinfo.send, sinfo.recv) delete(sinfo.searches.searches, res.Dest) } } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 91c530d4..eaa67fd0 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -43,7 +43,6 @@ type sessionInfo struct { time time.Time // Time we last received a packet mtuTime time.Time // time myMTU was last changed pingTime time.Time // time the first ping was sent since the last received packet - pingSend time.Time // time the last ping was sent coords []byte // coords of destination reset bool // reset if coords change tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation @@ -197,7 +196,6 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.time = now sinfo.mtuTime = now sinfo.pingTime = now - sinfo.pingSend = now sinfo.init = make(chan struct{}) sinfo.cancel = util.NewCancellation() higher := false From d7d0c2629c1e9f800d5e1e587ec73d118610a0fb Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 8 Feb 2020 17:04:00 -0600 Subject: [PATCH 0506/1109] don't deduplicate search responses, but limit the max number of nodes handled per response --- src/yggdrasil/search.go | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 5e1967c7..8d534d3c 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -25,6 +25,7 @@ import ( // This defines the time after which we time out a search (so it can restart). const search_RETRY_TIME = 3 * time.Second const search_STEP_TIME = 100 * time.Millisecond +const search_MAX_RES_SIZE = 16 // Information about an ongoing search. // Includes the target NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited. @@ -99,26 +100,22 @@ func (sinfo *searchInfo) handleDHTRes(res *dhtRes) { // Adds the information from a dhtRes to an ongoing search. // Info about a node that has already been visited is not re-added to the search. func (sinfo *searchInfo) addToSearch(res *dhtRes) { - // Get a (deduplicated) list of known nodes to check - temp := make(map[crypto.NodeID]*dhtInfo, len(sinfo.toVisit)+len(res.Infos)) - for _, info := range sinfo.toVisit { - temp[*info.getNodeID()] = info + // Used in sortng below + sortFunc := func(i, j int) bool { + // Should return true if i is closer to the destination than j + return dht_ordered(&sinfo.dest, sinfo.toVisit[i].getNodeID(), sinfo.toVisit[j].getNodeID()) } - // Add new results to the list + // Limit maximum number of results (mitigates DoS where nodes return a large number of bad results) + if len(res.Infos) > search_MAX_RES_SIZE { + sort.SliceStable(res.Infos, sortFunc) + res.Infos = res.Infos[:search_MAX_RES_SIZE] + } + // Append (without deduplication) to list of nodes to try for _, info := range res.Infos { - temp[*info.getNodeID()] = info - } - // Move list to toVisit - delete(temp, sinfo.visited) - sinfo.toVisit = sinfo.toVisit[:0] - for _, info := range temp { sinfo.toVisit = append(sinfo.toVisit, info) } // Sort - sort.SliceStable(sinfo.toVisit, func(i, j int) bool { - // Should return true if i is closer to the destination than j - return dht_ordered(&sinfo.dest, sinfo.toVisit[i].getNodeID(), sinfo.toVisit[j].getNodeID()) - }) + sort.SliceStable(sinfo.toVisit, sortFunc) // Remove anything too far away to be useful for idx, info := range sinfo.toVisit { if !dht_ordered(&sinfo.dest, info.getNodeID(), &sinfo.visited) { @@ -147,6 +144,7 @@ func (sinfo *searchInfo) doSearchStep() { sinfo.searches.router.dht.addCallback(&rq, sinfo.handleDHTRes) sinfo.searches.router.dht.ping(next, &sinfo.dest) sinfo.send++ + sinfo.time = time.Now() } } @@ -190,7 +188,6 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { // Closer to the destination, so update visited sinfo.searches.router.core.log.Debugln("Updating search:", &sinfo.dest, from.getNodeID(), sinfo.send, sinfo.recv) sinfo.visited = *from.getNodeID() - sinfo.time = time.Now() } them := from.getNodeID() var destMasked crypto.NodeID From d0e6846173ad255460e8521a8dc97beeb7d13e0b Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 8 Feb 2020 20:15:48 -0600 Subject: [PATCH 0507/1109] work in progress to make searches use parallel threads per response, so one malicious node doesn't block progress from honest ones --- src/yggdrasil/conn.go | 9 +-- src/yggdrasil/search.go | 136 ++++++++++++++++++++++------------------ 2 files changed, 80 insertions(+), 65 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 25605cd1..7091cf7b 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -130,8 +130,8 @@ func (c *Conn) search() error { close(done) } } - sinfo := c.core.router.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) - sinfo.continueSearch() + sinfo, infos := c.core.router.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) + sinfo.continueSearch(infos) } else { err = errors.New("search already exists") close(done) @@ -152,10 +152,11 @@ func (c *Conn) doSearch() { if !isIn { // Nothing was found, so create a new search searchCompleted := func(sinfo *sessionInfo, e error) {} - sinfo = c.core.router.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) + var infos []*dhtInfo + sinfo, infos = c.core.router.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo) // Start the search - sinfo.continueSearch() + sinfo.continueSearch(infos) } } c.core.router.Act(c.session, routerWork) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 8d534d3c..ce78fd1f 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -25,7 +25,6 @@ import ( // This defines the time after which we time out a search (so it can restart). const search_RETRY_TIME = 3 * time.Second const search_STEP_TIME = 100 * time.Millisecond -const search_MAX_RES_SIZE = 16 // Information about an ongoing search. // Includes the target NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited. @@ -34,7 +33,6 @@ type searchInfo struct { dest crypto.NodeID mask crypto.NodeID time time.Time - toVisit []*dhtInfo visited crypto.NodeID // Closest address visited so far callback func(*sessionInfo, error) // TODO context.Context for timeout and cancellation @@ -77,81 +75,62 @@ func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callba // If there is, it adds the response info to the search and triggers a new search step. // If there's no ongoing search, or we if the dhtRes finished the search (it was from the target node), then don't do anything more. func (sinfo *searchInfo) handleDHTRes(res *dhtRes) { - var doStep bool if res != nil { sinfo.recv++ if sinfo.checkDHTRes(res) { return // Search finished successfully } - // Add results to the search - sinfo.addToSearch(res) - // FIXME check this elsewhere so we don't need to create a from struct - from := dhtInfo{key: res.Key, coords: res.Coords} - doStep = sinfo.visited == *from.getNodeID() - } else { - doStep = true - } - if doStep { - // Continue the search - sinfo.doSearchStep() + // Use results to start an additional search thread + infos := append([]*dhtInfo(nil), res.Infos...) + infos = sinfo.getAllowedInfos(infos) + sinfo.continueSearch(infos) } } -// Adds the information from a dhtRes to an ongoing search. -// Info about a node that has already been visited is not re-added to the search. -func (sinfo *searchInfo) addToSearch(res *dhtRes) { - // Used in sortng below - sortFunc := func(i, j int) bool { - // Should return true if i is closer to the destination than j - return dht_ordered(&sinfo.dest, sinfo.toVisit[i].getNodeID(), sinfo.toVisit[j].getNodeID()) - } - // Limit maximum number of results (mitigates DoS where nodes return a large number of bad results) - if len(res.Infos) > search_MAX_RES_SIZE { - sort.SliceStable(res.Infos, sortFunc) - res.Infos = res.Infos[:search_MAX_RES_SIZE] - } - // Append (without deduplication) to list of nodes to try - for _, info := range res.Infos { - sinfo.toVisit = append(sinfo.toVisit, info) - } - // Sort - sort.SliceStable(sinfo.toVisit, sortFunc) - // Remove anything too far away to be useful - for idx, info := range sinfo.toVisit { - if !dht_ordered(&sinfo.dest, info.getNodeID(), &sinfo.visited) { - sinfo.toVisit = sinfo.toVisit[:idx] - break - } - } -} - -// If there are no nodes left toVisit, then this cleans up the search. +// If there has been no response in too long, then this cleans up the search. // Otherwise, it pops the closest node to the destination (in keyspace) off of the toVisit list and sends a dht ping. -func (sinfo *searchInfo) doSearchStep() { - if len(sinfo.toVisit) == 0 { - if time.Since(sinfo.time) > search_RETRY_TIME { - // Dead end and no response in too long, do cleanup - delete(sinfo.searches.searches, sinfo.dest) - sinfo.callback(nil, errors.New("search reached dead end")) - } +func (sinfo *searchInfo) doSearchStep(infos []*dhtInfo) { + if time.Since(sinfo.time) > search_RETRY_TIME { + // Dead end and no response in too long, do cleanup + // FIXME we should really let all the parallel search threads exist when info is empty, and then delete when no threads are left, instead of keeping them all around until things time out or exit successfully + delete(sinfo.searches.searches, sinfo.dest) + sinfo.callback(nil, errors.New("search reached dead end")) return } - // Send to the next search target - if len(sinfo.toVisit) > 0 { - next := sinfo.toVisit[0] - sinfo.toVisit = sinfo.toVisit[1:] + if len(infos) > 0 { + // Send to the next search target + next := infos[0] rq := dhtReqKey{next.key, sinfo.dest} sinfo.searches.router.dht.addCallback(&rq, sinfo.handleDHTRes) sinfo.searches.router.dht.ping(next, &sinfo.dest) sinfo.send++ - sinfo.time = time.Now() } } +// Get a list of search targets that are close enough to the destination to try +// Requires an initial list as input +func (sinfo *searchInfo) getAllowedInfos(infos []*dhtInfo) []*dhtInfo { + sort.SliceStable(infos, func(i, j int) bool { + // Should return true if i is closer to the destination than j + return dht_ordered(&sinfo.dest, infos[i].getNodeID(), infos[j].getNodeID()) + }) + // Remove anything too far away to be useful + for idx, info := range infos { + if !dht_ordered(&sinfo.dest, info.getNodeID(), &sinfo.visited) { + infos = infos[:idx] + break + } + } + return infos +} + // If we've recently sent a ping for this search, do nothing. // Otherwise, doSearchStep and schedule another continueSearch to happen after search_RETRY_TIME. -func (sinfo *searchInfo) continueSearch() { - sinfo.doSearchStep() +func (sinfo *searchInfo) continueSearch(infos []*dhtInfo) { + sinfo.doSearchStep(infos) + if len(infos) > 0 { + infos = infos[1:] // Remove the node we just tried + } // In case the search dies, try to spawn another thread later // Note that this will spawn multiple parallel searches as time passes // Any that die aren't restarted, but a new one will start later @@ -162,21 +141,55 @@ func (sinfo *searchInfo) continueSearch() { if newSearchInfo != sinfo { return } - sinfo.continueSearch() + // Get good infos here instead of at the top, to make sure we can always start things off with a continueSearch call to ourself + infos = sinfo.getAllowedInfos(infos) + sinfo.continueSearch(infos) }) }) } +// Initially start a search +func (sinfo *searchInfo) startSearch() { + loc := sinfo.searches.router.core.switchTable.getLocator() + var infos []*dhtInfo + infos = append(infos, &dhtInfo{ + key: sinfo.searches.router.core.boxPub, + coords: loc.getCoords(), + }) + // Start the search by asking ourself, useful if we're the destination + sinfo.continueSearch(infos) + // Start a timer to clean up the search if everything times out + var cleanupFunc func() + cleanupFunc = func() { + sinfo.searches.router.Act(nil, func() { + // FIXME this keeps the search alive forever if not for the searches map, fix that + newSearchInfo := sinfo.searches.searches[sinfo.dest] + if newSearchInfo != sinfo { + return + } + elapsed := time.Since(sinfo.time) + if elapsed > search_RETRY_TIME { + // cleanup + delete(sinfo.searches.searches, sinfo.dest) + sinfo.callback(nil, errors.New("search reached dead end")) + return + } + time.AfterFunc(search_RETRY_TIME - elapsed, cleanupFunc) + }) + } +} + // Calls create search, and initializes the iterative search parts of the struct before returning it. -func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo { +func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) (*searchInfo, []*dhtInfo) { sinfo := s.createSearch(dest, mask, callback) sinfo.visited = s.router.dht.nodeID loc := s.router.core.switchTable.getLocator() - sinfo.toVisit = append(sinfo.toVisit, &dhtInfo{ + var infos []*dhtInfo + infos = append(infos, &dhtInfo{ key: s.router.core.boxPub, coords: loc.getCoords(), }) // Start the search by asking ourself, useful if we're the destination - return sinfo + return sinfo, infos } // Checks if a dhtRes is good (called by handleDHTRes). @@ -188,6 +201,7 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { // Closer to the destination, so update visited sinfo.searches.router.core.log.Debugln("Updating search:", &sinfo.dest, from.getNodeID(), sinfo.send, sinfo.recv) sinfo.visited = *from.getNodeID() + sinfo.time = time.Now() } them := from.getNodeID() var destMasked crypto.NodeID From 8e05c6c6a740c45a0a4c55dd7f815745dd5b47ba Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 8 Feb 2020 20:26:37 -0600 Subject: [PATCH 0508/1109] better search cleanup, but needs more testing to make sure it really works --- src/yggdrasil/conn.go | 9 +++--- src/yggdrasil/search.go | 67 +++++++++++++++++------------------------ 2 files changed, 31 insertions(+), 45 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 7091cf7b..f6229036 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -130,8 +130,8 @@ func (c *Conn) search() error { close(done) } } - sinfo, infos := c.core.router.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) - sinfo.continueSearch(infos) + sinfo := c.core.router.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) + sinfo.startSearch() } else { err = errors.New("search already exists") close(done) @@ -152,11 +152,10 @@ func (c *Conn) doSearch() { if !isIn { // Nothing was found, so create a new search searchCompleted := func(sinfo *sessionInfo, e error) {} - var infos []*dhtInfo - sinfo, infos = c.core.router.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) + sinfo = c.core.router.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo) // Start the search - sinfo.continueSearch(infos) + sinfo.startSearch() } } c.core.router.Act(c.session, routerWork) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index ce78fd1f..bf8c7819 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -83,20 +83,15 @@ func (sinfo *searchInfo) handleDHTRes(res *dhtRes) { // Use results to start an additional search thread infos := append([]*dhtInfo(nil), res.Infos...) infos = sinfo.getAllowedInfos(infos) - sinfo.continueSearch(infos) + if len(infos) > 0 { + sinfo.continueSearch(infos) + } } } // If there has been no response in too long, then this cleans up the search. // Otherwise, it pops the closest node to the destination (in keyspace) off of the toVisit list and sends a dht ping. func (sinfo *searchInfo) doSearchStep(infos []*dhtInfo) { - if time.Since(sinfo.time) > search_RETRY_TIME { - // Dead end and no response in too long, do cleanup - // FIXME we should really let all the parallel search threads exist when info is empty, and then delete when no threads are left, instead of keeping them all around until things time out or exit successfully - delete(sinfo.searches.searches, sinfo.dest) - sinfo.callback(nil, errors.New("search reached dead end")) - return - } if len(infos) > 0 { // Send to the next search target next := infos[0] @@ -124,16 +119,12 @@ func (sinfo *searchInfo) getAllowedInfos(infos []*dhtInfo) []*dhtInfo { return infos } -// If we've recently sent a ping for this search, do nothing. -// Otherwise, doSearchStep and schedule another continueSearch to happen after search_RETRY_TIME. +// Run doSearchStep and schedule another continueSearch to happen after search_RETRY_TIME. +// Must not be called with an empty list of infos func (sinfo *searchInfo) continueSearch(infos []*dhtInfo) { sinfo.doSearchStep(infos) - if len(infos) > 0 { - infos = infos[1:] // Remove the node we just tried - } - // In case the search dies, try to spawn another thread later - // Note that this will spawn multiple parallel searches as time passes - // Any that die aren't restarted, but a new one will start later + infos = infos[1:] // Remove the node we just tried + // In case there's no response, try the next node in infos later time.AfterFunc(search_STEP_TIME, func() { sinfo.searches.router.Act(nil, func() { // FIXME this keeps the search alive forever if not for the searches map, fix that @@ -143,7 +134,9 @@ func (sinfo *searchInfo) continueSearch(infos []*dhtInfo) { } // Get good infos here instead of at the top, to make sure we can always start things off with a continueSearch call to ourself infos = sinfo.getAllowedInfos(infos) - sinfo.continueSearch(infos) + if len(infos) > 0 { + sinfo.continueSearch(infos) + } }) }) } @@ -156,40 +149,34 @@ func (sinfo *searchInfo) startSearch() { key: sinfo.searches.router.core.boxPub, coords: loc.getCoords(), }) - // Start the search by asking ourself, useful if we're the destination - sinfo.continueSearch(infos) - // Start a timer to clean up the search if everything times out - var cleanupFunc func() - cleanupFunc = func() { + // Start the search by asking ourself, useful if we're the destination + sinfo.continueSearch(infos) + // Start a timer to clean up the search if everything times out + var cleanupFunc func() + cleanupFunc = func() { sinfo.searches.router.Act(nil, func() { // FIXME this keeps the search alive forever if not for the searches map, fix that newSearchInfo := sinfo.searches.searches[sinfo.dest] if newSearchInfo != sinfo { return } - elapsed := time.Since(sinfo.time) - if elapsed > search_RETRY_TIME { - // cleanup - delete(sinfo.searches.searches, sinfo.dest) - sinfo.callback(nil, errors.New("search reached dead end")) - return - } - time.AfterFunc(search_RETRY_TIME - elapsed, cleanupFunc) - }) - } + elapsed := time.Since(sinfo.time) + if elapsed > search_RETRY_TIME { + // cleanup + delete(sinfo.searches.searches, sinfo.dest) + sinfo.callback(nil, errors.New("search reached dead end")) + return + } + time.AfterFunc(search_RETRY_TIME-elapsed, cleanupFunc) + }) + } } // Calls create search, and initializes the iterative search parts of the struct before returning it. -func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) (*searchInfo, []*dhtInfo) { +func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo { sinfo := s.createSearch(dest, mask, callback) sinfo.visited = s.router.dht.nodeID - loc := s.router.core.switchTable.getLocator() - var infos []*dhtInfo - infos = append(infos, &dhtInfo{ - key: s.router.core.boxPub, - coords: loc.getCoords(), - }) // Start the search by asking ourself, useful if we're the destination - return sinfo, infos + return sinfo } // Checks if a dhtRes is good (called by handleDHTRes). From 657777881be5d8db66b8db61acd0d256ef1c312a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 8 Feb 2020 20:33:35 -0600 Subject: [PATCH 0509/1109] actually schedule the search cleanup code to run --- src/yggdrasil/search.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index bf8c7819..584a056d 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -170,6 +170,7 @@ func (sinfo *searchInfo) startSearch() { time.AfterFunc(search_RETRY_TIME-elapsed, cleanupFunc) }) } + time.AfterFunc(search_RETRY_TIME, cleanupFunc) } // Calls create search, and initializes the iterative search parts of the struct before returning it. From c107f891d29246b219f0eb77a846b6a57ac4c381 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 16 Feb 2020 23:09:24 +0000 Subject: [PATCH 0510/1109] Implement pubkeys in API functions --- src/crypto/crypto.go | 10 ++++++++++ src/yggdrasil/conn.go | 8 +++++--- src/yggdrasil/dialer.go | 38 ++++++++++++++++++++++++++++++++------ src/yggdrasil/listener.go | 4 ++-- 4 files changed, 49 insertions(+), 11 deletions(-) diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go index 3a6e02d1..92adf890 100644 --- a/src/crypto/crypto.go +++ b/src/crypto/crypto.go @@ -194,6 +194,16 @@ type BoxSharedKey [BoxSharedKeyLen]byte // BoxNonce is the nonce used in NaCl-like crypto "box" operations (curve25519+xsalsa20+poly1305), and must not be reused for different messages encrypted using the same BoxSharedKey. type BoxNonce [BoxNonceLen]byte +// String returns a string representation of the "box" key. +func (k BoxPubKey) String() string { + return hex.EncodeToString(k[:]) +} + +// Network returns "pubkey" for "box" keys. +func (n BoxPubKey) Network() string { + return "pubkey" +} + // NewBoxKeys generates a new pair of public/private crypto box keys. func NewBoxKeys() (*BoxPubKey, *BoxPrivKey) { pubBytes, privBytes, err := box.GenerateKey(rand.Reader) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index f6229036..078dca35 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -353,13 +353,15 @@ func (c *Conn) Close() (err error) { // 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. func (c *Conn) LocalAddr() net.Addr { - return crypto.GetNodeID(&c.core.boxPub) + return c.core.boxPub } // RemoteAddr returns the complete node ID of the remote side of the connection. 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 - return c.nodeID + if c.session != nil { + return c.session.theirPermPub + } + return nil } // SetDeadline is equivalent to calling both SetReadDeadline and diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index 490502b4..293f6d0b 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -17,19 +17,32 @@ type Dialer struct { core *Core } -// Dial opens a session to the given node. The first parameter should be "nodeid" -// and the second parameter should contain a hexadecimal representation of the -// target node ID. It uses DialContext internally. +// Dial opens a session to the given node. The first parameter should be +// "pubkey" or "nodeid" and the second parameter should contain a hexadecimal +// representation of the target. It uses DialContext internally. 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. +// DialContext is used internally by Dial, and should only be used with a +// context that includes a timeout. It uses DialByNodeIDandMask internally when +// the network is "nodeid", or DialByPublicKey when the network is "pubkey". func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { var nodeID crypto.NodeID var nodeMask crypto.NodeID // Process switch network { + case "pubkey": + dest, err := hex.DecodeString(address) + if err != nil { + return nil, err + } + if len(dest) != crypto.BoxPubKeyLen { + return nil, errors.New("invalid key length supplied") + } + var pubKey crypto.BoxPubKey + copy(pubKey[:], dest) + return d.DialByPublicKey(ctx, &pubKey) case "nodeid": // A node ID was provided - we don't need to do anything special with it if tokens := strings.Split(address, "/"); len(tokens) == 2 { @@ -62,8 +75,9 @@ func (d *Dialer) DialContext(ctx context.Context, network, address string) (net. } } -// DialByNodeIDandMask opens a session to the given node based on raw -// NodeID parameters. If ctx is nil or has no timeout, then a default timeout of 6 seconds will apply, beginning *after* the search finishes. +// DialByNodeIDandMask opens a session to the given node based on raw 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(ctx context.Context, nodeID, nodeMask *crypto.NodeID) (net.Conn, error) { startDial := time.Now() conn := newConn(d.core, nodeID, nodeMask, nil) @@ -92,3 +106,15 @@ func (d *Dialer) DialByNodeIDandMask(ctx context.Context, nodeID, nodeMask *cryp return nil, errors.New("session handshake timeout") } } + +// DialByPublicKey opens a session to the given node based on the public key. 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) DialByPublicKey(ctx context.Context, pubKey *crypto.BoxPubKey) (net.Conn, error) { + nodeID := crypto.GetNodeID(pubKey) + var nodeMask crypto.NodeID + for i := range nodeMask { + nodeMask[i] = 0xFF + } + return d.DialByNodeIDandMask(ctx, nodeID, &nodeMask) +} diff --git a/src/yggdrasil/listener.go b/src/yggdrasil/listener.go index 63830970..1b908b42 100644 --- a/src/yggdrasil/listener.go +++ b/src/yggdrasil/listener.go @@ -39,7 +39,7 @@ func (l *Listener) Close() (err error) { return nil } -// Addr is not implemented for this type yet +// Addr returns the address of the listener func (l *Listener) Addr() net.Addr { - return nil + return l.core.boxPub } From 63936c11b500bb06285980591aeba590f86661d6 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 16 Feb 2020 23:21:58 +0000 Subject: [PATCH 0511/1109] Update tuntap module, return pointers --- src/tuntap/tun.go | 3 ++- src/yggdrasil/conn.go | 4 ++-- src/yggdrasil/listener.go | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 53a17466..656ecca7 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -251,7 +251,8 @@ func (tun *TunAdapter) _wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { } c = &s // Get the remote address and subnet of the other side - remoteNodeID := conn.RemoteAddr().(*crypto.NodeID) + remotePubKey := conn.RemoteAddr().(*crypto.BoxPubKey) + remoteNodeID := crypto.GetNodeID(remotePubKey) s.addr = *address.AddrForNodeID(remoteNodeID) s.snet = *address.SubnetForNodeID(remoteNodeID) // Work out if this is already a destination we already know about diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 078dca35..d18f196d 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -353,13 +353,13 @@ func (c *Conn) Close() (err error) { // 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. func (c *Conn) LocalAddr() net.Addr { - return c.core.boxPub + return &c.core.boxPub } // RemoteAddr returns the complete node ID of the remote side of the connection. func (c *Conn) RemoteAddr() net.Addr { if c.session != nil { - return c.session.theirPermPub + return &c.session.theirPermPub } return nil } diff --git a/src/yggdrasil/listener.go b/src/yggdrasil/listener.go index 1b908b42..74ef3e88 100644 --- a/src/yggdrasil/listener.go +++ b/src/yggdrasil/listener.go @@ -41,5 +41,5 @@ func (l *Listener) Close() (err error) { // Addr returns the address of the listener func (l *Listener) Addr() net.Addr { - return l.core.boxPub + return &l.core.boxPub } From d16505e4177926b6dd85bd36b9c3525efeda70a0 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 16 Feb 2020 23:26:18 +0000 Subject: [PATCH 0512/1109] Update CKR --- src/tuntap/conn.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index aa149063..24ea5ef3 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -92,8 +92,7 @@ func (s *tunConn) _read(bs []byte) (err error) { // The destination address isn't in our CKR allowed range skip = true } else if key, err := s.tun.ckr.getPublicKeyForAddress(srcAddr, addrlen); err == nil { - srcNodeID := crypto.GetNodeID(&key) - if *s.conn.RemoteAddr().(*crypto.NodeID) == *srcNodeID { + if *s.conn.RemoteAddr().(*crypto.BoxPubKey) == key { // This is the one allowed CKR case, where source and destination addresses are both good } else { // The CKR key associated with this address doesn't match the sender's NodeID @@ -169,8 +168,7 @@ func (s *tunConn) _write(bs []byte) (err error) { // The source address isn't in our CKR allowed range skip = true } else if key, err := s.tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { - dstNodeID := crypto.GetNodeID(&key) - if *s.conn.RemoteAddr().(*crypto.NodeID) == *dstNodeID { + if *s.conn.RemoteAddr().(*crypto.BoxPubKey) == key { // This is the one allowed CKR case, where source and destination addresses are both good } else { // The CKR key associated with this address doesn't match the sender's NodeID From 6b0b7046453ddecdd22da86bc1f98e8435f0d1ec Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 16 Feb 2020 23:30:47 +0000 Subject: [PATCH 0513/1109] Update comments --- src/yggdrasil/conn.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index d18f196d..37049c73 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -350,13 +350,14 @@ func (c *Conn) Close() (err error) { return } -// 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. +// LocalAddr returns the complete public key of the local side of the +// connection. This is always going to return your own node's node ID. func (c *Conn) LocalAddr() net.Addr { return &c.core.boxPub } -// RemoteAddr returns the complete node ID of the remote side of the connection. +// RemoteAddr returns the complete public key of the remote side of the +// connection. func (c *Conn) RemoteAddr() net.Addr { if c.session != nil { return &c.session.theirPermPub From 429189d11dff8f4709ea1910f81f1e3420474037 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 16 Feb 2020 23:44:20 +0000 Subject: [PATCH 0514/1109] Use 'curve25519' instead of 'pubkey' --- src/crypto/crypto.go | 4 ++-- src/yggdrasil/dialer.go | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go index 92adf890..211a0e54 100644 --- a/src/crypto/crypto.go +++ b/src/crypto/crypto.go @@ -199,9 +199,9 @@ func (k BoxPubKey) String() string { return hex.EncodeToString(k[:]) } -// Network returns "pubkey" for "box" keys. +// Network returns "curve25519" for "box" keys. func (n BoxPubKey) Network() string { - return "pubkey" + return "curve25519" } // NewBoxKeys generates a new pair of public/private crypto box keys. diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index 293f6d0b..9f58d305 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -18,21 +18,21 @@ type Dialer struct { } // Dial opens a session to the given node. The first parameter should be -// "pubkey" or "nodeid" and the second parameter should contain a hexadecimal -// representation of the target. It uses DialContext internally. +// "curve25519" or "nodeid" and the second parameter should contain a +// hexadecimal representation of the target. It uses DialContext internally. 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 when -// the network is "nodeid", or DialByPublicKey when the network is "pubkey". +// the network is "nodeid", or DialByPublicKey when the network is "curve25519". func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { var nodeID crypto.NodeID var nodeMask crypto.NodeID // Process switch network { - case "pubkey": + case "curve25519": dest, err := hex.DecodeString(address) if err != nil { return nil, err From 6c731c4efc9827115f6404c7ebcdf0c1a5d496c1 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 16 Feb 2020 23:45:11 +0000 Subject: [PATCH 0515/1109] Fix comment on LocalAddr --- src/yggdrasil/conn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 37049c73..eef57683 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -351,7 +351,7 @@ func (c *Conn) Close() (err error) { } // LocalAddr returns the complete public key of the local side of the -// connection. This is always going to return your own node's node ID. +// connection. This is always going to return your own node's public key. func (c *Conn) LocalAddr() net.Addr { return &c.core.boxPub } From 471fcd7fdfbb316922c0bdbb7b020d54218abcd2 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 16 Feb 2020 23:57:05 +0000 Subject: [PATCH 0516/1109] Update doc.go dial example --- src/yggdrasil/doc.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/doc.go b/src/yggdrasil/doc.go index 44d39b66..9b9dd738 100644 --- a/src/yggdrasil/doc.go +++ b/src/yggdrasil/doc.go @@ -113,9 +113,9 @@ a Dialer: // ... } -You can then dial using the 16-byte node ID in hexadecimal format, for example: +You can then dial using the node's public key in hexadecimal format, for example: - conn, err := dialer.Dial("nodeid", "24a58cfce691ec016b0f698f7be1bee983cea263781017e99ad3ef62b4ef710a45d6c1a072c5ce46131bd574b78818c9957042cafeeed13966f349e94eb771bf") + conn, err := dialer.Dial("curve25519", "55071be281f50d0abbda63aadc59755624280c44b2f1f47684317aa4e0325604") if err != nil { // ... } From 012bd9195dab228064d3135fec510086826b5095 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 17 Feb 2020 19:49:03 +0000 Subject: [PATCH 0517/1109] Update CHANGELOG.md --- CHANGELOG.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3950f7b..ab3e9666 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,31 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> +## [0.3.13] - 2020-02-21 +### Added +- Support for the Wireguard TUN driver, which now replaces Water and provides far better support and performance on Windows +- Windows `.msi` installer files are now supported (bundling the Wireguard TUN driver) +- NodeInfo code is now actorised, should be more reliable +- The DHT now tries to store the two closest nodes in either direction instead of one, such that if a node goes offline, the replacement is already known +- The Yggdrasil API now supports dialing a remote node using the public key instead of the Node ID + +### Changed +- The `-loglevel` command line parameter is now cumulative and automatically includes all levels below the one specified +- DHT search code has been significantly simplified and processes rumoured nodes in parallel, speeding up search time +- DHT search results are now sorted +- The systemd service now handles configuration generation in a different unit +- The Yggdrasil API now returns public keys instead of node IDs when querying for local and remote addresses + +### Fixed +- The multicast code no longer panics when shutting down the node +- A potential OOB error when calculating IPv4 flow labels (when tunnel routing is enabled) has been fixed +- A bug resulting in incorrect idle notifications in the switch should now be fixed +- MTUs are now using a common datatype throughout the codebase + +### Removed +- TAP mode has been removed entirely, since it is no longer supported with the Wireguard TUN package. Please note that if you are using TAP mode, you may need to revise your config! +- NetBSD support has been removed until the Wireguard TUN package supports NetBSD + ## [0.3.12] - 2019-11-24 ### Added - New API functions `SetMaximumSessionMTU` and `GetMaximumSessionMTU` From f308e81bf3e906e3c02d43a12b953269a5f82f09 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Tue, 18 Feb 2020 20:13:39 -0600 Subject: [PATCH 0518/1109] in the switch, keep a separate set of queues per peer instead of a global queue --- src/yggdrasil/api.go | 17 ++--- src/yggdrasil/link.go | 19 ------ src/yggdrasil/switch.go | 140 +++++++++++++++++++++------------------- 3 files changed, 82 insertions(+), 94 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 82d0aa93..4a6ae417 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -207,15 +207,16 @@ func (c *Core) GetSwitchQueues() SwitchQueues { HighestSize: switchTable.queues.maxsize, MaximumSize: switchTable.queues.totalMaxSize, } - for k, v := range switchTable.queues.bufs { - nexthop := switchTable.bestPortForCoords([]byte(k)) - queue := SwitchQueue{ - ID: k, - Size: v.size, - Packets: uint64(len(v.packets)), - Port: uint64(nexthop), + for port, pbuf := range switchTable.queues.bufs { + for k, v := range pbuf { + queue := SwitchQueue{ + ID: k, + Size: v.size, + Packets: uint64(len(v.packets)), + Port: uint64(port), + } + switchqueues.Queues = append(switchqueues.Queues, queue) } - switchqueues.Queues = append(switchqueues.Queues, queue) } } phony.Block(&c.switchTable, getSwitchQueues) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 157ea525..fb40fc08 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -282,13 +282,6 @@ func (intf *linkInterface) notifySending(size int, isLinkTraffic bool) { }) } -// called by an AfterFunc if we seem to be blocked in a send syscall for a long time -func (intf *linkInterface) _notifySyscall() { - intf.link.core.switchTable.Act(intf, func() { - intf.link.core.switchTable._sendingIn(intf.peer.port) - }) -} - // we just sent something, so cancel any pending timer to send keep-alive traffic func (intf *linkInterface) _cancelStallTimer() { if intf.stallTimer != nil { @@ -402,19 +395,7 @@ func (w *linkWriter) sendFrom(from phony.Actor, bss [][]byte, isLinkTraffic bool size += len(bs) } w.intf.notifySending(size, isLinkTraffic) - // start a timer that will fire if we get stuck in writeMsgs for an oddly long time - var once sync.Once - timer := time.AfterFunc(time.Millisecond, func() { - // 1 ms is kind of arbitrary - // the rationale is that this should be very long compared to a syscall - // but it's still short compared to end-to-end latency or human perception - once.Do(func() { - w.intf.Act(nil, w.intf._notifySyscall) - }) - }) w.intf.msgIO.writeMsgs(bss) - // Make sure we either stop the timer from doing anything or wait until it's done - once.Do(func() { timer.Stop() }) w.intf.notifySent(size, isLinkTraffic) // Cleanup for _, bs := range bss { diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 653b12f1..899d143d 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -177,7 +177,6 @@ type switchTable struct { phony.Inbox // Owns the below queues switch_buffers // Queues - not atomic so ONLY use through the actor idle map[switchPort]struct{} // idle peers - not atomic so ONLY use through the actor - sending map[switchPort]struct{} // peers known to be blocked in a send (somehow) } // Minimum allowed total size of switch queues. @@ -202,9 +201,8 @@ func (t *switchTable) init(core *Core) { t.queues.totalMaxSize = SwitchQueueTotalMinSize } core.config.Mutex.RUnlock() - t.queues.bufs = make(map[string]switch_buffer) + t.queues.bufs = make(map[switchPort]map[string]switch_buffer) t.idle = make(map[switchPort]struct{}) - t.sending = make(map[switchPort]struct{}) }) } @@ -666,27 +664,17 @@ func (t *switchTable) bestPortForCoords(coords []byte) switchPort { // Handle an incoming packet // Either send it to ourself, or to the first idle peer that's free // Returns true if the packet has been handled somehow, false if it should be queued -func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]struct{}, sending map[switchPort]struct{}) bool { +func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]struct{}) (bool, switchPort) { coords := switch_getPacketCoords(packet) closer := t.getCloser(coords) - if len(closer) == 0 { - // TODO? call the router directly, and remove the whole concept of a self peer? - self := t.core.peers.getPorts()[0] - self.sendPacketsFrom(t, [][]byte{packet}) - return true - } var best *closerInfo ports := t.core.peers.getPorts() for _, cinfo := range closer { to := ports[cinfo.elem.port] - //_, isIdle := idle[cinfo.elem.port] - _, isSending := sending[cinfo.elem.port] var update bool switch { case to == nil: // no port was found, ignore it - case isSending: - // the port is busy, ignore it case best == nil: // this is the first idle port we've found, so select it until we find a // better candidate port to use instead @@ -715,15 +703,20 @@ func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]struct{}, sen best = &b } } - if best != nil { - if _, isIdle := idle[best.elem.port]; isIdle { - delete(idle, best.elem.port) - ports[best.elem.port].sendPacketsFrom(t, [][]byte{packet}) - return true - } + if best == nil { + // No closer peers + // TODO? call the router directly, and remove the whole concept of a self peer? + self := t.core.peers.getPorts()[0] + self.sendPacketsFrom(t, [][]byte{packet}) + return true, 0 } - // Didn't find anyone idle to send it to - return false + if _, isIdle := idle[best.elem.port]; isIdle { + delete(idle, best.elem.port) + ports[best.elem.port].sendPacketsFrom(t, [][]byte{packet}) + return true, best.elem.port + } + // Best node isn't idle, so return port and let the packet be buffered + return false, best.elem.port } // Info about a buffered packet @@ -740,24 +733,29 @@ type switch_buffer struct { type switch_buffers struct { totalMaxSize uint64 - bufs map[string]switch_buffer // Buffers indexed by StreamID - size uint64 // Total size of all buffers, in bytes + bufs map[switchPort]map[string]switch_buffer // Buffers indexed by port and StreamID + size uint64 // Total size of all buffers, in bytes maxbufs int maxsize uint64 closer []closerInfo // Scratch space } 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 len(t.getCloser(coords)) == 0 { - for _, packet := range buf.packets { - util.PutBytes(packet.bytes) + for port, pbufs := range b.bufs { + for streamID, buf := range pbufs { + // Remove queues for which we have no next hop + packet := buf.packets[0] + coords := switch_getPacketCoords(packet.bytes) + if len(t.getCloser(coords)) == 0 { + for _, packet := range buf.packets { + util.PutBytes(packet.bytes) + } + b.size -= buf.size + delete(pbufs, streamID) } - b.size -= buf.size - delete(b.bufs, streamID) + } + if len(pbufs) == 0 { + delete(b.bufs, port) } } @@ -765,23 +763,28 @@ func (b *switch_buffers) _cleanup(t *switchTable) { // 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 + for port, pbufs := range b.bufs { + for streamID, buf := range pbufs { + 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(pbufs, streamID) + if len(pbufs) == 0 { + delete(b.bufs, port) + } + } else { + // Need to update the map, since buf was retrieved by value + pbufs[streamID] = buf + } + break } - 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 } } } @@ -799,32 +802,35 @@ func (t *switchTable) _handleIdle(port switchPort) bool { var psize int t.queues._cleanup(t) now := time.Now() + pbufs := t.queues.bufs[port] for psize < 65535 { var best string var bestPriority float64 - for streamID, buf := range t.queues.bufs { + for streamID, buf := range pbufs { // Filter over the streams that this node is closer to // Keep the one with the smallest queue packet := buf.packets[0] - coords := switch_getPacketCoords(packet.bytes) priority := float64(now.Sub(packet.time)) / float64(buf.size) - if priority >= bestPriority && t.portIsCloser(coords, port) { + if priority >= bestPriority { best = streamID bestPriority = priority } } if best != "" { - buf := t.queues.bufs[best] + buf := pbufs[best] var packet switch_packetInfo // TODO decide if this should be LIFO or FIFO packet, buf.packets = buf.packets[0], buf.packets[1:] buf.size -= uint64(len(packet.bytes)) t.queues.size -= uint64(len(packet.bytes)) if len(buf.packets) == 0 { - delete(t.queues.bufs, best) + delete(pbufs, best) + if len(pbufs) == 0 { + delete(t.queues.bufs, port) + } } else { // Need to update the map, since buf was retrieved by value - t.queues.bufs[best] = buf + pbufs[best] = buf } packets = append(packets, packet.bytes) psize += len(packet.bytes) @@ -848,11 +854,14 @@ func (t *switchTable) packetInFrom(from phony.Actor, bytes []byte) { func (t *switchTable) _packetIn(bytes []byte) { // Try to send it somewhere (or drop it if it's corrupt or at a dead end) - if !t._handleIn(bytes, t.idle, t.sending) { + if sent, best := t._handleIn(bytes, t.idle); !sent { // There's nobody free to take it right now, so queue it for later packet := switch_packetInfo{bytes, time.Now()} streamID := switch_getPacketStreamID(packet.bytes) - buf, bufExists := t.queues.bufs[streamID] + if _, isIn := t.queues.bufs[best]; !isIn { + t.queues.bufs[best] = make(map[string]switch_buffer) + } + buf, bufExists := t.queues.bufs[best][streamID] buf.packets = append(buf.packets, packet) buf.size += uint64(len(packet.bytes)) t.queues.size += uint64(len(packet.bytes)) @@ -860,13 +869,17 @@ func (t *switchTable) _packetIn(bytes []byte) { if t.queues.size > t.queues.maxsize { t.queues.maxsize = t.queues.size } - t.queues.bufs[streamID] = buf + t.queues.bufs[best][streamID] = buf if !bufExists { // Keep a track of the max total queue count. Only recalculate this // when the queue is new because otherwise repeating len(dict) might // cause unnecessary processing overhead - if len(t.queues.bufs) > t.queues.maxbufs { - t.queues.maxbufs = len(t.queues.bufs) + var count int + for _, pbufs := range t.queues.bufs { + count += len(pbufs) + } + if count > t.queues.maxbufs { + t.queues.maxbufs = count } } t.queues._cleanup(t) @@ -875,15 +888,8 @@ func (t *switchTable) _packetIn(bytes []byte) { func (t *switchTable) _idleIn(port switchPort) { // Try to find something to send to this peer - delete(t.sending, port) if !t._handleIdle(port) { // Didn't find anything ready to send yet, so stay idle t.idle[port] = struct{}{} } } - -func (t *switchTable) _sendingIn(port switchPort) { - if _, isIn := t.idle[port]; !isIn { - t.sending[port] = struct{}{} - } -} From d41da9a97f9bc42fbe75a58dc979289a9ba71d1a Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 20 Feb 2020 23:22:42 +0000 Subject: [PATCH 0519/1109] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index c02151f4..dcc9bd91 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,6 @@ some of the below: - Windows - FreeBSD - OpenBSD -- NetBSD - OpenWrt Please see our [Platforms](https://yggdrasil-network.github.io/platforms.html) pages for more From 7d590e31b0824b327c327427daac2b8c8b3b4e19 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 21 Feb 2020 19:14:40 +0000 Subject: [PATCH 0520/1109] Include yggdrasil-default-config.service --- contrib/deb/generate.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/deb/generate.sh b/contrib/deb/generate.sh index 90707a7c..50f37f4a 100644 --- a/contrib/deb/generate.sh +++ b/contrib/deb/generate.sh @@ -110,7 +110,7 @@ EOF cp yggdrasil /tmp/$PKGNAME/usr/bin/ cp yggdrasilctl /tmp/$PKGNAME/usr/bin/ -cp contrib/systemd/yggdrasil.service /tmp/$PKGNAME/etc/systemd/system/ +cp contrib/systemd/*.service /tmp/$PKGNAME/etc/systemd/system/ tar -czvf /tmp/$PKGNAME/data.tar.gz -C /tmp/$PKGNAME/ \ usr/bin/yggdrasil usr/bin/yggdrasilctl \ From d160eccab00443187e2343c707c60f37c7fbcfde Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 21 Feb 2020 19:32:36 +0000 Subject: [PATCH 0521/1109] Hopefully really actually fix it this time --- contrib/deb/generate.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/deb/generate.sh b/contrib/deb/generate.sh index 50f37f4a..1f3186a4 100644 --- a/contrib/deb/generate.sh +++ b/contrib/deb/generate.sh @@ -114,7 +114,8 @@ cp contrib/systemd/*.service /tmp/$PKGNAME/etc/systemd/system/ tar -czvf /tmp/$PKGNAME/data.tar.gz -C /tmp/$PKGNAME/ \ usr/bin/yggdrasil usr/bin/yggdrasilctl \ - etc/systemd/system/yggdrasil.service + etc/systemd/system/yggdrasil.service \ + etc/systemd/system/yggdrasil-default-config.service tar -czvf /tmp/$PKGNAME/control.tar.gz -C /tmp/$PKGNAME/debian . echo 2.0 > /tmp/$PKGNAME/debian-binary From 8075a609009b928d392bd1ab6113c6376cdb1238 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 8 Mar 2020 19:32:14 -0500 Subject: [PATCH 0522/1109] possibly fix memory leak (if this works, i don't yet understand how the leak was happening originally) --- src/yggdrasil/switch.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 653b12f1..5e4d3e92 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -800,7 +800,7 @@ func (t *switchTable) _handleIdle(port switchPort) bool { t.queues._cleanup(t) now := time.Now() for psize < 65535 { - var best string + var best *string var bestPriority float64 for streamID, buf := range t.queues.bufs { // Filter over the streams that this node is closer to @@ -809,22 +809,23 @@ func (t *switchTable) _handleIdle(port switchPort) bool { coords := switch_getPacketCoords(packet.bytes) priority := float64(now.Sub(packet.time)) / float64(buf.size) if priority >= bestPriority && t.portIsCloser(coords, port) { - best = streamID + b := streamID // copy since streamID is mutated in the loop + best = &b bestPriority = priority } } - if best != "" { - buf := t.queues.bufs[best] + if best != nil { + buf := t.queues.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.size -= uint64(len(packet.bytes)) t.queues.size -= uint64(len(packet.bytes)) if len(buf.packets) == 0 { - delete(t.queues.bufs, best) + delete(t.queues.bufs, *best) } else { // Need to update the map, since buf was retrieved by value - t.queues.bufs[best] = buf + t.queues.bufs[*best] = buf } packets = append(packets, packet.bytes) psize += len(packet.bytes) From 48098799958d5450c894821a1d768f475cb7c4d7 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Tue, 10 Mar 2020 01:03:07 -0500 Subject: [PATCH 0523/1109] refactor switch code so calling lookupTable.lookup does most of the important work --- src/yggdrasil/switch.go | 109 +++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 62 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index d4dd045a..ce5e3db6 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -596,14 +596,14 @@ func (t *switchTable) getCloser(dest []byte) []closerInfo { // Skip the iteration step if it's impossible to be closer return nil } - t.queues.closer = t.queues.closer[:0] + var closer []closerInfo for _, info := range table.elems { dist := info.locator.dist(dest) if dist < myDist { - t.queues.closer = append(t.queues.closer, closerInfo{info, dist}) + closer = append(closer, closerInfo{info, dist}) } } - return t.queues.closer + return closer } // Returns true if the peer is closer to the destination than ourself @@ -645,20 +645,41 @@ func switch_getFlowLabelFromCoords(in []byte) []byte { return []byte{} } -// Find the best port for a given set of coords -func (t *switchTable) bestPortForCoords(coords []byte) switchPort { - table := t.getTable() - var best switchPort - bestDist := table.self.dist(coords) - for to, elem := range table.elems { - dist := elem.locator.dist(coords) - if !(dist < bestDist) { +// Find the best port to forward to for a given set of coords +func (t *lookupTable) lookup(coords []byte) switchPort { + var bestPort switchPort + myDist := t.self.dist(coords) + bestDist := myDist + var bestElem tableElem + for _, info := range t.elems { + dist := info.locator.dist(coords) + if dist >= myDist { continue } - best = to - bestDist = dist + var update bool + switch { + case dist < bestDist: + // Closer to destination + update = true + case dist > bestDist: + // Further from destination + case info.locator.tstamp > bestElem.locator.tstamp: + // Newer root update + update = true + case info.locator.tstamp < bestElem.locator.tstamp: + // Older root update + case info.time.Before(bestElem.time): + // Received root update via this peer sooner + update = true + default: + } + if update { + bestPort = info.port + bestDist = dist + bestElem = info + } } - return best + return bestPort } // Handle an incoming packet @@ -666,57 +687,22 @@ func (t *switchTable) bestPortForCoords(coords []byte) switchPort { // Returns true if the packet has been handled somehow, false if it should be queued func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]struct{}) (bool, switchPort) { coords := switch_getPacketCoords(packet) - closer := t.getCloser(coords) - var best *closerInfo + table := t.getTable() + port := table.lookup(coords) ports := t.core.peers.getPorts() - for _, cinfo := range closer { - to := ports[cinfo.elem.port] - var update bool - switch { - case to == nil: - // no port was found, ignore it - case best == nil: - // this is the first idle port we've found, so select it until we find a - // better candidate port to use instead - update = true - case cinfo.dist < best.dist: - // the port takes a shorter path/is more direct than our current - // candidate, so select that instead - update = true - case cinfo.dist > best.dist: - // the port takes a longer path/is less direct than our current candidate, - // ignore it - case cinfo.elem.locator.tstamp > best.elem.locator.tstamp: - // has a newer tstamp from the root, so presumably a better path - update = true - case cinfo.elem.locator.tstamp < best.elem.locator.tstamp: - // has a n older tstamp, so presumably a worse path - case cinfo.elem.time.Before(best.elem.time): - // same tstamp, but got it earlier, so presumably a better path - //t.core.log.Println("DEBUG new best:", best.elem.time, cinfo.elem.time) - update = true - default: - // the search for a port has finished - } - if update { - b := cinfo // because cinfo gets mutated by the iteration - best = &b - } - } - if best == nil { - // No closer peers - // TODO? call the router directly, and remove the whole concept of a self peer? - self := t.core.peers.getPorts()[0] - self.sendPacketsFrom(t, [][]byte{packet}) + peer := ports[port] + if peer == nil { + // FIXME hack, if the peer disappeared durring a race then don't buffer return true, 0 } - if _, isIdle := idle[best.elem.port]; isIdle { - delete(idle, best.elem.port) - ports[best.elem.port].sendPacketsFrom(t, [][]byte{packet}) - return true, best.elem.port + if _, isIdle := idle[port]; isIdle || port == 0 { + // Either no closer peers, or the closest peer is idle + delete(idle, port) + peer.sendPacketsFrom(t, [][]byte{packet}) + return true, port } - // Best node isn't idle, so return port and let the packet be buffered - return false, best.elem.port + // There's a closer peer, but it's not idle, so buffer it + return false, port } // Info about a buffered packet @@ -737,7 +723,6 @@ type switch_buffers struct { size uint64 // Total size of all buffers, in bytes maxbufs int maxsize uint64 - closer []closerInfo // Scratch space } func (b *switch_buffers) _cleanup(t *switchTable) { From c1816ae86f138e7162840621e76057b25cfa0bf8 Mon Sep 17 00:00:00 2001 From: Rany <31795451+rany0@users.noreply.github.com> Date: Tue, 10 Mar 2020 16:47:41 +0200 Subject: [PATCH 0524/1109] Update usr.bin.yggdrasil --- contrib/apparmor/usr.bin.yggdrasil | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contrib/apparmor/usr.bin.yggdrasil b/contrib/apparmor/usr.bin.yggdrasil index e31a27b0..3dea142c 100644 --- a/contrib/apparmor/usr.bin.yggdrasil +++ b/contrib/apparmor/usr.bin.yggdrasil @@ -1,10 +1,11 @@ -# Last Modified: Sat Mar 9 06:08:02 2019 +# Last Modified: Tue Mar 10 16:38:14 2020 #include /usr/bin/yggdrasil { #include capability net_admin, + capability net_raw, network inet stream, network inet dgram, @@ -14,6 +15,7 @@ /lib/@{multiarch}/ld-*.so mr, /proc/sys/net/core/somaxconn r, + owner /sys/kernel/mm/transparent_hugepage/hpage_pmd_size r, /dev/net/tun rw, /usr/bin/yggdrasil mr, From b651e57203973c11475845c36626af6d95275e59 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 19 Mar 2020 21:11:17 -0500 Subject: [PATCH 0525/1109] allow searches to continue as long as the next hop is closer than the Nth closest node found so far where N is currently 16 instead of 1 (makes searches more reliable), and cache all intermediate search steps in the dht --- src/yggdrasil/dht.go | 4 +-- src/yggdrasil/search.go | 69 +++++++++++++++++++++++++++++++---------- 2 files changed, 54 insertions(+), 19 deletions(-) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 013fd1e4..8efc549f 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -260,9 +260,7 @@ func (t *dht) handleRes(res *dhtRes) { key: res.Key, coords: res.Coords, } - if t.isImportant(&rinfo) { - t.insert(&rinfo) - } + t.insert(&rinfo) for _, info := range res.Infos { if *info.getNodeID() == t.nodeID { continue diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 584a056d..bfec4e16 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -24,7 +24,8 @@ import ( // This defines the time after which we time out a search (so it can restart). const search_RETRY_TIME = 3 * time.Second -const search_STEP_TIME = 100 * time.Millisecond +const search_STEP_TIME = time.Second +const search_MAX_RESULTS = dht_lookup_size // Information about an ongoing search. // Includes the target NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited. @@ -33,7 +34,7 @@ type searchInfo struct { dest crypto.NodeID mask crypto.NodeID time time.Time - visited crypto.NodeID // Closest address visited so far + visited []*crypto.NodeID // Closest addresses visited so far callback func(*sessionInfo, error) // TODO context.Context for timeout and cancellation send uint64 // log number of requests sent @@ -75,6 +76,9 @@ func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callba // If there is, it adds the response info to the search and triggers a new search step. // If there's no ongoing search, or we if the dhtRes finished the search (it was from the target node), then don't do anything more. func (sinfo *searchInfo) handleDHTRes(res *dhtRes) { + if nfo := sinfo.searches.searches[sinfo.dest]; nfo != sinfo { + return // already done + } if res != nil { sinfo.recv++ if sinfo.checkDHTRes(res) { @@ -105,16 +109,32 @@ func (sinfo *searchInfo) doSearchStep(infos []*dhtInfo) { // Get a list of search targets that are close enough to the destination to try // Requires an initial list as input func (sinfo *searchInfo) getAllowedInfos(infos []*dhtInfo) []*dhtInfo { + var temp []*dhtInfo + for _, info := range infos { + if false && len(sinfo.visited) < search_MAX_RESULTS { + // We're not full on results yet, so don't block anything yet + } else if !dht_ordered(&sinfo.dest, info.getNodeID(), sinfo.visited[len(sinfo.visited)-1]) { + // Too far away + continue + } + var known bool + for _, nfo := range sinfo.visited { + if *nfo == *info.getNodeID() { + known = true + break + } + } + if !known { + temp = append(temp, info) + } + } + infos = append(infos[:0], temp...) // restrict to only the allowed infos sort.SliceStable(infos, func(i, j int) bool { // Should return true if i is closer to the destination than j return dht_ordered(&sinfo.dest, infos[i].getNodeID(), infos[j].getNodeID()) - }) - // Remove anything too far away to be useful - for idx, info := range infos { - if !dht_ordered(&sinfo.dest, info.getNodeID(), &sinfo.visited) { - infos = infos[:idx] - break - } + }) // Sort infos to start with the closest + if len(infos) > search_MAX_RESULTS { + infos = infos[:search_MAX_RESULTS] // Limit max number of infos } return infos } @@ -164,6 +184,7 @@ func (sinfo *searchInfo) startSearch() { if elapsed > search_RETRY_TIME { // cleanup delete(sinfo.searches.searches, sinfo.dest) + sinfo.searches.router.core.log.Debugln("search timeout:", &sinfo.dest, sinfo.send, sinfo.recv) sinfo.callback(nil, errors.New("search reached dead end")) return } @@ -176,7 +197,7 @@ func (sinfo *searchInfo) startSearch() { // Calls create search, and initializes the iterative search parts of the struct before returning it. func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo { sinfo := s.createSearch(dest, mask, callback) - sinfo.visited = s.router.dht.nodeID + sinfo.visited = append(sinfo.visited, &s.router.dht.nodeID) return sinfo } @@ -185,13 +206,29 @@ func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callb // Otherwise return false. func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { from := dhtInfo{key: res.Key, coords: res.Coords} - if *from.getNodeID() != sinfo.visited && dht_ordered(&sinfo.dest, from.getNodeID(), &sinfo.visited) { - // Closer to the destination, so update visited - sinfo.searches.router.core.log.Debugln("Updating search:", &sinfo.dest, from.getNodeID(), sinfo.send, sinfo.recv) - sinfo.visited = *from.getNodeID() - sinfo.time = time.Now() - } them := from.getNodeID() + var known bool + for _, v := range sinfo.visited { + if *v == *them { + known = true + break + } + } + if !known { + if len(sinfo.visited) < search_MAX_RESULTS || dht_ordered(&sinfo.dest, them, sinfo.visited[len(sinfo.visited)-1]) { + // Closer to the destination than the threshold, so update visited + sinfo.searches.router.core.log.Debugln("Updating search:", &sinfo.dest, them, sinfo.send, sinfo.recv) + sinfo.visited = append(sinfo.visited, them) + sort.SliceStable(sinfo.visited, func(i, j int) bool { + // Should return true if i is closer to the destination than j + return dht_ordered(&sinfo.dest, sinfo.visited[i], sinfo.visited[j]) + }) // Sort infos to start with the closest + if len(sinfo.visited) > search_MAX_RESULTS { + sinfo.visited = sinfo.visited[:search_MAX_RESULTS] + } + sinfo.time = time.Now() + } + } var destMasked crypto.NodeID var themMasked crypto.NodeID for idx := 0; idx < crypto.NodeIDLen; idx++ { From a09a83530f80b26a946c3296dbbd7193f037c63f Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 22 Mar 2020 18:42:42 -0500 Subject: [PATCH 0526/1109] update search description in comments --- src/yggdrasil/search.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index bfec4e16..e963babc 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -4,15 +4,13 @@ package yggdrasil // The basic idea is as follows: // We may know a NodeID (with a mask) and want to connect -// We begin a search by initializing a list of all nodes in our DHT, sorted by closest to the destination -// We then iteratively ping nodes from the search, marking each pinged node as visited -// We add any unvisited nodes from ping responses to the search, truncating to some maximum search size -// This stops when we either run out of nodes to ping (we hit a dead end where we can't make progress without going back), or we reach the destination -// A new search packet is sent immediately after receiving a response -// A new search packet is sent periodically, once per second, in case a packet was dropped (this slowly causes the search to become parallel if the search doesn't timeout but also doesn't finish within 1 second for whatever reason) - -// TODO? -// Some kind of max search steps, in case the node is offline, so we don't crawl through too much of the network looking for a destination that isn't there? +// We beign a search by sending a dht lookup to ourself +// Each time a node responds, we sort the results and filter to only include useful nodes +// We then periodically send a packet to the first node from the list (after re-filtering) +// This happens in parallel for each node that replies +// Meanwhile, we keep a list of the (up to) 16 closest nodes to the destination that we've visited +// We only consider an unvisited node useful if either the list isn't full or the unvisited node is closer to the destination than the furthest node on the list +// That gives the search some chance to recover if it hits a dead end where a node doesn't know everyone it should import ( "errors" From 30bfa04c477c6ffc18cb297c20a298f7957cf5ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Christian=20Gr=C3=BCnhage?= Date: Mon, 23 Mar 2020 23:26:41 +0100 Subject: [PATCH 0527/1109] add a progress bar to the ansible key generator --- contrib/ansible/genkeys.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/contrib/ansible/genkeys.go b/contrib/ansible/genkeys.go index 5213e8b8..1d7c222d 100644 --- a/contrib/ansible/genkeys.go +++ b/contrib/ansible/genkeys.go @@ -14,6 +14,7 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/cheggaaa/pb/v3" ) var numHosts = flag.Int("hosts", 1, "number of host vars to generate") @@ -29,6 +30,8 @@ type keySet struct { func main() { flag.Parse() + bar := pb.StartNew(*keyTries * 2 + *numHosts) + if *numHosts > *keyTries { println("Can't generate less keys than hosts.") return @@ -37,21 +40,25 @@ func main() { var encryptionKeys []keySet for i := 0; i < *numHosts+1; i++ { encryptionKeys = append(encryptionKeys, newBoxKey()) + bar.Increment() } encryptionKeys = sortKeySetArray(encryptionKeys) for i := 0; i < *keyTries-*numHosts-1; i++ { encryptionKeys[0] = newBoxKey() encryptionKeys = bubbleUpTo(encryptionKeys, 0) + bar.Increment() } var signatureKeys []keySet for i := 0; i < *numHosts+1; i++ { signatureKeys = append(signatureKeys, newSigKey()) + bar.Increment() } signatureKeys = sortKeySetArray(signatureKeys) for i := 0; i < *keyTries-*numHosts-1; i++ { signatureKeys[0] = newSigKey() signatureKeys = bubbleUpTo(signatureKeys, 0) + bar.Increment() } os.MkdirAll("host_vars", 0755) @@ -76,7 +83,9 @@ func main() { defer file.Close() file.WriteString(fmt.Sprintf("vault_yggdrasil_encryption_private_key: %v\n", hex.EncodeToString(encryptionKeys[i].priv))) file.WriteString(fmt.Sprintf("vault_yggdrasil_signing_private_key: %v\n", hex.EncodeToString(signatureKeys[i].priv))) + bar.Increment() } + bar.Finish() } func newBoxKey() keySet { From 1ac3a18aabe2e4f9e3f8cf6193e6bc48b00234e9 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 23 Mar 2020 18:03:31 -0500 Subject: [PATCH 0528/1109] Fix a typo in search.go's comments --- src/yggdrasil/search.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index e963babc..91f0490c 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -4,7 +4,7 @@ package yggdrasil // The basic idea is as follows: // We may know a NodeID (with a mask) and want to connect -// We beign a search by sending a dht lookup to ourself +// We begin a search by sending a dht lookup to ourself // Each time a node responds, we sort the results and filter to only include useful nodes // We then periodically send a packet to the first node from the list (after re-filtering) // This happens in parallel for each node that replies From aa4def2f8d8c499a8ec248ae9cf635ba54f75188 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 25 Mar 2020 22:46:01 +0000 Subject: [PATCH 0529/1109] Use Go 1.14.1 to build, update wireguard package references --- .circleci/config.yml | 10 ++++----- go.mod | 13 ++++++----- go.sum | 52 +++++++++++++++++++++++++++++--------------- 3 files changed, 46 insertions(+), 29 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3e223e55..3f555df5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ version: 2.1 jobs: build-linux: docker: - - image: circleci/golang:1.13.3 + - image: circleci/golang:1.14.1 steps: - checkout @@ -106,11 +106,11 @@ jobs: echo -e "Host *\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config - run: - name: Install Go 1.13.3 + name: Install Go 1.14.1 command: | cd /tmp - curl -LO https://dl.google.com/go/go1.13.3.darwin-amd64.pkg - sudo installer -pkg /tmp/go1.13.3.darwin-amd64.pkg -target / + curl -LO https://dl.google.com/go/go1.14.1.darwin-amd64.pkg + sudo installer -pkg /tmp/go1.14.1.darwin-amd64.pkg -target / #- run: # name: Install Gomobile @@ -146,7 +146,7 @@ jobs: build-other: docker: - - image: circleci/golang:1.13.3 + - image: circleci/golang:1.14.1 steps: - checkout diff --git a/go.mod b/go.mod index ea1f309b..04cd5b90 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.13 require ( github.com/Arceliar/phony v0.0.0-20191006174943-d0c68492aca0 + github.com/cheggaaa/pb/v3 v3.0.4 github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 github.com/hashicorp/go-syslog v1.0.0 github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible @@ -11,10 +12,10 @@ require ( github.com/mitchellh/mapstructure v1.1.2 github.com/vishvananda/netlink v1.0.0 github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect - golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 - golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 - golang.org/x/sys v0.0.0-20200103143344-a1369afcdac7 - golang.org/x/text v0.3.2 - golang.zx2c4.com/wireguard v0.0.20200121 - golang.zx2c4.com/wireguard/windows v0.0.35-0.20191123133119-cb4a03094c25 + golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d + golang.org/x/net v0.0.0-20200301022130-244492dfa37a + golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 + golang.org/x/text v0.3.3-0.20191230102452-929e72ca90de + golang.zx2c4.com/wireguard v0.0.20200320 + golang.zx2c4.com/wireguard/windows v0.1.0 ) diff --git a/go.sum b/go.sum index 63638837..6d08e865 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,11 @@ github.com/Arceliar/phony v0.0.0-20191006174943-d0c68492aca0 h1:p3puK8Sl2xK+2FnnIvY/C0N1aqJo2kbEsdAzU+Tnv48= github.com/Arceliar/phony v0.0.0-20191006174943-d0c68492aca0/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= +github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= +github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= +github.com/cheggaaa/pb/v3 v3.0.4 h1:QZEPYOj2ix6d5oEg63fbHmpolrnNiwjUsk+h74Yt4bM= +github.com/cheggaaa/pb/v3 v3.0.4/go.mod h1:7rgWxLrAUcFMkvJuv09+DYi7mMUYi8nO9iOWcvGJPfw= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 h1:WD8iJ37bRNwvETMfVTusVSAi0WdXTpfNVGY2aHycNKY= github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= @@ -8,9 +14,17 @@ github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible h1:b github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk8851WTVWlqMC1ecJH07Ctz+Ezxx4u54g= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0/go.mod h1:rUi0/YffDo1oXBOGn1KRq7Fr07LX48XEBecQnmwjsAo= -github.com/lxn/walk v0.0.0-20191031081659-c0bb82ae46cb/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= -github.com/lxn/win v0.0.0-20191024121223-cc00c7492fe1 h1:h0wbuSK8xUNmMwDdCxZx2OLdkVck6Bb31zj4CxCN5I4= -github.com/lxn/win v0.0.0-20191024121223-cc00c7492fe1/go.mod h1:ouWl4wViUNh8tPSIwxTVMuS014WakR1hqvBc2I0bMoA= +github.com/lxn/walk v0.0.0-20191128110447-55ccb3a9f5c1 h1:/QwQcwWVOQXcoNuV9tHx30gQ3q7jCE/rKcGjwzsa5tg= +github.com/lxn/walk v0.0.0-20191128110447-55ccb3a9f5c1/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= +github.com/lxn/win v0.0.0-20191128105842-2da648fda5b4 h1:5BmtGkQbch91lglMHQ9JIDGiYCL3kBRBA0ItZTvOcEI= +github.com/lxn/win v0.0.0-20191128105842-2da648fda5b4/go.mod h1:ouWl4wViUNh8tPSIwxTVMuS014WakR1hqvBc2I0bMoA= +github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/vishvananda/netlink v1.0.0 h1:bqNY2lgheFIu1meHUFSH3d7vG93AFyqg3oGbJCOJgSM= @@ -19,28 +33,30 @@ github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f h1:nBX3nTcmxEtHS github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 h1:sKJQZMuxjOAR/Uo2LBfU90onWEf1dF4C+0hPJCc9Mpc= -golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw= +golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200103143344-a1369afcdac7 h1:/W9OPMnnpmFXHYkcp2rQsbFUbRlRzfECQjmAFiOyHE8= -golang.org/x/sys v0.0.0-20200103143344-a1369afcdac7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200301040627-c5d0d7b4ec88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3-0.20191230102452-929e72ca90de h1:aYKJLPSrddB2N7/6OKyFqJ337SXpo61bBuvO5p1+7iY= +golang.org/x/text v0.3.3-0.20191230102452-929e72ca90de/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.zx2c4.com/wireguard v0.0.20191013-0.20191030132932-4cdf805b29b1 h1:KxtBKNgJUQG8vwZzJKkwBGOcqp95xLu6A6KIMde1kl0= -golang.zx2c4.com/wireguard v0.0.20191013-0.20191030132932-4cdf805b29b1/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4= -golang.zx2c4.com/wireguard v0.0.20200121 h1:vcswa5Q6f+sylDfjqyrVNNrjsFUUbPsgAQTBCAg/Qf8= -golang.zx2c4.com/wireguard v0.0.20200121/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4= -golang.zx2c4.com/wireguard/windows v0.0.35-0.20191123133119-cb4a03094c25 h1:TreP+furSwdqoSToFrwb1S5cwxb7jhOsnwj2MsDeT+4= -golang.zx2c4.com/wireguard/windows v0.0.35-0.20191123133119-cb4a03094c25/go.mod h1:EO8KCpT944a9CnwHJLZ1sl84FfIrY42fP/fcXUuYhKM= +golang.zx2c4.com/wireguard v0.0.20200122-0.20200214175355-9cbcff10dd3e/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4= +golang.zx2c4.com/wireguard v0.0.20200320 h1:1vE6zVeO7fix9cJX1Z9ZQ+ikPIIx7vIyU0o0tLDD88g= +golang.zx2c4.com/wireguard v0.0.20200320/go.mod h1:lDian4Sw4poJ04SgHh35nzMVwGSYlPumkdnHcucAQoY= +golang.zx2c4.com/wireguard/windows v0.1.0 h1:742izt2DAJBpIQT+DvrzN58P9p7fO4BUFOgMzY9qVhw= +golang.zx2c4.com/wireguard/windows v0.1.0/go.mod h1:EK7CxrFnicmYJ0ZCF6crBh2/EMMeSxMlqgLlwN0Kv9s= From 05c6006f51eb6f66fd6b39088d0f76a7e3715f45 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 28 Mar 2020 20:46:00 +0000 Subject: [PATCH 0530/1109] Update changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab3e9666..9b0216eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> +## [0.3.14] - 2020-03-28 +### Fixed +- Fixes a memory leak that may occur if packets are incorrectly never removed from a switch queue + +### Changed +- Make DHT searches a bit more reliable by tracking the 16 most recently visited nodes + ## [0.3.13] - 2020-02-21 ### Added - Support for the Wireguard TUN driver, which now replaces Water and provides far better support and performance on Windows From e926a3be6d2b9d475dd6b7a5677ec4442033ff74 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 29 Mar 2020 00:23:38 -0500 Subject: [PATCH 0531/1109] work in progress actorizing core.peers and replacing switch worker with per-peer switch-generated lookupTable --- contrib/ansible/genkeys.go | 4 +- src/yggdrasil/api.go | 23 ++-- src/yggdrasil/link.go | 7 +- src/yggdrasil/nodeinfo.go | 4 +- src/yggdrasil/peer.go | 119 ++++++++++-------- src/yggdrasil/router.go | 6 +- src/yggdrasil/switch.go | 244 +++++++++++++++++++------------------ 7 files changed, 221 insertions(+), 186 deletions(-) diff --git a/contrib/ansible/genkeys.go b/contrib/ansible/genkeys.go index 1d7c222d..681431b5 100644 --- a/contrib/ansible/genkeys.go +++ b/contrib/ansible/genkeys.go @@ -12,9 +12,9 @@ import ( "net" "os" + "github.com/cheggaaa/pb/v3" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" - "github.com/cheggaaa/pb/v3" ) var numHosts = flag.Int("hosts", 1, "number of host vars to generate") @@ -30,7 +30,7 @@ type keySet struct { func main() { flag.Parse() - bar := pb.StartNew(*keyTries * 2 + *numHosts) + bar := pb.StartNew(*keyTries*2 + *numHosts) if *numHosts > *keyTries { println("Can't generate less keys than hosts.") diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 4a6ae417..15e2acd6 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -110,7 +110,8 @@ type Session struct { // there is exactly one entry then this node is not connected to any other nodes // and is therefore isolated. func (c *Core) GetPeers() []Peer { - ports := c.peers.ports.Load().(map[switchPort]*peer) + var ports map[switchPort]*peer + phony.Block(&c.peers, func() { ports = c.peers.ports }) var peers []Peer var ps []switchPort for port := range ports { @@ -143,10 +144,14 @@ func (c *Core) GetPeers() []Peer { // isolated or not connected to any peers. func (c *Core) GetSwitchPeers() []SwitchPeer { var switchpeers []SwitchPeer - table := c.switchTable.table.Load().(lookupTable) - peers := c.peers.ports.Load().(map[switchPort]*peer) + var table *lookupTable + var ports map[switchPort]*peer + phony.Block(&c.peers, func() { + table = c.peers.table + ports = c.peers.ports + }) for _, elem := range table.elems { - peer, isIn := peers[elem.port] + peer, isIn := ports[elem.port] if !isIn { continue } @@ -325,8 +330,8 @@ func (c *Core) EncryptionPublicKey() string { // connected to any other nodes (effectively making you the root of a // single-node network). func (c *Core) Coords() []uint64 { - table := c.switchTable.table.Load().(lookupTable) - return wire_coordsBytestoUint64s(table.self.getCoords()) + loc := c.switchTable.getLocator() + return wire_coordsBytestoUint64s(loc.getCoords()) } // Address gets the IPv6 address of the Yggdrasil node. This is always a /128 @@ -490,7 +495,11 @@ func (c *Core) CallPeer(addr string, sintf string) error { // DisconnectPeer disconnects a peer once. This should be specified as a port // number. func (c *Core) DisconnectPeer(port uint64) error { - c.peers.removePeer(switchPort(port)) + c.peers.Act(nil, func() { + if p, isIn := c.peers.ports[switchPort(port)]; isIn { + p.Act(&c.peers, p._removeSelf) + } + }) return nil } diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index fb40fc08..fa6563f1 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -217,13 +217,16 @@ func (intf *linkInterface) handler() error { intf.link.mutex.Unlock() // Create peer shared := crypto.GetSharedKey(myLinkPriv, &meta.link) - intf.peer = intf.link.core.peers.newPeer(&meta.box, &meta.sig, shared, intf, func() { intf.msgIO.close() }) + phony.Block(&intf.link.core.peers, func() { + // FIXME don't use phony.Block, it's bad practice, even if it's safe here + intf.peer = intf.link.core.peers._newPeer(&meta.box, &meta.sig, shared, intf, func() { intf.msgIO.close() }) + }) if intf.peer == nil { return errors.New("failed to create peer") } defer func() { // More cleanup can go here - intf.link.core.peers.removePeer(intf.peer.port) + intf.peer.Act(nil, intf.peer._removeSelf) }() intf.peer.out = func(msgs [][]byte) { intf.writer.sendFrom(intf.peer, msgs, false) diff --git a/src/yggdrasil/nodeinfo.go b/src/yggdrasil/nodeinfo.go index fc6250d6..745756fe 100644 --- a/src/yggdrasil/nodeinfo.go +++ b/src/yggdrasil/nodeinfo.go @@ -187,9 +187,9 @@ func (m *nodeinfo) sendNodeInfo(key crypto.BoxPubKey, coords []byte, isResponse } func (m *nodeinfo) _sendNodeInfo(key crypto.BoxPubKey, coords []byte, isResponse bool) { - table := m.core.switchTable.table.Load().(lookupTable) + loc := m.core.switchTable.getLocator() nodeinfo := nodeinfoReqRes{ - SendCoords: table.self.getCoords(), + SendCoords: loc.getCoords(), IsResponse: isResponse, NodeInfo: m._getNodeInfo(), } diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 4206857e..7fa2b317 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -6,8 +6,6 @@ package yggdrasil import ( "encoding/hex" - "sync" - "sync/atomic" "time" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" @@ -21,17 +19,17 @@ import ( // In most cases, this involves passing the packet to the handler for outgoing traffic to another peer. // In other cases, its link protocol traffic is used to build the spanning tree, in which case this checks signatures and passes the message along to the switch. type peers struct { + phony.Inbox core *Core - mutex sync.Mutex // Synchronize writes to atomic - ports atomic.Value //map[switchPort]*peer, use CoW semantics + ports map[switchPort]*peer // use CoW semantics, share updated version with each peer + table *lookupTable // Sent from switch, share updated version with each peer } // Initializes the peers struct. func (ps *peers) init(c *Core) { - ps.mutex.Lock() - defer ps.mutex.Unlock() - ps.putPorts(make(map[switchPort]*peer)) ps.core = c + ps.ports = make(map[switchPort]*peer) + ps.table = new(lookupTable) } func (ps *peers) reconfigure() { @@ -80,16 +78,6 @@ func (ps *peers) getAllowedEncryptionPublicKeys() []string { return ps.core.config.Current.AllowedEncryptionPublicKeys } -// Atomically gets a map[switchPort]*peer of known peers. -func (ps *peers) getPorts() map[switchPort]*peer { - return ps.ports.Load().(map[switchPort]*peer) -} - -// Stores a map[switchPort]*peer (note that you should take a mutex before store operations to avoid conflicts with other nodes attempting to read/change/store at the same time). -func (ps *peers) putPorts(ports map[switchPort]*peer) { - ps.ports.Store(ports) -} - // Information known about a peer, including their box/sig keys, precomputed shared keys (static and ephemeral) and a handler for their outgoing traffic type peer struct { phony.Inbox @@ -110,10 +98,31 @@ type peer struct { // The below aren't actually useful internally, they're just gathered for getPeers statistics bytesSent uint64 bytesRecvd uint64 + ports map[switchPort]*peer + table *lookupTable +} + +func (ps *peers) updateTables(from phony.Actor, table *lookupTable) { + ps.Act(from, func() { + ps.table = table + ps._updatePeers() + }) +} + +func (ps *peers) _updatePeers() { + ports := ps.ports + table := ps.table + for _, peer := range ps.ports { + p := peer // peer is mutated during iteration + p.Act(ps, func() { + p.ports = ports + p.table = table + }) + } } // Creates a new peer with the specified box, sig, and linkShared keys, using the lowest unoccupied port number. -func (ps *peers) newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShared *crypto.BoxSharedKey, intf *linkInterface, closer func()) *peer { +func (ps *peers) _newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShared *crypto.BoxSharedKey, intf *linkInterface, closer func()) *peer { now := time.Now() p := peer{box: *box, sig: *sig, @@ -125,9 +134,7 @@ func (ps *peers) newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShare core: ps.core, intf: intf, } - ps.mutex.Lock() - defer ps.mutex.Unlock() - oldPorts := ps.getPorts() + oldPorts := ps.ports newPorts := make(map[switchPort]*peer) for k, v := range oldPorts { newPorts[k] = v @@ -139,46 +146,49 @@ func (ps *peers) newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShare break } } - ps.putPorts(newPorts) + ps.ports = newPorts + ps._updatePeers() return &p } -// Removes a peer for a given port, if one exists. -func (ps *peers) removePeer(port switchPort) { - if port == 0 { - return - } // Can't remove self peer - phony.Block(&ps.core.router, func() { - ps.core.switchTable.forgetPeer(port) +func (p *peer) _removeSelf() { + p.core.peers.Act(p, func() { + p.core.peers._removePeer(p) }) - ps.mutex.Lock() - oldPorts := ps.getPorts() - p, isIn := oldPorts[port] +} + +// Removes a peer for a given port, if one exists. +func (ps *peers) _removePeer(p *peer) { + if q := ps.ports[p.port]; p.port == 0 || q != p { + return + } // Can't remove self peer or nonexistant peer + ps.core.switchTable.forgetPeer(p.port) + oldPorts := ps.ports newPorts := make(map[switchPort]*peer) for k, v := range oldPorts { newPorts[k] = v } - delete(newPorts, port) - ps.putPorts(newPorts) - ps.mutex.Unlock() - if isIn { - if p.close != nil { - p.close() - } - close(p.done) + delete(newPorts, p.port) + if p.close != nil { + p.close() } + close(p.done) + ps.ports = newPorts + ps._updatePeers() } // If called, sends a notification to each peer that they should send a new switch message. // Mainly called by the switch after an update. func (ps *peers) sendSwitchMsgs(from phony.Actor) { - ports := ps.getPorts() - for _, p := range ports { - if p.port == 0 { - continue + ps.Act(from, func() { + for _, peer := range ps.ports { + p := peer + if p.port == 0 { + continue + } + p.Act(ps, p._sendSwitchMsg) } - p.Act(from, p._sendSwitchMsg) - } + }) } // This must be launched in a separate goroutine by whatever sets up the peer struct. @@ -236,12 +246,16 @@ func (p *peer) _handlePacket(packet []byte) { // Called to handle traffic or protocolTraffic packets. // In either case, this reads from the coords of the packet header, does a switch lookup, and forwards to the next node. func (p *peer) _handleTraffic(packet []byte) { - table := p.core.switchTable.getTable() - if _, isIn := table.elems[p.port]; !isIn && p.port != 0 { + if _, isIn := p.table.elems[p.port]; !isIn && p.port != 0 { // Drop traffic if the peer isn't in the switch return } - p.core.switchTable.packetInFrom(p, packet) + coords := switch_getPacketCoords(packet) + next := p.table.lookup(coords) + if nPeer, isIn := p.ports[next]; isIn { + nPeer.sendPacketsFrom(p, [][]byte{packet}) + } + //p.core.switchTable.packetInFrom(p, packet) } func (p *peer) sendPacketsFrom(from phony.Actor, packets [][]byte) { @@ -259,6 +273,7 @@ func (p *peer) _sendPackets(packets [][]byte) { size += len(packet) } p.bytesSent += uint64(size) + // FIXME need to manage queues here or else things can block! p.out(packets) } @@ -335,7 +350,8 @@ func (p *peer) _handleSwitchMsg(packet []byte) { return } if len(msg.Hops) < 1 { - p.core.peers.removePeer(p.port) + p._removeSelf() + return } var loc switchLocator prevKey := msg.Root @@ -346,7 +362,8 @@ func (p *peer) _handleSwitchMsg(packet []byte) { loc.coords = append(loc.coords, hop.Port) bs := getBytesForSig(&hop.Next, &sigMsg) if !crypto.Verify(&prevKey, bs, &hop.Sig) { - p.core.peers.removePeer(p.port) + p._removeSelf() + return } prevKey = hop.Next } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index b08a12d3..ac4d655d 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -61,7 +61,11 @@ func (r *router) init(core *Core) { linkType: "self", }, } - p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &crypto.BoxSharedKey{}, &self, nil) + var p *peer + phony.Block(&r.core.peers, func() { + // FIXME don't block here! + p = r.core.peers._newPeer(&r.core.boxPub, &r.core.sigPub, &crypto.BoxSharedKey{}, &self, nil) + }) p.out = func(packets [][]byte) { r.handlePackets(p, packets) } r.out = func(bs []byte) { p.handlePacketFrom(r, bs) } r.nodeinfo.init(r.core) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index ce5e3db6..33f2a1bd 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -12,13 +12,12 @@ package yggdrasil // A little annoying to do with constant changes from backpressure import ( - "math/rand" + //"math/rand" "sync" - "sync/atomic" "time" "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/Arceliar/phony" ) @@ -172,8 +171,6 @@ type switchTable struct { mutex sync.RWMutex // Lock for reads/writes of switchData parent switchPort // Port of whatever peer is our parent, or self if we're root data switchData // - updater atomic.Value // *sync.Once - table atomic.Value // lookupTable phony.Inbox // Owns the below queues switch_buffers // Queues - not atomic so ONLY use through the actor idle map[switchPort]struct{} // idle peers - not atomic so ONLY use through the actor @@ -190,8 +187,6 @@ func (t *switchTable) init(core *Core) { locator := switchLocator{root: t.key, tstamp: now.Unix()} peers := make(map[switchPort]peerInfo) t.data = switchData{locator: locator, peers: peers} - t.updater.Store(&sync.Once{}) - t.table.Store(lookupTable{}) t.drop = make(map[crypto.SigPubKey]int64) phony.Block(t, func() { core.config.Mutex.RLock() @@ -204,6 +199,7 @@ func (t *switchTable) init(core *Core) { t.queues.bufs = make(map[switchPort]map[string]switch_buffer) t.idle = make(map[switchPort]struct{}) }) + t.updateTable() } func (t *switchTable) reconfigure() { @@ -254,7 +250,7 @@ func (t *switchTable) cleanRoot() { t.time = now if t.data.locator.root != t.key { t.data.seq++ - t.updater.Store(&sync.Once{}) + t.updateTable() t.core.router.reset(nil) } t.data.locator = switchLocator{root: t.key, tstamp: now.Unix()} @@ -292,7 +288,7 @@ func (t *switchTable) forgetPeer(port switchPort) { t.mutex.Lock() defer t.mutex.Unlock() delete(t.data.peers, port) - t.updater.Store(&sync.Once{}) + defer t.updateTable() if port != t.parent { return } @@ -528,7 +524,7 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep t.core.peers.sendSwitchMsgs(t) } if true || doUpdate { - t.updater.Store(&sync.Once{}) + t.updateTable() } return } @@ -566,13 +562,7 @@ func (t *switchTable) updateTable() { time: pinfo.time, } } - t.table.Store(newTable) -} - -// Returns a copy of the atomically-updated table used for switch lookups -func (t *switchTable) getTable() lookupTable { - t.updater.Load().(*sync.Once).Do(t.updateTable) - return t.table.Load().(lookupTable) + t.core.peers.updateTables(nil, &newTable) // TODO not be from nil } // Starts the switch worker @@ -589,6 +579,7 @@ type closerInfo struct { // Return a map of ports onto distance, keeping only ports closer to the destination than this node // If the map is empty (or nil), then no peer is closer +/* func (t *switchTable) getCloser(dest []byte) []closerInfo { table := t.getTable() myDist := table.self.dist(dest) @@ -605,8 +596,10 @@ func (t *switchTable) getCloser(dest []byte) []closerInfo { } return closer } +*/ // Returns true if the peer is closer to the destination than ourself +/* func (t *switchTable) portIsCloser(dest []byte, port switchPort) bool { table := t.getTable() if info, isIn := table.elems[port]; isIn { @@ -617,6 +610,7 @@ func (t *switchTable) portIsCloser(dest []byte, port switchPort) bool { return false } } +*/ // Get the coords of a packet without decoding func switch_getPacketCoords(packet []byte) []byte { @@ -686,23 +680,26 @@ func (t *lookupTable) lookup(coords []byte) switchPort { // Either send it to ourself, or to the first idle peer that's free // Returns true if the packet has been handled somehow, false if it should be queued func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]struct{}) (bool, switchPort) { - coords := switch_getPacketCoords(packet) - table := t.getTable() - port := table.lookup(coords) - ports := t.core.peers.getPorts() - peer := ports[port] - if peer == nil { - // FIXME hack, if the peer disappeared durring a race then don't buffer - return true, 0 - } - if _, isIdle := idle[port]; isIdle || port == 0 { - // Either no closer peers, or the closest peer is idle - delete(idle, port) - peer.sendPacketsFrom(t, [][]byte{packet}) - return true, port - } - // There's a closer peer, but it's not idle, so buffer it - return false, port + /* + coords := switch_getPacketCoords(packet) + table := t.getTable() + port := table.lookup(coords) + ports := t.core.peers.getPorts() + peer := ports[port] + if peer == nil { + // FIXME hack, if the peer disappeared durring a race then don't buffer + return true, 0 + } + if _, isIdle := idle[port]; isIdle || port == 0 { + // Either no closer peers, or the closest peer is idle + delete(idle, port) + peer.sendPacketsFrom(t, [][]byte{packet}) + return true, port + } + // There's a closer peer, but it's not idle, so buffer it + return false, port + */ + return true, 0 } // Info about a buffered packet @@ -726,52 +723,54 @@ type switch_buffers struct { } func (b *switch_buffers) _cleanup(t *switchTable) { - for port, pbufs := range b.bufs { - for streamID, buf := range pbufs { - // Remove queues for which we have no next hop - packet := buf.packets[0] - coords := switch_getPacketCoords(packet.bytes) - if len(t.getCloser(coords)) == 0 { - for _, packet := range buf.packets { - util.PutBytes(packet.bytes) - } - b.size -= buf.size - delete(pbufs, streamID) - } - } - if len(pbufs) == 0 { - delete(b.bufs, port) - } - } - - for b.size > b.totalMaxSize { - // Drop a random queue - target := rand.Uint64() % b.size - var size uint64 // running total + /* for port, pbufs := range b.bufs { for streamID, buf := range pbufs { - 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(pbufs, streamID) - if len(pbufs) == 0 { - delete(b.bufs, port) + // Remove queues for which we have no next hop + packet := buf.packets[0] + coords := switch_getPacketCoords(packet.bytes) + if len(t.getCloser(coords)) == 0 { + for _, packet := range buf.packets { + util.PutBytes(packet.bytes) } - } else { - // Need to update the map, since buf was retrieved by value - pbufs[streamID] = buf + b.size -= buf.size + delete(pbufs, streamID) } - break + } + if len(pbufs) == 0 { + delete(b.bufs, port) } } - } + + for b.size > b.totalMaxSize { + // Drop a random queue + target := rand.Uint64() % b.size + var size uint64 // running total + for port, pbufs := range b.bufs { + for streamID, buf := range pbufs { + 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(pbufs, streamID) + if len(pbufs) == 0 { + delete(b.bufs, port) + } + } else { + // Need to update the map, since buf was retrieved by value + pbufs[streamID] = buf + } + break + } + } + } + */ } // Handles incoming idle notifications @@ -779,57 +778,60 @@ func (b *switch_buffers) _cleanup(t *switchTable) { // 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) bool { // TODO? only send packets for which this is the best next hop that isn't currently blocked sending - to := t.core.peers.getPorts()[port] - if to == nil { - return true - } - var packets [][]byte - var psize int - t.queues._cleanup(t) - now := time.Now() - pbufs := t.queues.bufs[port] - for psize < 65535 { - var best *string - var bestPriority float64 - for streamID, buf := range pbufs { - // Filter over the streams that this node is closer to - // Keep the one with the smallest queue - packet := buf.packets[0] - priority := float64(now.Sub(packet.time)) / float64(buf.size) - if priority >= bestPriority { - b := streamID // copy since streamID is mutated in the loop - best = &b - bestPriority = priority - } + /* + to := t.core.peers.getPorts()[port] + if to == nil { + return true } - if best != nil { - buf := pbufs[*best] - var packet switch_packetInfo - // TODO decide if this should be LIFO or FIFO - packet, buf.packets = buf.packets[0], buf.packets[1:] - buf.size -= uint64(len(packet.bytes)) - t.queues.size -= uint64(len(packet.bytes)) - if len(buf.packets) == 0 { - delete(pbufs, *best) - if len(pbufs) == 0 { - delete(t.queues.bufs, port) + var packets [][]byte + var psize int + t.queues._cleanup(t) + now := time.Now() + pbufs := t.queues.bufs[port] + for psize < 65535 { + var best *string + var bestPriority float64 + for streamID, buf := range pbufs { + // Filter over the streams that this node is closer to + // Keep the one with the smallest queue + packet := buf.packets[0] + priority := float64(now.Sub(packet.time)) / float64(buf.size) + if priority >= bestPriority { + b := streamID // copy since streamID is mutated in the loop + best = &b + bestPriority = priority } - } else { - // Need to update the map, since buf was retrieved by value - pbufs[*best] = buf - } - packets = append(packets, packet.bytes) - psize += len(packet.bytes) - } else { - // Finished finding packets - break + if best != nil { + buf := pbufs[*best] + var packet switch_packetInfo + // TODO decide if this should be LIFO or FIFO + packet, buf.packets = buf.packets[0], buf.packets[1:] + buf.size -= uint64(len(packet.bytes)) + t.queues.size -= uint64(len(packet.bytes)) + if len(buf.packets) == 0 { + delete(pbufs, *best) + if len(pbufs) == 0 { + delete(t.queues.bufs, port) + } + } else { + // Need to update the map, since buf was retrieved by value + pbufs[*best] = buf + + } + packets = append(packets, packet.bytes) + psize += len(packet.bytes) + } else { + // Finished finding packets + break + } } - } - if len(packets) > 0 { - to.sendPacketsFrom(t, packets) - return true - } + if len(packets) > 0 { + to.sendPacketsFrom(t, packets) + return true + } + return false + */ return false } From d47797088f52ebfb32ee292b8f6563634863b7d0 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 29 Mar 2020 00:48:41 -0500 Subject: [PATCH 0532/1109] fix shutdown deadlock --- src/yggdrasil/core.go | 2 ++ src/yggdrasil/switch.go | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 98a5c6e1..dcb5bc7a 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -195,8 +195,10 @@ func (c *Core) _stop() { c.addPeerTimer.Stop() } c.link.stop() + /* FIXME this deadlocks, need a waitgroup or something to coordinate shutdown for _, peer := range c.GetPeers() { c.DisconnectPeer(peer.Port) } + */ c.log.Infoln("Stopped") } diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 33f2a1bd..7ccb6c94 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -250,7 +250,7 @@ func (t *switchTable) cleanRoot() { t.time = now if t.data.locator.root != t.key { t.data.seq++ - t.updateTable() + defer t.updateTable() t.core.router.reset(nil) } t.data.locator = switchLocator{root: t.key, tstamp: now.Unix()} @@ -524,7 +524,7 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep t.core.peers.sendSwitchMsgs(t) } if true || doUpdate { - t.updateTable() + defer t.updateTable() } return } From 15b850be6e6e1bd02753edbcd0155ac08928149d Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 29 Mar 2020 01:38:32 -0500 Subject: [PATCH 0533/1109] fix deadlock when running updateTable in the switch --- src/yggdrasil/switch.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 7ccb6c94..ab2e1194 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -199,7 +199,7 @@ func (t *switchTable) init(core *Core) { t.queues.bufs = make(map[switchPort]map[string]switch_buffer) t.idle = make(map[switchPort]struct{}) }) - t.updateTable() + t._updateTable() } func (t *switchTable) reconfigure() { @@ -250,7 +250,7 @@ func (t *switchTable) cleanRoot() { t.time = now if t.data.locator.root != t.key { t.data.seq++ - defer t.updateTable() + defer t._updateTable() t.core.router.reset(nil) } t.data.locator = switchLocator{root: t.key, tstamp: now.Unix()} @@ -288,7 +288,7 @@ func (t *switchTable) forgetPeer(port switchPort) { t.mutex.Lock() defer t.mutex.Unlock() delete(t.data.peers, port) - defer t.updateTable() + defer t._updateTable() if port != t.parent { return } @@ -524,7 +524,7 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep t.core.peers.sendSwitchMsgs(t) } if true || doUpdate { - defer t.updateTable() + defer t._updateTable() } return } @@ -534,7 +534,7 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep // The rest of these are related to the switch worker // This is called via a sync.Once to update the atomically readable subset of switch information that gets used for routing decisions. -func (t *switchTable) updateTable() { +func (t *switchTable) _updateTable() { // WARNING this should only be called from within t.data.updater.Do() // It relies on the sync.Once for synchronization with messages and lookups // TODO use a pre-computed faster lookup table @@ -543,8 +543,6 @@ func (t *switchTable) updateTable() { // Each struct has stores the best port to forward to, and a next coord map // Move to struct, then iterate over coord maps until you dead end // The last port before the dead end should be the closest - t.mutex.RLock() - defer t.mutex.RUnlock() newTable := lookupTable{ self: t.data.locator.clone(), elems: make(map[switchPort]tableElem, len(t.data.peers)), From 9834f222db65efab838a1a2403b8e039109742f2 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 29 Mar 2020 19:01:50 -0500 Subject: [PATCH 0534/1109] more work in progress actorizing the remaining parts of the switch --- src/yggdrasil/api.go | 7 ++- src/yggdrasil/dht.go | 12 ++-- src/yggdrasil/link.go | 4 +- src/yggdrasil/nodeinfo.go | 3 +- src/yggdrasil/peer.go | 37 +++++++----- src/yggdrasil/router.go | 18 +++++- src/yggdrasil/search.go | 3 +- src/yggdrasil/session.go | 5 +- src/yggdrasil/switch.go | 123 +++++++++++++++++--------------------- 9 files changed, 111 insertions(+), 101 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 15e2acd6..a722dc52 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -330,8 +330,11 @@ func (c *Core) EncryptionPublicKey() string { // connected to any other nodes (effectively making you the root of a // single-node network). func (c *Core) Coords() []uint64 { - loc := c.switchTable.getLocator() - return wire_coordsBytestoUint64s(loc.getCoords()) + var coords []byte + phony.Block(&c.router, func() { + coords = c.router.table.self.getCoords() + }) + return wire_coordsBytestoUint64s(coords) } // Address gets the IPv6 address of the Yggdrasil node. This is always a /128 diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 8efc549f..56d03ed1 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -186,11 +186,9 @@ func dht_ordered(first, second, third *crypto.NodeID) bool { // Update info about the node that sent the request. func (t *dht) handleReq(req *dhtReq) { // Send them what they asked for - loc := t.router.core.switchTable.getLocator() - coords := loc.getCoords() res := dhtRes{ Key: t.router.core.boxPub, - Coords: coords, + Coords: t.router.table.self.getCoords(), Dest: req.Dest, Infos: t.lookup(&req.Dest, false), } @@ -300,11 +298,9 @@ func (t *dht) ping(info *dhtInfo, target *crypto.NodeID) { if target == nil { target = &t.nodeID } - loc := t.router.core.switchTable.getLocator() - coords := loc.getCoords() req := dhtReq{ Key: t.router.core.boxPub, - Coords: coords, + Coords: t.router.table.self.getCoords(), Dest: *target, } t.sendReq(&req, info) @@ -378,7 +374,7 @@ func (t *dht) getImportant() []*dhtInfo { }) // Keep the ones that are no further than the closest seen so far minDist := ^uint64(0) - loc := t.router.core.switchTable.getLocator() + loc := t.router.table.self important := infos[:0] for _, info := range infos { dist := uint64(loc.dist(info.coords)) @@ -416,7 +412,7 @@ func (t *dht) isImportant(ninfo *dhtInfo) bool { } important := t.getImportant() // Check if ninfo is of equal or greater importance to what we already know - loc := t.router.core.switchTable.getLocator() + loc := t.router.table.self ndist := uint64(loc.dist(ninfo.coords)) minDist := ^uint64(0) for _, info := range important { diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index fa6563f1..978e8eab 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -300,7 +300,7 @@ func (intf *linkInterface) notifyBlockedSend() { intf.Act(nil, func() { if intf.sendTimer != nil { //As far as we know, we're still trying to send, and the timer fired. - intf.link.core.switchTable.blockPeer(intf.peer.port) + intf.link.core.switchTable.blockPeer(intf, intf.peer.port) } }) } @@ -340,7 +340,7 @@ func (intf *linkInterface) notifyStalled() { intf.stallTimer.Stop() intf.stallTimer = nil intf.stalled = true - intf.link.core.switchTable.blockPeer(intf.peer.port) + intf.link.core.switchTable.blockPeer(intf, intf.peer.port) } }) } diff --git a/src/yggdrasil/nodeinfo.go b/src/yggdrasil/nodeinfo.go index 745756fe..b179d20b 100644 --- a/src/yggdrasil/nodeinfo.go +++ b/src/yggdrasil/nodeinfo.go @@ -18,6 +18,7 @@ type nodeinfo struct { myNodeInfo NodeInfoPayload callbacks map[crypto.BoxPubKey]nodeinfoCallback cache map[crypto.BoxPubKey]nodeinfoCached + table *lookupTable } type nodeinfoCached struct { @@ -187,7 +188,7 @@ func (m *nodeinfo) sendNodeInfo(key crypto.BoxPubKey, coords []byte, isResponse } func (m *nodeinfo) _sendNodeInfo(key crypto.BoxPubKey, coords []byte, isResponse bool) { - loc := m.core.switchTable.getLocator() + loc := m.table.self nodeinfo := nodeinfoReqRes{ SendCoords: loc.getCoords(), IsResponse: isResponse, diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 7fa2b317..9acb9321 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -162,7 +162,7 @@ func (ps *peers) _removePeer(p *peer) { if q := ps.ports[p.port]; p.port == 0 || q != p { return } // Can't remove self peer or nonexistant peer - ps.core.switchTable.forgetPeer(p.port) + ps.core.switchTable.forgetPeer(ps, p.port) oldPorts := ps.ports newPorts := make(map[switchPort]*peer) for k, v := range oldPorts { @@ -328,7 +328,7 @@ func (p *peer) _handleLinkTraffic(bs []byte) { // Gets a switchMsg from the switch, adds signed next-hop info for this peer, and sends it to them. func (p *peer) _sendSwitchMsg() { - msg := p.core.switchTable.getMsg() + msg := p.table.getMsg() if msg == nil { return } @@ -367,19 +367,26 @@ func (p *peer) _handleSwitchMsg(packet []byte) { } prevKey = hop.Next } - p.core.switchTable.handleMsg(&msg, p.port) - if !p.core.switchTable.checkRoot(&msg) { - // Bad switch message - p.dinfo = nil - return - } - // Pass a message to the dht informing it that this peer (still) exists - loc.coords = loc.coords[:len(loc.coords)-1] - p.dinfo = &dhtInfo{ - key: p.box, - coords: loc.getCoords(), - } - p._updateDHT() + p.core.switchTable.Act(p, func() { + if !p.core.switchTable._checkRoot(&msg) { + // Bad switch message + p.Act(&p.core.switchTable, func() { + p.dinfo = nil + }) + } else { + // handle the message + p.core.switchTable._handleMsg(&msg, p.port, false) + p.Act(&p.core.switchTable, func() { + // Pass a message to the dht informing it that this peer (still) exists + loc.coords = loc.coords[:len(loc.coords)-1] + p.dinfo = &dhtInfo{ + key: p.box, + coords: loc.getCoords(), + } + p._updateDHT() + }) + } + }) } // This generates the bytes that we sign or check the signature of for a switchMsg. diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index ac4d655d..40b8303f 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -46,6 +46,7 @@ type router struct { nodeinfo nodeinfo searches searches sessions sessions + table *lookupTable // has a copy of our locator } // Initializes the router struct, which includes setting up channels to/from the adapter. @@ -77,6 +78,21 @@ func (r *router) init(core *Core) { r.sessions.init(r) } +func (r *router) updateTable(from phony.Actor, table *lookupTable) { + r.Act(from, func() { + r.table = table + r.nodeinfo.Act(r, func() { + r.nodeinfo.table = table + }) + for _, ses := range r.sessions.sinfos { + sinfo := ses + sinfo.Act(r, func() { + sinfo.table = table + }) + } + }) +} + // Reconfigures the router and any child modules. This should only ever be run // by the router actor. func (r *router) reconfigure() { @@ -130,7 +146,7 @@ func (r *router) reset(from phony.Actor) { func (r *router) doMaintenance() { phony.Block(r, func() { // Any periodic maintenance stuff goes here - r.core.switchTable.doMaintenance() + r.core.switchTable.doMaintenance(r) r.dht.doMaintenance() r.sessions.cleanup() }) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 91f0490c..febde3d8 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -161,11 +161,10 @@ func (sinfo *searchInfo) continueSearch(infos []*dhtInfo) { // Initially start a search func (sinfo *searchInfo) startSearch() { - loc := sinfo.searches.router.core.switchTable.getLocator() var infos []*dhtInfo infos = append(infos, &dhtInfo{ key: sinfo.searches.router.core.boxPub, - coords: loc.getCoords(), + coords: sinfo.searches.router.table.self.getCoords(), }) // Start the search by asking ourself, useful if we're the destination sinfo.continueSearch(infos) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index eaa67fd0..01c2cdfb 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -52,6 +52,7 @@ type sessionInfo struct { cancel util.Cancellation // Used to terminate workers conn *Conn // The associated Conn object callbacks []chan func() // Finished work from crypto workers + table *lookupTable // table.self is a locator where we get our coords } // Represents a session ping/pong packet, and includes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU. @@ -217,6 +218,7 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.myHandle = *crypto.NewHandle() sinfo.theirAddr = *address.AddrForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) + sinfo.table = ss.router.table ss.sinfos[sinfo.myHandle] = &sinfo ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle return &sinfo @@ -266,8 +268,7 @@ func (ss *sessions) removeSession(sinfo *sessionInfo) { // Returns a session ping appropriate for the given session info. func (sinfo *sessionInfo) _getPing() sessionPing { - loc := sinfo.sessions.router.core.switchTable.getLocator() - coords := loc.getCoords() + coords := sinfo.table.self.getCoords() ping := sessionPing{ SendPermPub: sinfo.sessions.router.core.boxPub, Handle: sinfo.myHandle, diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index ab2e1194..2661b460 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -12,12 +12,9 @@ package yggdrasil // A little annoying to do with constant changes from backpressure import ( - //"math/rand" - "sync" "time" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" - //"github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/Arceliar/phony" ) @@ -149,6 +146,7 @@ type tableElem struct { type lookupTable struct { self switchLocator elems map[switchPort]tableElem + _msg switchMsg } // This is switch information which is mutable and needs to be modified by other goroutines, but is not accessed atomically. @@ -168,7 +166,6 @@ type switchTable struct { key crypto.SigPubKey // Our own key time time.Time // Time when locator.tstamp was last updated drop map[crypto.SigPubKey]int64 // Tstamp associated with a dropped root - mutex sync.RWMutex // Lock for reads/writes of switchData parent switchPort // Port of whatever peer is our parent, or self if we're root data switchData // phony.Inbox // Owns the below @@ -208,24 +205,17 @@ func (t *switchTable) reconfigure() { t.core.peers.reconfigure() } -// Safely gets a copy of this node's locator. -func (t *switchTable) getLocator() switchLocator { - t.mutex.RLock() - defer t.mutex.RUnlock() - return t.data.locator.clone() -} - // Regular maintenance to possibly timeout/reset the root and similar. -func (t *switchTable) doMaintenance() { - // Periodic maintenance work to keep things internally consistent - t.mutex.Lock() // Write lock - defer t.mutex.Unlock() // Release lock when we're done - t.cleanRoot() - t.cleanDropped() +func (t *switchTable) doMaintenance(from phony.Actor) { + t.Act(from, func() { + // Periodic maintenance work to keep things internally consistent + t._cleanRoot() + t._cleanDropped() + }) } // Updates the root periodically if it is ourself, or promotes ourself to root if we're better than the current root or if the current root has timed out. -func (t *switchTable) cleanRoot() { +func (t *switchTable) _cleanRoot() { // TODO rethink how this is done?... // Get rid of the root if it looks like its timed out now := time.Now() @@ -259,49 +249,49 @@ func (t *switchTable) cleanRoot() { } // Blocks and, if possible, unparents a peer -func (t *switchTable) blockPeer(port switchPort) { - t.mutex.Lock() - defer t.mutex.Unlock() - peer, isIn := t.data.peers[port] - if !isIn { - return - } - peer.blocked = true - t.data.peers[port] = peer - if port != t.parent { - return - } - t.parent = 0 - for _, info := range t.data.peers { - if info.port == port { - continue +func (t *switchTable) blockPeer(from phony.Actor, port switchPort) { + t.Act(from, func() { + peer, isIn := t.data.peers[port] + if !isIn { + return } - t.unlockedHandleMsg(&info.msg, info.port, true) - } - t.unlockedHandleMsg(&peer.msg, peer.port, true) + peer.blocked = true + t.data.peers[port] = peer + if port != t.parent { + return + } + t.parent = 0 + for _, info := range t.data.peers { + if info.port == port { + continue + } + t._handleMsg(&info.msg, info.port, true) + } + t._handleMsg(&peer.msg, peer.port, true) + }) } // Removes a peer. // Must be called by the router actor with a lambda that calls this. // If the removed peer was this node's parent, it immediately tries to find a new parent. -func (t *switchTable) forgetPeer(port switchPort) { - t.mutex.Lock() - defer t.mutex.Unlock() - delete(t.data.peers, port) - defer t._updateTable() - if port != t.parent { - return - } - t.parent = 0 - for _, info := range t.data.peers { - t.unlockedHandleMsg(&info.msg, info.port, true) - } +func (t *switchTable) forgetPeer(from phony.Actor, port switchPort) { + t.Act(from, func() { + delete(t.data.peers, port) + defer t._updateTable() + if port != t.parent { + return + } + t.parent = 0 + for _, info := range t.data.peers { + t._handleMsg(&info.msg, info.port, true) + } + }) } // Dropped is a list of roots that are better than the current root, but stopped sending new timestamps. // If we switch to a new root, and that root is better than an old root that previously timed out, then we can clean up the old dropped root infos. // This function is called periodically to do that cleanup. -func (t *switchTable) cleanDropped() { +func (t *switchTable) _cleanDropped() { // TODO? only call this after root changes, not periodically for root := range t.drop { if !firstIsBetter(&root, &t.data.locator.root) { @@ -327,9 +317,7 @@ type switchMsgHop struct { } // This returns a *switchMsg to a copy of this node's current switchMsg, which can safely have additional information appended to Hops and sent to a peer. -func (t *switchTable) getMsg() *switchMsg { - t.mutex.RLock() - defer t.mutex.RUnlock() +func (t *switchTable) _getMsg() *switchMsg { if t.parent == 0 { return &switchMsg{Root: t.key, TStamp: t.data.locator.tstamp} } else if parent, isIn := t.data.peers[t.parent]; isIn { @@ -341,14 +329,18 @@ func (t *switchTable) getMsg() *switchMsg { } } +func (t *lookupTable) getMsg() *switchMsg { + msg := t._msg + msg.Hops = append([]switchMsgHop(nil), t._msg.Hops...) + return &msg +} + // This function checks that the root information in a switchMsg is OK. // In particular, that the root is better, or else the same as the current root but with a good timestamp, and that this root+timestamp haven't been dropped due to timeout. -func (t *switchTable) checkRoot(msg *switchMsg) bool { +func (t *switchTable) _checkRoot(msg *switchMsg) bool { // returns false if it's a dropped root, not a better root, or has an older timestamp // returns true otherwise // used elsewhere to keep inserting peers into the dht only if root info is OK - t.mutex.RLock() - defer t.mutex.RUnlock() dropTstamp, isIn := t.drop[msg.Root] switch { case isIn && dropTstamp >= msg.TStamp: @@ -364,20 +356,13 @@ func (t *switchTable) checkRoot(msg *switchMsg) bool { } } -// This is a mutexed wrapper to unlockedHandleMsg, and is called by the peer structs in peers.go to pass a switchMsg for that peer into the switch. -func (t *switchTable) handleMsg(msg *switchMsg, fromPort switchPort) { - t.mutex.Lock() - defer t.mutex.Unlock() - t.unlockedHandleMsg(msg, fromPort, false) -} - // This updates the switch with information about a peer. // Then the tricky part, it decides if it should update our own locator as a result. // That happens if this node is already our parent, or is advertising a better root, or is advertising a better path to the same root, etc... // There are a lot of very delicate order sensitive checks here, so its' best to just read the code if you need to understand what it's doing. // It's very important to not change the order of the statements in the case function unless you're absolutely sure that it's safe, including safe if used alongside nodes that used the previous order. // Set the third arg to true if you're reprocessing an old message, e.g. to find a new parent after one disconnects, to avoid updating some timing related things. -func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, reprocessing bool) { +func (t *switchTable) _handleMsg(msg *switchMsg, fromPort switchPort, reprocessing bool) { // TODO directly use a switchMsg instead of switchMessage + sigs now := time.Now() // Set up the sender peerInfo @@ -500,10 +485,10 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep if peer.port == sender.port { continue } - t.unlockedHandleMsg(&peer.msg, peer.port, true) + t._handleMsg(&peer.msg, peer.port, true) } // Process the sender last, to avoid keeping them as a parent if at all possible. - t.unlockedHandleMsg(&sender.msg, sender.port, true) + t._handleMsg(&sender.msg, sender.port, true) case now.Sub(t.time) < switch_throttle: // We've already gotten an update from this root recently, so ignore this one to avoid flooding. case sender.locator.tstamp > t.data.locator.tstamp: @@ -521,7 +506,7 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep } t.data.locator = sender.locator t.parent = sender.port - t.core.peers.sendSwitchMsgs(t) + defer t.core.peers.sendSwitchMsgs(t) } if true || doUpdate { defer t._updateTable() @@ -560,7 +545,9 @@ func (t *switchTable) _updateTable() { time: pinfo.time, } } - t.core.peers.updateTables(nil, &newTable) // TODO not be from nil + newTable._msg = *t._getMsg() + t.core.peers.updateTables(t, &newTable) + t.core.router.updateTable(t, &newTable) } // Starts the switch worker From 7a314afb3114f246801defc3b879f8d94fd022d0 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Tue, 31 Mar 2020 18:14:20 -0500 Subject: [PATCH 0535/1109] check if an error was returned by Core._init and return it if so --- contrib/ansible/genkeys.go | 4 ++-- src/yggdrasil/core.go | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/contrib/ansible/genkeys.go b/contrib/ansible/genkeys.go index 1d7c222d..681431b5 100644 --- a/contrib/ansible/genkeys.go +++ b/contrib/ansible/genkeys.go @@ -12,9 +12,9 @@ import ( "net" "os" + "github.com/cheggaaa/pb/v3" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" - "github.com/cheggaaa/pb/v3" ) var numHosts = flag.Int("hosts", 1, "number of host vars to generate") @@ -30,7 +30,7 @@ type keySet struct { func main() { flag.Parse() - bar := pb.StartNew(*keyTries * 2 + *numHosts) + bar := pb.StartNew(*keyTries*2 + *numHosts) if *numHosts > *keyTries { println("Can't generate less keys than hosts.") diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 98a5c6e1..d88b787d 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -160,7 +160,10 @@ func (c *Core) _start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState } c.log.Infoln("Starting up...") - c._init() + if err := c._init(); err != nil { + c.log.Errorln("Failed to initialize core") + return nil, err + } if err := c.link.init(c); err != nil { c.log.Errorln("Failed to start link interfaces") From 945930aa2ccbc327ae6bef0ec8db36b65a398a17 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 3 Apr 2020 00:32:26 -0500 Subject: [PATCH 0536/1109] WIP have peer actors queue packets, temporarily a single simple FIFO queue with head drop --- src/yggdrasil/api.go | 29 ---- src/yggdrasil/link.go | 18 +-- src/yggdrasil/packetqueue.go | 39 +++++ src/yggdrasil/peer.go | 43 ++++-- src/yggdrasil/router.go | 9 +- src/yggdrasil/switch.go | 291 +---------------------------------- 6 files changed, 91 insertions(+), 338 deletions(-) create mode 100644 src/yggdrasil/packetqueue.go diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index a722dc52..31ece6b8 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -199,35 +199,6 @@ func (c *Core) GetDHT() []DHTEntry { return dhtentries } -// GetSwitchQueues returns information about the switch queues that are -// currently in effect. These values can change within an instant. -func (c *Core) GetSwitchQueues() SwitchQueues { - var switchqueues SwitchQueues - switchTable := &c.switchTable - getSwitchQueues := func() { - switchqueues = SwitchQueues{ - Count: uint64(len(switchTable.queues.bufs)), - Size: switchTable.queues.size, - HighestCount: uint64(switchTable.queues.maxbufs), - HighestSize: switchTable.queues.maxsize, - MaximumSize: switchTable.queues.totalMaxSize, - } - for port, pbuf := range switchTable.queues.bufs { - for k, v := range pbuf { - queue := SwitchQueue{ - ID: k, - Size: v.size, - Packets: uint64(len(v.packets)), - Port: uint64(port), - } - switchqueues.Queues = append(switchqueues.Queues, queue) - } - } - } - phony.Block(&c.switchTable, getSwitchQueues) - return switchqueues -} - // GetSessions returns a list of open sessions from this node to other nodes. func (c *Core) GetSessions() []Session { var sessions []Session diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 978e8eab..15017993 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -62,7 +62,7 @@ type linkInterface struct { keepAliveTimer *time.Timer // Fires to send keep-alive traffic stallTimer *time.Timer // Fires to signal that no incoming traffic (including keep-alive) has been seen closeTimer *time.Timer // Fires when the link has been idle so long we need to close it - inSwitch bool // True if the switch is tracking this link + isIdle bool // True if the peer actor knows the link is idle stalled bool // True if we haven't been receiving any response traffic unstalled bool // False if an idle notification to the switch hasn't been sent because we stalled (or are first starting up) } @@ -278,7 +278,7 @@ const ( func (intf *linkInterface) notifySending(size int, isLinkTraffic bool) { intf.Act(&intf.writer, func() { if !isLinkTraffic { - intf.inSwitch = false + intf.isIdle = false } intf.sendTimer = time.AfterFunc(sendTime, intf.notifyBlockedSend) intf._cancelStallTimer() @@ -311,7 +311,7 @@ func (intf *linkInterface) notifySent(size int, isLinkTraffic bool) { intf.sendTimer.Stop() intf.sendTimer = nil if !isLinkTraffic { - intf._notifySwitch() + intf._notifyIdle() } if size > 0 && intf.stallTimer == nil { intf.stallTimer = time.AfterFunc(stallTime, intf.notifyStalled) @@ -320,15 +320,13 @@ func (intf *linkInterface) notifySent(size int, isLinkTraffic bool) { } // Notify the switch that we're ready for more traffic, assuming we're not in a stalled state -func (intf *linkInterface) _notifySwitch() { - if !intf.inSwitch { +func (intf *linkInterface) _notifyIdle() { + if !intf.isIdle { if intf.stalled { intf.unstalled = false } else { - intf.inSwitch = true - intf.link.core.switchTable.Act(intf, func() { - intf.link.core.switchTable._idleIn(intf.peer.port) - }) + intf.isIdle = true + intf.peer.Act(intf, intf.peer._handleIdle) } } } @@ -364,7 +362,7 @@ func (intf *linkInterface) notifyRead(size int) { } intf.stalled = false if !intf.unstalled { - intf._notifySwitch() + intf._notifyIdle() intf.unstalled = true } if size > 0 && intf.stallTimer == nil { diff --git a/src/yggdrasil/packetqueue.go b/src/yggdrasil/packetqueue.go new file mode 100644 index 00000000..ac66c0da --- /dev/null +++ b/src/yggdrasil/packetqueue.go @@ -0,0 +1,39 @@ +package yggdrasil + +import "github.com/yggdrasil-network/yggdrasil-go/src/util" + +// TODO take max size from config +const MAX_PACKET_QUEUE_SIZE = 1048576 // 1 MB + +// TODO separate queues per e.g. traffic flow +type packetQueue struct { + packets [][]byte + size uint32 +} + +func (q *packetQueue) cleanup() { + for q.size > MAX_PACKET_QUEUE_SIZE { + if packet, success := q.pop(); success { + util.PutBytes(packet) + } else { + panic("attempted to drop packet from empty queue") + break + } + } +} + +func (q *packetQueue) push(packet []byte) { + q.packets = append(q.packets, packet) + q.size += uint32(len(packet)) + q.cleanup() +} + +func (q *packetQueue) pop() ([]byte, bool) { + if len(q.packets) > 0 { + packet := q.packets[0] + q.packets = q.packets[1:] + q.size -= uint32(len(packet)) + return packet, true + } + return nil, false +} diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 9acb9321..bc9de04c 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -100,6 +100,8 @@ type peer struct { bytesRecvd uint64 ports map[switchPort]*peer table *lookupTable + queue packetQueue + idle bool } func (ps *peers) updateTables(from phony.Actor, table *lookupTable) { @@ -243,6 +245,13 @@ func (p *peer) _handlePacket(packet []byte) { } } +// Get the coords of a packet without decoding +func peer_getPacketCoords(packet []byte) []byte { + _, pTypeLen := wire_decode_uint64(packet) + coords, _ := wire_decode_coords(packet[pTypeLen:]) + return coords +} + // Called to handle traffic or protocolTraffic packets. // In either case, this reads from the coords of the packet header, does a switch lookup, and forwards to the next node. func (p *peer) _handleTraffic(packet []byte) { @@ -250,7 +259,7 @@ func (p *peer) _handleTraffic(packet []byte) { // Drop traffic if the peer isn't in the switch return } - coords := switch_getPacketCoords(packet) + coords := peer_getPacketCoords(packet) next := p.table.lookup(coords) if nPeer, isIn := p.ports[next]; isIn { nPeer.sendPacketsFrom(p, [][]byte{packet}) @@ -264,17 +273,33 @@ func (p *peer) sendPacketsFrom(from phony.Actor, packets [][]byte) { }) } -// This just calls p.out(packet) for now. func (p *peer) _sendPackets(packets [][]byte) { - // Is there ever a case where something more complicated is needed? - // What if p.out blocks? - var size int for _, packet := range packets { - size += len(packet) + p.queue.push(packet) + } + if p.idle { + p.idle = false + p._handleIdle() + } +} + +func (p *peer) _handleIdle() { + var packets [][]byte + var size uint64 + for size < 65535 { + if packet, success := p.queue.pop(); success { + packets = append(packets, packet) + size += uint64(len(packet)) + } else { + break + } + } + if len(packets) > 0 { + p.bytesSent += uint64(size) + p.out(packets) + } else { + p.idle = true } - p.bytesSent += uint64(size) - // FIXME need to manage queues here or else things can block! - p.out(packets) } // This wraps the packet in the inner (ephemeral) and outer (permanent) crypto layers. diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 40b8303f..1be94661 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -67,7 +67,14 @@ func (r *router) init(core *Core) { // FIXME don't block here! p = r.core.peers._newPeer(&r.core.boxPub, &r.core.sigPub, &crypto.BoxSharedKey{}, &self, nil) }) - p.out = func(packets [][]byte) { r.handlePackets(p, packets) } + p.out = func(packets [][]byte) { + r.handlePackets(p, packets) + r.Act(p, func() { + // after the router handle the packets, notify the peer that it's ready for more + p.Act(r, p._handleIdle) + }) + } + p.Act(r, p._handleIdle) r.out = func(bs []byte) { p.handlePacketFrom(r, bs) } r.nodeinfo.init(r.core) r.core.config.Mutex.RLock() diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 2661b460..091596b5 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -164,13 +164,11 @@ type switchData struct { type switchTable struct { core *Core key crypto.SigPubKey // Our own key + phony.Inbox // Owns the below time time.Time // Time when locator.tstamp was last updated drop map[crypto.SigPubKey]int64 // Tstamp associated with a dropped root parent switchPort // Port of whatever peer is our parent, or self if we're root data switchData // - phony.Inbox // Owns the below - queues switch_buffers // Queues - not atomic so ONLY use through the actor - idle map[switchPort]struct{} // idle peers - not atomic so ONLY use through the actor } // Minimum allowed total size of switch queues. @@ -185,18 +183,7 @@ func (t *switchTable) init(core *Core) { peers := make(map[switchPort]peerInfo) t.data = switchData{locator: locator, peers: peers} t.drop = make(map[crypto.SigPubKey]int64) - phony.Block(t, func() { - core.config.Mutex.RLock() - if core.config.Current.SwitchOptions.MaxTotalQueueSize > SwitchQueueTotalMinSize { - t.queues.totalMaxSize = core.config.Current.SwitchOptions.MaxTotalQueueSize - } else { - t.queues.totalMaxSize = SwitchQueueTotalMinSize - } - core.config.Mutex.RUnlock() - t.queues.bufs = make(map[switchPort]map[string]switch_buffer) - t.idle = make(map[switchPort]struct{}) - }) - t._updateTable() + phony.Block(t, t._updateTable) } func (t *switchTable) reconfigure() { @@ -557,73 +544,6 @@ func (t *switchTable) start() error { return nil } -type closerInfo struct { - elem tableElem - dist int -} - -// Return a map of ports onto distance, keeping only ports closer to the destination than this node -// If the map is empty (or nil), then no peer is closer -/* -func (t *switchTable) getCloser(dest []byte) []closerInfo { - table := t.getTable() - myDist := table.self.dist(dest) - if myDist == 0 { - // Skip the iteration step if it's impossible to be closer - return nil - } - var closer []closerInfo - for _, info := range table.elems { - dist := info.locator.dist(dest) - if dist < myDist { - closer = append(closer, closerInfo{info, dist}) - } - } - return closer -} -*/ - -// Returns true if the peer is closer to the destination than ourself -/* -func (t *switchTable) portIsCloser(dest []byte, port switchPort) bool { - table := t.getTable() - if info, isIn := table.elems[port]; isIn { - theirDist := info.locator.dist(dest) - myDist := table.self.dist(dest) - return theirDist < myDist - } else { - return false - } -} -*/ - -// Get the coords of a packet without decoding -func switch_getPacketCoords(packet []byte) []byte { - _, pTypeLen := wire_decode_uint64(packet) - coords, _ := wire_decode_coords(packet[pTypeLen:]) - return coords -} - -// Returns a unique string for each stream of traffic -// Equal to coords -// The sender may append arbitrary info to the end of coords (as long as it's begins with a 0x00) to designate separate traffic streams -// Currently, it's the IPv6 next header type and the first 2 uint16 of the next header -// This is equivalent to the TCP/UDP protocol numbers and the source / dest ports -// TODO figure out if something else would make more sense (other transport protocols?) -func switch_getPacketStreamID(packet []byte) string { - return string(switch_getPacketCoords(packet)) -} - -// Returns the flowlabel from a given set of coords -func switch_getFlowLabelFromCoords(in []byte) []byte { - for i, v := range in { - if v == 0 { - return in[i+1:] - } - } - return []byte{} -} - // Find the best port to forward to for a given set of coords func (t *lookupTable) lookup(coords []byte) switchPort { var bestPort switchPort @@ -660,210 +580,3 @@ func (t *lookupTable) lookup(coords []byte) switchPort { } return bestPort } - -// Handle an incoming packet -// Either send it to ourself, or to the first idle peer that's free -// Returns true if the packet has been handled somehow, false if it should be queued -func (t *switchTable) _handleIn(packet []byte, idle map[switchPort]struct{}) (bool, switchPort) { - /* - coords := switch_getPacketCoords(packet) - table := t.getTable() - port := table.lookup(coords) - ports := t.core.peers.getPorts() - peer := ports[port] - if peer == nil { - // FIXME hack, if the peer disappeared durring a race then don't buffer - return true, 0 - } - if _, isIdle := idle[port]; isIdle || port == 0 { - // Either no closer peers, or the closest peer is idle - delete(idle, port) - peer.sendPacketsFrom(t, [][]byte{packet}) - return true, port - } - // There's a closer peer, but it's not idle, so buffer it - return false, port - */ - return true, 0 -} - -// Info about a buffered packet -type switch_packetInfo struct { - bytes []byte - time time.Time // Timestamp of when the packet arrived -} - -// 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 - size uint64 // Total queue size in bytes -} - -type switch_buffers struct { - totalMaxSize uint64 - bufs map[switchPort]map[string]switch_buffer // Buffers indexed by port and StreamID - size uint64 // Total size of all buffers, in bytes - maxbufs int - maxsize uint64 -} - -func (b *switch_buffers) _cleanup(t *switchTable) { - /* - for port, pbufs := range b.bufs { - for streamID, buf := range pbufs { - // Remove queues for which we have no next hop - packet := buf.packets[0] - coords := switch_getPacketCoords(packet.bytes) - if len(t.getCloser(coords)) == 0 { - for _, packet := range buf.packets { - util.PutBytes(packet.bytes) - } - b.size -= buf.size - delete(pbufs, streamID) - } - } - if len(pbufs) == 0 { - delete(b.bufs, port) - } - } - - for b.size > b.totalMaxSize { - // Drop a random queue - target := rand.Uint64() % b.size - var size uint64 // running total - for port, pbufs := range b.bufs { - for streamID, buf := range pbufs { - 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(pbufs, streamID) - if len(pbufs) == 0 { - delete(b.bufs, port) - } - } else { - // Need to update the map, since buf was retrieved by value - pbufs[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) bool { - // TODO? only send packets for which this is the best next hop that isn't currently blocked sending - /* - to := t.core.peers.getPorts()[port] - if to == nil { - return true - } - var packets [][]byte - var psize int - t.queues._cleanup(t) - now := time.Now() - pbufs := t.queues.bufs[port] - for psize < 65535 { - var best *string - var bestPriority float64 - for streamID, buf := range pbufs { - // Filter over the streams that this node is closer to - // Keep the one with the smallest queue - packet := buf.packets[0] - priority := float64(now.Sub(packet.time)) / float64(buf.size) - if priority >= bestPriority { - b := streamID // copy since streamID is mutated in the loop - best = &b - bestPriority = priority - } - } - if best != nil { - buf := pbufs[*best] - var packet switch_packetInfo - // TODO decide if this should be LIFO or FIFO - packet, buf.packets = buf.packets[0], buf.packets[1:] - buf.size -= uint64(len(packet.bytes)) - t.queues.size -= uint64(len(packet.bytes)) - if len(buf.packets) == 0 { - delete(pbufs, *best) - if len(pbufs) == 0 { - delete(t.queues.bufs, port) - } - } else { - // Need to update the map, since buf was retrieved by value - pbufs[*best] = buf - - } - packets = append(packets, packet.bytes) - psize += len(packet.bytes) - } else { - // Finished finding packets - break - } - } - if len(packets) > 0 { - to.sendPacketsFrom(t, packets) - return true - } - return false - */ - return false -} - -func (t *switchTable) packetInFrom(from phony.Actor, bytes []byte) { - t.Act(from, func() { - t._packetIn(bytes) - }) -} - -func (t *switchTable) _packetIn(bytes []byte) { - // Try to send it somewhere (or drop it if it's corrupt or at a dead end) - if sent, best := t._handleIn(bytes, t.idle); !sent { - // There's nobody free to take it right now, so queue it for later - packet := switch_packetInfo{bytes, time.Now()} - streamID := switch_getPacketStreamID(packet.bytes) - if _, isIn := t.queues.bufs[best]; !isIn { - t.queues.bufs[best] = make(map[string]switch_buffer) - } - buf, bufExists := t.queues.bufs[best][streamID] - buf.packets = append(buf.packets, packet) - buf.size += uint64(len(packet.bytes)) - t.queues.size += uint64(len(packet.bytes)) - // Keep a track of the max total queue size - if t.queues.size > t.queues.maxsize { - t.queues.maxsize = t.queues.size - } - t.queues.bufs[best][streamID] = buf - if !bufExists { - // Keep a track of the max total queue count. Only recalculate this - // when the queue is new because otherwise repeating len(dict) might - // cause unnecessary processing overhead - var count int - for _, pbufs := range t.queues.bufs { - count += len(pbufs) - } - if count > t.queues.maxbufs { - t.queues.maxbufs = count - } - } - t.queues._cleanup(t) - } -} - -func (t *switchTable) _idleIn(port switchPort) { - // Try to find something to send to this peer - if !t._handleIdle(port) { - // Didn't find anything ready to send yet, so stay idle - t.idle[port] = struct{}{} - } -} From 09efdfef9a5ec99ac8ce38c179063127bb6cebad Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 3 Apr 2020 19:26:48 -0500 Subject: [PATCH 0537/1109] fix bug in switch actor's cleanRoot, strict nonce handling at the session level, and add separate queues per stream to the packetqueue code --- src/yggdrasil/packetqueue.go | 115 ++++++++++++++++++++++++++++++----- src/yggdrasil/session.go | 12 +--- src/yggdrasil/switch.go | 4 +- 3 files changed, 105 insertions(+), 26 deletions(-) diff --git a/src/yggdrasil/packetqueue.go b/src/yggdrasil/packetqueue.go index ac66c0da..ff717258 100644 --- a/src/yggdrasil/packetqueue.go +++ b/src/yggdrasil/packetqueue.go @@ -1,38 +1,125 @@ package yggdrasil -import "github.com/yggdrasil-network/yggdrasil-go/src/util" +import ( + "time" + + "github.com/yggdrasil-network/yggdrasil-go/src/util" +) // TODO take max size from config -const MAX_PACKET_QUEUE_SIZE = 1048576 // 1 MB +const MAX_PACKET_QUEUE_SIZE = 4 * 1048576 // 4 MB + +type pqStreamID string + +type pqPacketInfo struct { + packet []byte + time time.Time +} + +type pqStream struct { + infos []pqPacketInfo + size uint64 +} // TODO separate queues per e.g. traffic flow type packetQueue struct { - packets [][]byte - size uint32 + streams map[pqStreamID]pqStream + size uint64 } func (q *packetQueue) cleanup() { for q.size > MAX_PACKET_QUEUE_SIZE { - if packet, success := q.pop(); success { - util.PutBytes(packet) + // TODO? drop from a random stream + // odds proportional to size? bandwidth? + // always using the worst is exploitable -> flood 1 packet per random stream + // find the stream that's using the most bandwidth + now := time.Now() + var worst pqStreamID + for id := range q.streams { + worst = id + break // get a random ID to start + } + worstStream := q.streams[worst] + worstSize := float64(worstStream.size) + worstAge := now.Sub(worstStream.infos[0].time).Seconds() + for id, stream := range q.streams { + thisSize := float64(stream.size) + thisAge := now.Sub(stream.infos[0].time).Seconds() + // cross multiply to avoid division by zero issues + if worstSize*thisAge < thisSize*worstAge { + // worstSize/worstAge < thisSize/thisAge -> this uses more bandwidth + worst = id + worstStream = stream + worstSize = thisSize + worstAge = thisAge + } + } + // Drop the oldest packet from the worst stream + packet := worstStream.infos[0].packet + worstStream.infos = worstStream.infos[1:] + worstStream.size -= uint64(len(packet)) + q.size -= uint64(len(packet)) + util.PutBytes(packet) + // save the modified stream to queues + if len(worstStream.infos) > 0 { + q.streams[worst] = worstStream } else { - panic("attempted to drop packet from empty queue") - break + delete(q.streams, worst) } } } func (q *packetQueue) push(packet []byte) { - q.packets = append(q.packets, packet) - q.size += uint32(len(packet)) + if q.streams == nil { + q.streams = make(map[pqStreamID]pqStream) + } + // get stream + id := pqStreamID(peer_getPacketCoords(packet)) // just coords for now + stream := q.streams[id] + // update stream + stream.infos = append(stream.infos, pqPacketInfo{packet, time.Now()}) + stream.size += uint64(len(packet)) + // save update to queues + q.streams[id] = stream + q.size += uint64(len(packet)) q.cleanup() } func (q *packetQueue) pop() ([]byte, bool) { - if len(q.packets) > 0 { - packet := q.packets[0] - q.packets = q.packets[1:] - q.size -= uint32(len(packet)) + if len(q.streams) > 0 { + // get the stream that uses the least bandwidth + now := time.Now() + var best pqStreamID + for id := range q.streams { + best = id + break // get a random ID to start + } + bestStream := q.streams[best] + bestSize := float64(bestStream.size) + bestAge := now.Sub(bestStream.infos[0].time).Seconds() + for id, stream := range q.streams { + thisSize := float64(stream.size) + thisAge := now.Sub(stream.infos[0].time).Seconds() + // cross multiply to avoid division by zero issues + if bestSize*thisAge > thisSize*bestAge { + // bestSize/bestAge > thisSize/thisAge -> this uses less bandwidth + best = id + bestStream = stream + bestSize = thisSize + bestAge = thisAge + } + } + // get the oldest packet from the best stream + packet := bestStream.infos[0].packet + bestStream.infos = bestStream.infos[1:] + bestStream.size -= uint64(len(packet)) + q.size -= uint64(len(packet)) + // save the modified stream to queues + if len(bestStream.infos) > 0 { + q.streams[best] = bestStream + } else { + delete(q.streams, best) + } return packet, true } return nil, false diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 01c2cdfb..223ea33f 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -16,9 +16,6 @@ import ( "github.com/Arceliar/phony" ) -// Duration that we keep track of old nonces per session, to allow some out-of-order packet delivery -const nonceWindow = time.Second - // All the information we know about an active session. // This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API. type sessionInfo struct { @@ -394,14 +391,9 @@ func (sinfo *sessionInfo) _getMTU() MTU { return sinfo.myMTU } -// Checks if a packet's nonce is recent enough to fall within the window of allowed packets, and not already received. +// Checks if a packet's nonce is newer than any previously received func (sinfo *sessionInfo) _nonceIsOK(theirNonce *crypto.BoxNonce) bool { - // The bitmask is to allow for some non-duplicate out-of-order packets - if theirNonce.Minus(&sinfo.theirNonce) > 0 { - // This is newer than the newest nonce we've seen - return true - } - return time.Since(sinfo.time) < nonceWindow + return theirNonce.Minus(&sinfo.theirNonce) > 0 } // Updates the nonce mask by (possibly) shifting the bitmask and setting the bit corresponding to this nonce to 1, and then updating the most recent nonce diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 091596b5..4f9044cd 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -227,10 +227,10 @@ func (t *switchTable) _cleanRoot() { t.time = now if t.data.locator.root != t.key { t.data.seq++ - defer t._updateTable() - t.core.router.reset(nil) + defer t.core.router.reset(nil) } t.data.locator = switchLocator{root: t.key, tstamp: now.Unix()} + t._updateTable() // updates base copy of switch msg in lookupTable t.core.peers.sendSwitchMsgs(t) } } From 9d0969db2be1bff624a641158544db687f3d2427 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 5 Apr 2020 14:57:05 -0500 Subject: [PATCH 0538/1109] prevent a hypothetical block on link message sending --- src/yggdrasil/link.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 15017993..78986286 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -229,10 +229,18 @@ func (intf *linkInterface) handler() error { intf.peer.Act(nil, intf.peer._removeSelf) }() intf.peer.out = func(msgs [][]byte) { - intf.writer.sendFrom(intf.peer, msgs, false) + // nil to prevent it from blocking if the link is somehow frozen + // this is safe because another packet won't be sent until the link notifies + // the peer that it's ready for one + intf.writer.sendFrom(nil, msgs, false) } intf.peer.linkOut = func(bs []byte) { - intf.writer.sendFrom(intf.peer, [][]byte{bs}, true) + // nil to prevent it from blocking if the link is somehow frozen + // FIXME this is hypothetically not safe, the peer shouldn't be sending + // additional packets until this one finishes, otherwise this could leak + // memory if writing happens slower than link packets are generated... + // that seems unlikely, so it's a lesser evil than deadlocking for now + intf.writer.sendFrom(nil, [][]byte{bs}, true) } themAddr := address.AddrForNodeID(crypto.GetNodeID(&intf.info.box)) themAddrString := net.IP(themAddr[:]).String() From 9c818c6278473a923e400832b94daf93ab977c12 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 26 Apr 2020 07:33:03 -0500 Subject: [PATCH 0539/1109] work-in-progress on a new sim --- build | 2 +- cmd/yggdrasilsim/main.go | 15 +++++++ cmd/yggdrasilsim/node.go | 23 ++++++++++ cmd/yggdrasilsim/store.go | 41 ++++++++++++++++++ src/yggdrasil/simlink.go | 88 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 cmd/yggdrasilsim/main.go create mode 100644 cmd/yggdrasilsim/node.go create mode 100644 cmd/yggdrasilsim/store.go create mode 100644 src/yggdrasil/simlink.go diff --git a/build b/build index 66f94403..6b93ca77 100755 --- a/build +++ b/build @@ -45,7 +45,7 @@ elif [ $ANDROID ]; then github.com/yggdrasil-network/yggdrasil-extras/src/mobile \ github.com/yggdrasil-network/yggdrasil-extras/src/dummy else - for CMD in yggdrasil yggdrasilctl ; do + for CMD in yggdrasil yggdrasilctl yggdrasilsim; do echo "Building: $CMD" go build $ARGS -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" ./cmd/$CMD diff --git a/cmd/yggdrasilsim/main.go b/cmd/yggdrasilsim/main.go new file mode 100644 index 00000000..40fd9ce4 --- /dev/null +++ b/cmd/yggdrasilsim/main.go @@ -0,0 +1,15 @@ +package main + +import ( +//"github.com/yggdrasil-network/yggdrasil-go/src/address" +//"github.com/yggdrasil-network/yggdrasil-go/src/config" +//"github.com/yggdrasil-network/yggdrasil-go/src/crypto" +//"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" +) + +func main() { + store := makeStoreSquareGrid(4) + var block chan struct{} + <-block + panic(store) +} diff --git a/cmd/yggdrasilsim/node.go b/cmd/yggdrasilsim/node.go new file mode 100644 index 00000000..e23b5109 --- /dev/null +++ b/cmd/yggdrasilsim/node.go @@ -0,0 +1,23 @@ +package main + +import ( + "io/ioutil" + + "github.com/gologme/log" + + //"github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/config" + //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" +) + +type simNode struct { + core yggdrasil.Core + id int +} + +func newNode(id int) *simNode { + n := simNode{id: id} + n.core.Start(config.GenerateConfig(), log.New(ioutil.Discard, "", 0)) + return &n +} diff --git a/cmd/yggdrasilsim/store.go b/cmd/yggdrasilsim/store.go new file mode 100644 index 00000000..6fce81a4 --- /dev/null +++ b/cmd/yggdrasilsim/store.go @@ -0,0 +1,41 @@ +package main + +type nodeStore map[int]*simNode + +func makeStoreSingle() nodeStore { + s := make(nodeStore) + s[0] = newNode(0) + return s +} + +func linkNodes(a *simNode, b *simNode) { + la := a.core.NewSimlink() + lb := b.core.NewSimlink() + la.SetDestination(lb) + lb.SetDestination(la) + la.Start() + lb.Start() +} + +func makeStoreSquareGrid(sideLength int) nodeStore { + store := make(nodeStore) + nNodes := sideLength * sideLength + idxs := make([]int, 0, nNodes) + // TODO shuffle nodeIDs + for idx := 1; idx <= nNodes; idx++ { + idxs = append(idxs, idx) + } + for _, idx := range idxs { + n := newNode(idx) + store[idx] = n + } + for idx := 0; idx < nNodes; idx++ { + if (idx % sideLength) != 0 { + linkNodes(store[idxs[idx]], store[idxs[idx-1]]) + } + if idx >= sideLength { + linkNodes(store[idxs[idx]], store[idxs[idx-sideLength]]) + } + } + return store +} diff --git a/src/yggdrasil/simlink.go b/src/yggdrasil/simlink.go new file mode 100644 index 00000000..33332645 --- /dev/null +++ b/src/yggdrasil/simlink.go @@ -0,0 +1,88 @@ +package yggdrasil + +import ( + "errors" + "github.com/Arceliar/phony" + "github.com/yggdrasil-network/yggdrasil-go/src/util" +) + +type Simlink struct { + phony.Inbox + rch chan []byte + dest *Simlink + link *linkInterface + started bool +} + +func (s *Simlink) readMsg() ([]byte, error) { + bs := <-s.rch + if bs != nil { + return bs, nil + } else { + return nil, errors.New("read from closed Simlink") + } +} + +func (s *Simlink) _recvMetaBytes() ([]byte, error) { + return s.readMsg() +} + +func (s *Simlink) _sendMetaBytes(bs []byte) error { + _, err := s.writeMsgs([][]byte{bs}) + return err +} + +func (s *Simlink) close() error { + close(s.rch) + return nil +} + +func (s *Simlink) writeMsgs(msgs [][]byte) (int, error) { + if s.dest == nil { + return 0, errors.New("write to unpaired Simlink") + } + var size int + for _, msg := range msgs { + size += len(msg) + bs := append(util.GetBytes(), msg...) + phony.Block(s, func() { + s.dest.Act(s, func() { + defer func() { recover() }() + s.dest.rch <- bs + }) + }) + } + return size, nil +} + +func (c *Core) NewSimlink() *Simlink { + s := &Simlink{rch: make(chan []byte, 1)} + n := "Simlink" + s.link, _ = c.link.create(s, n, n, n, n, false, true) + return s +} + +func (s *Simlink) SetDestination(dest *Simlink) error { + var err error + phony.Block(s, func() { + if s.dest != nil { + err = errors.New("destination already set") + } else { + s.dest = dest + } + }) + return err +} + +func (s *Simlink) Start() error { + var err error + phony.Block(s, func() { + if s.started { + err = errors.New("already started") + } else { + s.started = true + go s.link.handler() + } + }) + return err +} From 5db93be4df4ae6eabe749bdeedffd6e3f4acf63c Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 26 Apr 2020 09:59:30 -0500 Subject: [PATCH 0540/1109] more sim work --- cmd/yggdrasilsim/node.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/cmd/yggdrasilsim/node.go b/cmd/yggdrasilsim/node.go index e23b5109..65e6a805 100644 --- a/cmd/yggdrasilsim/node.go +++ b/cmd/yggdrasilsim/node.go @@ -5,19 +5,24 @@ import ( "github.com/gologme/log" - //"github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/config" - //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) type simNode struct { - core yggdrasil.Core - id int + core yggdrasil.Core + id int + nodeID crypto.NodeID + dialer *yggdrasil.Dialer + listener *yggdrasil.Listener } func newNode(id int) *simNode { n := simNode{id: id} n.core.Start(config.GenerateConfig(), log.New(ioutil.Discard, "", 0)) + n.nodeID = *n.core.NodeID() + n.dialer, _ = n.core.ConnDialer() + n.listener, _ = n.core.ConnListen() return &n } From 6d895708602bd8c623c0f4f4b6a90fc5ce496e62 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 2 May 2020 06:44:51 -0500 Subject: [PATCH 0541/1109] eliminate most sync.Pool use, gives a safer but slightly slower interface --- src/crypto/crypto.go | 18 ++++++++++++------ src/tuntap/conn.go | 5 ----- src/tuntap/iface.go | 16 +++++++--------- src/util/bytes_mobile.go | 21 --------------------- src/util/bytes_other.go | 18 ------------------ src/yggdrasil/conn.go | 6 +++--- src/yggdrasil/link.go | 4 ---- src/yggdrasil/packetqueue.go | 4 +--- src/yggdrasil/peer.go | 3 --- src/yggdrasil/pool.go | 20 ++++++++++++++++++++ src/yggdrasil/router.go | 4 ---- src/yggdrasil/session.go | 15 +-------------- src/yggdrasil/stream.go | 7 ++++--- src/yggdrasil/wire.go | 8 +++++--- 14 files changed, 53 insertions(+), 96 deletions(-) delete mode 100644 src/util/bytes_mobile.go delete mode 100644 src/util/bytes_other.go create mode 100644 src/yggdrasil/pool.go diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go index 211a0e54..595e6f40 100644 --- a/src/crypto/crypto.go +++ b/src/crypto/crypto.go @@ -17,12 +17,11 @@ import ( "crypto/rand" "crypto/sha512" "encoding/hex" + "sync" "golang.org/x/crypto/curve25519" "golang.org/x/crypto/ed25519" "golang.org/x/crypto/nacl/box" - - "github.com/yggdrasil-network/yggdrasil-go/src/util" ) //////////////////////////////////////////////////////////////////////////////// @@ -225,29 +224,36 @@ func GetSharedKey(myPrivKey *BoxPrivKey, return (*BoxSharedKey)(&shared) } +// pool is used internally by BoxOpen and BoxSeal to avoid allocating temporary space +var pool = sync.Pool{New: func() interface{} { return []byte(nil) }} + // BoxOpen returns a message and true if it successfully opens a crypto box using the provided shared key and nonce. +// The boxed input slice's backing array is reused for the unboxed output when possible. func BoxOpen(shared *BoxSharedKey, boxed []byte, nonce *BoxNonce) ([]byte, bool) { - out := util.GetBytes() s := (*[BoxSharedKeyLen]byte)(shared) n := (*[BoxNonceLen]byte)(nonce) - unboxed, success := box.OpenAfterPrecomputation(out, boxed, n, s) + temp := append(pool.Get().([]byte), boxed...) + unboxed, success := box.OpenAfterPrecomputation(boxed[:0], temp, n, s) + pool.Put(temp[:0]) return unboxed, success } // BoxSeal seals a crypto box using the provided shared key, returning the box and the nonce needed to decrypt it. // If nonce is nil, a random BoxNonce will be used and returned. // If nonce is non-nil, then nonce.Increment() will be called before using it, and the incremented BoxNonce is what is returned. +// The unboxed input slice's backing array is reused for the boxed output when possible. func BoxSeal(shared *BoxSharedKey, unboxed []byte, nonce *BoxNonce) ([]byte, *BoxNonce) { if nonce == nil { nonce = NewBoxNonce() } nonce.Increment() - out := util.GetBytes() s := (*[BoxSharedKeyLen]byte)(shared) n := (*[BoxNonceLen]byte)(nonce) - boxed := box.SealAfterPrecomputation(out, unboxed, n, s) + temp := append(pool.Get().([]byte), unboxed...) + boxed := box.SealAfterPrecomputation(unboxed[:0], temp, n, s) + pool.Put(temp[:0]) return boxed, nonce } diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go index 24ea5ef3..ddd89e9b 100644 --- a/src/tuntap/conn.go +++ b/src/tuntap/conn.go @@ -44,13 +44,11 @@ func (s *tunConn) _read(bs []byte) (err error) { select { case <-s.stop: err = errors.New("session was already closed") - util.PutBytes(bs) return default: } if len(bs) == 0 { err = errors.New("read packet with 0 size") - util.PutBytes(bs) return } ipv4 := len(bs) > 20 && bs[0]&0xf0 == 0x40 @@ -107,7 +105,6 @@ func (s *tunConn) _read(bs []byte) (err error) { } if skip { err = errors.New("address not allowed") - util.PutBytes(bs) return } s.tun.writer.writeFrom(s, bs) @@ -125,7 +122,6 @@ func (s *tunConn) _write(bs []byte) (err error) { select { case <-s.stop: err = errors.New("session was already closed") - util.PutBytes(bs) return default: } @@ -183,7 +179,6 @@ func (s *tunConn) _write(bs []byte) (err error) { } if skip { err = errors.New("address not allowed") - util.PutBytes(bs) return } msg := yggdrasil.FlowKeyMessage{ diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 1e5902e8..9250665a 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -3,7 +3,6 @@ package tuntap import ( "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" - "github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" "github.com/Arceliar/phony" @@ -14,6 +13,7 @@ const TUN_OFFSET_BYTES = 4 type tunWriter struct { phony.Inbox tun *TunAdapter + buf [TUN_OFFSET_BYTES + 65536]byte } func (w *tunWriter) writeFrom(from phony.Actor, b []byte) { @@ -25,15 +25,13 @@ func (w *tunWriter) writeFrom(from phony.Actor, b []byte) { // write is pretty loose with the memory safety rules, e.g. it assumes it can // read w.tun.iface.IsTap() safely func (w *tunWriter) _write(b []byte) { - defer util.PutBytes(b) var written int var err error n := len(b) if n == 0 { return } - temp := append(util.ResizeBytes(util.GetBytes(), TUN_OFFSET_BYTES), b...) - defer util.PutBytes(temp) + temp := append(w.buf[:TUN_OFFSET_BYTES], b...) written, err = w.tun.iface.Write(temp, TUN_OFFSET_BYTES) if err != nil { w.tun.Act(w, func() { @@ -51,22 +49,23 @@ func (w *tunWriter) _write(b []byte) { type tunReader struct { phony.Inbox tun *TunAdapter + buf [TUN_OFFSET_BYTES + 65536]byte } func (r *tunReader) _read() { // Get a slice to store the packet in - recvd := util.ResizeBytes(util.GetBytes(), int(r.tun.mtu)+TUN_OFFSET_BYTES) // Wait for a packet to be delivered to us through the TUN adapter - n, err := r.tun.iface.Read(recvd, TUN_OFFSET_BYTES) + n, err := r.tun.iface.Read(r.buf[:], TUN_OFFSET_BYTES) if n <= TUN_OFFSET_BYTES || err != nil { r.tun.log.Errorln("Error reading TUN:", err) ferr := r.tun.iface.Flush() if ferr != nil { r.tun.log.Errorln("Unable to flush packets:", ferr) } - util.PutBytes(recvd) } else { - r.tun.handlePacketFrom(r, recvd[TUN_OFFSET_BYTES:n+TUN_OFFSET_BYTES], err) + bs := make([]byte, n, n+crypto.BoxOverhead) // extra capacity for later... + copy(bs, r.buf[TUN_OFFSET_BYTES:n+TUN_OFFSET_BYTES]) + r.tun.handlePacketFrom(r, bs, err) } if err == nil { // Now read again @@ -175,7 +174,6 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { _, known := tun.dials[dstString] tun.dials[dstString] = append(tun.dials[dstString], bs) for len(tun.dials[dstString]) > 32 { - util.PutBytes(tun.dials[dstString][0]) tun.dials[dstString] = tun.dials[dstString][1:] } if !known { diff --git a/src/util/bytes_mobile.go b/src/util/bytes_mobile.go deleted file mode 100644 index f862c0cd..00000000 --- a/src/util/bytes_mobile.go +++ /dev/null @@ -1,21 +0,0 @@ -//+build mobile - -package util - -import "runtime/debug" - -func init() { - debug.SetGCPercent(25) -} - -// GetBytes always returns a nil slice on mobile platforms. -func GetBytes() []byte { - return nil -} - -// PutBytes does literally nothing on mobile platforms. -// This is done rather than keeping a free list of bytes on platforms with memory constraints. -// It's needed to help keep memory usage low enough to fall under the limits set for e.g. iOS NEPacketTunnelProvider apps. -func PutBytes(bs []byte) { - return -} diff --git a/src/util/bytes_other.go b/src/util/bytes_other.go deleted file mode 100644 index 7c966087..00000000 --- a/src/util/bytes_other.go +++ /dev/null @@ -1,18 +0,0 @@ -//+build !mobile - -package util - -import "sync" - -// This is used to buffer recently used slices of bytes, to prevent allocations in the hot loops. -var byteStore = sync.Pool{New: func() interface{} { return []byte(nil) }} - -// GetBytes returns a 0-length (possibly nil) slice of bytes from a free list, so it may have a larger capacity. -func GetBytes() []byte { - return byteStore.Get().([]byte)[:0] -} - -// PutBytes stores a slice in a free list, where it can potentially be reused to prevent future allocations. -func PutBytes(bs []byte) { - byteStore.Put(bs) -} diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index eef57683..ae34e45a 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -252,7 +252,6 @@ func (c *Conn) Read(b []byte) (int, error) { } // Copy results to the output slice and clean up copy(b, bs) - util.PutBytes(bs) // Return the number of bytes copied to the slice, along with any error return n, err } @@ -323,10 +322,11 @@ func (c *Conn) writeNoCopy(msg FlowKeyMessage) error { // returned. func (c *Conn) Write(b []byte) (int, error) { written := len(b) - msg := FlowKeyMessage{Message: append(util.GetBytes(), b...)} + bs := make([]byte, 0, len(b)+crypto.BoxOverhead) + bs = append(bs, b...) + msg := FlowKeyMessage{Message: bs} err := c.writeNoCopy(msg) if err != nil { - util.PutBytes(msg.Message) written = 0 } return written, err diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 78986286..733b9ac1 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -406,10 +406,6 @@ func (w *linkWriter) sendFrom(from phony.Actor, bss [][]byte, isLinkTraffic bool w.intf.notifySending(size, isLinkTraffic) w.intf.msgIO.writeMsgs(bss) w.intf.notifySent(size, isLinkTraffic) - // Cleanup - for _, bs := range bss { - util.PutBytes(bs) - } }) } diff --git a/src/yggdrasil/packetqueue.go b/src/yggdrasil/packetqueue.go index ff717258..2000ffa6 100644 --- a/src/yggdrasil/packetqueue.go +++ b/src/yggdrasil/packetqueue.go @@ -2,8 +2,6 @@ package yggdrasil import ( "time" - - "github.com/yggdrasil-network/yggdrasil-go/src/util" ) // TODO take max size from config @@ -59,7 +57,7 @@ func (q *packetQueue) cleanup() { worstStream.infos = worstStream.infos[1:] worstStream.size -= uint64(len(packet)) q.size -= uint64(len(packet)) - util.PutBytes(packet) + pool_putBytes(packet) // save the modified stream to queues if len(worstStream.infos) > 0 { q.streams[worst] = worstStream diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index bc9de04c..7eef9a11 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -9,7 +9,6 @@ import ( "time" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" - "github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/Arceliar/phony" ) @@ -241,7 +240,6 @@ func (p *peer) _handlePacket(packet []byte) { case wire_LinkProtocolTraffic: p._handleLinkTraffic(packet) default: - util.PutBytes(packet) } } @@ -347,7 +345,6 @@ func (p *peer) _handleLinkTraffic(bs []byte) { case wire_SwitchMsg: p._handleSwitchMsg(payload) default: - util.PutBytes(bs) } } diff --git a/src/yggdrasil/pool.go b/src/yggdrasil/pool.go new file mode 100644 index 00000000..e95236a5 --- /dev/null +++ b/src/yggdrasil/pool.go @@ -0,0 +1,20 @@ +package yggdrasil + +import "sync" + +// Used internally to reduce allocations in the hot loop +// I.e. packets being switched or between the crypto and the switch +// For safety reasons, these must not escape this package +var pool = sync.Pool{New: func() interface{} { return []byte(nil) }} + +func pool_getBytes(size int) []byte { + bs := pool.Get().([]byte) + if cap(bs) < size { + bs = make([]byte, size) + } + return bs[:size] +} + +func pool_putBytes(bs []byte) { + pool.Put(bs) +} diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 1be94661..71d92609 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -29,7 +29,6 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" - "github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/Arceliar/phony" ) @@ -178,14 +177,12 @@ func (r *router) _handlePacket(packet []byte) { // Handles incoming traffic, i.e. encapuslated ordinary IPv6 packets. // Passes them to the crypto session worker to be decrypted and sent to the adapter. func (r *router) _handleTraffic(packet []byte) { - defer util.PutBytes(packet) p := wire_trafficPacket{} if !p.decode(packet) { return } sinfo, isIn := r.sessions.getSessionForHandle(&p.Handle) if !isIn { - util.PutBytes(p.Payload) return } sinfo.recv(r, &p) @@ -231,7 +228,6 @@ func (r *router) _handleProto(packet []byte) { case wire_DHTLookupResponse: r._handleDHTRes(bs, &p.FromKey) default: - util.PutBytes(packet) } } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 223ea33f..360f2a1b 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -448,12 +448,9 @@ func (sinfo *sessionInfo) _recvPacket(p *wire_trafficPacket) { select { case <-sinfo.init: default: - // TODO find a better way to drop things until initialized - util.PutBytes(p.Payload) return } if !sinfo._nonceIsOK(&p.Nonce) { - util.PutBytes(p.Payload) return } k := sinfo.sharedSesKey @@ -463,11 +460,9 @@ func (sinfo *sessionInfo) _recvPacket(p *wire_trafficPacket) { poolFunc := func() { bs, isOK = crypto.BoxOpen(&k, p.Payload, &p.Nonce) callback := func() { - util.PutBytes(p.Payload) if !isOK || k != sinfo.sharedSesKey || !sinfo._nonceIsOK(&p.Nonce) { // Either we failed to decrypt, or the session was updated, or we // received this packet in the mean time - util.PutBytes(bs) return } sinfo._updateNonce(&p.Nonce) @@ -485,8 +480,6 @@ func (sinfo *sessionInfo) _send(msg FlowKeyMessage) { select { case <-sinfo.init: default: - // TODO find a better way to drop things until initialized - util.PutBytes(msg.Message) return } sinfo.bytesSent += uint64(len(msg.Message)) @@ -505,14 +498,8 @@ func (sinfo *sessionInfo) _send(msg FlowKeyMessage) { ch := make(chan func(), 1) poolFunc := func() { p.Payload, _ = crypto.BoxSeal(&k, msg.Message, &p.Nonce) + packet := p.encode() callback := func() { - // Encoding may block on a util.GetBytes(), so kept out of the worker pool - packet := p.encode() - // Cleanup - util.PutBytes(msg.Message) - util.PutBytes(p.Payload) - // Send the packet - // TODO replace this with a send to the peer struct if that becomes an actor sinfo.sessions.router.Act(sinfo, func() { sinfo.sessions.router.out(packet) }) diff --git a/src/yggdrasil/stream.go b/src/yggdrasil/stream.go index 4ab37c29..be1398fc 100644 --- a/src/yggdrasil/stream.go +++ b/src/yggdrasil/stream.go @@ -6,8 +6,6 @@ import ( "fmt" "io" "net" - - "github.com/yggdrasil-network/yggdrasil-go/src/util" ) // Test that this matches the interface we expect @@ -46,6 +44,9 @@ func (s *stream) writeMsgs(bss [][]byte) (int, error) { } s.outputBuffer = buf[:0] // So we can reuse the same underlying array later _, err := buf.WriteTo(s.rwc) + for _, bs := range bss { + pool_putBytes(bs) + } // TODO only include number of bytes from bs *successfully* written? return written, err } @@ -112,7 +113,7 @@ func (s *stream) readMsgFromBuffer() ([]byte, error) { if msgLen > streamMsgSize { return nil, errors.New("oversized message") } - msg := util.ResizeBytes(util.GetBytes(), int(msgLen)) + msg := pool_getBytes(int(msgLen)) _, err = io.ReadFull(s.inputBuffer, msg) return msg, err } diff --git a/src/yggdrasil/wire.go b/src/yggdrasil/wire.go index 18a647d8..9746d46e 100644 --- a/src/yggdrasil/wire.go +++ b/src/yggdrasil/wire.go @@ -9,7 +9,6 @@ package yggdrasil import ( "github.com/yggdrasil-network/yggdrasil-go/src/crypto" - "github.com/yggdrasil-network/yggdrasil-go/src/util" ) const ( @@ -230,8 +229,9 @@ type wire_trafficPacket struct { } // Encodes a wire_trafficPacket into its wire format. +// The returned slice was taken from the pool. func (p *wire_trafficPacket) encode() []byte { - bs := util.GetBytes() + bs := pool_getBytes(0) bs = wire_put_uint64(wire_Traffic, bs) bs = wire_put_coords(p.Coords, bs) bs = append(bs, p.Handle[:]...) @@ -241,7 +241,9 @@ func (p *wire_trafficPacket) encode() []byte { } // Decodes an encoded wire_trafficPacket into the struct, returning true if successful. +// Either way, the argument slice is added to the pool. func (p *wire_trafficPacket) decode(bs []byte) bool { + defer pool_putBytes(bs) var pType uint64 switch { case !wire_chop_uint64(&pType, &bs): @@ -255,7 +257,7 @@ func (p *wire_trafficPacket) decode(bs []byte) bool { case !wire_chop_slice(p.Nonce[:], &bs): return false } - p.Payload = append(util.GetBytes(), bs...) + p.Payload = append(p.Payload, bs...) return true } From 72afa0502990b12e6a172090c3621cf35fc9f3de Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 2 May 2020 10:01:09 -0500 Subject: [PATCH 0542/1109] test dial/listen in the sim --- build | 2 +- cmd/yggdrasilsim/dial.go | 60 ++++++++++++++++++++++++++++++++++++++++ cmd/yggdrasilsim/main.go | 4 +-- src/yggdrasil/simlink.go | 3 +- 4 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 cmd/yggdrasilsim/dial.go diff --git a/build b/build index 6b93ca77..0d0da7e2 100755 --- a/build +++ b/build @@ -45,7 +45,7 @@ elif [ $ANDROID ]; then github.com/yggdrasil-network/yggdrasil-extras/src/mobile \ github.com/yggdrasil-network/yggdrasil-extras/src/dummy else - for CMD in yggdrasil yggdrasilctl yggdrasilsim; do + for CMD in yggdrasil yggdrasilctl; do echo "Building: $CMD" go build $ARGS -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" ./cmd/$CMD diff --git a/cmd/yggdrasilsim/dial.go b/cmd/yggdrasilsim/dial.go new file mode 100644 index 00000000..5713fdd7 --- /dev/null +++ b/cmd/yggdrasilsim/dial.go @@ -0,0 +1,60 @@ +package main + +import ( + "fmt" + "sort" + "time" + + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" +) + +func doListen(recvNode *simNode) { + for { + c, err := recvNode.listener.Accept() + if err != nil { + panic(err) + } + c.Close() + } +} + +func dialTest(sendNode, recvNode *simNode) { + if sendNode.id == recvNode.id { + fmt.Println("Skipping dial to self") + return + } + var mask crypto.NodeID + for idx := range mask { + mask[idx] = 0xff + } + for { + c, err := sendNode.dialer.DialByNodeIDandMask(nil, &recvNode.nodeID, &mask) + if c != nil { + c.Close() + return + } + if err != nil { + fmt.Println("Dial failed:", err) + } + time.Sleep(time.Second) + } +} + +func dialStore(store nodeStore) { + var nodeIdxs []int + for idx, n := range store { + nodeIdxs = append(nodeIdxs, idx) + go doListen(n) + } + sort.Slice(nodeIdxs, func(i, j int) bool { + return nodeIdxs[i] < nodeIdxs[j] + }) + for _, idx := range nodeIdxs { + sendNode := store[idx] + for _, jdx := range nodeIdxs { + recvNode := store[jdx] + fmt.Printf("Dialing from node %d to node %d / %d...\n", idx, jdx, len(store)) + dialTest(sendNode, recvNode) + } + } +} diff --git a/cmd/yggdrasilsim/main.go b/cmd/yggdrasilsim/main.go index 40fd9ce4..fcbcfc97 100644 --- a/cmd/yggdrasilsim/main.go +++ b/cmd/yggdrasilsim/main.go @@ -9,7 +9,5 @@ import ( func main() { store := makeStoreSquareGrid(4) - var block chan struct{} - <-block - panic(store) + dialStore(store) } diff --git a/src/yggdrasil/simlink.go b/src/yggdrasil/simlink.go index 33332645..e846f3ba 100644 --- a/src/yggdrasil/simlink.go +++ b/src/yggdrasil/simlink.go @@ -3,7 +3,6 @@ package yggdrasil import ( "errors" "github.com/Arceliar/phony" - "github.com/yggdrasil-network/yggdrasil-go/src/util" ) type Simlink struct { @@ -44,7 +43,7 @@ func (s *Simlink) writeMsgs(msgs [][]byte) (int, error) { var size int for _, msg := range msgs { size += len(msg) - bs := append(util.GetBytes(), msg...) + bs := append([]byte(nil), msg...) phony.Block(s, func() { s.dest.Act(s, func() { defer func() { recover() }() From 15162ee952c3789cd5aed7ae74e36f9a31a11866 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 2 May 2020 10:51:26 -0500 Subject: [PATCH 0543/1109] fix a panic from a doubly closed channel in the simlink --- src/yggdrasil/simlink.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/yggdrasil/simlink.go b/src/yggdrasil/simlink.go index e846f3ba..736ee633 100644 --- a/src/yggdrasil/simlink.go +++ b/src/yggdrasil/simlink.go @@ -14,12 +14,11 @@ type Simlink struct { } func (s *Simlink) readMsg() ([]byte, error) { - bs := <-s.rch - if bs != nil { - return bs, nil - } else { + bs, ok := <-s.rch + if !ok { return nil, errors.New("read from closed Simlink") } + return bs, nil } func (s *Simlink) _recvMetaBytes() ([]byte, error) { @@ -32,6 +31,7 @@ func (s *Simlink) _sendMetaBytes(bs []byte) error { } func (s *Simlink) close() error { + defer func() { recover() }() close(s.rch) return nil } From 402cfc0f005219580e3bb8da48e36c77ad65a377 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 2 May 2020 10:56:17 -0500 Subject: [PATCH 0544/1109] undo remaining trivial change to build --- build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build b/build index 0d0da7e2..66f94403 100755 --- a/build +++ b/build @@ -45,7 +45,7 @@ elif [ $ANDROID ]; then github.com/yggdrasil-network/yggdrasil-extras/src/mobile \ github.com/yggdrasil-network/yggdrasil-extras/src/dummy else - for CMD in yggdrasil yggdrasilctl; do + for CMD in yggdrasil yggdrasilctl ; do echo "Building: $CMD" go build $ARGS -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" ./cmd/$CMD From 20ef5910136c1aa214dce826fee0ce5d85b76150 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 2 May 2020 11:16:11 -0500 Subject: [PATCH 0545/1109] fix some crashes with races during peer setup --- cmd/yggdrasilsim/dial.go | 1 + cmd/yggdrasilsim/main.go | 7 ------- src/yggdrasil/link.go | 30 +++++++++++++++--------------- src/yggdrasil/peer.go | 4 +++- src/yggdrasil/router.go | 10 +++++----- src/yggdrasil/simlink.go | 6 +++++- 6 files changed, 29 insertions(+), 29 deletions(-) diff --git a/cmd/yggdrasilsim/dial.go b/cmd/yggdrasilsim/dial.go index 5713fdd7..c7892d40 100644 --- a/cmd/yggdrasilsim/dial.go +++ b/cmd/yggdrasilsim/dial.go @@ -9,6 +9,7 @@ import ( ) func doListen(recvNode *simNode) { + // TODO be able to stop the listeners somehow so they don't leak across different tests for { c, err := recvNode.listener.Accept() if err != nil { diff --git a/cmd/yggdrasilsim/main.go b/cmd/yggdrasilsim/main.go index fcbcfc97..25504c92 100644 --- a/cmd/yggdrasilsim/main.go +++ b/cmd/yggdrasilsim/main.go @@ -1,12 +1,5 @@ package main -import ( -//"github.com/yggdrasil-network/yggdrasil-go/src/address" -//"github.com/yggdrasil-network/yggdrasil-go/src/config" -//"github.com/yggdrasil-network/yggdrasil-go/src/crypto" -//"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" -) - func main() { store := makeStoreSquareGrid(4) dialStore(store) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 733b9ac1..7f6b9b56 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -217,9 +217,23 @@ func (intf *linkInterface) handler() error { intf.link.mutex.Unlock() // Create peer shared := crypto.GetSharedKey(myLinkPriv, &meta.link) + out := func(msgs [][]byte) { + // nil to prevent it from blocking if the link is somehow frozen + // this is safe because another packet won't be sent until the link notifies + // the peer that it's ready for one + intf.writer.sendFrom(nil, msgs, false) + } + linkOut := func(bs []byte) { + // nil to prevent it from blocking if the link is somehow frozen + // FIXME this is hypothetically not safe, the peer shouldn't be sending + // additional packets until this one finishes, otherwise this could leak + // memory if writing happens slower than link packets are generated... + // that seems unlikely, so it's a lesser evil than deadlocking for now + intf.writer.sendFrom(nil, [][]byte{bs}, true) + } phony.Block(&intf.link.core.peers, func() { // FIXME don't use phony.Block, it's bad practice, even if it's safe here - intf.peer = intf.link.core.peers._newPeer(&meta.box, &meta.sig, shared, intf, func() { intf.msgIO.close() }) + intf.peer = intf.link.core.peers._newPeer(&meta.box, &meta.sig, shared, intf, func() { intf.msgIO.close() }, out, linkOut) }) if intf.peer == nil { return errors.New("failed to create peer") @@ -228,20 +242,6 @@ func (intf *linkInterface) handler() error { // More cleanup can go here intf.peer.Act(nil, intf.peer._removeSelf) }() - intf.peer.out = func(msgs [][]byte) { - // nil to prevent it from blocking if the link is somehow frozen - // this is safe because another packet won't be sent until the link notifies - // the peer that it's ready for one - intf.writer.sendFrom(nil, msgs, false) - } - intf.peer.linkOut = func(bs []byte) { - // nil to prevent it from blocking if the link is somehow frozen - // FIXME this is hypothetically not safe, the peer shouldn't be sending - // additional packets until this one finishes, otherwise this could leak - // memory if writing happens slower than link packets are generated... - // that seems unlikely, so it's a lesser evil than deadlocking for now - intf.writer.sendFrom(nil, [][]byte{bs}, true) - } themAddr := address.AddrForNodeID(crypto.GetNodeID(&intf.info.box)) themAddrString := net.IP(themAddr[:]).String() themString := fmt.Sprintf("%s@%s", themAddrString, intf.info.remote) diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 7eef9a11..801691a0 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -123,7 +123,7 @@ func (ps *peers) _updatePeers() { } // Creates a new peer with the specified box, sig, and linkShared keys, using the lowest unoccupied port number. -func (ps *peers) _newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShared *crypto.BoxSharedKey, intf *linkInterface, closer func()) *peer { +func (ps *peers) _newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShared *crypto.BoxSharedKey, intf *linkInterface, closer func(), out func([][]byte), linkOut func([]byte)) *peer { now := time.Now() p := peer{box: *box, sig: *sig, @@ -134,6 +134,8 @@ func (ps *peers) _newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShar close: closer, core: ps.core, intf: intf, + out: out, + linkOut: linkOut, } oldPorts := ps.ports newPorts := make(map[switchPort]*peer) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 71d92609..1bb14c4c 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -62,17 +62,17 @@ func (r *router) init(core *Core) { }, } var p *peer - phony.Block(&r.core.peers, func() { - // FIXME don't block here! - p = r.core.peers._newPeer(&r.core.boxPub, &r.core.sigPub, &crypto.BoxSharedKey{}, &self, nil) - }) - p.out = func(packets [][]byte) { + peerOut := func(packets [][]byte) { r.handlePackets(p, packets) r.Act(p, func() { // after the router handle the packets, notify the peer that it's ready for more p.Act(r, p._handleIdle) }) } + phony.Block(&r.core.peers, func() { + // FIXME don't block here! + p = r.core.peers._newPeer(&r.core.boxPub, &r.core.sigPub, &crypto.BoxSharedKey{}, &self, nil, peerOut, nil) + }) p.Act(r, p._handleIdle) r.out = func(bs []byte) { p.handlePacketFrom(r, bs) } r.nodeinfo.init(r.core) diff --git a/src/yggdrasil/simlink.go b/src/yggdrasil/simlink.go index 736ee633..f830c215 100644 --- a/src/yggdrasil/simlink.go +++ b/src/yggdrasil/simlink.go @@ -57,7 +57,11 @@ func (s *Simlink) writeMsgs(msgs [][]byte) (int, error) { func (c *Core) NewSimlink() *Simlink { s := &Simlink{rch: make(chan []byte, 1)} n := "Simlink" - s.link, _ = c.link.create(s, n, n, n, n, false, true) + var err error + s.link, err = c.link.create(s, n, n, n, n, false, true) + if err != nil { + panic(err) + } return s } From a115d18595e748dc1deddc5fd12a56bb0f747014 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 2 May 2020 22:26:41 +0100 Subject: [PATCH 0546/1109] Refactor the multicast code a bit --- src/multicast/multicast.go | 275 ++++++++++++++++++------------------- 1 file changed, 134 insertions(+), 141 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 93bf94bb..6ceef0aa 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -15,40 +15,57 @@ import ( "golang.org/x/net/ipv6" ) +const ( + // GroupAddr contains the multicast group and port used for multicast packets. + GroupAddr = "[ff02::114]:9001" +) + // Multicast represents the multicast advertisement and discovery mechanism used // by Yggdrasil to find peers on the same subnet. When a beacon is received on a // configured multicast interface, Yggdrasil will attempt to peer with that node // automatically. type Multicast struct { phony.Inbox - core *yggdrasil.Core - config *config.NodeState - log *log.Logger - sock *ipv6.PacketConn - groupAddr string - listeners map[string]*listenerInfo - listenPort uint16 - isOpen bool - announcer *time.Timer - platformhandler *time.Timer + core *yggdrasil.Core + config *config.NodeState + log *log.Logger + sock *ipv6.PacketConn + groupAddr *net.UDPAddr + listeners map[string]*multicastInterface + listenPort uint16 + isOpen bool + interfaceMonitor *time.Timer + announcer *time.Timer + platformhandler *time.Timer } -type listenerInfo struct { +type multicastInterface struct { + phony.Inbox + sock *ipv6.PacketConn + destAddr net.UDPAddr listener *yggdrasil.TcpListener - time time.Time + zone string + timer *time.Timer interval time.Duration + send chan<- beacon + stop chan interface{} +} + +type beacon struct { + llAddr string + zone string } // Init prepares the multicast interface for use. -func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) error { +func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) (err error) { m.core = core m.config = state m.log = log - m.listeners = make(map[string]*listenerInfo) + m.listeners = make(map[string]*multicastInterface) current := m.config.GetCurrent() m.listenPort = current.LinkLocalTCPPort - m.groupAddr = "[ff02::114]:9001" - return nil + m.groupAddr, err = net.ResolveUDPAddr("udp6", GroupAddr) + return } // Start starts the multicast interface. This launches goroutines which will @@ -71,7 +88,7 @@ func (m *Multicast) _start() error { return nil } m.log.Infoln("Starting multicast module") - addr, err := net.ResolveUDPAddr("udp", m.groupAddr) + addr, err := net.ResolveUDPAddr("udp", GroupAddr) if err != nil { return err } @@ -91,7 +108,7 @@ func (m *Multicast) _start() error { m.isOpen = true go m.listen() m.Act(m, m.multicastStarted) - m.Act(m, m.announce) + m.Act(m, m.monitorInterfaceChanges) return nil } @@ -118,9 +135,14 @@ func (m *Multicast) Stop() error { func (m *Multicast) _stop() error { m.log.Infoln("Stopping multicast module") m.isOpen = false - if m.announcer != nil { - m.announcer.Stop() - } + /* + if m.monitorInterfaceChanges != nil { + m.monitorInterfaceChanges.Stop() + } + if m.sendBeacons != nil { + m.sendBeacons.Stop() + } + */ if m.platformhandler != nil { m.platformhandler.Stop() } @@ -156,6 +178,83 @@ func (m *Multicast) _updateConfig(config *config.NodeConfig) { m.log.Debugln("Reloaded multicast configuration successfully") } +func (m *Multicast) monitorInterfaceChanges() { + interfaces := m.Interfaces() + + // Look for interfaces we don't know about yet. + for name, intf := range interfaces { + if _, ok := m.listeners[name]; !ok { + // Look up interface addresses. + addrs, err := intf.Addrs() + if err != nil { + continue + } + // Find the first link-local address. + for _, addr := range addrs { + addrIP, _, _ := net.ParseCIDR(addr.String()) + // Join the multicast group. + m.sock.JoinGroup(&intf, m.groupAddr) + // Construct a listener on this address. + listenaddr := fmt.Sprintf("[%s%%%s]:%d", addrIP, intf.Name, m.listenPort) + listener, err := m.core.ListenTCP(listenaddr) + if err != nil { + m.log.Warnln("Not multicasting on", name, "due to error:", err) + continue + } + // This is a new interface. Start an announcer for it. + multicastInterface := &multicastInterface{ + sock: m.sock, + destAddr: *m.groupAddr, + listener: listener, + stop: make(chan interface{}), + zone: name, + } + multicastInterface.Act(multicastInterface, multicastInterface.announce) + m.listeners[name] = multicastInterface + m.log.Infoln("Started multicasting on", name) + break + } + } + } + // Look for interfaces we knew about but are no longer there. + for name, intf := range m.listeners { + if _, ok := interfaces[name]; !ok { + // This is a disappeared interface. Stop the announcer. + close(intf.stop) + delete(m.listeners, name) + m.log.Infoln("Stopped multicasting on", name) + } + } + // Queue the next check. + m.interfaceMonitor = time.AfterFunc(time.Second, func() { + m.Act(m, m.monitorInterfaceChanges) + }) +} + +func (m *multicastInterface) announce() { + // Check if the multicast interface has been stopped. This will happen + // if it disappears from the system or goes down. + select { + case <-m.stop: + return + default: + } + // Send the beacon. + lladdr := m.listener.Listener.Addr().String() + if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil { + a.Zone = "" + msg := []byte(a.String()) + m.sock.WriteTo(msg, nil, &m.destAddr) + } + // Queue the next beacon. + if m.interval.Seconds() < 15 { + m.interval += time.Second + } + m.timer = time.AfterFunc(m.interval, func() { + m.Act(m, m.announce) + }) +} + // GetInterfaces returns the currently known/enabled multicast interfaces. It is // expected that UpdateInterfaces has been called at least once before calling // this method. @@ -183,6 +282,19 @@ func (m *Multicast) Interfaces() map[string]net.Interface { // Ignore point-to-point interfaces continue } + addrs, _ := iface.Addrs() + hasLLAddr := false + for _, addr := range addrs { + addrIP, _, _ := net.ParseCIDR(addr.String()) + if addrIP.To4() == nil && addrIP.IsLinkLocalUnicast() { + hasLLAddr = true + break + } + } + if !hasLLAddr { + // Ignore interfaces without link-local addresses + continue + } for _, expr := range exprs { // Compile each regular expression e, err := regexp.Compile(expr) @@ -198,127 +310,8 @@ func (m *Multicast) Interfaces() map[string]net.Interface { return interfaces } -func (m *Multicast) announce() { - groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr) - if err != nil { - panic(err) - } - destAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr) - if err != nil { - panic(err) - } - interfaces := m.Interfaces() - // There might be interfaces that we configured listeners for but are no - // longer up - if that's the case then we should stop the listeners - for name, info := range m.listeners { - // Prepare our stop function! - stop := func() { - info.listener.Stop() - delete(m.listeners, name) - m.log.Debugln("No longer multicasting on", name) - } - // If the interface is no longer visible on the system then stop the - // listener, as another one will be started further down - if _, ok := interfaces[name]; !ok { - stop() - continue - } - // It's possible that the link-local listener address has changed so if - // that is the case then we should clean up the interface listener - found := false - listenaddr, err := net.ResolveTCPAddr("tcp6", info.listener.Listener.Addr().String()) - if err != nil { - stop() - continue - } - // Find the interface that matches the listener - if intf, err := net.InterfaceByName(name); err == nil { - if addrs, err := intf.Addrs(); err == nil { - // Loop through the addresses attached to that listener and see if any - // of them match the current address of the listener - for _, addr := range addrs { - if ip, _, err := net.ParseCIDR(addr.String()); err == nil { - // Does the interface address match our listener address? - if ip.Equal(listenaddr.IP) { - found = true - break - } - } - } - } - } - // If the address has not been found on the adapter then we should stop - // and clean up the TCP listener. A new one will be created below if a - // suitable link-local address is found - if !found { - stop() - } - } - // Now that we have a list of valid interfaces from the operating system, - // we can start checking if we can send multicasts on them - for _, iface := range interfaces { - // Find interface addresses - addrs, err := iface.Addrs() - if err != nil { - panic(err) - } - for _, addr := range addrs { - addrIP, _, _ := net.ParseCIDR(addr.String()) - // Ignore IPv4 addresses - if addrIP.To4() != nil { - continue - } - // Ignore non-link-local addresses - if !addrIP.IsLinkLocalUnicast() { - continue - } - // Join the multicast group - m.sock.JoinGroup(&iface, groupAddr) - // Try and see if we already have a TCP listener for this interface - var info *listenerInfo - if nfo, ok := m.listeners[iface.Name]; !ok || nfo.listener.Listener == nil { - // No listener was found - let's create one - listenaddr := fmt.Sprintf("[%s%%%s]:%d", addrIP, iface.Name, m.listenPort) - if li, err := m.core.ListenTCP(listenaddr); err == nil { - m.log.Debugln("Started multicasting on", iface.Name) - // Store the listener so that we can stop it later if needed - info = &listenerInfo{listener: li, time: time.Now()} - m.listeners[iface.Name] = info - } else { - m.log.Warnln("Not multicasting on", iface.Name, "due to error:", err) - } - } else { - // An existing listener was found - info = m.listeners[iface.Name] - } - // Make sure nothing above failed for some reason - if info == nil { - continue - } - if time.Since(info.time) < info.interval { - continue - } - // Get the listener details and construct the multicast beacon - lladdr := info.listener.Listener.Addr().String() - if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil { - a.Zone = "" - destAddr.Zone = iface.Name - msg := []byte(a.String()) - m.sock.WriteTo(msg, nil, destAddr) - } - if info.interval.Seconds() < 15 { - info.interval += time.Second - } - break - } - } - m.announcer = time.AfterFunc(time.Second, func() { - m.Act(m, m.announce) - }) -} - func (m *Multicast) listen() { - groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr) + groupAddr, err := net.ResolveUDPAddr("udp6", GroupAddr) if err != nil { panic(err) } From 0c7cf65d27fe734bda4d9bc334e95c8a9fea2e13 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 2 May 2020 22:33:25 +0100 Subject: [PATCH 0547/1109] Move some logging back to debug --- src/multicast/multicast.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 6ceef0aa..d944b7ff 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -211,7 +211,7 @@ func (m *Multicast) monitorInterfaceChanges() { } multicastInterface.Act(multicastInterface, multicastInterface.announce) m.listeners[name] = multicastInterface - m.log.Infoln("Started multicasting on", name) + m.log.Debugln("Started multicasting on", name) break } } @@ -222,7 +222,7 @@ func (m *Multicast) monitorInterfaceChanges() { // This is a disappeared interface. Stop the announcer. close(intf.stop) delete(m.listeners, name) - m.log.Infoln("Stopped multicasting on", name) + m.log.Debugln("Stopped multicasting on", name) } } // Queue the next check. From 127b7e311c737fb23142941d16a1b7e8cdb1cf5f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 2 May 2020 22:37:12 +0100 Subject: [PATCH 0548/1109] Clean up a bit --- src/multicast/multicast.go | 41 ++++++++++++++------------------------ 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index d944b7ff..28c9b4ec 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -26,17 +26,16 @@ const ( // automatically. type Multicast struct { phony.Inbox - core *yggdrasil.Core - config *config.NodeState - log *log.Logger - sock *ipv6.PacketConn - groupAddr *net.UDPAddr - listeners map[string]*multicastInterface - listenPort uint16 - isOpen bool - interfaceMonitor *time.Timer - announcer *time.Timer - platformhandler *time.Timer + core *yggdrasil.Core + config *config.NodeState + log *log.Logger + sock *ipv6.PacketConn + groupAddr *net.UDPAddr + listeners map[string]*multicastInterface + listenPort uint16 + isOpen bool + monitor *time.Timer + platformhandler *time.Timer } type multicastInterface struct { @@ -47,15 +46,9 @@ type multicastInterface struct { zone string timer *time.Timer interval time.Duration - send chan<- beacon stop chan interface{} } -type beacon struct { - llAddr string - zone string -} - // Init prepares the multicast interface for use. func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) (err error) { m.core = core @@ -135,14 +128,10 @@ func (m *Multicast) Stop() error { func (m *Multicast) _stop() error { m.log.Infoln("Stopping multicast module") m.isOpen = false - /* - if m.monitorInterfaceChanges != nil { - m.monitorInterfaceChanges.Stop() - } - if m.sendBeacons != nil { - m.sendBeacons.Stop() - } - */ + for name := range m.listeners { + close(m.listeners[name].stop) + delete(m.listeners, name) + } if m.platformhandler != nil { m.platformhandler.Stop() } @@ -226,7 +215,7 @@ func (m *Multicast) monitorInterfaceChanges() { } } // Queue the next check. - m.interfaceMonitor = time.AfterFunc(time.Second, func() { + m.monitor = time.AfterFunc(time.Second, func() { m.Act(m, m.monitorInterfaceChanges) }) } From 02e1cb180d046e2a9d120de00556456f5c6887ad Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 2 May 2020 17:23:20 -0500 Subject: [PATCH 0549/1109] possibly reduce multicast cpu usage even more --- src/multicast/multicast.go | 43 +++++++++++++++++++----------- src/multicast/multicast_darwin.go | 4 +-- src/multicast/multicast_other.go | 2 +- src/multicast/multicast_unix.go | 2 +- src/multicast/multicast_windows.go | 2 +- 5 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 28c9b4ec..4f0a3ff9 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -36,6 +36,8 @@ type Multicast struct { isOpen bool monitor *time.Timer platformhandler *time.Timer + _interfaces map[string]net.Interface + _interfaceAddrs map[string][]net.Addr } type multicastInterface struct { @@ -100,8 +102,8 @@ func (m *Multicast) _start() error { m.isOpen = true go m.listen() - m.Act(m, m.multicastStarted) - m.Act(m, m.monitorInterfaceChanges) + m.Act(nil, m._multicastStarted) + m.Act(nil, m._monitorInterfaceChanges) return nil } @@ -145,7 +147,7 @@ func (m *Multicast) _stop() error { // and then signals the various module goroutines to reconfigure themselves if // needed. func (m *Multicast) UpdateConfig(config *config.NodeConfig) { - m.Act(m, func() { m._updateConfig(config) }) + m.Act(nil, func() { m._updateConfig(config) }) } func (m *Multicast) _updateConfig(config *config.NodeConfig) { @@ -167,17 +169,14 @@ func (m *Multicast) _updateConfig(config *config.NodeConfig) { m.log.Debugln("Reloaded multicast configuration successfully") } -func (m *Multicast) monitorInterfaceChanges() { - interfaces := m.Interfaces() +func (m *Multicast) _monitorInterfaceChanges() { + m._updateInterfaces() // update interfaces and interfaceAddrs // Look for interfaces we don't know about yet. - for name, intf := range interfaces { + for name, intf := range m._interfaces { if _, ok := m.listeners[name]; !ok { // Look up interface addresses. - addrs, err := intf.Addrs() - if err != nil { - continue - } + addrs := m._interfaceAddrs[intf.Name] // Find the first link-local address. for _, addr := range addrs { addrIP, _, _ := net.ParseCIDR(addr.String()) @@ -198,7 +197,7 @@ func (m *Multicast) monitorInterfaceChanges() { stop: make(chan interface{}), zone: name, } - multicastInterface.Act(multicastInterface, multicastInterface.announce) + multicastInterface.Act(m, multicastInterface._announce) m.listeners[name] = multicastInterface m.log.Debugln("Started multicasting on", name) break @@ -207,7 +206,7 @@ func (m *Multicast) monitorInterfaceChanges() { } // Look for interfaces we knew about but are no longer there. for name, intf := range m.listeners { - if _, ok := interfaces[name]; !ok { + if _, ok := m._interfaces[name]; !ok { // This is a disappeared interface. Stop the announcer. close(intf.stop) delete(m.listeners, name) @@ -216,11 +215,11 @@ func (m *Multicast) monitorInterfaceChanges() { } // Queue the next check. m.monitor = time.AfterFunc(time.Second, func() { - m.Act(m, m.monitorInterfaceChanges) + m.Act(nil, m._monitorInterfaceChanges) }) } -func (m *multicastInterface) announce() { +func (m *multicastInterface) _announce() { // Check if the multicast interface has been stopped. This will happen // if it disappears from the system or goes down. select { @@ -240,7 +239,7 @@ func (m *multicastInterface) announce() { m.interval += time.Second } m.timer = time.AfterFunc(m.interval, func() { - m.Act(m, m.announce) + m.Act(nil, m._announce) }) } @@ -248,6 +247,14 @@ func (m *multicastInterface) announce() { // expected that UpdateInterfaces has been called at least once before calling // this method. func (m *Multicast) Interfaces() map[string]net.Interface { + var interfaces map[string]net.Interface + phony.Block(m, func() { + interfaces = m._interfaces + }) + return interfaces +} + +func (m *Multicast) _updateInterfaces() { interfaces := make(map[string]net.Interface) // Get interface expressions from config current := m.config.GetCurrent() @@ -258,6 +265,7 @@ func (m *Multicast) Interfaces() map[string]net.Interface { panic(err) } // Work out which interfaces to announce on + interfaceAddrs := make(map[string][]net.Addr) for _, iface := range allifaces { if iface.Flags&net.FlagUp == 0 { // Ignore interfaces that are down @@ -293,10 +301,12 @@ func (m *Multicast) Interfaces() map[string]net.Interface { // Does the interface match the regular expression? Store it if so if e.MatchString(iface.Name) { interfaces[iface.Name] = iface + interfaceAddrs[iface.Name] = addrs } } } - return interfaces + m._interfaces = interfaces + m._interfaceAddrs = interfaceAddrs } func (m *Multicast) listen() { @@ -333,6 +343,7 @@ func (m *Multicast) listen() { if addr.IP.String() != from.IP.String() { continue } + // Note that m.Interfaces would block if it was being run by the actor itself if _, ok := m.Interfaces()[from.Zone]; ok { addr.Zone = "" if err := m.core.CallPeer("tcp://"+addr.String(), from.Zone); err != nil { diff --git a/src/multicast/multicast_darwin.go b/src/multicast/multicast_darwin.go index 4cfef9e9..e020106b 100644 --- a/src/multicast/multicast_darwin.go +++ b/src/multicast/multicast_darwin.go @@ -31,7 +31,7 @@ import ( var awdlGoroutineStarted bool -func (m *Multicast) multicastStarted() { +func (m *Multicast) _multicastStarted() { C.StopAWDLBrowsing() for intf := range m.Interfaces() { if intf == "awdl0" { @@ -40,7 +40,7 @@ func (m *Multicast) multicastStarted() { } } m.platformhandler = time.AfterFunc(time.Minute, func() { - m.Act(m, m.multicastStarted) + m.Act(m, m._multicastStarted) }) } diff --git a/src/multicast/multicast_other.go b/src/multicast/multicast_other.go index 16ea15d6..dfcf625f 100644 --- a/src/multicast/multicast_other.go +++ b/src/multicast/multicast_other.go @@ -4,7 +4,7 @@ package multicast import "syscall" -func (m *Multicast) multicastStarted() { +func (m *Multicast) _multicastStarted() { } diff --git a/src/multicast/multicast_unix.go b/src/multicast/multicast_unix.go index 9d6a01a8..1ff48b17 100644 --- a/src/multicast/multicast_unix.go +++ b/src/multicast/multicast_unix.go @@ -5,7 +5,7 @@ package multicast import "syscall" import "golang.org/x/sys/unix" -func (m *Multicast) multicastStarted() { +func (m *Multicast) _multicastStarted() { } diff --git a/src/multicast/multicast_windows.go b/src/multicast/multicast_windows.go index 7a846b1e..3666faaa 100644 --- a/src/multicast/multicast_windows.go +++ b/src/multicast/multicast_windows.go @@ -5,7 +5,7 @@ package multicast import "syscall" import "golang.org/x/sys/windows" -func (m *Multicast) multicastStarted() { +func (m *Multicast) _multicastStarted() { } From de79401bb228695a78b6244f9e85037ef46c5062 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 3 May 2020 02:50:04 -0500 Subject: [PATCH 0550/1109] only call (net.Interface).Addrs() once per minute per interface --- src/multicast/multicast.go | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 4f0a3ff9..aaa21151 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -37,7 +37,12 @@ type Multicast struct { monitor *time.Timer platformhandler *time.Timer _interfaces map[string]net.Interface - _interfaceAddrs map[string][]net.Addr + _interfaceAddrs map[string]addrInfo +} + +type addrInfo struct { + addrs []net.Addr + time time.Time } type multicastInterface struct { @@ -176,7 +181,7 @@ func (m *Multicast) _monitorInterfaceChanges() { for name, intf := range m._interfaces { if _, ok := m.listeners[name]; !ok { // Look up interface addresses. - addrs := m._interfaceAddrs[intf.Name] + addrs := m._interfaceAddrs[intf.Name].addrs // Find the first link-local address. for _, addr := range addrs { addrIP, _, _ := net.ParseCIDR(addr.String()) @@ -265,7 +270,7 @@ func (m *Multicast) _updateInterfaces() { panic(err) } // Work out which interfaces to announce on - interfaceAddrs := make(map[string][]net.Addr) + interfaceAddrs := make(map[string]addrInfo) for _, iface := range allifaces { if iface.Flags&net.FlagUp == 0 { // Ignore interfaces that are down @@ -279,9 +284,16 @@ func (m *Multicast) _updateInterfaces() { // Ignore point-to-point interfaces continue } - addrs, _ := iface.Addrs() + var aInfo addrInfo + var isIn bool + if aInfo, isIn = m._interfaceAddrs[iface.Name]; isIn && time.Since(aInfo.time) < time.Minute { + // don't call iface.Addrs, it's unlikely things have changed + } else { + aInfo.addrs, _ = iface.Addrs() + aInfo.time = time.Now() + } hasLLAddr := false - for _, addr := range addrs { + for _, addr := range aInfo.addrs { addrIP, _, _ := net.ParseCIDR(addr.String()) if addrIP.To4() == nil && addrIP.IsLinkLocalUnicast() { hasLLAddr = true @@ -301,7 +313,7 @@ func (m *Multicast) _updateInterfaces() { // Does the interface match the regular expression? Store it if so if e.MatchString(iface.Name) { interfaces[iface.Name] = iface - interfaceAddrs[iface.Name] = addrs + interfaceAddrs[iface.Name] = aInfo } } } From 95f4ec52a40ad1e8d3d1c72d80dd50dbbb8553ce Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 3 May 2020 05:06:59 -0500 Subject: [PATCH 0551/1109] save only the link-local addresses for multicast --- src/multicast/multicast.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index aaa21151..fc7b1373 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -292,15 +292,15 @@ func (m *Multicast) _updateInterfaces() { aInfo.addrs, _ = iface.Addrs() aInfo.time = time.Now() } - hasLLAddr := false + lladdrs := aInfo.addrs[:0] for _, addr := range aInfo.addrs { addrIP, _, _ := net.ParseCIDR(addr.String()) if addrIP.To4() == nil && addrIP.IsLinkLocalUnicast() { - hasLLAddr = true - break + lladdrs = append(lladdrs, addr) } } - if !hasLLAddr { + aInfo.addrs = lladdrs + if len(lladdrs) == 0 { // Ignore interfaces without link-local addresses continue } From e849b3e1192f94f14f6ea0e0af50275956689171 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 8 May 2020 23:23:48 +0100 Subject: [PATCH 0552/1109] Initial support for pinning public keys in peering strings --- src/yggdrasil/link.go | 57 +++++++++++++++++++++++++++++++++++++------ src/yggdrasil/tcp.go | 42 ++++++++++++++++++------------- 2 files changed, 75 insertions(+), 24 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 157ea525..c0ae23b5 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -1,6 +1,7 @@ package yggdrasil import ( + "bytes" "encoding/hex" "errors" "fmt" @@ -50,6 +51,7 @@ type linkInterface struct { name string link *link peer *peer + options linkOptions msgIO linkInterfaceMsgIO info linkInfo incoming bool @@ -67,6 +69,10 @@ type linkInterface struct { unstalled bool // False if an idle notification to the switch hasn't been sent because we stalled (or are first starting up) } +type linkOptions struct { + pinningInfo *url.Userinfo +} + func (l *link) init(c *Core) error { l.core = c l.mutex.Lock() @@ -92,13 +98,19 @@ func (l *link) call(uri string, sintf string) error { return fmt.Errorf("peer %s is not correctly formatted (%s)", uri, err) } pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/") + tcpOpts := tcpOptions{} + if u.User != nil { + tcpOpts.pinningInfo = u.User + } switch u.Scheme { case "tcp": - l.tcp.call(u.Host, nil, sintf, nil) + l.tcp.call(u.Host, tcpOpts, sintf) case "socks": - l.tcp.call(pathtokens[0], u.Host, sintf, nil) + tcpOpts.socksProxyAddr = u.Host + l.tcp.call(pathtokens[0], tcpOpts, sintf) case "tls": - l.tcp.call(u.Host, nil, sintf, l.tcp.tls.forDialer) + tcpOpts.upgrade = l.tcp.tls.forDialer + l.tcp.call(u.Host, tcpOpts, sintf) default: return errors.New("unknown call scheme: " + u.Scheme) } @@ -122,12 +134,13 @@ func (l *link) listen(uri string) error { } } -func (l *link) create(msgIO linkInterfaceMsgIO, name, linkType, local, remote string, incoming, force bool) (*linkInterface, error) { +func (l *link) create(msgIO linkInterfaceMsgIO, name, linkType, local, remote string, incoming, force bool, options linkOptions) (*linkInterface, error) { // Technically anything unique would work for names, but let's pick something human readable, just for debugging intf := linkInterface{ - name: name, - link: l, - msgIO: msgIO, + name: name, + link: l, + options: options, + msgIO: msgIO, info: linkInfo{ linkType: linkType, local: local, @@ -181,6 +194,36 @@ func (intf *linkInterface) handler() error { intf.link.core.log.Errorln("Failed to connect to node: " + intf.name + " version: " + fmt.Sprintf("%d.%d", meta.ver, meta.minorVer)) return errors.New("failed to connect: wrong version") } + // Check if the remote side matches the keys we expected. This is a bit of a weak + // check - in future versions we really should check a signature or something like that. + if pinning := intf.options.pinningInfo; pinning != nil { + allowed := true + keytype := pinning.Username() + if pubkey, ok := pinning.Password(); ok { + switch keytype { + case "curve25519": + boxPub, err := hex.DecodeString(pubkey) + if err != nil || len(boxPub) != crypto.BoxPubKeyLen { + allowed = false + break + } + allowed = bytes.Compare(boxPub, meta.box[:]) == 0 + case "ed25519": + sigPub, err := hex.DecodeString(pubkey) + if err != nil || len(sigPub) != crypto.SigPubKeyLen { + allowed = false + break + } + allowed = bytes.Compare(sigPub, meta.sig[:]) == 0 + } + } else { + allowed = false + } + if !allowed { + intf.link.core.log.Errorf("Failed to connect to node: %q sent key that does not match pinned %q key", intf.name, keytype) + return fmt.Errorf("failed to connect: host does not match pinned %q key", pinning.Username()) + } + } // Check if we're authorized to connect to this key / IP if intf.incoming && !intf.force && !intf.link.core.peers.isAllowedEncryptionPublicKey(&meta.box) { intf.link.core.log.Warnf("%s connection from %s forbidden: AllowedEncryptionPublicKeys does not contain key %s", diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 9cca4193..a1b6402f 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -57,6 +57,12 @@ type TcpUpgrade struct { name string } +type tcpOptions struct { + linkOptions + upgrade *TcpUpgrade + socksProxyAddr string +} + func (l *TcpListener) Stop() { defer func() { recover() }() close(l.stop) @@ -221,7 +227,10 @@ func (t *tcp) listener(l *TcpListener, listenaddr string) { return } t.waitgroup.Add(1) - go t.handler(sock, true, nil, l.upgrade) + options := tcpOptions{ + upgrade: l.upgrade, + } + go t.handler(sock, true, options) } } @@ -239,12 +248,12 @@ func (t *tcp) startCalling(saddr string) bool { // If the dial is successful, it launches the handler. // When finished, it removes the outgoing call, so reconnection attempts can be made later. // This all happens in a separate goroutine that it spawns. -func (t *tcp) call(saddr string, options interface{}, sintf string, upgrade *TcpUpgrade) { +func (t *tcp) call(saddr string, options tcpOptions, sintf string) { go func() { callname := saddr callproto := "TCP" - if upgrade != nil { - callproto = strings.ToUpper(upgrade.name) + if options.upgrade != nil { + callproto = strings.ToUpper(options.upgrade.name) } if sintf != "" { callname = fmt.Sprintf("%s/%s/%s", callproto, saddr, sintf) @@ -263,12 +272,11 @@ func (t *tcp) call(saddr string, options interface{}, sintf string, upgrade *Tcp }() var conn net.Conn var err error - socksaddr, issocks := options.(string) - if issocks { + if options.socksProxyAddr != "" { if sintf != "" { return } - dialerdst, er := net.ResolveTCPAddr("tcp", socksaddr) + dialerdst, er := net.ResolveTCPAddr("tcp", options.socksProxyAddr) if er != nil { return } @@ -282,7 +290,7 @@ func (t *tcp) call(saddr string, options interface{}, sintf string, upgrade *Tcp return } t.waitgroup.Add(1) - t.handler(conn, false, saddr, nil) + t.handler(conn, false, options) } else { dst, err := net.ResolveTCPAddr("tcp", saddr) if err != nil { @@ -348,19 +356,19 @@ func (t *tcp) call(saddr string, options interface{}, sintf string, upgrade *Tcp return } t.waitgroup.Add(1) - t.handler(conn, false, nil, upgrade) + t.handler(conn, false, options) } }() } -func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}, upgrade *TcpUpgrade) { +func (t *tcp) handler(sock net.Conn, incoming bool, options tcpOptions) { defer t.waitgroup.Done() // Happens after sock.close defer sock.Close() t.setExtraOptions(sock) var upgraded bool - if upgrade != nil { + if options.upgrade != nil { var err error - if sock, err = upgrade.upgrade(sock); err != nil { + if sock, err = options.upgrade.upgrade(sock); err != nil { t.link.core.log.Errorln("TCP handler upgrade failed:", err) return } else { @@ -370,14 +378,14 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}, upgrade stream := stream{} stream.init(sock) var name, proto, local, remote string - if socksaddr, issocks := options.(string); issocks { - name = "socks://" + sock.RemoteAddr().String() + "/" + socksaddr + if options.socksProxyAddr != "" { + name = "socks://" + sock.RemoteAddr().String() + "/" + options.socksProxyAddr proto = "socks" local, _, _ = net.SplitHostPort(sock.LocalAddr().String()) - remote, _, _ = net.SplitHostPort(socksaddr) + remote, _, _ = net.SplitHostPort(options.socksProxyAddr) } else { if upgraded { - proto = upgrade.name + proto = options.upgrade.name name = proto + "://" + sock.RemoteAddr().String() } else { proto = "tcp" @@ -387,7 +395,7 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}, upgrade remote, _, _ = net.SplitHostPort(sock.RemoteAddr().String()) } force := net.ParseIP(strings.Split(remote, "%")[0]).IsLinkLocalUnicast() - link, err := t.link.core.link.create(&stream, name, proto, local, remote, incoming, force) + link, err := t.link.core.link.create(&stream, name, proto, local, remote, incoming, force, options.linkOptions) if err != nil { t.link.core.log.Println(err) panic(err) From fbf59184ee4a7005fc5768a251826c1633637160 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 9 May 2020 00:43:19 +0100 Subject: [PATCH 0553/1109] Use query string instead, allow specifying multiple keys (might be useful for DNS RR) --- src/yggdrasil/link.go | 66 +++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index c0ae23b5..40d128e3 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -70,7 +70,8 @@ type linkInterface struct { } type linkOptions struct { - pinningInfo *url.Userinfo + pinnedCurve25519Keys []crypto.BoxPubKey + pinnedEd25519Keys []crypto.SigPubKey } func (l *link) init(c *Core) error { @@ -99,8 +100,27 @@ func (l *link) call(uri string, sintf string) error { } pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/") tcpOpts := tcpOptions{} - if u.User != nil { - tcpOpts.pinningInfo = u.User + if pubkeys, ok := u.Query()["curve25519"]; ok && len(pubkeys) > 0 { + for _, pubkey := range pubkeys { + if boxPub, err := hex.DecodeString(pubkey); err != nil { + var boxPubKey crypto.BoxPubKey + copy(boxPubKey[:], boxPub) + tcpOpts.pinnedCurve25519Keys = append( + tcpOpts.pinnedCurve25519Keys, boxPubKey, + ) + } + } + } + if pubkeys, ok := u.Query()["ed25519"]; ok && len(pubkeys) > 0 { + for _, pubkey := range pubkeys { + if sigPub, err := hex.DecodeString(pubkey); err != nil { + var sigPubKey crypto.SigPubKey + copy(sigPubKey[:], sigPub) + tcpOpts.pinnedEd25519Keys = append( + tcpOpts.pinnedEd25519Keys, sigPubKey, + ) + } + } } switch u.Scheme { case "tcp": @@ -196,32 +216,24 @@ func (intf *linkInterface) handler() error { } // Check if the remote side matches the keys we expected. This is a bit of a weak // check - in future versions we really should check a signature or something like that. - if pinning := intf.options.pinningInfo; pinning != nil { - allowed := true - keytype := pinning.Username() - if pubkey, ok := pinning.Password(); ok { - switch keytype { - case "curve25519": - boxPub, err := hex.DecodeString(pubkey) - if err != nil || len(boxPub) != crypto.BoxPubKeyLen { - allowed = false - break - } - allowed = bytes.Compare(boxPub, meta.box[:]) == 0 - case "ed25519": - sigPub, err := hex.DecodeString(pubkey) - if err != nil || len(sigPub) != crypto.SigPubKeyLen { - allowed = false - break - } - allowed = bytes.Compare(sigPub, meta.sig[:]) == 0 - } - } else { - allowed = false + if pinned := intf.options.pinnedCurve25519Keys; len(pinned) > 0 { + allowed := false + for _, key := range pinned { + allowed = allowed || (bytes.Compare(key[:], meta.box[:]) == 0) } if !allowed { - intf.link.core.log.Errorf("Failed to connect to node: %q sent key that does not match pinned %q key", intf.name, keytype) - return fmt.Errorf("failed to connect: host does not match pinned %q key", pinning.Username()) + intf.link.core.log.Errorf("Failed to connect to node: %q sent curve25519 key that does not match pinned keys", intf.name) + return fmt.Errorf("failed to connect: host sent curve25519 key that does not match pinned keys") + } + } + if pinned := intf.options.pinnedEd25519Keys; len(pinned) > 0 { + allowed := false + for _, key := range pinned { + allowed = allowed || (bytes.Compare(key[:], meta.sig[:]) == 0) + } + if !allowed { + intf.link.core.log.Errorf("Failed to connect to node: %q sent ed25519 key that does not match pinned keys", intf.name) + return fmt.Errorf("failed to connect: host sent ed25519 key that does not match pinned keys") } } // Check if we're authorized to connect to this key / IP From 58345ac198acafbfb219a52a1e66358483347394 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 9 May 2020 10:53:58 +0100 Subject: [PATCH 0554/1109] Track proxy addr and real peer addr in SOCKS mode --- src/yggdrasil/tcp.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index a1b6402f..6f97da82 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -61,6 +61,7 @@ type tcpOptions struct { linkOptions upgrade *TcpUpgrade socksProxyAddr string + socksPeerAddr string } func (l *TcpListener) Stop() { @@ -290,6 +291,7 @@ func (t *tcp) call(saddr string, options tcpOptions, sintf string) { return } t.waitgroup.Add(1) + options.socksPeerAddr = saddr t.handler(conn, false, options) } else { dst, err := net.ResolveTCPAddr("tcp", saddr) @@ -379,10 +381,10 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options tcpOptions) { stream.init(sock) var name, proto, local, remote string if options.socksProxyAddr != "" { - name = "socks://" + sock.RemoteAddr().String() + "/" + options.socksProxyAddr + name = "socks://" + sock.RemoteAddr().String() + "/" + options.socksPeerAddr proto = "socks" local, _, _ = net.SplitHostPort(sock.LocalAddr().String()) - remote, _, _ = net.SplitHostPort(options.socksProxyAddr) + remote, _, _ = net.SplitHostPort(options.socksPeerAddr) } else { if upgraded { proto = options.upgrade.name From 8b180e941aa7ccf178a7e82c5a11d172c23d6331 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 9 May 2020 11:24:32 +0100 Subject: [PATCH 0555/1109] Add SOCKS proxy auth (closes #423) --- src/yggdrasil/link.go | 6 ++++++ src/yggdrasil/tcp.go | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 40d128e3..5c4f0e68 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -17,6 +17,7 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/util" + "golang.org/x/net/proxy" "github.com/Arceliar/phony" ) @@ -127,6 +128,11 @@ func (l *link) call(uri string, sintf string) error { l.tcp.call(u.Host, tcpOpts, sintf) case "socks": tcpOpts.socksProxyAddr = u.Host + if u.User != nil { + tcpOpts.socksProxyAuth = &proxy.Auth{} + tcpOpts.socksProxyAuth.User = u.User.Username() + tcpOpts.socksProxyAuth.Password, _ = u.User.Password() + } l.tcp.call(pathtokens[0], tcpOpts, sintf) case "tls": tcpOpts.upgrade = l.tcp.tls.forDialer diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 6f97da82..e137391a 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -61,6 +61,7 @@ type tcpOptions struct { linkOptions upgrade *TcpUpgrade socksProxyAddr string + socksProxyAuth *proxy.Auth socksPeerAddr string } @@ -282,7 +283,7 @@ func (t *tcp) call(saddr string, options tcpOptions, sintf string) { return } var dialer proxy.Dialer - dialer, err = proxy.SOCKS5("tcp", dialerdst.String(), nil, proxy.Direct) + dialer, err = proxy.SOCKS5("tcp", dialerdst.String(), options.socksProxyAuth, proxy.Direct) if err != nil { return } From 13a2d99fdc427753b3ccd433e0bdfee8fa5930a8 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 9 May 2020 11:26:09 +0100 Subject: [PATCH 0556/1109] Set SOCKS peer addr to resolved address --- src/yggdrasil/tcp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index e137391a..c81c4ddf 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -292,7 +292,7 @@ func (t *tcp) call(saddr string, options tcpOptions, sintf string) { return } t.waitgroup.Add(1) - options.socksPeerAddr = saddr + options.socksPeerAddr = conn.RemoteAddr().String() t.handler(conn, false, options) } else { dst, err := net.ResolveTCPAddr("tcp", saddr) From 7779d86c5b6ff1ccc531552ac5d0108f98aa5d66 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 9 May 2020 05:56:36 -0500 Subject: [PATCH 0557/1109] maybe fix multicast deadlock on darwin --- src/multicast/multicast_darwin.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/multicast/multicast_darwin.go b/src/multicast/multicast_darwin.go index e020106b..91cb5aee 100644 --- a/src/multicast/multicast_darwin.go +++ b/src/multicast/multicast_darwin.go @@ -33,14 +33,14 @@ var awdlGoroutineStarted bool func (m *Multicast) _multicastStarted() { C.StopAWDLBrowsing() - for intf := range m.Interfaces() { + for intf := range m._interfaces { if intf == "awdl0" { C.StartAWDLBrowsing() break } } m.platformhandler = time.AfterFunc(time.Minute, func() { - m.Act(m, m._multicastStarted) + m.Act(nil, m._multicastStarted) }) } From 9dfe0f4b4b7cf38b4c5e32ba20ee9d7de8cccdd6 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 9 May 2020 12:08:29 +0100 Subject: [PATCH 0558/1109] Fix hjson dependency? --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 04cd5b90..bf8e7571 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/cheggaaa/pb/v3 v3.0.4 github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 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.2-0.20200316202735-d5d0e8b0617d+incompatible github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/mitchellh/mapstructure v1.1.2 github.com/vishvananda/netlink v1.0.0 diff --git a/go.sum b/go.sum index 6d08e865..f67b6806 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,8 @@ github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 h1:WD8iJ37bRNwvETMfVTu github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible h1:bLQ2Ve+eW65id3b8xEMQiAwJT4qGZeywAEMLvXjznvw= -github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= +github.com/hjson/hjson-go v3.0.2-0.20200316202735-d5d0e8b0617d+incompatible h1:v6BPcb9q9U6JDVsuizxBr/piVB/2Y1Q5GWoBybvZVWI= +github.com/hjson/hjson-go v3.0.2-0.20200316202735-d5d0e8b0617d+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk8851WTVWlqMC1ecJH07Ctz+Ezxx4u54g= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0/go.mod h1:rUi0/YffDo1oXBOGn1KRq7Fr07LX48XEBecQnmwjsAo= github.com/lxn/walk v0.0.0-20191128110447-55ccb3a9f5c1 h1:/QwQcwWVOQXcoNuV9tHx30gQ3q7jCE/rKcGjwzsa5tg= From 2a2ad76479a58ca01842453d89d370b70cc24b4d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 9 May 2020 12:38:20 +0100 Subject: [PATCH 0559/1109] Use maps instead of slices --- src/yggdrasil/link.go | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 5c4f0e68..03e60076 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -1,7 +1,6 @@ package yggdrasil import ( - "bytes" "encoding/hex" "errors" "fmt" @@ -71,8 +70,8 @@ type linkInterface struct { } type linkOptions struct { - pinnedCurve25519Keys []crypto.BoxPubKey - pinnedEd25519Keys []crypto.SigPubKey + pinnedCurve25519Keys map[crypto.BoxPubKey]struct{} + pinnedEd25519Keys map[crypto.SigPubKey]struct{} } func (l *link) init(c *Core) error { @@ -102,24 +101,22 @@ func (l *link) call(uri string, sintf string) error { pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/") tcpOpts := tcpOptions{} if pubkeys, ok := u.Query()["curve25519"]; ok && len(pubkeys) > 0 { + tcpOpts.pinnedCurve25519Keys = make(map[crypto.BoxPubKey]struct{}) for _, pubkey := range pubkeys { if boxPub, err := hex.DecodeString(pubkey); err != nil { var boxPubKey crypto.BoxPubKey copy(boxPubKey[:], boxPub) - tcpOpts.pinnedCurve25519Keys = append( - tcpOpts.pinnedCurve25519Keys, boxPubKey, - ) + tcpOpts.pinnedCurve25519Keys[boxPubKey] = struct{}{} } } } if pubkeys, ok := u.Query()["ed25519"]; ok && len(pubkeys) > 0 { + tcpOpts.pinnedEd25519Keys = make(map[crypto.SigPubKey]struct{}) for _, pubkey := range pubkeys { if sigPub, err := hex.DecodeString(pubkey); err != nil { var sigPubKey crypto.SigPubKey copy(sigPubKey[:], sigPub) - tcpOpts.pinnedEd25519Keys = append( - tcpOpts.pinnedEd25519Keys, sigPubKey, - ) + tcpOpts.pinnedEd25519Keys[sigPubKey] = struct{}{} } } } @@ -222,22 +219,14 @@ func (intf *linkInterface) handler() error { } // Check if the remote side matches the keys we expected. This is a bit of a weak // check - in future versions we really should check a signature or something like that. - if pinned := intf.options.pinnedCurve25519Keys; len(pinned) > 0 { - allowed := false - for _, key := range pinned { - allowed = allowed || (bytes.Compare(key[:], meta.box[:]) == 0) - } - if !allowed { + if pinned := intf.options.pinnedCurve25519Keys; pinned != nil { + if _, allowed := pinned[meta.box]; !allowed { intf.link.core.log.Errorf("Failed to connect to node: %q sent curve25519 key that does not match pinned keys", intf.name) return fmt.Errorf("failed to connect: host sent curve25519 key that does not match pinned keys") } } - if pinned := intf.options.pinnedEd25519Keys; len(pinned) > 0 { - allowed := false - for _, key := range pinned { - allowed = allowed || (bytes.Compare(key[:], meta.sig[:]) == 0) - } - if !allowed { + if pinned := intf.options.pinnedEd25519Keys; pinned != nil { + if _, allowed := pinned[meta.sig]; !allowed { intf.link.core.log.Errorf("Failed to connect to node: %q sent ed25519 key that does not match pinned keys", intf.name) return fmt.Errorf("failed to connect: host sent ed25519 key that does not match pinned keys") } From f70b2ebceaff118e67541f3d9d0f714a00fe0dc7 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 9 May 2020 12:49:02 +0100 Subject: [PATCH 0560/1109] Fix bad check --- src/yggdrasil/link.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 03e60076..0b37eaa2 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -103,7 +103,7 @@ func (l *link) call(uri string, sintf string) error { if pubkeys, ok := u.Query()["curve25519"]; ok && len(pubkeys) > 0 { tcpOpts.pinnedCurve25519Keys = make(map[crypto.BoxPubKey]struct{}) for _, pubkey := range pubkeys { - if boxPub, err := hex.DecodeString(pubkey); err != nil { + if boxPub, err := hex.DecodeString(pubkey); err == nil { var boxPubKey crypto.BoxPubKey copy(boxPubKey[:], boxPub) tcpOpts.pinnedCurve25519Keys[boxPubKey] = struct{}{} @@ -113,7 +113,7 @@ func (l *link) call(uri string, sintf string) error { if pubkeys, ok := u.Query()["ed25519"]; ok && len(pubkeys) > 0 { tcpOpts.pinnedEd25519Keys = make(map[crypto.SigPubKey]struct{}) for _, pubkey := range pubkeys { - if sigPub, err := hex.DecodeString(pubkey); err != nil { + if sigPub, err := hex.DecodeString(pubkey); err == nil { var sigPubKey crypto.SigPubKey copy(sigPubKey[:], sigPub) tcpOpts.pinnedEd25519Keys[sigPubKey] = struct{}{} From dc128121e57bd659ece83217b58a50d74a330ae0 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 16 May 2020 09:25:57 -0500 Subject: [PATCH 0561/1109] update switch blockPeer/unblockPeer logic and dht reset when coords change --- src/yggdrasil/dht.go | 13 +++++++------ src/yggdrasil/link.go | 1 + src/yggdrasil/peer.go | 27 +++++++++++++-------------- src/yggdrasil/switch.go | 20 +++++++++++++++++--- 4 files changed, 38 insertions(+), 23 deletions(-) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 56d03ed1..f40ac3c7 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -89,6 +89,11 @@ func (t *dht) reconfigure() { // Resets the DHT in response to coord changes. // This empties all info from the DHT and drops outstanding requests. func (t *dht) reset() { + for _, info := range t.table { + if t.isImportant(info) { + t.ping(info, nil) + } + } t.reqs = make(map[dhtReqKey]time.Time) t.table = make(map[crypto.NodeID]*dhtInfo) t.imp = nil @@ -144,12 +149,8 @@ func (t *dht) insert(info *dhtInfo) { // Insert a peer into the table if it hasn't been pinged lately, to keep peers from dropping func (t *dht) insertPeer(info *dhtInfo) { - oldInfo, isIn := t.table[*info.getNodeID()] - if !isIn || time.Since(oldInfo.recv) > dht_max_delay+30*time.Second { - // TODO? also check coords? - newInfo := *info // Insert a copy - t.insert(&newInfo) - } + t.insert(info) // FIXME this resets timers / ping counts / etc, so it seems kind of dangerous + t.ping(info, nil) // This is a quick fix to the above, ping them immediately... } // Return true if first/second/third are (partially) ordered correctly. diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 7f6b9b56..539d0488 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -376,6 +376,7 @@ func (intf *linkInterface) notifyRead(size int) { if size > 0 && intf.stallTimer == nil { intf.stallTimer = time.AfterFunc(keepAliveTime, intf.notifyDoKeepAlive) } + intf.link.core.switchTable.unblockPeer(intf, intf.peer.port) }) } diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 801691a0..31bba661 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -194,21 +194,20 @@ func (ps *peers) sendSwitchMsgs(from phony.Actor) { }) } -// This must be launched in a separate goroutine by whatever sets up the peer struct. -// It handles link protocol traffic. -func (p *peer) start() { - var updateDHT func() - updateDHT = func() { - phony.Block(p, func() { - select { - case <-p.done: - default: - p._updateDHT() - time.AfterFunc(time.Second, updateDHT) +func (ps *peers) updateDHT(from phony.Actor) { + ps.Act(from, func() { + for _, peer := range ps.ports { + p := peer + if p.port == 0 { + continue } - }) - } - updateDHT() + p.Act(ps, p._updateDHT) + } + }) +} + +// This must be launched in a separate goroutine by whatever sets up the peer struct. +func (p *peer) start() { // Just for good measure, immediately send a switch message to this peer when we start p.Act(nil, p._sendSwitchMsg) } diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 4f9044cd..6ab9a02b 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -239,11 +239,12 @@ func (t *switchTable) _cleanRoot() { func (t *switchTable) blockPeer(from phony.Actor, port switchPort) { t.Act(from, func() { peer, isIn := t.data.peers[port] - if !isIn { + if !isIn || peer.blocked { return } peer.blocked = true t.data.peers[port] = peer + t._updateTable() if port != t.parent { return } @@ -258,6 +259,18 @@ func (t *switchTable) blockPeer(from phony.Actor, port switchPort) { }) } +func (t *switchTable) unblockPeer(from phony.Actor, port switchPort) { + t.Act(from, func() { + peer, isIn := t.data.peers[port] + if !isIn || !peer.blocked { + return + } + peer.blocked = false + t.data.peers[port] = peer + t._updateTable() + }) +} + // Removes a peer. // Must be called by the router actor with a lambda that calls this. // If the removed peer was this node's parent, it immediately tries to find a new parent. @@ -482,11 +495,12 @@ func (t *switchTable) _handleMsg(msg *switchMsg, fromPort switchPort, reprocessi // The timestamp was updated, so we need to update locally and send to our peers. updateRoot = true } + // Note that we depend on the LIFO order of the stack of defers here... if updateRoot { if !equiv(&sender.locator, &t.data.locator) { doUpdate = true t.data.seq++ - t.core.router.reset(nil) + defer t.core.router.reset(t) } if t.data.locator.tstamp != sender.locator.tstamp { t.time = now @@ -495,7 +509,7 @@ func (t *switchTable) _handleMsg(msg *switchMsg, fromPort switchPort, reprocessi t.parent = sender.port defer t.core.peers.sendSwitchMsgs(t) } - if true || doUpdate { + if doUpdate { defer t._updateTable() } return From 052de98f126f50341ca60e41064fe485f77858d6 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 16 May 2020 17:07:47 -0500 Subject: [PATCH 0562/1109] work-in-progress on buffering overhaul --- src/yggdrasil/api.go | 8 ++-- src/yggdrasil/link.go | 74 +++++++++++++++++++++------- src/yggdrasil/packetqueue.go | 82 +++++++++++++++---------------- src/yggdrasil/peer.go | 56 ++++++++++++++-------- src/yggdrasil/router.go | 93 +++++++++++++++++++++++++----------- 5 files changed, 202 insertions(+), 111 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 31ece6b8..66ee9b81 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -123,10 +123,10 @@ func (c *Core) GetPeers() []Peer { var info Peer phony.Block(p, func() { info = Peer{ - Endpoint: p.intf.name, + Endpoint: p.intf.name(), BytesSent: p.bytesSent, BytesRecvd: p.bytesRecvd, - Protocol: p.intf.info.linkType, + Protocol: p.intf.interfaceType(), Port: uint64(port), Uptime: time.Since(p.firstSeen), } @@ -163,8 +163,8 @@ func (c *Core) GetSwitchPeers() []SwitchPeer { BytesSent: peer.bytesSent, BytesRecvd: peer.bytesRecvd, Port: uint64(elem.port), - Protocol: peer.intf.info.linkType, - Endpoint: peer.intf.info.remote, + Protocol: peer.intf.interfaceType(), + Endpoint: peer.intf.remote(), } copy(info.PublicKey[:], peer.box[:]) }) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 539d0488..3b3cfdb6 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -47,7 +47,7 @@ type linkInterfaceMsgIO interface { } type linkInterface struct { - name string + lname string link *link peer *peer msgIO linkInterfaceMsgIO @@ -125,7 +125,7 @@ func (l *link) listen(uri string) error { func (l *link) create(msgIO linkInterfaceMsgIO, name, linkType, local, remote string, incoming, force bool) (*linkInterface, error) { // Technically anything unique would work for names, but let's pick something human readable, just for debugging intf := linkInterface{ - name: name, + lname: name, link: l, msgIO: msgIO, info: linkInfo{ @@ -178,7 +178,7 @@ func (intf *linkInterface) handler() error { } base := version_getBaseMetadata() if meta.ver > base.ver || meta.ver == base.ver && meta.minorVer > base.minorVer { - intf.link.core.log.Errorln("Failed to connect to node: " + intf.name + " version: " + fmt.Sprintf("%d.%d", meta.ver, meta.minorVer)) + intf.link.core.log.Errorln("Failed to connect to node: " + intf.lname + " version: " + fmt.Sprintf("%d.%d", meta.ver, meta.minorVer)) return errors.New("failed to connect: wrong version") } // Check if we're authorized to connect to this key / IP @@ -217,23 +217,9 @@ func (intf *linkInterface) handler() error { intf.link.mutex.Unlock() // Create peer shared := crypto.GetSharedKey(myLinkPriv, &meta.link) - out := func(msgs [][]byte) { - // nil to prevent it from blocking if the link is somehow frozen - // this is safe because another packet won't be sent until the link notifies - // the peer that it's ready for one - intf.writer.sendFrom(nil, msgs, false) - } - linkOut := func(bs []byte) { - // nil to prevent it from blocking if the link is somehow frozen - // FIXME this is hypothetically not safe, the peer shouldn't be sending - // additional packets until this one finishes, otherwise this could leak - // memory if writing happens slower than link packets are generated... - // that seems unlikely, so it's a lesser evil than deadlocking for now - intf.writer.sendFrom(nil, [][]byte{bs}, true) - } phony.Block(&intf.link.core.peers, func() { // FIXME don't use phony.Block, it's bad practice, even if it's safe here - intf.peer = intf.link.core.peers._newPeer(&meta.box, &meta.sig, shared, intf, func() { intf.msgIO.close() }, out, linkOut) + intf.peer = intf.link.core.peers._newPeer(&meta.box, &meta.sig, shared, intf) }) if intf.peer == nil { return errors.New("failed to create peer") @@ -275,6 +261,58 @@ func (intf *linkInterface) handler() error { //////////////////////////////////////////////////////////////////////////////// +// linkInterface needs to match the peerInterface type needed by the peers + +func (intf *linkInterface) out(bss [][]byte) { + intf.Act(nil, func() { + // nil to prevent it from blocking if the link is somehow frozen + // this is safe because another packet won't be sent until the link notifies + // the peer that it's ready for one + intf.writer.sendFrom(nil, bss, false) + }) +} + +func (intf *linkInterface) linkOut(bs []byte) { + intf.Act(nil, func() { + // nil to prevent it from blocking if the link is somehow frozen + // FIXME this is hypothetically not safe, the peer shouldn't be sending + // additional packets until this one finishes, otherwise this could leak + // memory if writing happens slower than link packets are generated... + // that seems unlikely, so it's a lesser evil than deadlocking for now + intf.writer.sendFrom(nil, [][]byte{bs}, true) + }) +} + +func (intf *linkInterface) notifyQueued(seq uint64) { + // This is the part where we want non-nil 'from' fields + intf.Act(intf.peer, func() { + if !intf.isIdle { + intf.peer.dropFromQueue(intf, seq) + } + }) +} + +func (intf *linkInterface) close() { + intf.Act(nil, func() { intf.msgIO.close() }) +} + +func (intf *linkInterface) name() string { + return intf.lname +} + +func (intf *linkInterface) local() string { + return intf.info.local +} + +func (intf *linkInterface) remote() string { + return intf.info.remote +} + +func (intf *linkInterface) interfaceType() string { + return intf.info.linkType +} + +//////////////////////////////////////////////////////////////////////////////// const ( sendTime = 1 * time.Second // How long to wait before deciding a send is blocked keepAliveTime = 2 * time.Second // How long to wait before sending a keep-alive response if we have no real traffic to send diff --git a/src/yggdrasil/packetqueue.go b/src/yggdrasil/packetqueue.go index 2000ffa6..7abdaea7 100644 --- a/src/yggdrasil/packetqueue.go +++ b/src/yggdrasil/packetqueue.go @@ -4,9 +4,6 @@ import ( "time" ) -// TODO take max size from config -const MAX_PACKET_QUEUE_SIZE = 4 * 1048576 // 4 MB - type pqStreamID string type pqPacketInfo struct { @@ -25,46 +22,50 @@ type packetQueue struct { size uint64 } -func (q *packetQueue) cleanup() { - for q.size > MAX_PACKET_QUEUE_SIZE { - // TODO? drop from a random stream - // odds proportional to size? bandwidth? - // always using the worst is exploitable -> flood 1 packet per random stream - // find the stream that's using the most bandwidth - now := time.Now() - var worst pqStreamID - for id := range q.streams { +// drop will remove a packet from the queue, returning it to the pool +// returns true if a packet was removed, false otherwise +func (q *packetQueue) drop() bool { + if q.size == 0 { + return false + } + // TODO? drop from a random stream + // odds proportional to size? bandwidth? + // always using the worst is exploitable -> flood 1 packet per random stream + // find the stream that's using the most bandwidth + now := time.Now() + var worst pqStreamID + for id := range q.streams { + worst = id + break // get a random ID to start + } + worstStream := q.streams[worst] + worstSize := float64(worstStream.size) + worstAge := now.Sub(worstStream.infos[0].time).Seconds() + for id, stream := range q.streams { + thisSize := float64(stream.size) + thisAge := now.Sub(stream.infos[0].time).Seconds() + // cross multiply to avoid division by zero issues + if worstSize*thisAge < thisSize*worstAge { + // worstSize/worstAge < thisSize/thisAge -> this uses more bandwidth worst = id - break // get a random ID to start - } - worstStream := q.streams[worst] - worstSize := float64(worstStream.size) - worstAge := now.Sub(worstStream.infos[0].time).Seconds() - for id, stream := range q.streams { - thisSize := float64(stream.size) - thisAge := now.Sub(stream.infos[0].time).Seconds() - // cross multiply to avoid division by zero issues - if worstSize*thisAge < thisSize*worstAge { - // worstSize/worstAge < thisSize/thisAge -> this uses more bandwidth - worst = id - worstStream = stream - worstSize = thisSize - worstAge = thisAge - } - } - // Drop the oldest packet from the worst stream - packet := worstStream.infos[0].packet - worstStream.infos = worstStream.infos[1:] - worstStream.size -= uint64(len(packet)) - q.size -= uint64(len(packet)) - pool_putBytes(packet) - // save the modified stream to queues - if len(worstStream.infos) > 0 { - q.streams[worst] = worstStream - } else { - delete(q.streams, worst) + worstStream = stream + worstSize = thisSize + worstAge = thisAge } } + // Drop the oldest packet from the worst stream + packet := worstStream.infos[0].packet + worstStream.infos = worstStream.infos[1:] + worstStream.size -= uint64(len(packet)) + q.size -= uint64(len(packet)) + pool_putBytes(packet) + // save the modified stream to queues + if len(worstStream.infos) > 0 { + q.streams[worst] = worstStream + } else { + delete(q.streams, worst) + } + return true } func (q *packetQueue) push(packet []byte) { @@ -80,7 +81,6 @@ func (q *packetQueue) push(packet []byte) { // save update to queues q.streams[id] = stream q.size += uint64(len(packet)) - q.cleanup() } func (q *packetQueue) pop() ([]byte, bool) { diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 31bba661..31ea5f46 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -77,29 +77,38 @@ func (ps *peers) getAllowedEncryptionPublicKeys() []string { return ps.core.config.Current.AllowedEncryptionPublicKeys } +type peerInterface interface { + out([][]byte) + linkOut([]byte) + notifyQueued(uint64) + close() + // These next ones are only used by the API + name() string + local() string + remote() string + interfaceType() string +} + // Information known about a peer, including their box/sig keys, precomputed shared keys (static and ephemeral) and a handler for their outgoing traffic type peer struct { phony.Inbox core *Core - intf *linkInterface + intf peerInterface port switchPort box crypto.BoxPubKey sig crypto.SigPubKey shared crypto.BoxSharedKey linkShared crypto.BoxSharedKey endpoint string - firstSeen time.Time // To track uptime for getPeers - linkOut func([]byte) // used for protocol traffic (bypasses the switch) - dinfo *dhtInfo // used to keep the DHT working - out func([][]byte) // Set up by whatever created the peers struct, used to send packets to other nodes - done (chan struct{}) // closed to exit the linkLoop - close func() // Called when a peer is removed, to close the underlying connection, or via admin api + firstSeen time.Time // To track uptime for getPeers + dinfo *dhtInfo // used to keep the DHT working // The below aren't actually useful internally, they're just gathered for getPeers statistics bytesSent uint64 bytesRecvd uint64 ports map[switchPort]*peer table *lookupTable queue packetQueue + seq uint64 // this and idle are used to detect when to drop packets from queue idle bool } @@ -123,19 +132,15 @@ func (ps *peers) _updatePeers() { } // Creates a new peer with the specified box, sig, and linkShared keys, using the lowest unoccupied port number. -func (ps *peers) _newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShared *crypto.BoxSharedKey, intf *linkInterface, closer func(), out func([][]byte), linkOut func([]byte)) *peer { +func (ps *peers) _newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShared *crypto.BoxSharedKey, intf peerInterface) *peer { now := time.Now() p := peer{box: *box, + core: ps.core, + intf: intf, sig: *sig, shared: *crypto.GetSharedKey(&ps.core.boxPriv, box), linkShared: *linkShared, firstSeen: now, - done: make(chan struct{}), - close: closer, - core: ps.core, - intf: intf, - out: out, - linkOut: linkOut, } oldPorts := ps.ports newPorts := make(map[switchPort]*peer) @@ -172,10 +177,7 @@ func (ps *peers) _removePeer(p *peer) { newPorts[k] = v } delete(newPorts, p.port) - if p.close != nil { - p.close() - } - close(p.done) + p.intf.close() ps.ports = newPorts ps._updatePeers() } @@ -295,12 +297,26 @@ func (p *peer) _handleIdle() { } if len(packets) > 0 { p.bytesSent += uint64(size) - p.out(packets) + p.intf.out(packets) } else { p.idle = true } } +func (p *peer) dropFromQueue(from phony.Actor, seq uint64) { + p.Act(from, func() { + switch { + case seq != p.seq: + case p.queue.drop(): + p.intf.notifyQueued(p.seq) + } + if seq != p.seq { + return + } + + }) +} + // This wraps the packet in the inner (ephemeral) and outer (permanent) crypto layers. // It sends it to p.linkOut, which bypasses the usual packet queues. func (p *peer) _sendLinkPacket(packet []byte) { @@ -316,7 +332,7 @@ func (p *peer) _sendLinkPacket(packet []byte) { Payload: bs, } packet = linkPacket.encode() - p.linkOut(packet) + p.intf.linkOut(packet) } // Decrypts the outer (permanent) and inner (ephemeral) crypto layers on link traffic. diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 1bb14c4c..303ada69 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -45,6 +45,8 @@ type router struct { nodeinfo nodeinfo searches searches sessions sessions + intf routerInterface + peer *peer table *lookupTable // has a copy of our locator } @@ -53,28 +55,17 @@ func (r *router) init(core *Core) { r.core = core r.addr = *address.AddrForNodeID(&r.dht.nodeID) r.subnet = *address.SubnetForNodeID(&r.dht.nodeID) - self := linkInterface{ - name: "(self)", - info: linkInfo{ - local: "(self)", - remote: "(self)", - linkType: "self", - }, - } - var p *peer - peerOut := func(packets [][]byte) { - r.handlePackets(p, packets) - r.Act(p, func() { - // after the router handle the packets, notify the peer that it's ready for more - p.Act(r, p._handleIdle) - }) - } + r.intf.router = r phony.Block(&r.core.peers, func() { // FIXME don't block here! - p = r.core.peers._newPeer(&r.core.boxPub, &r.core.sigPub, &crypto.BoxSharedKey{}, &self, nil, peerOut, nil) + r.peer = r.core.peers._newPeer(&r.core.boxPub, &r.core.sigPub, &crypto.BoxSharedKey{}, &r.intf) }) - p.Act(r, p._handleIdle) - r.out = func(bs []byte) { p.handlePacketFrom(r, bs) } + r.peer.Act(r, r.peer._handleIdle) + r.out = func(bs []byte) { + r.intf.Act(r, func() { + r.peer.handlePacketFrom(&r.intf, bs) + }) + } r.nodeinfo.init(r.core) r.core.config.Mutex.RLock() r.nodeinfo.setNodeInfo(r.core.config.Current.NodeInfo, r.core.config.Current.NodeInfoPrivacy) @@ -123,15 +114,6 @@ func (r *router) start() error { return nil } -// In practice, the switch will call this with 1 packet -func (r *router) handlePackets(from phony.Actor, packets [][]byte) { - r.Act(from, func() { - for _, packet := range packets { - r._handlePacket(packet) - } - }) -} - // Insert a peer info into the dht, TODO? make the dht a separate actor func (r *router) insertPeer(from phony.Actor, info *dhtInfo) { r.Act(from, func() { @@ -275,3 +257,58 @@ func (r *router) _handleNodeInfo(bs []byte, fromKey *crypto.BoxPubKey) { req.SendPermPub = *fromKey r.nodeinfo.handleNodeInfo(r, &req) } + +//////////////////////////////////////////////////////////////////////////////// + +// routerInterface is a helper that implements peerInterface +type routerInterface struct { + phony.Inbox + router *router + busy bool +} + +func (intf *routerInterface) out(bss [][]byte) { + intf.Act(intf.router.peer, func() { + intf.router.Act(intf, func() { + for _, bs := range bss { + intf.router._handlePacket(bs) + } + // we may block due to the above + // so we send a message to ourself, that we'd handle after unblocking + // that message tells us to tell the interface that we're finally idle again + intf.router.Act(nil, func() { + intf.Act(intf.router, intf._handleIdle) + }) + intf.Act(intf.router, intf._handleBusy) + }) + }) +} + +func (intf *routerInterface) _handleBusy() { + intf.busy = true +} + +func (intf *routerInterface) _handleIdle() { + intf.busy = false + intf.router.peer.Act(intf, intf.router.peer._handleIdle) +} + +func (intf *routerInterface) linkOut(_ []byte) {} + +func (intf *routerInterface) notifyQueued(seq uint64) { + intf.Act(intf.router.peer, func() { + if intf.busy { + intf.router.peer.dropFromQueue(intf, seq) + } + }) +} + +func (intf *routerInterface) close() {} + +func (intf *routerInterface) name() string { return "(self)" } + +func (intf *routerInterface) local() string { return "(self)" } + +func (intf *routerInterface) remote() string { return "(self)" } + +func (intf *routerInterface) interfaceType() string { return "self" } From b132560f651ad5f04b977517803a1d6016b1e9a5 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 16 May 2020 17:24:26 -0500 Subject: [PATCH 0563/1109] it helps to actually run the notifyQueued stuff... --- src/yggdrasil/peer.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 31ea5f46..ada29214 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -281,6 +281,8 @@ func (p *peer) _sendPackets(packets [][]byte) { if p.idle { p.idle = false p._handleIdle() + } else { + p.intf.notifyQueued(p.seq) } } @@ -296,6 +298,7 @@ func (p *peer) _handleIdle() { } } if len(packets) > 0 { + p.seq++ p.bytesSent += uint64(size) p.intf.out(packets) } else { From b17a035a05214c8066eaea613f86f176cfd7d33c Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 16 May 2020 17:40:11 -0500 Subject: [PATCH 0564/1109] workarounds to dropping being too aggressive --- src/yggdrasil/packetqueue.go | 5 +++++ src/yggdrasil/peer.go | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/packetqueue.go b/src/yggdrasil/packetqueue.go index 7abdaea7..e37d5bb3 100644 --- a/src/yggdrasil/packetqueue.go +++ b/src/yggdrasil/packetqueue.go @@ -55,6 +55,11 @@ func (q *packetQueue) drop() bool { } // Drop the oldest packet from the worst stream packet := worstStream.infos[0].packet + if q.size-uint64(len(packet)) < streamMsgSize { + // TODO something better + // We don't want to drop *all* packets, so lets save 1 batch worth... + return false + } worstStream.infos = worstStream.infos[1:] worstStream.size -= uint64(len(packet)) q.size -= uint64(len(packet)) diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index ada29214..0c195c6d 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -289,7 +289,7 @@ func (p *peer) _sendPackets(packets [][]byte) { func (p *peer) _handleIdle() { var packets [][]byte var size uint64 - for size < 65535 { + for size < streamMsgSize { if packet, success := p.queue.pop(); success { packets = append(packets, packet) size += uint64(len(packet)) From 62b9fab5f822f018940062ca8914852f41a8ce0b Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 16 May 2020 18:56:04 -0500 Subject: [PATCH 0565/1109] more work-in-progress, debugging why things are dropping so often --- src/yggdrasil/link.go | 31 +++++++++++++------------------ src/yggdrasil/packetqueue.go | 2 +- src/yggdrasil/peer.go | 5 +---- 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 3b3cfdb6..80989507 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -63,8 +63,7 @@ type linkInterface struct { stallTimer *time.Timer // Fires to signal that no incoming traffic (including keep-alive) has been seen closeTimer *time.Timer // Fires when the link has been idle so long we need to close it isIdle bool // True if the peer actor knows the link is idle - stalled bool // True if we haven't been receiving any response traffic - unstalled bool // False if an idle notification to the switch hasn't been sent because we stalled (or are first starting up) + blocked bool // True if we've blocked the peer in the switch } func (l *link) init(c *Core) error { @@ -235,6 +234,7 @@ func (intf *linkInterface) handler() error { strings.ToUpper(intf.info.linkType), themString, intf.info.local) // Start things go intf.peer.start() + intf.Act(nil, intf._notifyIdle) intf.reader.Act(nil, intf.reader._read) // Wait for the reader to finish // TODO find a way to do this without keeping live goroutines around @@ -344,8 +344,9 @@ func (intf *linkInterface) _cancelStallTimer() { // through other links, if alternatives exist func (intf *linkInterface) notifyBlockedSend() { intf.Act(nil, func() { - if intf.sendTimer != nil { + if intf.sendTimer != nil && !intf.blocked { //As far as we know, we're still trying to send, and the timer fired. + intf.blocked = true intf.link.core.switchTable.blockPeer(intf, intf.peer.port) } }) @@ -365,25 +366,21 @@ func (intf *linkInterface) notifySent(size int, isLinkTraffic bool) { }) } -// Notify the switch that we're ready for more traffic, assuming we're not in a stalled state +// Notify the peer that we're ready for more traffic func (intf *linkInterface) _notifyIdle() { if !intf.isIdle { - if intf.stalled { - intf.unstalled = false - } else { - intf.isIdle = true - intf.peer.Act(intf, intf.peer._handleIdle) - } + intf.isIdle = true + intf.peer.Act(intf, intf.peer._handleIdle) } } // Set the peer as stalled, to prevent them from returning to the switch until a read succeeds func (intf *linkInterface) notifyStalled() { intf.Act(nil, func() { // Sent from a time.AfterFunc - if intf.stallTimer != nil { + if intf.stallTimer != nil && !intf.blocked { intf.stallTimer.Stop() intf.stallTimer = nil - intf.stalled = true + intf.blocked = true intf.link.core.switchTable.blockPeer(intf, intf.peer.port) } }) @@ -406,15 +403,13 @@ func (intf *linkInterface) notifyRead(size int) { intf.stallTimer.Stop() intf.stallTimer = nil } - intf.stalled = false - if !intf.unstalled { - intf._notifyIdle() - intf.unstalled = true - } if size > 0 && intf.stallTimer == nil { intf.stallTimer = time.AfterFunc(keepAliveTime, intf.notifyDoKeepAlive) } - intf.link.core.switchTable.unblockPeer(intf, intf.peer.port) + if intf.blocked { + intf.blocked = false + intf.link.core.switchTable.unblockPeer(intf, intf.peer.port) + } }) } diff --git a/src/yggdrasil/packetqueue.go b/src/yggdrasil/packetqueue.go index e37d5bb3..caabe671 100644 --- a/src/yggdrasil/packetqueue.go +++ b/src/yggdrasil/packetqueue.go @@ -55,7 +55,7 @@ func (q *packetQueue) drop() bool { } // Drop the oldest packet from the worst stream packet := worstStream.infos[0].packet - if q.size-uint64(len(packet)) < streamMsgSize { + if false && q.size-uint64(len(packet)) < streamMsgSize { // TODO something better // We don't want to drop *all* packets, so lets save 1 batch worth... return false diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 0c195c6d..f88eb8bf 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -311,12 +311,9 @@ func (p *peer) dropFromQueue(from phony.Actor, seq uint64) { switch { case seq != p.seq: case p.queue.drop(): + p.core.log.Debugln("DEBUG dropped:", p.port, p.queue.size) p.intf.notifyQueued(p.seq) } - if seq != p.seq { - return - } - }) } From 527d44391666305d818d0b83fd9c38a85d74406c Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 17 May 2020 07:21:09 -0500 Subject: [PATCH 0566/1109] move where the queue size check before dropping would occur --- src/yggdrasil/packetqueue.go | 5 ----- src/yggdrasil/peer.go | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/yggdrasil/packetqueue.go b/src/yggdrasil/packetqueue.go index caabe671..7abdaea7 100644 --- a/src/yggdrasil/packetqueue.go +++ b/src/yggdrasil/packetqueue.go @@ -55,11 +55,6 @@ func (q *packetQueue) drop() bool { } // Drop the oldest packet from the worst stream packet := worstStream.infos[0].packet - if false && q.size-uint64(len(packet)) < streamMsgSize { - // TODO something better - // We don't want to drop *all* packets, so lets save 1 batch worth... - return false - } worstStream.infos = worstStream.infos[1:] worstStream.size -= uint64(len(packet)) q.size -= uint64(len(packet)) diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index f88eb8bf..d3f7047d 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -310,6 +310,7 @@ func (p *peer) dropFromQueue(from phony.Actor, seq uint64) { p.Act(from, func() { switch { case seq != p.seq: + //case p.queue.size < streamMsgSize: case p.queue.drop(): p.core.log.Debugln("DEBUG dropped:", p.port, p.queue.size) p.intf.notifyQueued(p.seq) From 15ac2595aa96a0c1a29fe6a1927a4366dd03f3f4 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 17 May 2020 08:22:02 -0500 Subject: [PATCH 0567/1109] use a dedicated per-stream writer goroutine, send messages to it over a 1-buffered channel, this eliminates most of the false positive blocking that causes drops --- src/yggdrasil/link.go | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 80989507..d439c309 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -256,6 +256,11 @@ func (intf *linkInterface) handler() error { intf.link.core.log.Infof("Disconnected %s: %s, source %s", strings.ToUpper(intf.info.linkType), themString, intf.info.local) } + intf.writer.Act(nil, func() { + if intf.writer.worker != nil { + close(intf.writer.worker) + } + }) return err } @@ -428,7 +433,8 @@ func (intf *linkInterface) notifyDoKeepAlive() { type linkWriter struct { phony.Inbox - intf *linkInterface + intf *linkInterface + worker chan [][]byte } func (w *linkWriter) sendFrom(from phony.Actor, bss [][]byte, isLinkTraffic bool) { @@ -437,8 +443,19 @@ func (w *linkWriter) sendFrom(from phony.Actor, bss [][]byte, isLinkTraffic bool for _, bs := range bss { size += len(bs) } + if w.worker == nil { + w.worker = make(chan [][]byte, 1) + go func() { + for bss := range w.worker { + w.intf.msgIO.writeMsgs(bss) + } + }() + } w.intf.notifySending(size, isLinkTraffic) - w.intf.msgIO.writeMsgs(bss) + func() { + defer func() { recover() }() + w.worker <- bss + }() w.intf.notifySent(size, isLinkTraffic) }) } From 0dcc555eabd40cb8cce10ef5fdcf2589aec18ca5 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 17 May 2020 08:34:22 -0500 Subject: [PATCH 0568/1109] cleaner startup/shutdown of the link writer's worker --- src/yggdrasil/link.go | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index d439c309..5676ebe5 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -136,6 +136,7 @@ func (l *link) create(msgIO linkInterfaceMsgIO, name, linkType, local, remote st force: force, } intf.writer.intf = &intf + intf.writer.worker = make(chan [][]byte, 1) intf.reader.intf = &intf intf.reader.err = make(chan error) return &intf, nil @@ -151,6 +152,15 @@ func (l *link) stop() error { func (intf *linkInterface) handler() error { // TODO split some of this into shorter functions, so it's easier to read, and for the FIXME duplicate peer issue mentioned later + go func() { + for bss := range intf.writer.worker { + intf.msgIO.writeMsgs(bss) + } + }() + defer intf.writer.Act(nil, func() { + intf.writer.closed = true + close(intf.writer.worker) + }) myLinkPub, myLinkPriv := crypto.NewBoxKeys() meta := version_getBaseMetadata() meta.box = intf.link.core.boxPub @@ -256,11 +266,6 @@ func (intf *linkInterface) handler() error { intf.link.core.log.Infof("Disconnected %s: %s, source %s", strings.ToUpper(intf.info.linkType), themString, intf.info.local) } - intf.writer.Act(nil, func() { - if intf.writer.worker != nil { - close(intf.writer.worker) - } - }) return err } @@ -435,27 +440,20 @@ type linkWriter struct { phony.Inbox intf *linkInterface worker chan [][]byte + closed bool } func (w *linkWriter) sendFrom(from phony.Actor, bss [][]byte, isLinkTraffic bool) { w.Act(from, func() { + if w.closed { + return + } var size int for _, bs := range bss { size += len(bs) } - if w.worker == nil { - w.worker = make(chan [][]byte, 1) - go func() { - for bss := range w.worker { - w.intf.msgIO.writeMsgs(bss) - } - }() - } w.intf.notifySending(size, isLinkTraffic) - func() { - defer func() { recover() }() - w.worker <- bss - }() + w.worker <- bss w.intf.notifySent(size, isLinkTraffic) }) } From 6e92af1cd26da323db8d83bf52ca1d850ab6df8a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 17 May 2020 08:49:40 -0500 Subject: [PATCH 0569/1109] re-enable a minimum queue size of ~1 big packet --- src/yggdrasil/peer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index d3f7047d..361a0ea1 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -310,7 +310,7 @@ func (p *peer) dropFromQueue(from phony.Actor, seq uint64) { p.Act(from, func() { switch { case seq != p.seq: - //case p.queue.size < streamMsgSize: + case p.queue.size < streamMsgSize: case p.queue.drop(): p.core.log.Debugln("DEBUG dropped:", p.port, p.queue.size) p.intf.notifyQueued(p.seq) From 7720e169f26d4609de81ee5dcd83fe96df4cfe64 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 17 May 2020 12:09:40 -0500 Subject: [PATCH 0570/1109] when we detect we're blocked, only drop packets often enough to make sure the existing queue's size is non-increasing, and always drop the worst packet from a random flow with odds based on the total size of packets queued for that flow --- src/yggdrasil/packetqueue.go | 32 ++++++++++---------------------- src/yggdrasil/peer.go | 20 ++++++++++++-------- 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/src/yggdrasil/packetqueue.go b/src/yggdrasil/packetqueue.go index 7abdaea7..464bc6ce 100644 --- a/src/yggdrasil/packetqueue.go +++ b/src/yggdrasil/packetqueue.go @@ -1,6 +1,7 @@ package yggdrasil import ( + "math/rand" "time" ) @@ -28,32 +29,19 @@ func (q *packetQueue) drop() bool { if q.size == 0 { return false } - // TODO? drop from a random stream - // odds proportional to size? bandwidth? - // always using the worst is exploitable -> flood 1 packet per random stream - // find the stream that's using the most bandwidth - now := time.Now() + // select a random stream, odds based on stream size + offset := rand.Uint64() % q.size var worst pqStreamID - for id := range q.streams { - worst = id - break // get a random ID to start - } - worstStream := q.streams[worst] - worstSize := float64(worstStream.size) - worstAge := now.Sub(worstStream.infos[0].time).Seconds() + var size uint64 for id, stream := range q.streams { - thisSize := float64(stream.size) - thisAge := now.Sub(stream.infos[0].time).Seconds() - // cross multiply to avoid division by zero issues - if worstSize*thisAge < thisSize*worstAge { - // worstSize/worstAge < thisSize/thisAge -> this uses more bandwidth - worst = id - worstStream = stream - worstSize = thisSize - worstAge = thisAge + worst = id + size += stream.size + if size >= offset { + break } } - // Drop the oldest packet from the worst stream + // drop the oldest packet from the stream + worstStream := q.streams[worst] packet := worstStream.infos[0].packet worstStream.infos = worstStream.infos[1:] worstStream.size -= uint64(len(packet)) diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 361a0ea1..31103074 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -110,6 +110,7 @@ type peer struct { queue packetQueue seq uint64 // this and idle are used to detect when to drop packets from queue idle bool + drop bool // set to true if we're dropping packets from the queue } func (ps *peers) updateTables(from phony.Actor, table *lookupTable) { @@ -275,13 +276,19 @@ func (p *peer) sendPacketsFrom(from phony.Actor, packets [][]byte) { } func (p *peer) _sendPackets(packets [][]byte) { + size := p.queue.size for _, packet := range packets { p.queue.push(packet) } - if p.idle { + switch { + case p.idle: p.idle = false p._handleIdle() - } else { + case p.drop: + for p.queue.size > size { + p.queue.drop() + } + default: p.intf.notifyQueued(p.seq) } } @@ -303,17 +310,14 @@ func (p *peer) _handleIdle() { p.intf.out(packets) } else { p.idle = true + p.drop = false } } func (p *peer) dropFromQueue(from phony.Actor, seq uint64) { p.Act(from, func() { - switch { - case seq != p.seq: - case p.queue.size < streamMsgSize: - case p.queue.drop(): - p.core.log.Debugln("DEBUG dropped:", p.port, p.queue.size) - p.intf.notifyQueued(p.seq) + if seq == p.seq { + p.drop = true } }) } From d96ae156a11f03d491d987954662bf83b1cb2482 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 17 May 2020 12:27:43 -0500 Subject: [PATCH 0571/1109] slight change to peer function names/args --- src/yggdrasil/peer.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 31103074..d8d14cfc 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -264,22 +264,20 @@ func (p *peer) _handleTraffic(packet []byte) { coords := peer_getPacketCoords(packet) next := p.table.lookup(coords) if nPeer, isIn := p.ports[next]; isIn { - nPeer.sendPacketsFrom(p, [][]byte{packet}) + nPeer.sendPacketFrom(p, packet) } //p.core.switchTable.packetInFrom(p, packet) } -func (p *peer) sendPacketsFrom(from phony.Actor, packets [][]byte) { +func (p *peer) sendPacketFrom(from phony.Actor, packet []byte) { p.Act(from, func() { - p._sendPackets(packets) + p._sendPacket(packet) }) } -func (p *peer) _sendPackets(packets [][]byte) { +func (p *peer) _sendPacket(packet []byte) { size := p.queue.size - for _, packet := range packets { - p.queue.push(packet) - } + p.queue.push(packet) switch { case p.idle: p.idle = false From ff3c8cb687561b2485f7d6ad8e5723a8127aaa7a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 17 May 2020 12:58:57 -0500 Subject: [PATCH 0572/1109] less aggresive queue size reduction --- src/yggdrasil/peer.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index d8d14cfc..02e92f9a 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -108,6 +108,7 @@ type peer struct { ports map[switchPort]*peer table *lookupTable queue packetQueue + max uint64 seq uint64 // this and idle are used to detect when to drop packets from queue idle bool drop bool // set to true if we're dropping packets from the queue @@ -276,14 +277,13 @@ func (p *peer) sendPacketFrom(from phony.Actor, packet []byte) { } func (p *peer) _sendPacket(packet []byte) { - size := p.queue.size p.queue.push(packet) switch { case p.idle: p.idle = false p._handleIdle() case p.drop: - for p.queue.size > size { + for p.queue.size > p.max { p.queue.drop() } default: @@ -306,6 +306,9 @@ func (p *peer) _handleIdle() { p.seq++ p.bytesSent += uint64(size) p.intf.out(packets) + if p.drop { + p.max = p.queue.size + } } else { p.idle = true p.drop = false From d43b93f60a771566280d34bc058f42b48f168ae6 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 17 May 2020 13:23:15 -0500 Subject: [PATCH 0573/1109] safer check for the queues if we're blocked on a send, should work even if we're blocked on a link packet send --- src/yggdrasil/link.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 5676ebe5..a0ce5d87 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -63,6 +63,7 @@ type linkInterface struct { stallTimer *time.Timer // Fires to signal that no incoming traffic (including keep-alive) has been seen closeTimer *time.Timer // Fires when the link has been idle so long we need to close it isIdle bool // True if the peer actor knows the link is idle + isSending bool // True between a notifySending and a notifySent blocked bool // True if we've blocked the peer in the switch } @@ -296,7 +297,7 @@ func (intf *linkInterface) linkOut(bs []byte) { func (intf *linkInterface) notifyQueued(seq uint64) { // This is the part where we want non-nil 'from' fields intf.Act(intf.peer, func() { - if !intf.isIdle { + if intf.isSending { intf.peer.dropFromQueue(intf, seq) } }) @@ -336,6 +337,7 @@ func (intf *linkInterface) notifySending(size int, isLinkTraffic bool) { if !isLinkTraffic { intf.isIdle = false } + intf.isSending = true intf.sendTimer = time.AfterFunc(sendTime, intf.notifyBlockedSend) intf._cancelStallTimer() }) @@ -370,6 +372,7 @@ func (intf *linkInterface) notifySent(size int, isLinkTraffic bool) { if !isLinkTraffic { intf._notifyIdle() } + intf.isSending = false if size > 0 && intf.stallTimer == nil { intf.stallTimer = time.AfterFunc(stallTime, intf.notifyStalled) } From cf2edc99d1d7d16316c30522c4fac0be3a45da34 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 17 May 2020 13:32:58 -0500 Subject: [PATCH 0574/1109] correctly set peer.max --- src/yggdrasil/peer.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 02e92f9a..339ea5a7 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -306,9 +306,7 @@ func (p *peer) _handleIdle() { p.seq++ p.bytesSent += uint64(size) p.intf.out(packets) - if p.drop { - p.max = p.queue.size - } + p.max = p.queue.size } else { p.idle = true p.drop = false @@ -319,6 +317,7 @@ func (p *peer) dropFromQueue(from phony.Actor, seq uint64) { p.Act(from, func() { if seq == p.seq { p.drop = true + p.max = p.queue.size } }) } From 59c5644a52cbc5316fd5c2dc596706aa1c35d635 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 23 May 2020 10:08:23 -0500 Subject: [PATCH 0575/1109] some peer/link cleanup --- src/yggdrasil/link.go | 42 ++++++++++++++++++++---------------------- src/yggdrasil/peer.go | 2 +- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index a0ce5d87..dc61892f 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -62,7 +62,6 @@ type linkInterface struct { keepAliveTimer *time.Timer // Fires to send keep-alive traffic stallTimer *time.Timer // Fires to signal that no incoming traffic (including keep-alive) has been seen closeTimer *time.Timer // Fires when the link has been idle so long we need to close it - isIdle bool // True if the peer actor knows the link is idle isSending bool // True between a notifySending and a notifySent blocked bool // True if we've blocked the peer in the switch } @@ -279,7 +278,7 @@ func (intf *linkInterface) out(bss [][]byte) { // nil to prevent it from blocking if the link is somehow frozen // this is safe because another packet won't be sent until the link notifies // the peer that it's ready for one - intf.writer.sendFrom(nil, bss, false) + intf.writer.sendFrom(nil, bss) }) } @@ -290,7 +289,7 @@ func (intf *linkInterface) linkOut(bs []byte) { // additional packets until this one finishes, otherwise this could leak // memory if writing happens slower than link packets are generated... // that seems unlikely, so it's a lesser evil than deadlocking for now - intf.writer.sendFrom(nil, [][]byte{bs}, true) + intf.writer.sendFrom(nil, [][]byte{bs}) }) } @@ -332,11 +331,8 @@ const ( ) // notify the intf that we're currently sending -func (intf *linkInterface) notifySending(size int, isLinkTraffic bool) { +func (intf *linkInterface) notifySending(size int) { intf.Act(&intf.writer, func() { - if !isLinkTraffic { - intf.isIdle = false - } intf.isSending = true intf.sendTimer = time.AfterFunc(sendTime, intf.notifyBlockedSend) intf._cancelStallTimer() @@ -365,13 +361,18 @@ func (intf *linkInterface) notifyBlockedSend() { } // notify the intf that we've finished sending, returning the peer to the switch -func (intf *linkInterface) notifySent(size int, isLinkTraffic bool) { +func (intf *linkInterface) notifySent(size int) { intf.Act(&intf.writer, func() { - intf.sendTimer.Stop() - intf.sendTimer = nil - if !isLinkTraffic { - intf._notifyIdle() + if intf.sendTimer != nil { + intf.sendTimer.Stop() + intf.sendTimer = nil } + if intf.keepAliveTimer != nil { + // TODO? unset this when we start sending, not when we finish... + intf.keepAliveTimer.Stop() + intf.keepAliveTimer = nil + } + intf._notifyIdle() intf.isSending = false if size > 0 && intf.stallTimer == nil { intf.stallTimer = time.AfterFunc(stallTime, intf.notifyStalled) @@ -381,10 +382,7 @@ func (intf *linkInterface) notifySent(size int, isLinkTraffic bool) { // Notify the peer that we're ready for more traffic func (intf *linkInterface) _notifyIdle() { - if !intf.isIdle { - intf.isIdle = true - intf.peer.Act(intf, intf.peer._handleIdle) - } + intf.peer.Act(intf, intf.peer._handleIdle) } // Set the peer as stalled, to prevent them from returning to the switch until a read succeeds @@ -416,8 +414,8 @@ func (intf *linkInterface) notifyRead(size int) { intf.stallTimer.Stop() intf.stallTimer = nil } - if size > 0 && intf.stallTimer == nil { - intf.stallTimer = time.AfterFunc(keepAliveTime, intf.notifyDoKeepAlive) + if size > 0 && intf.keepAliveTimer == nil { + intf.keepAliveTimer = time.AfterFunc(keepAliveTime, intf.notifyDoKeepAlive) } if intf.blocked { intf.blocked = false @@ -432,7 +430,7 @@ func (intf *linkInterface) notifyDoKeepAlive() { if intf.stallTimer != nil { intf.stallTimer.Stop() intf.stallTimer = nil - intf.writer.sendFrom(nil, [][]byte{nil}, true) // Empty keep-alive traffic + intf.writer.sendFrom(nil, [][]byte{nil}) // Empty keep-alive traffic } }) } @@ -446,7 +444,7 @@ type linkWriter struct { closed bool } -func (w *linkWriter) sendFrom(from phony.Actor, bss [][]byte, isLinkTraffic bool) { +func (w *linkWriter) sendFrom(from phony.Actor, bss [][]byte) { w.Act(from, func() { if w.closed { return @@ -455,9 +453,9 @@ func (w *linkWriter) sendFrom(from phony.Actor, bss [][]byte, isLinkTraffic bool for _, bs := range bss { size += len(bs) } - w.intf.notifySending(size, isLinkTraffic) + w.intf.notifySending(size) w.worker <- bss - w.intf.notifySent(size, isLinkTraffic) + w.intf.notifySent(size) }) } diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 339ea5a7..3cfc0b4f 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -317,7 +317,7 @@ func (p *peer) dropFromQueue(from phony.Actor, seq uint64) { p.Act(from, func() { if seq == p.seq { p.drop = true - p.max = p.queue.size + p.max = p.queue.size + streamMsgSize } }) } From ef1e506a0c05dc163b5db89b5bf4e30be4cbf761 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 23 May 2020 10:23:55 -0500 Subject: [PATCH 0576/1109] work-in-progress on more cleanup --- src/yggdrasil/api.go | 6 ++-- src/yggdrasil/core.go | 6 ++-- src/yggdrasil/link.go | 70 +++++++++++++++++++------------------- src/yggdrasil/simlink.go | 2 +- src/yggdrasil/stream.go | 2 +- src/yggdrasil/switch.go | 2 +- src/yggdrasil/tcp.go | 50 +++++++++++++-------------- src/yggdrasil/tcp_linux.go | 6 ++-- src/yggdrasil/tls.go | 4 +-- 9 files changed, 74 insertions(+), 74 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 66ee9b81..b5b8d362 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -257,14 +257,14 @@ func (c *Core) ConnDialer() (*Dialer, error) { // "Listen" configuration item, e.g. // tcp://a.b.c.d:e func (c *Core) ListenTCP(uri string) (*TcpListener, error) { - return c.link.tcp.listen(uri, nil) + return c.links.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) + return c.links.tcp.listen(uri, c.links.tcp.tls.forListener) } // NodeID gets the node ID. This is derived from your router encryption keys. @@ -463,7 +463,7 @@ func (c *Core) RemovePeer(addr string, sintf string) error { // This does not add the peer to the peer list, so if the connection drops, the // peer will not be called again automatically. func (c *Core) CallPeer(addr string, sintf string) error { - return c.link.call(addr, sintf) + return c.links.call(addr, sintf) } // DisconnectPeer disconnects a peer once. This should be specified as a port diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index f7664942..4ac678df 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -29,7 +29,7 @@ type Core struct { switchTable switchTable peers peers router router - link link + links links log *log.Logger addPeerTimer *time.Timer } @@ -165,7 +165,7 @@ func (c *Core) _start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState return nil, err } - if err := c.link.init(c); err != nil { + if err := c.links.init(c); err != nil { c.log.Errorln("Failed to start link interfaces") return nil, err } @@ -197,7 +197,7 @@ func (c *Core) _stop() { if c.addPeerTimer != nil { c.addPeerTimer.Stop() } - c.link.stop() + c.links.stop() /* FIXME this deadlocks, need a waitgroup or something to coordinate shutdown for _, peer := range c.GetPeers() { c.DisconnectPeer(peer.Port) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index dc61892f..9776ee50 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -20,7 +20,7 @@ import ( "github.com/Arceliar/phony" ) -type link struct { +type links struct { core *Core mutex sync.RWMutex // protects interfaces below interfaces map[linkInfo]*linkInterface @@ -37,7 +37,7 @@ type linkInfo struct { remote string // Remote name or address } -type linkInterfaceMsgIO interface { +type linkMsgIO interface { readMsg() ([]byte, error) writeMsgs([][]byte) (int, error) close() error @@ -48,9 +48,9 @@ type linkInterfaceMsgIO interface { type linkInterface struct { lname string - link *link + links *links peer *peer - msgIO linkInterfaceMsgIO + msgIO linkMsgIO info linkInfo incoming bool force bool @@ -66,7 +66,7 @@ type linkInterface struct { blocked bool // True if we've blocked the peer in the switch } -func (l *link) init(c *Core) error { +func (l *links) init(c *Core) error { l.core = c l.mutex.Lock() l.interfaces = make(map[linkInfo]*linkInterface) @@ -81,11 +81,11 @@ func (l *link) init(c *Core) error { return nil } -func (l *link) reconfigure() { +func (l *links) reconfigure() { l.tcp.reconfigure() } -func (l *link) call(uri string, sintf string) error { +func (l *links) call(uri string, sintf string) error { u, err := url.Parse(uri) if err != nil { return fmt.Errorf("peer %s is not correctly formatted (%s)", uri, err) @@ -104,7 +104,7 @@ func (l *link) call(uri string, sintf string) error { return nil } -func (l *link) listen(uri string) error { +func (l *links) listen(uri string) error { u, err := url.Parse(uri) if err != nil { return fmt.Errorf("listener %s is not correctly formatted (%s)", uri, err) @@ -121,11 +121,11 @@ func (l *link) listen(uri string) error { } } -func (l *link) create(msgIO linkInterfaceMsgIO, name, linkType, local, remote string, incoming, force bool) (*linkInterface, error) { +func (l *links) create(msgIO linkMsgIO, name, linkType, local, remote string, incoming, force bool) (*linkInterface, error) { // Technically anything unique would work for names, but let's pick something human readable, just for debugging intf := linkInterface{ lname: name, - link: l, + links: l, msgIO: msgIO, info: linkInfo{ linkType: linkType, @@ -142,7 +142,7 @@ func (l *link) create(msgIO linkInterfaceMsgIO, name, linkType, local, remote st return &intf, nil } -func (l *link) stop() error { +func (l *links) stop() error { close(l.stopped) if err := l.tcp.stop(); err != nil { return err @@ -163,8 +163,8 @@ func (intf *linkInterface) handler() error { }) myLinkPub, myLinkPriv := crypto.NewBoxKeys() meta := version_getBaseMetadata() - meta.box = intf.link.core.boxPub - meta.sig = intf.link.core.sigPub + meta.box = intf.links.core.boxPub + meta.sig = intf.links.core.sigPub meta.link = *myLinkPub metaBytes := meta.encode() // TODO timeouts on send/recv (goroutine for send/recv, channel select w/ timer) @@ -187,12 +187,12 @@ func (intf *linkInterface) handler() error { } base := version_getBaseMetadata() if meta.ver > base.ver || meta.ver == base.ver && meta.minorVer > base.minorVer { - intf.link.core.log.Errorln("Failed to connect to node: " + intf.lname + " version: " + fmt.Sprintf("%d.%d", meta.ver, meta.minorVer)) + intf.links.core.log.Errorln("Failed to connect to node: " + intf.lname + " version: " + fmt.Sprintf("%d.%d", meta.ver, meta.minorVer)) return errors.New("failed to connect: wrong version") } // Check if we're authorized to connect to this key / IP - if intf.incoming && !intf.force && !intf.link.core.peers.isAllowedEncryptionPublicKey(&meta.box) { - intf.link.core.log.Warnf("%s connection from %s forbidden: AllowedEncryptionPublicKeys does not contain key %s", + if intf.incoming && !intf.force && !intf.links.core.peers.isAllowedEncryptionPublicKey(&meta.box) { + intf.links.core.log.Warnf("%s connection from %s forbidden: AllowedEncryptionPublicKeys does not contain key %s", strings.ToUpper(intf.info.linkType), intf.info.remote, hex.EncodeToString(meta.box[:])) intf.msgIO.close() return nil @@ -200,12 +200,12 @@ func (intf *linkInterface) handler() error { // Check if we already have a link to this node intf.info.box = meta.box intf.info.sig = meta.sig - intf.link.mutex.Lock() - if oldIntf, isIn := intf.link.interfaces[intf.info]; isIn { - intf.link.mutex.Unlock() + intf.links.mutex.Lock() + if oldIntf, isIn := intf.links.interfaces[intf.info]; isIn { + intf.links.mutex.Unlock() // FIXME we should really return an error and let the caller block instead // That lets them do things like close connections on its own, avoid printing a connection message in the first place, etc. - intf.link.core.log.Debugln("DEBUG: found existing interface for", intf.name) + intf.links.core.log.Debugln("DEBUG: found existing interface for", intf.name) intf.msgIO.close() if !intf.incoming { // Block outgoing connection attempts until the existing connection closes @@ -214,21 +214,21 @@ func (intf *linkInterface) handler() error { return nil } else { intf.closed = make(chan struct{}) - intf.link.interfaces[intf.info] = intf + intf.links.interfaces[intf.info] = intf defer func() { - intf.link.mutex.Lock() - delete(intf.link.interfaces, intf.info) - intf.link.mutex.Unlock() + intf.links.mutex.Lock() + delete(intf.links.interfaces, intf.info) + intf.links.mutex.Unlock() close(intf.closed) }() - intf.link.core.log.Debugln("DEBUG: registered interface for", intf.name) + intf.links.core.log.Debugln("DEBUG: registered interface for", intf.name) } - intf.link.mutex.Unlock() + intf.links.mutex.Unlock() // Create peer shared := crypto.GetSharedKey(myLinkPriv, &meta.link) - phony.Block(&intf.link.core.peers, func() { + phony.Block(&intf.links.core.peers, func() { // FIXME don't use phony.Block, it's bad practice, even if it's safe here - intf.peer = intf.link.core.peers._newPeer(&meta.box, &meta.sig, shared, intf) + intf.peer = intf.links.core.peers._newPeer(&meta.box, &meta.sig, shared, intf) }) if intf.peer == nil { return errors.New("failed to create peer") @@ -240,7 +240,7 @@ func (intf *linkInterface) handler() error { themAddr := address.AddrForNodeID(crypto.GetNodeID(&intf.info.box)) themAddrString := net.IP(themAddr[:]).String() themString := fmt.Sprintf("%s@%s", themAddrString, intf.info.remote) - intf.link.core.log.Infof("Connected %s: %s, source %s", + intf.links.core.log.Infof("Connected %s: %s, source %s", strings.ToUpper(intf.info.linkType), themString, intf.info.local) // Start things go intf.peer.start() @@ -252,7 +252,7 @@ func (intf *linkInterface) handler() error { defer close(done) go func() { select { - case <-intf.link.stopped: + case <-intf.links.stopped: intf.msgIO.close() case <-done: } @@ -260,10 +260,10 @@ func (intf *linkInterface) handler() error { err = <-intf.reader.err // TODO don't report an error if it's just a 'use of closed network connection' if err != nil { - intf.link.core.log.Infof("Disconnected %s: %s, source %s; error: %s", + intf.links.core.log.Infof("Disconnected %s: %s, source %s; error: %s", strings.ToUpper(intf.info.linkType), themString, intf.info.local, err) } else { - intf.link.core.log.Infof("Disconnected %s: %s, source %s", + intf.links.core.log.Infof("Disconnected %s: %s, source %s", strings.ToUpper(intf.info.linkType), themString, intf.info.local) } return err @@ -355,7 +355,7 @@ func (intf *linkInterface) notifyBlockedSend() { if intf.sendTimer != nil && !intf.blocked { //As far as we know, we're still trying to send, and the timer fired. intf.blocked = true - intf.link.core.switchTable.blockPeer(intf, intf.peer.port) + intf.links.core.switchTable.blockPeer(intf, intf.peer.port) } }) } @@ -392,7 +392,7 @@ func (intf *linkInterface) notifyStalled() { intf.stallTimer.Stop() intf.stallTimer = nil intf.blocked = true - intf.link.core.switchTable.blockPeer(intf, intf.peer.port) + intf.links.core.switchTable.blockPeer(intf, intf.peer.port) } }) } @@ -419,7 +419,7 @@ func (intf *linkInterface) notifyRead(size int) { } if intf.blocked { intf.blocked = false - intf.link.core.switchTable.unblockPeer(intf, intf.peer.port) + intf.links.core.switchTable.unblockPeer(intf, intf.peer.port) } }) } diff --git a/src/yggdrasil/simlink.go b/src/yggdrasil/simlink.go index f830c215..6c04a8c0 100644 --- a/src/yggdrasil/simlink.go +++ b/src/yggdrasil/simlink.go @@ -58,7 +58,7 @@ func (c *Core) NewSimlink() *Simlink { s := &Simlink{rch: make(chan []byte, 1)} n := "Simlink" var err error - s.link, err = c.link.create(s, n, n, n, n, false, true) + s.link, err = c.links.create(s, n, n, n, n, false, true) if err != nil { panic(err) } diff --git a/src/yggdrasil/stream.go b/src/yggdrasil/stream.go index be1398fc..afa97c76 100644 --- a/src/yggdrasil/stream.go +++ b/src/yggdrasil/stream.go @@ -9,7 +9,7 @@ import ( ) // Test that this matches the interface we expect -var _ = linkInterfaceMsgIO(&stream{}) +var _ = linkMsgIO(&stream{}) type stream struct { rwc io.ReadWriteCloser diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 6ab9a02b..a5c099ba 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -188,7 +188,7 @@ func (t *switchTable) init(core *Core) { func (t *switchTable) reconfigure() { // This is where reconfiguration would go, if we had anything useful to do. - t.core.link.reconfigure() + t.core.links.reconfigure() t.core.peers.reconfigure() } diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 9cca4193..17b34e91 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -33,7 +33,7 @@ const tcp_ping_interval = (default_timeout * 2 / 3) // The TCP listener and information about active TCP connections, to avoid duplication. type tcp struct { - link *link + links *links waitgroup sync.WaitGroup mutex sync.Mutex // Protecting the below listeners map[string]*TcpListener @@ -86,8 +86,8 @@ func (t *tcp) getAddr() *net.TCPAddr { } // Initializes the struct. -func (t *tcp) init(l *link) error { - t.link = l +func (t *tcp) init(l *links) error { + t.links = l t.tls.init(t) t.mutex.Lock() t.calls = make(map[string]struct{}) @@ -95,9 +95,9 @@ func (t *tcp) init(l *link) error { t.listeners = make(map[string]*TcpListener) t.mutex.Unlock() - t.link.core.config.Mutex.RLock() - defer t.link.core.config.Mutex.RUnlock() - for _, listenaddr := range t.link.core.config.Current.Listen { + t.links.core.config.Mutex.RLock() + defer t.links.core.config.Mutex.RUnlock() + for _, listenaddr := range t.links.core.config.Current.Listen { switch listenaddr[:6] { case "tcp://": if _, err := t.listen(listenaddr[6:], nil); err != nil { @@ -108,7 +108,7 @@ func (t *tcp) init(l *link) error { return err } default: - t.link.core.log.Errorln("Failed to add listener: listener", listenaddr, "is not correctly formatted, ignoring") + t.links.core.log.Errorln("Failed to add listener: listener", listenaddr, "is not correctly formatted, ignoring") } } @@ -126,35 +126,35 @@ func (t *tcp) stop() error { } func (t *tcp) reconfigure() { - t.link.core.config.Mutex.RLock() - added := util.Difference(t.link.core.config.Current.Listen, t.link.core.config.Previous.Listen) - deleted := util.Difference(t.link.core.config.Previous.Listen, t.link.core.config.Current.Listen) - t.link.core.config.Mutex.RUnlock() + t.links.core.config.Mutex.RLock() + added := util.Difference(t.links.core.config.Current.Listen, t.links.core.config.Previous.Listen) + deleted := util.Difference(t.links.core.config.Previous.Listen, t.links.core.config.Current.Listen) + t.links.core.config.Mutex.RUnlock() if len(added) > 0 || len(deleted) > 0 { for _, a := range added { 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) + t.links.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) + t.links.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.links.core.log.Errorln("Failed to add listener: listener", a, "is not correctly formatted, ignoring") } } for _, d := range deleted { if d[:6] != "tcp://" && d[:6] != "tls://" { - t.link.core.log.Errorln("Failed to delete listener: listener", d, "is not correctly formatted, ignoring") + t.links.core.log.Errorln("Failed to delete listener: listener", d, "is not correctly formatted, ignoring") continue } t.mutex.Lock() if listener, ok := t.listeners[d[6:]]; ok { t.mutex.Unlock() listener.Stop() - t.link.core.log.Infoln("Stopped TCP listener:", d[6:]) + t.links.core.log.Infoln("Stopped TCP listener:", d[6:]) } else { t.mutex.Unlock() } @@ -202,13 +202,13 @@ func (t *tcp) listener(l *TcpListener, listenaddr string) { } // And here we go! defer func() { - t.link.core.log.Infoln("Stopping TCP listener on:", l.Listener.Addr().String()) + t.links.core.log.Infoln("Stopping TCP listener on:", l.Listener.Addr().String()) l.Listener.Close() t.mutex.Lock() delete(t.listeners, listenaddr) t.mutex.Unlock() }() - t.link.core.log.Infoln("Listening for TCP on:", l.Listener.Addr().String()) + t.links.core.log.Infoln("Listening for TCP on:", l.Listener.Addr().String()) go func() { <-l.stop l.Listener.Close() @@ -217,7 +217,7 @@ func (t *tcp) listener(l *TcpListener, listenaddr string) { for { sock, err := l.Listener.Accept() if err != nil { - t.link.core.log.Errorln("Failed to accept connection:", err) + t.links.core.log.Errorln("Failed to accept connection:", err) return } t.waitgroup.Add(1) @@ -344,7 +344,7 @@ func (t *tcp) call(saddr string, options interface{}, sintf string, upgrade *Tcp } conn, err = dialer.Dial("tcp", dst.String()) if err != nil { - t.link.core.log.Debugf("Failed to dial %s: %s", callproto, err) + t.links.core.log.Debugf("Failed to dial %s: %s", callproto, err) return } t.waitgroup.Add(1) @@ -361,7 +361,7 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}, upgrade if upgrade != nil { var err error if sock, err = upgrade.upgrade(sock); err != nil { - t.link.core.log.Errorln("TCP handler upgrade failed:", err) + t.links.core.log.Errorln("TCP handler upgrade failed:", err) return } else { upgraded = true @@ -387,12 +387,12 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}, upgrade remote, _, _ = net.SplitHostPort(sock.RemoteAddr().String()) } force := net.ParseIP(strings.Split(remote, "%")[0]).IsLinkLocalUnicast() - link, err := t.link.core.link.create(&stream, name, proto, local, remote, incoming, force) + link, err := t.links.create(&stream, name, proto, local, remote, incoming, force) if err != nil { - t.link.core.log.Println(err) + t.links.core.log.Println(err) panic(err) } - t.link.core.log.Debugln("DEBUG: starting handler for", name) + t.links.core.log.Debugln("DEBUG: starting handler for", name) err = link.handler() - t.link.core.log.Debugln("DEBUG: stopped handler for", name, err) + t.links.core.log.Debugln("DEBUG: stopped handler for", name, err) } diff --git a/src/yggdrasil/tcp_linux.go b/src/yggdrasil/tcp_linux.go index 9ec3c10f..e18f92b1 100644 --- a/src/yggdrasil/tcp_linux.go +++ b/src/yggdrasil/tcp_linux.go @@ -20,10 +20,10 @@ func (t *tcp) tcpContext(network, address string, c syscall.RawConn) error { // Log any errors if bbr != nil { - t.link.core.log.Debugln("Failed to set tcp_congestion_control to bbr for socket, SetsockoptString error:", bbr) + t.links.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) + t.links.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 @@ -38,7 +38,7 @@ func (t *tcp) getControl(sintf string) func(string, string, syscall.RawConn) err } c.Control(btd) if err != nil { - t.link.core.log.Debugln("Failed to set SO_BINDTODEVICE:", sintf) + t.links.core.log.Debugln("Failed to set SO_BINDTODEVICE:", sintf) } return t.tcpContext(network, address, c) } diff --git a/src/yggdrasil/tls.go b/src/yggdrasil/tls.go index 7212c4df..e2861aca 100644 --- a/src/yggdrasil/tls.go +++ b/src/yggdrasil/tls.go @@ -34,7 +34,7 @@ func (t *tcptls) init(tcp *tcp) { } edpriv := make(ed25519.PrivateKey, ed25519.PrivateKeySize) - copy(edpriv[:], tcp.link.core.sigPriv[:]) + copy(edpriv[:], tcp.links.core.sigPriv[:]) certBuf := &bytes.Buffer{} @@ -42,7 +42,7 @@ func (t *tcptls) init(tcp *tcp) { pubtemp := x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{ - CommonName: hex.EncodeToString(tcp.link.core.sigPub[:]), + CommonName: hex.EncodeToString(tcp.links.core.sigPub[:]), }, NotBefore: time.Now(), NotAfter: time.Now().Add(time.Hour * 24 * 365), From 59896f17fd66566b49bc56cc8e963dade5e06c29 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 23 May 2020 10:28:57 -0500 Subject: [PATCH 0577/1109] more cleanup --- src/yggdrasil/link.go | 82 +++++++++++++++++++++++----------------- src/yggdrasil/peer.go | 16 +------- src/yggdrasil/simlink.go | 2 +- 3 files changed, 50 insertions(+), 50 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 9776ee50..067c2ecc 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -21,11 +21,11 @@ import ( ) type links struct { - core *Core - mutex sync.RWMutex // protects interfaces below - interfaces map[linkInfo]*linkInterface - tcp tcp // TCP interface support - stopped chan struct{} + core *Core + mutex sync.RWMutex // protects links below + links map[linkInfo]*link + tcp tcp // TCP interface support + stopped chan struct{} // TODO timeout (to remove from switch), read from config.ReadTimeout } @@ -46,7 +46,7 @@ type linkMsgIO interface { _recvMetaBytes() ([]byte, error) } -type linkInterface struct { +type link struct { lname string links *links peer *peer @@ -55,8 +55,8 @@ type linkInterface struct { incoming bool force bool closed chan struct{} - reader linkReader // Reads packets, notifies this linkInterface, passes packets to switch - writer linkWriter // Writes packets, notifies this linkInterface + reader linkReader // Reads packets, notifies this link, passes packets to switch + writer linkWriter // Writes packets, notifies this link phony.Inbox // Protects the below sendTimer *time.Timer // Fires to signal that sending is blocked keepAliveTimer *time.Timer // Fires to send keep-alive traffic @@ -69,7 +69,7 @@ type linkInterface struct { func (l *links) init(c *Core) error { l.core = c l.mutex.Lock() - l.interfaces = make(map[linkInfo]*linkInterface) + l.links = make(map[linkInfo]*link) l.mutex.Unlock() l.stopped = make(chan struct{}) @@ -121,9 +121,9 @@ func (l *links) listen(uri string) error { } } -func (l *links) create(msgIO linkMsgIO, name, linkType, local, remote string, incoming, force bool) (*linkInterface, error) { +func (l *links) create(msgIO linkMsgIO, name, linkType, local, remote string, incoming, force bool) (*link, error) { // Technically anything unique would work for names, but let's pick something human readable, just for debugging - intf := linkInterface{ + intf := link{ lname: name, links: l, msgIO: msgIO, @@ -150,7 +150,7 @@ func (l *links) stop() error { return nil } -func (intf *linkInterface) handler() error { +func (intf *link) handler() error { // TODO split some of this into shorter functions, so it's easier to read, and for the FIXME duplicate peer issue mentioned later go func() { for bss := range intf.writer.worker { @@ -201,7 +201,7 @@ func (intf *linkInterface) handler() error { intf.info.box = meta.box intf.info.sig = meta.sig intf.links.mutex.Lock() - if oldIntf, isIn := intf.links.interfaces[intf.info]; isIn { + if oldIntf, isIn := intf.links.links[intf.info]; isIn { intf.links.mutex.Unlock() // FIXME we should really return an error and let the caller block instead // That lets them do things like close connections on its own, avoid printing a connection message in the first place, etc. @@ -214,10 +214,10 @@ func (intf *linkInterface) handler() error { return nil } else { intf.closed = make(chan struct{}) - intf.links.interfaces[intf.info] = intf + intf.links.links[intf.info] = intf defer func() { intf.links.mutex.Lock() - delete(intf.links.interfaces, intf.info) + delete(intf.links.links, intf.info) intf.links.mutex.Unlock() close(intf.closed) }() @@ -271,9 +271,21 @@ func (intf *linkInterface) handler() error { //////////////////////////////////////////////////////////////////////////////// -// linkInterface needs to match the peerInterface type needed by the peers +// link needs to match the linkInterface type needed by the peers -func (intf *linkInterface) out(bss [][]byte) { +type linkInterface interface { + out([][]byte) + linkOut([]byte) + notifyQueued(uint64) + close() + // These next ones are only used by the API + name() string + local() string + remote() string + interfaceType() string +} + +func (intf *link) out(bss [][]byte) { intf.Act(nil, func() { // nil to prevent it from blocking if the link is somehow frozen // this is safe because another packet won't be sent until the link notifies @@ -282,7 +294,7 @@ func (intf *linkInterface) out(bss [][]byte) { }) } -func (intf *linkInterface) linkOut(bs []byte) { +func (intf *link) linkOut(bs []byte) { intf.Act(nil, func() { // nil to prevent it from blocking if the link is somehow frozen // FIXME this is hypothetically not safe, the peer shouldn't be sending @@ -293,7 +305,7 @@ func (intf *linkInterface) linkOut(bs []byte) { }) } -func (intf *linkInterface) notifyQueued(seq uint64) { +func (intf *link) notifyQueued(seq uint64) { // This is the part where we want non-nil 'from' fields intf.Act(intf.peer, func() { if intf.isSending { @@ -302,23 +314,23 @@ func (intf *linkInterface) notifyQueued(seq uint64) { }) } -func (intf *linkInterface) close() { +func (intf *link) close() { intf.Act(nil, func() { intf.msgIO.close() }) } -func (intf *linkInterface) name() string { +func (intf *link) name() string { return intf.lname } -func (intf *linkInterface) local() string { +func (intf *link) local() string { return intf.info.local } -func (intf *linkInterface) remote() string { +func (intf *link) remote() string { return intf.info.remote } -func (intf *linkInterface) interfaceType() string { +func (intf *link) interfaceType() string { return intf.info.linkType } @@ -331,7 +343,7 @@ const ( ) // notify the intf that we're currently sending -func (intf *linkInterface) notifySending(size int) { +func (intf *link) notifySending(size int) { intf.Act(&intf.writer, func() { intf.isSending = true intf.sendTimer = time.AfterFunc(sendTime, intf.notifyBlockedSend) @@ -340,7 +352,7 @@ func (intf *linkInterface) notifySending(size int) { } // we just sent something, so cancel any pending timer to send keep-alive traffic -func (intf *linkInterface) _cancelStallTimer() { +func (intf *link) _cancelStallTimer() { if intf.stallTimer != nil { intf.stallTimer.Stop() intf.stallTimer = nil @@ -350,7 +362,7 @@ func (intf *linkInterface) _cancelStallTimer() { // This gets called from a time.AfterFunc, and notifies the switch that we appear // to have gotten blocked on a write, so the switch should start routing traffic // through other links, if alternatives exist -func (intf *linkInterface) notifyBlockedSend() { +func (intf *link) notifyBlockedSend() { intf.Act(nil, func() { if intf.sendTimer != nil && !intf.blocked { //As far as we know, we're still trying to send, and the timer fired. @@ -361,7 +373,7 @@ func (intf *linkInterface) notifyBlockedSend() { } // notify the intf that we've finished sending, returning the peer to the switch -func (intf *linkInterface) notifySent(size int) { +func (intf *link) notifySent(size int) { intf.Act(&intf.writer, func() { if intf.sendTimer != nil { intf.sendTimer.Stop() @@ -381,12 +393,12 @@ func (intf *linkInterface) notifySent(size int) { } // Notify the peer that we're ready for more traffic -func (intf *linkInterface) _notifyIdle() { +func (intf *link) _notifyIdle() { intf.peer.Act(intf, intf.peer._handleIdle) } // Set the peer as stalled, to prevent them from returning to the switch until a read succeeds -func (intf *linkInterface) notifyStalled() { +func (intf *link) notifyStalled() { intf.Act(nil, func() { // Sent from a time.AfterFunc if intf.stallTimer != nil && !intf.blocked { intf.stallTimer.Stop() @@ -398,7 +410,7 @@ func (intf *linkInterface) notifyStalled() { } // reset the close timer -func (intf *linkInterface) notifyReading() { +func (intf *link) notifyReading() { intf.Act(&intf.reader, func() { if intf.closeTimer != nil { intf.closeTimer.Stop() @@ -408,7 +420,7 @@ func (intf *linkInterface) notifyReading() { } // wake up the link if it was stalled, and (if size > 0) prepare to send keep-alive traffic -func (intf *linkInterface) notifyRead(size int) { +func (intf *link) notifyRead(size int) { intf.Act(&intf.reader, func() { if intf.stallTimer != nil { intf.stallTimer.Stop() @@ -425,7 +437,7 @@ func (intf *linkInterface) notifyRead(size int) { } // We need to send keep-alive traffic now -func (intf *linkInterface) notifyDoKeepAlive() { +func (intf *link) notifyDoKeepAlive() { intf.Act(nil, func() { // Sent from a time.AfterFunc if intf.stallTimer != nil { intf.stallTimer.Stop() @@ -439,7 +451,7 @@ func (intf *linkInterface) notifyDoKeepAlive() { type linkWriter struct { phony.Inbox - intf *linkInterface + intf *link worker chan [][]byte closed bool } @@ -463,7 +475,7 @@ func (w *linkWriter) sendFrom(from phony.Actor, bss [][]byte) { type linkReader struct { phony.Inbox - intf *linkInterface + intf *link err chan error } diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 3cfc0b4f..4463bc6d 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -77,23 +77,11 @@ func (ps *peers) getAllowedEncryptionPublicKeys() []string { return ps.core.config.Current.AllowedEncryptionPublicKeys } -type peerInterface interface { - out([][]byte) - linkOut([]byte) - notifyQueued(uint64) - close() - // These next ones are only used by the API - name() string - local() string - remote() string - interfaceType() string -} - // Information known about a peer, including their box/sig keys, precomputed shared keys (static and ephemeral) and a handler for their outgoing traffic type peer struct { phony.Inbox core *Core - intf peerInterface + intf linkInterface port switchPort box crypto.BoxPubKey sig crypto.SigPubKey @@ -134,7 +122,7 @@ func (ps *peers) _updatePeers() { } // Creates a new peer with the specified box, sig, and linkShared keys, using the lowest unoccupied port number. -func (ps *peers) _newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShared *crypto.BoxSharedKey, intf peerInterface) *peer { +func (ps *peers) _newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShared *crypto.BoxSharedKey, intf linkInterface) *peer { now := time.Now() p := peer{box: *box, core: ps.core, diff --git a/src/yggdrasil/simlink.go b/src/yggdrasil/simlink.go index 6c04a8c0..6675981a 100644 --- a/src/yggdrasil/simlink.go +++ b/src/yggdrasil/simlink.go @@ -9,7 +9,7 @@ type Simlink struct { phony.Inbox rch chan []byte dest *Simlink - link *linkInterface + link *link started bool } From bc48e4bb809aa875caabf711a58acf7911e81c24 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 23 May 2020 11:11:11 -0500 Subject: [PATCH 0578/1109] fix deadlock in conn (unsafe use of phony.Block) --- src/yggdrasil/conn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index eef57683..a217d88e 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -153,7 +153,7 @@ func (c *Conn) doSearch() { // Nothing was found, so create a new search searchCompleted := func(sinfo *sessionInfo, e error) {} sinfo = c.core.router.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) - c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo) + c.core.log.Debugf("%s DHT search started: %p", fmt.Sprintf("conn=%p", c), sinfo) // Start the search sinfo.startSearch() } From 7063ddcc7369e95e219b373b353ea2cd1d6b234c Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 23 May 2020 11:16:03 -0500 Subject: [PATCH 0579/1109] slightly cleaner fix to conn String deadlock issue --- src/yggdrasil/conn.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index a217d88e..4cd8b205 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -145,7 +145,8 @@ func (c *Conn) search() error { } // Used in session keep-alive traffic -func (c *Conn) doSearch() { +func (c *Conn) _doSearch() { + s := fmt.Sprintf("conn=%p", c) routerWork := func() { // Check to see if there is a search already matching the destination sinfo, isIn := c.core.router.searches.searches[*c.nodeID] @@ -153,7 +154,7 @@ func (c *Conn) doSearch() { // Nothing was found, so create a new search searchCompleted := func(sinfo *sessionInfo, e error) {} sinfo = c.core.router.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) - c.core.log.Debugf("%s DHT search started: %p", fmt.Sprintf("conn=%p", c), sinfo) + c.core.log.Debugf("%s DHT search started: %p", s, sinfo) // Start the search sinfo.startSearch() } @@ -269,7 +270,7 @@ func (c *Conn) _write(msg FlowKeyMessage) error { case time.Since(c.session.time) > 6*time.Second: if c.session.time.Before(c.session.pingTime) && time.Since(c.session.pingTime) > 6*time.Second { // TODO double check that the above condition is correct - c.doSearch() + c._doSearch() } else { c.session.ping(c.session) // TODO send from self if this becomes an actor } From f2b9e95895167d54d53837c5b5ab731ec327982f Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 23 May 2020 12:21:01 -0500 Subject: [PATCH 0580/1109] simplify routerInterface --- src/yggdrasil/router.go | 43 +++++++++-------------------------------- 1 file changed, 9 insertions(+), 34 deletions(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 303ada69..cfb75a06 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -62,9 +62,7 @@ func (r *router) init(core *Core) { }) r.peer.Act(r, r.peer._handleIdle) r.out = func(bs []byte) { - r.intf.Act(r, func() { - r.peer.handlePacketFrom(&r.intf, bs) - }) + r.peer.handlePacketFrom(r, bs) } r.nodeinfo.init(r.core) r.core.config.Mutex.RLock() @@ -262,46 +260,23 @@ func (r *router) _handleNodeInfo(bs []byte, fromKey *crypto.BoxPubKey) { // routerInterface is a helper that implements peerInterface type routerInterface struct { - phony.Inbox router *router - busy bool } func (intf *routerInterface) out(bss [][]byte) { - intf.Act(intf.router.peer, func() { - intf.router.Act(intf, func() { - for _, bs := range bss { - intf.router._handlePacket(bs) - } - // we may block due to the above - // so we send a message to ourself, that we'd handle after unblocking - // that message tells us to tell the interface that we're finally idle again - intf.router.Act(nil, func() { - intf.Act(intf.router, intf._handleIdle) - }) - intf.Act(intf.router, intf._handleBusy) - }) + // Note that this is run in the peer's goroutine + intf.router.Act(intf.router.peer, func() { + for _, bs := range bss { + intf.router._handlePacket(bs) + } }) -} - -func (intf *routerInterface) _handleBusy() { - intf.busy = true -} - -func (intf *routerInterface) _handleIdle() { - intf.busy = false - intf.router.peer.Act(intf, intf.router.peer._handleIdle) + //intf.router.peer.Act(nil, intf.router.peer._handleIdle) + intf.router.peer._handleIdle() } func (intf *routerInterface) linkOut(_ []byte) {} -func (intf *routerInterface) notifyQueued(seq uint64) { - intf.Act(intf.router.peer, func() { - if intf.busy { - intf.router.peer.dropFromQueue(intf, seq) - } - }) -} +func (intf *routerInterface) notifyQueued(seq uint64) {} func (intf *routerInterface) close() {} From 77ded84ea580ae219802b96d5fb6bed389bfe998 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 23 May 2020 12:21:23 -0500 Subject: [PATCH 0581/1109] simplify routerInterface --- src/yggdrasil/router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index cfb75a06..2ab38555 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -258,7 +258,7 @@ func (r *router) _handleNodeInfo(bs []byte, fromKey *crypto.BoxPubKey) { //////////////////////////////////////////////////////////////////////////////// -// routerInterface is a helper that implements peerInterface +// routerInterface is a helper that implements linkInterface type routerInterface struct { router *router } From c2d6e9e8f1603f41c1aebaad5a6ec7023e34edc7 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 24 May 2020 14:09:06 -0500 Subject: [PATCH 0582/1109] close listener when a multicast interface is removed --- src/multicast/multicast.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index fc7b1373..ad097b86 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -136,6 +136,7 @@ func (m *Multicast) _stop() error { m.log.Infoln("Stopping multicast module") m.isOpen = false for name := range m.listeners { + m.listeners[name].listener.Listener.Close() close(m.listeners[name].stop) delete(m.listeners, name) } @@ -213,6 +214,7 @@ func (m *Multicast) _monitorInterfaceChanges() { for name, intf := range m.listeners { if _, ok := m._interfaces[name]; !ok { // This is a disappeared interface. Stop the announcer. + intf.listener.Listener.Close() close(intf.stop) delete(m.listeners, name) m.log.Debugln("Stopped multicasting on", name) From 1e471e3712d14d7434b14e6d21b99be169bfaec5 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 24 May 2020 14:43:38 -0500 Subject: [PATCH 0583/1109] back to master's version of multicast, lets try rewriting it again --- src/multicast/multicast.go | 292 ++++++++++++++++++------------------- 1 file changed, 140 insertions(+), 152 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index ad097b86..30f66151 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -15,57 +15,38 @@ import ( "golang.org/x/net/ipv6" ) -const ( - // GroupAddr contains the multicast group and port used for multicast packets. - GroupAddr = "[ff02::114]:9001" -) - // Multicast represents the multicast advertisement and discovery mechanism used // by Yggdrasil to find peers on the same subnet. When a beacon is received on a // configured multicast interface, Yggdrasil will attempt to peer with that node // automatically. type Multicast struct { phony.Inbox - core *yggdrasil.Core - config *config.NodeState - log *log.Logger - sock *ipv6.PacketConn - groupAddr *net.UDPAddr - listeners map[string]*multicastInterface - listenPort uint16 - isOpen bool - monitor *time.Timer - platformhandler *time.Timer - _interfaces map[string]net.Interface - _interfaceAddrs map[string]addrInfo + core *yggdrasil.Core + config *config.NodeState + log *log.Logger + sock *ipv6.PacketConn + groupAddr string + listeners map[string]*listenerInfo + listenPort uint16 + isOpen bool } -type addrInfo struct { - addrs []net.Addr - time time.Time -} - -type multicastInterface struct { - phony.Inbox - sock *ipv6.PacketConn - destAddr net.UDPAddr +type listenerInfo struct { listener *yggdrasil.TcpListener - zone string - timer *time.Timer + time time.Time interval time.Duration - stop chan interface{} } // Init prepares the multicast interface for use. -func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) (err error) { +func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) error { m.core = core m.config = state m.log = log - m.listeners = make(map[string]*multicastInterface) + m.listeners = make(map[string]*listenerInfo) current := m.config.GetCurrent() m.listenPort = current.LinkLocalTCPPort - m.groupAddr, err = net.ResolveUDPAddr("udp6", GroupAddr) - return + m.groupAddr = "[ff02::114]:9001" + return nil } // Start starts the multicast interface. This launches goroutines which will @@ -88,7 +69,7 @@ func (m *Multicast) _start() error { return nil } m.log.Infoln("Starting multicast module") - addr, err := net.ResolveUDPAddr("udp", GroupAddr) + addr, err := net.ResolveUDPAddr("udp", m.groupAddr) if err != nil { return err } @@ -108,7 +89,7 @@ func (m *Multicast) _start() error { m.isOpen = true go m.listen() m.Act(nil, m._multicastStarted) - m.Act(nil, m._monitorInterfaceChanges) + m.Act(nil, m._announce) return nil } @@ -135,14 +116,6 @@ func (m *Multicast) Stop() error { func (m *Multicast) _stop() error { m.log.Infoln("Stopping multicast module") m.isOpen = false - for name := range m.listeners { - m.listeners[name].listener.Listener.Close() - close(m.listeners[name].stop) - delete(m.listeners, name) - } - if m.platformhandler != nil { - m.platformhandler.Stop() - } if m.sock != nil { m.sock.Close() } @@ -175,93 +148,10 @@ func (m *Multicast) _updateConfig(config *config.NodeConfig) { m.log.Debugln("Reloaded multicast configuration successfully") } -func (m *Multicast) _monitorInterfaceChanges() { - m._updateInterfaces() // update interfaces and interfaceAddrs - - // Look for interfaces we don't know about yet. - for name, intf := range m._interfaces { - if _, ok := m.listeners[name]; !ok { - // Look up interface addresses. - addrs := m._interfaceAddrs[intf.Name].addrs - // Find the first link-local address. - for _, addr := range addrs { - addrIP, _, _ := net.ParseCIDR(addr.String()) - // Join the multicast group. - m.sock.JoinGroup(&intf, m.groupAddr) - // Construct a listener on this address. - listenaddr := fmt.Sprintf("[%s%%%s]:%d", addrIP, intf.Name, m.listenPort) - listener, err := m.core.ListenTCP(listenaddr) - if err != nil { - m.log.Warnln("Not multicasting on", name, "due to error:", err) - continue - } - // This is a new interface. Start an announcer for it. - multicastInterface := &multicastInterface{ - sock: m.sock, - destAddr: *m.groupAddr, - listener: listener, - stop: make(chan interface{}), - zone: name, - } - multicastInterface.Act(m, multicastInterface._announce) - m.listeners[name] = multicastInterface - m.log.Debugln("Started multicasting on", name) - break - } - } - } - // Look for interfaces we knew about but are no longer there. - for name, intf := range m.listeners { - if _, ok := m._interfaces[name]; !ok { - // This is a disappeared interface. Stop the announcer. - intf.listener.Listener.Close() - close(intf.stop) - delete(m.listeners, name) - m.log.Debugln("Stopped multicasting on", name) - } - } - // Queue the next check. - m.monitor = time.AfterFunc(time.Second, func() { - m.Act(nil, m._monitorInterfaceChanges) - }) -} - -func (m *multicastInterface) _announce() { - // Check if the multicast interface has been stopped. This will happen - // if it disappears from the system or goes down. - select { - case <-m.stop: - return - default: - } - // Send the beacon. - lladdr := m.listener.Listener.Addr().String() - if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil { - a.Zone = "" - msg := []byte(a.String()) - m.sock.WriteTo(msg, nil, &m.destAddr) - } - // Queue the next beacon. - if m.interval.Seconds() < 15 { - m.interval += time.Second - } - m.timer = time.AfterFunc(m.interval, func() { - m.Act(nil, m._announce) - }) -} - // GetInterfaces returns the currently known/enabled multicast interfaces. It is // expected that UpdateInterfaces has been called at least once before calling // this method. func (m *Multicast) Interfaces() map[string]net.Interface { - var interfaces map[string]net.Interface - phony.Block(m, func() { - interfaces = m._interfaces - }) - return interfaces -} - -func (m *Multicast) _updateInterfaces() { interfaces := make(map[string]net.Interface) // Get interface expressions from config current := m.config.GetCurrent() @@ -272,7 +162,6 @@ func (m *Multicast) _updateInterfaces() { panic(err) } // Work out which interfaces to announce on - interfaceAddrs := make(map[string]addrInfo) for _, iface := range allifaces { if iface.Flags&net.FlagUp == 0 { // Ignore interfaces that are down @@ -286,26 +175,6 @@ func (m *Multicast) _updateInterfaces() { // Ignore point-to-point interfaces continue } - var aInfo addrInfo - var isIn bool - if aInfo, isIn = m._interfaceAddrs[iface.Name]; isIn && time.Since(aInfo.time) < time.Minute { - // don't call iface.Addrs, it's unlikely things have changed - } else { - aInfo.addrs, _ = iface.Addrs() - aInfo.time = time.Now() - } - lladdrs := aInfo.addrs[:0] - for _, addr := range aInfo.addrs { - addrIP, _, _ := net.ParseCIDR(addr.String()) - if addrIP.To4() == nil && addrIP.IsLinkLocalUnicast() { - lladdrs = append(lladdrs, addr) - } - } - aInfo.addrs = lladdrs - if len(lladdrs) == 0 { - // Ignore interfaces without link-local addresses - continue - } for _, expr := range exprs { // Compile each regular expression e, err := regexp.Compile(expr) @@ -315,16 +184,136 @@ func (m *Multicast) _updateInterfaces() { // Does the interface match the regular expression? Store it if so if e.MatchString(iface.Name) { interfaces[iface.Name] = iface - interfaceAddrs[iface.Name] = aInfo } } } - m._interfaces = interfaces - m._interfaceAddrs = interfaceAddrs + return interfaces +} + +func (m *Multicast) _announce() { + if !m.isOpen { + return + } + groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr) + if err != nil { + panic(err) + } + destAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr) + if err != nil { + panic(err) + } + interfaces := m.Interfaces() + // There might be interfaces that we configured listeners for but are no + // longer up - if that's the case then we should stop the listeners + for name, info := range m.listeners { + // Prepare our stop function! + stop := func() { + info.listener.Stop() + delete(m.listeners, name) + m.log.Debugln("No longer multicasting on", name) + } + // If the interface is no longer visible on the system then stop the + // listener, as another one will be started further down + if _, ok := interfaces[name]; !ok { + stop() + continue + } + // It's possible that the link-local listener address has changed so if + // that is the case then we should clean up the interface listener + found := false + listenaddr, err := net.ResolveTCPAddr("tcp6", info.listener.Listener.Addr().String()) + if err != nil { + stop() + continue + } + // Find the interface that matches the listener + if intf, err := net.InterfaceByName(name); err == nil { + if addrs, err := intf.Addrs(); err == nil { + // Loop through the addresses attached to that listener and see if any + // of them match the current address of the listener + for _, addr := range addrs { + if ip, _, err := net.ParseCIDR(addr.String()); err == nil { + // Does the interface address match our listener address? + if ip.Equal(listenaddr.IP) { + found = true + break + } + } + } + } + } + // If the address has not been found on the adapter then we should stop + // and clean up the TCP listener. A new one will be created below if a + // suitable link-local address is found + if !found { + stop() + } + } + // Now that we have a list of valid interfaces from the operating system, + // we can start checking if we can send multicasts on them + for _, iface := range interfaces { + // Find interface addresses + addrs, err := iface.Addrs() + if err != nil { + panic(err) + } + for _, addr := range addrs { + addrIP, _, _ := net.ParseCIDR(addr.String()) + // Ignore IPv4 addresses + if addrIP.To4() != nil { + continue + } + // Ignore non-link-local addresses + if !addrIP.IsLinkLocalUnicast() { + continue + } + // Join the multicast group + m.sock.JoinGroup(&iface, groupAddr) + // Try and see if we already have a TCP listener for this interface + var info *listenerInfo + if nfo, ok := m.listeners[iface.Name]; !ok || nfo.listener.Listener == nil { + // No listener was found - let's create one + listenaddr := fmt.Sprintf("[%s%%%s]:%d", addrIP, iface.Name, m.listenPort) + if li, err := m.core.ListenTCP(listenaddr); err == nil { + m.log.Debugln("Started multicasting on", iface.Name) + // Store the listener so that we can stop it later if needed + info = &listenerInfo{listener: li, time: time.Now()} + m.listeners[iface.Name] = info + } else { + m.log.Warnln("Not multicasting on", iface.Name, "due to error:", err) + } + } else { + // An existing listener was found + info = m.listeners[iface.Name] + } + // Make sure nothing above failed for some reason + if info == nil { + continue + } + if time.Since(info.time) < info.interval { + continue + } + // Get the listener details and construct the multicast beacon + lladdr := info.listener.Listener.Addr().String() + if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil { + a.Zone = "" + destAddr.Zone = iface.Name + msg := []byte(a.String()) + m.sock.WriteTo(msg, nil, destAddr) + } + if info.interval.Seconds() < 15 { + info.interval += time.Second + } + break + } + } + time.AfterFunc(time.Second, func() { + m.Act(nil, m._announce) + }) } func (m *Multicast) listen() { - groupAddr, err := net.ResolveUDPAddr("udp6", GroupAddr) + groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr) if err != nil { panic(err) } @@ -357,7 +346,6 @@ func (m *Multicast) listen() { if addr.IP.String() != from.IP.String() { continue } - // Note that m.Interfaces would block if it was being run by the actor itself if _, ok := m.Interfaces()[from.Zone]; ok { addr.Zone = "" if err := m.core.CallPeer("tcp://"+addr.String(), from.Zone); err != nil { From 98816f34b233bbe0a9d44fa8d3050fd2b9052606 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 24 May 2020 15:24:39 -0500 Subject: [PATCH 0584/1109] don't spam calls to net.Interfaces and net.Interface.Addrs (hopefully) --- src/multicast/multicast.go | 74 +++++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 21 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 30f66151..7de3cfab 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -21,14 +21,20 @@ import ( // automatically. type Multicast struct { phony.Inbox - core *yggdrasil.Core - config *config.NodeState - log *log.Logger - sock *ipv6.PacketConn - groupAddr string - listeners map[string]*listenerInfo - listenPort uint16 - isOpen bool + core *yggdrasil.Core + config *config.NodeState + log *log.Logger + sock *ipv6.PacketConn + groupAddr string + listeners map[string]*listenerInfo + listenPort uint16 + isOpen bool + _interfaces map[string]interfaceInfo +} + +type interfaceInfo struct { + iface net.Interface + addrs []net.Addr } type listenerInfo struct { @@ -43,6 +49,7 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log m.config = state m.log = log m.listeners = make(map[string]*listenerInfo) + m._interfaces = make(map[string]interfaceInfo) current := m.config.GetCurrent() m.listenPort = current.LinkLocalTCPPort m.groupAddr = "[ff02::114]:9001" @@ -148,10 +155,35 @@ func (m *Multicast) _updateConfig(config *config.NodeConfig) { m.log.Debugln("Reloaded multicast configuration successfully") } -// GetInterfaces returns the currently known/enabled multicast interfaces. It is -// expected that UpdateInterfaces has been called at least once before calling -// this method. +func (m *Multicast) _updateInterfaces() { + interfaces := make(map[string]interfaceInfo) + intfs := m.getAllowedInterfaces() + for _, intf := range intfs { + addrs, err := intf.Addrs() + if err != nil { + m.log.Warnf("Failed up get addresses for interface %s: %s", intf.Name, err) + continue + } + interfaces[intf.Name] = interfaceInfo{ + iface: intf, + addrs: addrs, + } + } + m._interfaces = interfaces +} + func (m *Multicast) Interfaces() map[string]net.Interface { + interfaces := make(map[string]net.Interface) + phony.Block(m, func() { + for _, info := range m._interfaces { + interfaces[info.iface.Name] = info.iface + } + }) + return interfaces +} + +// getAllowedInterfaces returns the currently known/enabled multicast interfaces. +func (m *Multicast) getAllowedInterfaces() map[string]net.Interface { interfaces := make(map[string]net.Interface) // Get interface expressions from config current := m.config.GetCurrent() @@ -194,6 +226,7 @@ func (m *Multicast) _announce() { if !m.isOpen { return } + m._updateInterfaces() groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr) if err != nil { panic(err) @@ -202,7 +235,6 @@ func (m *Multicast) _announce() { if err != nil { panic(err) } - interfaces := m.Interfaces() // There might be interfaces that we configured listeners for but are no // longer up - if that's the case then we should stop the listeners for name, info := range m.listeners { @@ -214,7 +246,7 @@ func (m *Multicast) _announce() { } // If the interface is no longer visible on the system then stop the // listener, as another one will be started further down - if _, ok := interfaces[name]; !ok { + if _, ok := m._interfaces[name]; !ok { stop() continue } @@ -251,13 +283,9 @@ func (m *Multicast) _announce() { } // Now that we have a list of valid interfaces from the operating system, // we can start checking if we can send multicasts on them - for _, iface := range interfaces { - // Find interface addresses - addrs, err := iface.Addrs() - if err != nil { - panic(err) - } - for _, addr := range addrs { + for _, info := range m._interfaces { + iface := info.iface + for _, addr := range info.addrs { addrIP, _, _ := net.ParseCIDR(addr.String()) // Ignore IPv4 addresses if addrIP.To4() != nil { @@ -346,7 +374,11 @@ func (m *Multicast) listen() { if addr.IP.String() != from.IP.String() { continue } - if _, ok := m.Interfaces()[from.Zone]; ok { + var interfaces map[string]interfaceInfo + phony.Block(m, func() { + interfaces = m._interfaces + }) + if _, ok := interfaces[from.Zone]; ok { addr.Zone = "" if err := m.core.CallPeer("tcp://"+addr.String(), from.Zone); err != nil { m.log.Debugln("Call from multicast failed:", err) From 7778a47a8f224cf4e16cf8008fbed62d8ee18ceb Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 24 May 2020 15:46:18 -0500 Subject: [PATCH 0585/1109] fix darwin compile problem --- src/multicast/multicast_darwin.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/multicast/multicast_darwin.go b/src/multicast/multicast_darwin.go index 91cb5aee..ceff5b44 100644 --- a/src/multicast/multicast_darwin.go +++ b/src/multicast/multicast_darwin.go @@ -32,6 +32,9 @@ import ( var awdlGoroutineStarted bool func (m *Multicast) _multicastStarted() { + if !m.isOpen { + return + } C.StopAWDLBrowsing() for intf := range m._interfaces { if intf == "awdl0" { @@ -39,7 +42,7 @@ func (m *Multicast) _multicastStarted() { break } } - m.platformhandler = time.AfterFunc(time.Minute, func() { + time.AfterFunc(time.Minute, func() { m.Act(nil, m._multicastStarted) }) } From 95743085457314b4b0e8115133a3aaef19e494de Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 24 May 2020 17:24:50 -0500 Subject: [PATCH 0586/1109] have the peer delay setting a max buffer size, in case things have unblocked in the mean time --- src/yggdrasil/peer.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 4463bc6d..ce120b8d 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -303,10 +303,12 @@ func (p *peer) _handleIdle() { func (p *peer) dropFromQueue(from phony.Actor, seq uint64) { p.Act(from, func() { - if seq == p.seq { - p.drop = true - p.max = p.queue.size + streamMsgSize - } + p.Act(nil, func() { + if seq == p.seq { + p.drop = true + p.max = p.queue.size + streamMsgSize + } + }) }) } From 4382368b0891d75aa6bbcd5fd4d9de8e01a67f40 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 24 May 2020 17:43:35 -0500 Subject: [PATCH 0587/1109] make sure the peer isn't idle before entering drop mode --- src/yggdrasil/peer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index ce120b8d..b9641fc0 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -304,7 +304,7 @@ func (p *peer) _handleIdle() { func (p *peer) dropFromQueue(from phony.Actor, seq uint64) { p.Act(from, func() { p.Act(nil, func() { - if seq == p.seq { + if seq == p.seq && !p.idle { p.drop = true p.max = p.queue.size + streamMsgSize } From 38dcbb1e2f8c755d718abe12c41d540698ed5bab Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 24 May 2020 17:49:48 -0500 Subject: [PATCH 0588/1109] cleaner way to handle seq/idle checks for the peer --- src/yggdrasil/peer.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index b9641fc0..3976c8f6 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -290,8 +290,8 @@ func (p *peer) _handleIdle() { break } } + p.seq++ if len(packets) > 0 { - p.seq++ p.bytesSent += uint64(size) p.intf.out(packets) p.max = p.queue.size @@ -304,7 +304,7 @@ func (p *peer) _handleIdle() { func (p *peer) dropFromQueue(from phony.Actor, seq uint64) { p.Act(from, func() { p.Act(nil, func() { - if seq == p.seq && !p.idle { + if seq == p.seq { p.drop = true p.max = p.queue.size + streamMsgSize } From f9bc0b7aee8086bcac6cd35ba78b93cbf2ee5148 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 25 May 2020 11:49:25 -0500 Subject: [PATCH 0589/1109] use a more elaborate precomputed lookup table from the switch --- src/yggdrasil/switch.go | 116 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 110 insertions(+), 6 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index a5c099ba..a3961c31 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -93,6 +93,20 @@ func (l *switchLocator) dist(dest []byte) int { return dist } +func (l *switchLocator) ldist(sl *switchLocator) int { + lca := -1 + for idx := 0; idx < len(l.coords); idx++ { + if idx >= len(sl.coords) { + break + } + if l.coords[idx] != sl.coords[idx] { + break + } + lca = idx + } + return len(l.coords) + len(sl.coords) - 2*(lca+1) +} + // Gets coords in wire encoded format, with *no* length prefix. func (l *switchLocator) getCoords() []byte { bs := make([]byte, 0, len(l.coords)) @@ -140,13 +154,15 @@ type tableElem struct { port switchPort locator switchLocator time time.Time + next map[switchPort]*tableElem } // This is the subset of the information about all peers needed to make routing decisions, and it stored separately in an atomically accessed table, which gets hammered in the "hot loop" of the routing logic (see: peer.handleTraffic in peers.go). type lookupTable struct { - self switchLocator - elems map[switchPort]tableElem - _msg switchMsg + self switchLocator + elems map[switchPort]tableElem // all switch peers, just for sanity checks + API/debugging + _start tableElem // used for lookups + _msg switchMsg } // This is switch information which is mutable and needs to be modified by other goroutines, but is not accessed atomically. @@ -517,10 +533,83 @@ func (t *switchTable) _handleMsg(msg *switchMsg, fromPort switchPort, reprocessi //////////////////////////////////////////////////////////////////////////////// -// The rest of these are related to the switch worker +// The rest of these are related to the switch lookup table + +func (t *switchTable) _updateTable() { + newTable := lookupTable{ + self: t.data.locator.clone(), + elems: make(map[switchPort]tableElem, len(t.data.peers)), + _msg: *t._getMsg(), + } + newTable._init() + for _, pinfo := range t.data.peers { + if pinfo.locator.root != newTable.self.root { + continue + } + loc := pinfo.locator.clone() + loc.coords = loc.coords[:len(loc.coords)-1] // Remove the them->self link + elem := tableElem{ + locator: loc, + port: pinfo.port, + time: pinfo.time, + } + newTable._insert(&elem) + newTable.elems[pinfo.port] = elem + } + t.core.peers.updateTables(t, &newTable) + t.core.router.updateTable(t, &newTable) +} + +func (t *lookupTable) _init() { + // WARNING: this relies on the convention that the self port is 0 + self := tableElem{locator: t.self} // create self elem + t._start = self // initialize _start to self + t._insert(&self) // insert self into table +} + +func (t *lookupTable) _insert(elem *tableElem) { + // This is a helper that should only be run during _updateTable + here := &t._start + for idx := 0; idx <= len(elem.locator.coords); idx++ { + refLoc := here.locator + refLoc.coords = refLoc.coords[:idx] // Note that this is length idx (starts at length 0) + oldDist := refLoc.ldist(&here.locator) + newDist := refLoc.ldist(&elem.locator) + var update bool + switch { + case newDist < oldDist: // new elem is closer to this point in the tree + update = true + case newDist > oldDist: // new elem is too far + case elem.locator.tstamp > refLoc.tstamp: // new elem has a closer timestamp + update = true + case elem.locator.tstamp < refLoc.tstamp: // new elem's timestamp is too old + case elem.time.Before(here.time): // same dist+timestamp, but new elem delivered it faster + update = true + } + if update { + here.port = elem.port + here.locator = elem.locator + here.time = elem.time + // Problem: here is a value, so this doesn't actually update anything... + } + if idx < len(elem.locator.coords) { + if here.next == nil { + here.next = make(map[switchPort]*tableElem) + } + var next *tableElem + var ok bool + if next, ok = here.next[elem.locator.coords[idx]]; !ok { + nextVal := *elem + next = &nextVal + here.next[next.locator.coords[idx]] = next + } + here = next + } + } +} // This is called via a sync.Once to update the atomically readable subset of switch information that gets used for routing decisions. -func (t *switchTable) _updateTable() { +func (t *switchTable) old_updateTable() { // WARNING this should only be called from within t.data.updater.Do() // It relies on the sync.Once for synchronization with messages and lookups // TODO use a pre-computed faster lookup table @@ -558,8 +647,23 @@ func (t *switchTable) start() error { return nil } -// Find the best port to forward to for a given set of coords func (t *lookupTable) lookup(coords []byte) switchPort { + var offset int + here := &t._start + for offset < len(coords) { + port, l := wire_decode_uint64(coords[offset:]) + offset += l + if next, ok := here.next[switchPort(port)]; ok { + here = next + } else { + break + } + } + return here.port +} + +// Find the best port to forward to for a given set of coords +func (t *lookupTable) old_lookup(coords []byte) switchPort { var bestPort switchPort myDist := t.self.dist(coords) bestDist := myDist From 40bfd207f56c26159322b7dbc6bd7a9b443a0c2e Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 25 May 2020 12:23:38 -0500 Subject: [PATCH 0590/1109] don't store every node we hear from in the DHT, only ones we already know about or that are important --- src/yggdrasil/dht.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 8efc549f..0b951c9d 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -260,7 +260,9 @@ func (t *dht) handleRes(res *dhtRes) { key: res.Key, coords: res.Coords, } - t.insert(&rinfo) + if _, isIn := t.table[*rinfo.getNodeID()]; isIn || t.isImportant(&rinfo) { + t.insert(&rinfo) + } for _, info := range res.Infos { if *info.getNodeID() == t.nodeID { continue From eefabb5f9fd128aa51350c9e81c6b771f2f32984 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 25 May 2020 12:44:06 -0500 Subject: [PATCH 0591/1109] disregard nodes if they're unimportant, even if they're already in the DHT --- src/yggdrasil/dht.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 0b951c9d..013fd1e4 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -260,7 +260,7 @@ func (t *dht) handleRes(res *dhtRes) { key: res.Key, coords: res.Coords, } - if _, isIn := t.table[*rinfo.getNodeID()]; isIn || t.isImportant(&rinfo) { + if t.isImportant(&rinfo) { t.insert(&rinfo) } for _, info := range res.Infos { From 761ae531cb7eddf5c88779c746a0f41b67934b6d Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 25 May 2020 15:19:32 -0500 Subject: [PATCH 0592/1109] work-in-progress faster queue logic --- src/yggdrasil/packetqueue.go | 85 +++++++----------------------------- 1 file changed, 16 insertions(+), 69 deletions(-) diff --git a/src/yggdrasil/packetqueue.go b/src/yggdrasil/packetqueue.go index 464bc6ce..d91a18ef 100644 --- a/src/yggdrasil/packetqueue.go +++ b/src/yggdrasil/packetqueue.go @@ -1,10 +1,15 @@ package yggdrasil +/* import ( "math/rand" "time" ) +*/ +// TODO separate queues per e.g. traffic flow +// For now, we put everything in queue +/* type pqStreamID string type pqPacketInfo struct { @@ -13,13 +18,15 @@ type pqPacketInfo struct { } type pqStream struct { + id string infos []pqPacketInfo - size uint64 + size int } +*/ -// TODO separate queues per e.g. traffic flow type packetQueue struct { - streams map[pqStreamID]pqStream + //streams []pqStream + packets [][]byte size uint64 } @@ -29,83 +36,23 @@ func (q *packetQueue) drop() bool { if q.size == 0 { return false } - // select a random stream, odds based on stream size - offset := rand.Uint64() % q.size - var worst pqStreamID - var size uint64 - for id, stream := range q.streams { - worst = id - size += stream.size - if size >= offset { - break - } - } - // drop the oldest packet from the stream - worstStream := q.streams[worst] - packet := worstStream.infos[0].packet - worstStream.infos = worstStream.infos[1:] - worstStream.size -= uint64(len(packet)) + packet := q.packets[0] + q.packets = q.packets[1:] q.size -= uint64(len(packet)) pool_putBytes(packet) - // save the modified stream to queues - if len(worstStream.infos) > 0 { - q.streams[worst] = worstStream - } else { - delete(q.streams, worst) - } return true } func (q *packetQueue) push(packet []byte) { - if q.streams == nil { - q.streams = make(map[pqStreamID]pqStream) - } - // get stream - id := pqStreamID(peer_getPacketCoords(packet)) // just coords for now - stream := q.streams[id] - // update stream - stream.infos = append(stream.infos, pqPacketInfo{packet, time.Now()}) - stream.size += uint64(len(packet)) - // save update to queues - q.streams[id] = stream + q.packets = append(q.packets, packet) q.size += uint64(len(packet)) } func (q *packetQueue) pop() ([]byte, bool) { - if len(q.streams) > 0 { - // get the stream that uses the least bandwidth - now := time.Now() - var best pqStreamID - for id := range q.streams { - best = id - break // get a random ID to start - } - bestStream := q.streams[best] - bestSize := float64(bestStream.size) - bestAge := now.Sub(bestStream.infos[0].time).Seconds() - for id, stream := range q.streams { - thisSize := float64(stream.size) - thisAge := now.Sub(stream.infos[0].time).Seconds() - // cross multiply to avoid division by zero issues - if bestSize*thisAge > thisSize*bestAge { - // bestSize/bestAge > thisSize/thisAge -> this uses less bandwidth - best = id - bestStream = stream - bestSize = thisSize - bestAge = thisAge - } - } - // get the oldest packet from the best stream - packet := bestStream.infos[0].packet - bestStream.infos = bestStream.infos[1:] - bestStream.size -= uint64(len(packet)) + if q.size > 0 { + packet := q.packets[0] + q.packets = q.packets[1:] q.size -= uint64(len(packet)) - // save the modified stream to queues - if len(bestStream.infos) > 0 { - q.streams[best] = bestStream - } else { - delete(q.streams, best) - } return packet, true } return nil, false From 1f65ffb310c12343bcd56a53399661046f5490a7 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 25 May 2020 16:07:56 -0500 Subject: [PATCH 0593/1109] work-in-progress heap-based queue structure --- src/yggdrasil/packetqueue.go | 89 ++++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 19 deletions(-) diff --git a/src/yggdrasil/packetqueue.go b/src/yggdrasil/packetqueue.go index d91a18ef..358aaeb1 100644 --- a/src/yggdrasil/packetqueue.go +++ b/src/yggdrasil/packetqueue.go @@ -1,15 +1,13 @@ package yggdrasil -/* import ( - "math/rand" + "container/heap" "time" ) -*/ // TODO separate queues per e.g. traffic flow // For now, we put everything in queue -/* + type pqStreamID string type pqPacketInfo struct { @@ -18,15 +16,13 @@ type pqPacketInfo struct { } type pqStream struct { - id string + id pqStreamID infos []pqPacketInfo - size int + size uint64 } -*/ type packetQueue struct { - //streams []pqStream - packets [][]byte + streams []pqStream size uint64 } @@ -36,24 +32,79 @@ func (q *packetQueue) drop() bool { if q.size == 0 { return false } - packet := q.packets[0] - q.packets = q.packets[1:] - q.size -= uint64(len(packet)) - pool_putBytes(packet) + var longestIdx int + for idx := range q.streams { + if q.streams[idx].size > q.streams[longestIdx].size { + longestIdx = idx + } + } + stream := heap.Remove(q, longestIdx).(pqStream) + info := stream.infos[0] + if len(stream.infos) > 1 { + stream.infos = stream.infos[1:] + stream.size -= uint64(len(info.packet)) + heap.Push(q, stream) + } + pool_putBytes(info.packet) return true } func (q *packetQueue) push(packet []byte) { - q.packets = append(q.packets, packet) - q.size += uint64(len(packet)) + id := pqStreamID(peer_getPacketCoords(packet)) // just coords for now + info := pqPacketInfo{packet: packet, time: time.Now()} + for idx := range q.streams { + if q.streams[idx].id == id { + q.streams[idx].infos = append(q.streams[idx].infos, info) + q.streams[idx].size += uint64(len(packet)) + q.size += uint64(len(packet)) + return + } + } + stream := pqStream{id: id, size: uint64(len(packet))} + stream.infos = append(stream.infos, info) + heap.Push(q, stream) } func (q *packetQueue) pop() ([]byte, bool) { if q.size > 0 { - packet := q.packets[0] - q.packets = q.packets[1:] - q.size -= uint64(len(packet)) - return packet, true + stream := heap.Pop(q).(pqStream) + info := stream.infos[0] + if len(stream.infos) > 1 { + stream.infos = stream.infos[1:] + stream.size -= uint64(len(info.packet)) + heap.Push(q, stream) + } + return info.packet, true } return nil, false } + +//////////////////////////////////////////////////////////////////////////////// + +// Interface methods for packetQueue to satisfy heap.Interface + +func (q *packetQueue) Len() int { + return len(q.streams) +} + +func (q *packetQueue) Less(i, j int) bool { + return q.streams[i].infos[0].time.Before(q.streams[j].infos[0].time) +} + +func (q *packetQueue) Swap(i, j int) { + q.streams[i], q.streams[j] = q.streams[j], q.streams[i] +} + +func (q *packetQueue) Push(x interface{}) { + stream := x.(pqStream) + q.streams = append(q.streams, stream) + q.size += stream.size +} + +func (q *packetQueue) Pop() interface{} { + idx := len(q.streams) - 1 + stream := q.streams[idx] + q.streams = q.streams[:idx] + q.size -= stream.size + return stream +} From 8cca565ac46a4e412ffe7c9089d1c4ecb86fb492 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 25 May 2020 22:08:53 +0100 Subject: [PATCH 0594/1109] Update go.mod/go.sum for yggdrasil-extras for iOS builds --- go.mod | 1 + go.sum | 2 ++ 2 files changed, 3 insertions(+) diff --git a/go.mod b/go.mod index bf8e7571..cea62b2b 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/mitchellh/mapstructure v1.1.2 github.com/vishvananda/netlink v1.0.0 github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect + github.com/yggdrasil-network/yggdrasil-extras v0.0.0-20200525205615-6c8a4a2e8855 // indirect golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d golang.org/x/net v0.0.0-20200301022130-244492dfa37a golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 diff --git a/go.sum b/go.sum index f67b6806..013abbeb 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,8 @@ github.com/vishvananda/netlink v1.0.0 h1:bqNY2lgheFIu1meHUFSH3d7vG93AFyqg3oGbJCO github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f h1:nBX3nTcmxEtHSERBJaIo1Qa26VwRaopnZmfDQUXsF4I= github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= +github.com/yggdrasil-network/yggdrasil-extras v0.0.0-20200525205615-6c8a4a2e8855 h1:xLQihK8bAKOEDii/Z39dHTgSJzetm2TQ1YKRPRX87R4= +github.com/yggdrasil-network/yggdrasil-extras v0.0.0-20200525205615-6c8a4a2e8855/go.mod h1:xQdsh08Io6nV4WRnOVTe6gI8/2iTvfLDQ0CYa5aMt+I= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw= From 8345ae1fa304b6f097903336fa5c37084b047832 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 25 May 2020 19:08:04 -0500 Subject: [PATCH 0595/1109] don't allow ygg tcp connections to/from a local ygg address --- src/yggdrasil/tcp.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index c81c4ddf..83305ab6 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -25,6 +25,7 @@ import ( "golang.org/x/net/proxy" + "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/util" ) @@ -397,6 +398,18 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options tcpOptions) { local, _, _ = net.SplitHostPort(sock.LocalAddr().String()) remote, _, _ = net.SplitHostPort(sock.RemoteAddr().String()) } + localIP := net.ParseIP(local) + if localIP = localIP.To16(); localIP != nil { + var laddr address.Address + var lsubnet address.Subnet + copy(laddr[:], localIP) + copy(lsubnet[:], localIP) + if laddr.IsValid() || lsubnet.IsValid() { + // The local address is with the network address/prefix range + // This would route ygg over ygg, which we don't want + return + } + } force := net.ParseIP(strings.Split(remote, "%")[0]).IsLinkLocalUnicast() link, err := t.link.core.link.create(&stream, name, proto, local, remote, incoming, force, options.linkOptions) if err != nil { From 85eec5ba8e63116632a408aa6aa27937fc55b09a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 25 May 2020 19:13:37 -0500 Subject: [PATCH 0596/1109] tcp ygg-over-ygg debug logging --- src/yggdrasil/tcp.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 83305ab6..129fc0ee 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -407,6 +407,7 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options tcpOptions) { if laddr.IsValid() || lsubnet.IsValid() { // The local address is with the network address/prefix range // This would route ygg over ygg, which we don't want + t.link.core.log.Debugln("Dropping ygg-tunneled connection", local, remote) return } } From 674d8b58b652967b65994518cfc438f00017ee9b Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 25 May 2020 19:27:17 -0500 Subject: [PATCH 0597/1109] get things compiling again --- src/yggdrasil/tcp.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index d37d6e62..f686553f 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -407,7 +407,10 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options tcpOptions) { if laddr.IsValid() || lsubnet.IsValid() { // The local address is with the network address/prefix range // This would route ygg over ygg, which we don't want - t.link.core.log.Debugln("Dropping ygg-tunneled connection", local, remote) + // FIXME ideally this check should happen outside of the core library + // Maybe dial/listen at the application level + // Then pass a net.Conn to the core library (after these kinds of checks are done) + t.links.core.log.Debugln("Dropping ygg-tunneled connection", local, remote) return } } From 09f9f4e8e4fdc2aafb65bbc31a88551341dded80 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 25 May 2020 20:09:57 -0500 Subject: [PATCH 0598/1109] use heap.Fix instead of heap.Remove + heap.Push when updating queues, this is theoretically faster --- src/yggdrasil/packetqueue.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/yggdrasil/packetqueue.go b/src/yggdrasil/packetqueue.go index 358aaeb1..6273e6c8 100644 --- a/src/yggdrasil/packetqueue.go +++ b/src/yggdrasil/packetqueue.go @@ -38,12 +38,16 @@ func (q *packetQueue) drop() bool { longestIdx = idx } } - stream := heap.Remove(q, longestIdx).(pqStream) + stream := q.streams[longestIdx] info := stream.infos[0] if len(stream.infos) > 1 { stream.infos = stream.infos[1:] stream.size -= uint64(len(info.packet)) - heap.Push(q, stream) + q.streams[longestIdx] = stream + q.size -= uint64(len(info.packet)) + heap.Fix(q, longestIdx) + } else { + heap.Remove(q, longestIdx) } pool_putBytes(info.packet) return true @@ -67,12 +71,16 @@ func (q *packetQueue) push(packet []byte) { func (q *packetQueue) pop() ([]byte, bool) { if q.size > 0 { - stream := heap.Pop(q).(pqStream) + stream := q.streams[0] info := stream.infos[0] if len(stream.infos) > 1 { stream.infos = stream.infos[1:] stream.size -= uint64(len(info.packet)) - heap.Push(q, stream) + q.streams[0] = stream + q.size -= uint64(len(info.packet)) + heap.Fix(q, 0) + } else { + heap.Remove(q, 0) } return info.packet, true } From 1df305d31cde064ae5ca3fddc46d1e94d891015d Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 27 May 2020 18:53:14 -0500 Subject: [PATCH 0599/1109] simplify how blocking is detected and packets are dequeued --- src/yggdrasil/link.go | 11 +---------- src/yggdrasil/peer.go | 16 +++++++--------- src/yggdrasil/router.go | 6 +++--- 3 files changed, 11 insertions(+), 22 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index e0620d10..0dd97ec5 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -326,7 +326,6 @@ func (intf *link) handler() error { type linkInterface interface { out([][]byte) linkOut([]byte) - notifyQueued(uint64) close() // These next ones are only used by the API name() string @@ -355,15 +354,6 @@ func (intf *link) linkOut(bs []byte) { }) } -func (intf *link) notifyQueued(seq uint64) { - // This is the part where we want non-nil 'from' fields - intf.Act(intf.peer, func() { - if intf.isSending { - intf.peer.dropFromQueue(intf, seq) - } - }) -} - func (intf *link) close() { intf.Act(nil, func() { intf.msgIO.close() }) } @@ -398,6 +388,7 @@ func (intf *link) notifySending(size int) { intf.isSending = true intf.sendTimer = time.AfterFunc(sendTime, intf.notifyBlockedSend) intf._cancelStallTimer() + intf.peer.notifyBlocked(intf) }) } diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 3976c8f6..f04ab280 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -266,23 +266,20 @@ func (p *peer) sendPacketFrom(from phony.Actor, packet []byte) { func (p *peer) _sendPacket(packet []byte) { p.queue.push(packet) - switch { - case p.idle: + if p.idle { p.idle = false p._handleIdle() - case p.drop: + } else if p.drop { for p.queue.size > p.max { p.queue.drop() } - default: - p.intf.notifyQueued(p.seq) } } func (p *peer) _handleIdle() { var packets [][]byte var size uint64 - for size < streamMsgSize { + for { if packet, success := p.queue.pop(); success { packets = append(packets, packet) size += uint64(len(packet)) @@ -297,16 +294,17 @@ func (p *peer) _handleIdle() { p.max = p.queue.size } else { p.idle = true - p.drop = false } + p.drop = false } -func (p *peer) dropFromQueue(from phony.Actor, seq uint64) { +func (p *peer) notifyBlocked(from phony.Actor) { p.Act(from, func() { + seq := p.seq p.Act(nil, func() { if seq == p.seq { p.drop = true - p.max = p.queue.size + streamMsgSize + p.max = 2*p.queue.size + streamMsgSize } }) }) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 2ab38555..d387346e 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -270,14 +270,14 @@ func (intf *routerInterface) out(bss [][]byte) { intf.router._handlePacket(bs) } }) - //intf.router.peer.Act(nil, intf.router.peer._handleIdle) + // This should now immediately make the peer idle again + // So the self-peer shouldn't end up buffering anything + // We let backpressure act as a throttle instead intf.router.peer._handleIdle() } func (intf *routerInterface) linkOut(_ []byte) {} -func (intf *routerInterface) notifyQueued(seq uint64) {} - func (intf *routerInterface) close() {} func (intf *routerInterface) name() string { return "(self)" } From 905c28f7b213ac2f6cdf471e4af1587e307ea32f Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 27 May 2020 19:31:17 -0500 Subject: [PATCH 0600/1109] fix some issues with the rewritten switch lookup tables --- src/yggdrasil/switch.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index a3961c31..ae14d772 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -444,6 +444,9 @@ func (t *switchTable) _handleMsg(msg *switchMsg, fromPort switchPort, reprocessi } } } + if sender.blocked != oldSender.blocked { + doUpdate = true + } // Update sender t.data.peers[fromPort] = sender // Decide if we should also update our root info to make the sender our parent @@ -543,7 +546,9 @@ func (t *switchTable) _updateTable() { } newTable._init() for _, pinfo := range t.data.peers { - if pinfo.locator.root != newTable.self.root { + if pinfo.blocked || + pinfo.locator.root != newTable.self.root || + pinfo.key == t.key { continue } loc := pinfo.locator.clone() From 8775075c18192676d8a61e2d9e6218df06a9fb05 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 27 May 2020 19:35:19 -0500 Subject: [PATCH 0601/1109] debugging --- src/yggdrasil/switch.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index ae14d772..ebdeea45 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -528,7 +528,7 @@ func (t *switchTable) _handleMsg(msg *switchMsg, fromPort switchPort, reprocessi t.parent = sender.port defer t.core.peers.sendSwitchMsgs(t) } - if doUpdate { + if true || doUpdate { defer t._updateTable() } return From 3dc22427125103f2ac3e1dbe3664bf38c7a8b167 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 30 May 2020 10:32:15 -0500 Subject: [PATCH 0602/1109] fix handling of keepAliveTimer and blocked state in link.go --- src/yggdrasil/link.go | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 0dd97ec5..7a843b68 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -387,19 +387,14 @@ func (intf *link) notifySending(size int) { intf.Act(&intf.writer, func() { intf.isSending = true intf.sendTimer = time.AfterFunc(sendTime, intf.notifyBlockedSend) - intf._cancelStallTimer() + if intf.keepAliveTimer != nil { + intf.keepAliveTimer.Stop() + intf.keepAliveTimer = nil + } intf.peer.notifyBlocked(intf) }) } -// we just sent something, so cancel any pending timer to send keep-alive traffic -func (intf *link) _cancelStallTimer() { - if intf.stallTimer != nil { - intf.stallTimer.Stop() - intf.stallTimer = nil - } -} - // This gets called from a time.AfterFunc, and notifies the switch that we appear // to have gotten blocked on a write, so the switch should start routing traffic // through other links, if alternatives exist @@ -441,11 +436,13 @@ func (intf *link) _notifyIdle() { // Set the peer as stalled, to prevent them from returning to the switch until a read succeeds func (intf *link) notifyStalled() { intf.Act(nil, func() { // Sent from a time.AfterFunc - if intf.stallTimer != nil && !intf.blocked { + if intf.stallTimer != nil { intf.stallTimer.Stop() intf.stallTimer = nil - intf.blocked = true - intf.links.core.switchTable.blockPeer(intf, intf.peer.port) + if !intf.blocked { + intf.blocked = true + intf.links.core.switchTable.blockPeer(intf, intf.peer.port) + } } }) } @@ -480,9 +477,9 @@ func (intf *link) notifyRead(size int) { // We need to send keep-alive traffic now func (intf *link) notifyDoKeepAlive() { intf.Act(nil, func() { // Sent from a time.AfterFunc - if intf.stallTimer != nil { - intf.stallTimer.Stop() - intf.stallTimer = nil + if intf.keepAliveTimer != nil { + intf.keepAliveTimer.Stop() + intf.keepAliveTimer = nil intf.writer.sendFrom(nil, [][]byte{nil}) // Empty keep-alive traffic } }) From 5e170e22e116423fb44310482709062614b5b07d Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 30 May 2020 10:47:54 -0500 Subject: [PATCH 0603/1109] more switch fixes --- src/yggdrasil/switch.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index ebdeea45..9cea91eb 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -392,6 +392,9 @@ func (t *switchTable) _handleMsg(msg *switchMsg, fromPort switchPort, reprocessi sender.key = prevKey prevKey = hop.Next } + if sender.key == t.key { + return // Don't peer with ourself via different interfaces + } sender.msg = *msg sender.port = fromPort sender.time = now @@ -516,8 +519,8 @@ func (t *switchTable) _handleMsg(msg *switchMsg, fromPort switchPort, reprocessi } // Note that we depend on the LIFO order of the stack of defers here... if updateRoot { + doUpdate = true if !equiv(&sender.locator, &t.data.locator) { - doUpdate = true t.data.seq++ defer t.core.router.reset(t) } @@ -528,8 +531,8 @@ func (t *switchTable) _handleMsg(msg *switchMsg, fromPort switchPort, reprocessi t.parent = sender.port defer t.core.peers.sendSwitchMsgs(t) } - if true || doUpdate { - defer t._updateTable() + if doUpdate { + t._updateTable() } return } @@ -546,9 +549,7 @@ func (t *switchTable) _updateTable() { } newTable._init() for _, pinfo := range t.data.peers { - if pinfo.blocked || - pinfo.locator.root != newTable.self.root || - pinfo.key == t.key { + if pinfo.blocked || pinfo.locator.root != newTable.self.root { continue } loc := pinfo.locator.clone() From 0f28862e99f43b2fee347cb51a4c250daf0d3f2e Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 30 May 2020 10:48:59 -0500 Subject: [PATCH 0604/1109] remove unused sequence number from switch --- src/yggdrasil/switch.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 9cea91eb..be97466c 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -171,7 +171,6 @@ type switchData struct { // All data that's mutable and used by exported Table methods // To be read/written with atomic.Value Store/Load calls locator switchLocator - seq uint64 // Sequence number, reported to peers, so they know about changes peers map[switchPort]peerInfo msg *switchMsg } @@ -242,7 +241,6 @@ func (t *switchTable) _cleanRoot() { t.parent = switchPort(0) t.time = now if t.data.locator.root != t.key { - t.data.seq++ defer t.core.router.reset(nil) } t.data.locator = switchLocator{root: t.key, tstamp: now.Unix()} @@ -521,7 +519,6 @@ func (t *switchTable) _handleMsg(msg *switchMsg, fromPort switchPort, reprocessi if updateRoot { doUpdate = true if !equiv(&sender.locator, &t.data.locator) { - t.data.seq++ defer t.core.router.reset(t) } if t.data.locator.tstamp != sender.locator.tstamp { From c83b070c692de8c1c62fed9836c23a08ef0e39ad Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 30 May 2020 13:12:49 -0500 Subject: [PATCH 0605/1109] remove old switch lookup functions --- src/yggdrasil/switch.go | 69 ----------------------------------------- 1 file changed, 69 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index be97466c..ed2edf2e 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -611,38 +611,6 @@ func (t *lookupTable) _insert(elem *tableElem) { } } -// This is called via a sync.Once to update the atomically readable subset of switch information that gets used for routing decisions. -func (t *switchTable) old_updateTable() { - // WARNING this should only be called from within t.data.updater.Do() - // It relies on the sync.Once for synchronization with messages and lookups - // TODO use a pre-computed faster lookup table - // Instead of checking distance for every destination every time - // Array of structs, indexed by first coord that differs from self - // Each struct has stores the best port to forward to, and a next coord map - // Move to struct, then iterate over coord maps until you dead end - // The last port before the dead end should be the closest - newTable := lookupTable{ - self: t.data.locator.clone(), - elems: make(map[switchPort]tableElem, len(t.data.peers)), - } - for _, pinfo := range t.data.peers { - //if !pinfo.forward { continue } - if pinfo.locator.root != newTable.self.root { - continue - } - loc := pinfo.locator.clone() - loc.coords = loc.coords[:len(loc.coords)-1] // Remove the them->self link - newTable.elems[pinfo.port] = tableElem{ - locator: loc, - port: pinfo.port, - time: pinfo.time, - } - } - newTable._msg = *t._getMsg() - t.core.peers.updateTables(t, &newTable) - t.core.router.updateTable(t, &newTable) -} - // Starts the switch worker func (t *switchTable) start() error { t.core.log.Infoln("Starting switch") @@ -664,40 +632,3 @@ func (t *lookupTable) lookup(coords []byte) switchPort { } return here.port } - -// Find the best port to forward to for a given set of coords -func (t *lookupTable) old_lookup(coords []byte) switchPort { - var bestPort switchPort - myDist := t.self.dist(coords) - bestDist := myDist - var bestElem tableElem - for _, info := range t.elems { - dist := info.locator.dist(coords) - if dist >= myDist { - continue - } - var update bool - switch { - case dist < bestDist: - // Closer to destination - update = true - case dist > bestDist: - // Further from destination - case info.locator.tstamp > bestElem.locator.tstamp: - // Newer root update - update = true - case info.locator.tstamp < bestElem.locator.tstamp: - // Older root update - case info.time.Before(bestElem.time): - // Received root update via this peer sooner - update = true - default: - } - if update { - bestPort = info.port - bestDist = dist - bestElem = info - } - } - return bestPort -} From 3fded209df6942d52c02fb8d4b0e0401f94ae042 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 6 Jun 2020 12:30:54 -0500 Subject: [PATCH 0606/1109] try to fix some possible races with how peers are added/removed and how they're blocked in the switch when they enter a bad state --- src/yggdrasil/link.go | 38 ++++++++++++++++++----------- src/yggdrasil/switch.go | 54 ++++++++++++++++++++++++++--------------- 2 files changed, 58 insertions(+), 34 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 7a843b68..5222644b 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -64,8 +64,9 @@ type link struct { keepAliveTimer *time.Timer // Fires to send keep-alive traffic stallTimer *time.Timer // Fires to signal that no incoming traffic (including keep-alive) has been seen closeTimer *time.Timer // Fires when the link has been idle so long we need to close it - isSending bool // True between a notifySending and a notifySent - blocked bool // True if we've blocked the peer in the switch + readUnblocked bool // True if we've sent a read message unblocking this peer in the switch + writeUnblocked bool // True if we've sent a write message unblocking this peer in the swithc + shutdown bool // True if we're shutting down, avoids sending some messages that could race with new peers being crated in the same port } type linkOptions struct { @@ -285,7 +286,10 @@ func (intf *link) handler() error { } defer func() { // More cleanup can go here - intf.peer.Act(nil, intf.peer._removeSelf) + intf.Act(nil, func() { + intf.shutdown = true + intf.peer.Act(intf, intf.peer._removeSelf) + }) }() themAddr := address.AddrForNodeID(crypto.GetNodeID(&intf.info.box)) themAddrString := net.IP(themAddr[:]).String() @@ -385,7 +389,6 @@ const ( // notify the intf that we're currently sending func (intf *link) notifySending(size int) { intf.Act(&intf.writer, func() { - intf.isSending = true intf.sendTimer = time.AfterFunc(sendTime, intf.notifyBlockedSend) if intf.keepAliveTimer != nil { intf.keepAliveTimer.Stop() @@ -400,10 +403,14 @@ func (intf *link) notifySending(size int) { // through other links, if alternatives exist func (intf *link) notifyBlockedSend() { intf.Act(nil, func() { - if intf.sendTimer != nil && !intf.blocked { + if intf.sendTimer != nil { //As far as we know, we're still trying to send, and the timer fired. - intf.blocked = true - intf.links.core.switchTable.blockPeer(intf, intf.peer.port) + intf.sendTimer.Stop() + intf.sendTimer = nil + if !intf.shutdown && intf.writeUnblocked { + intf.writeUnblocked = false + intf.links.core.switchTable.blockPeer(intf, intf.peer.port, true) + } } }) } @@ -421,10 +428,13 @@ func (intf *link) notifySent(size int) { intf.keepAliveTimer = nil } intf._notifyIdle() - intf.isSending = false if size > 0 && intf.stallTimer == nil { intf.stallTimer = time.AfterFunc(stallTime, intf.notifyStalled) } + if !intf.shutdown && !intf.writeUnblocked { + intf.writeUnblocked = true + intf.links.core.switchTable.unblockPeer(intf, intf.peer.port, true) + } }) } @@ -439,9 +449,9 @@ func (intf *link) notifyStalled() { if intf.stallTimer != nil { intf.stallTimer.Stop() intf.stallTimer = nil - if !intf.blocked { - intf.blocked = true - intf.links.core.switchTable.blockPeer(intf, intf.peer.port) + if !intf.shutdown && intf.readUnblocked { + intf.readUnblocked = false + intf.links.core.switchTable.blockPeer(intf, intf.peer.port, false) } } }) @@ -467,9 +477,9 @@ func (intf *link) notifyRead(size int) { if size > 0 && intf.keepAliveTimer == nil { intf.keepAliveTimer = time.AfterFunc(keepAliveTime, intf.notifyDoKeepAlive) } - if intf.blocked { - intf.blocked = false - intf.links.core.switchTable.unblockPeer(intf, intf.peer.port) + if !intf.shutdown && !intf.readUnblocked { + intf.readUnblocked = true + intf.links.core.switchTable.unblockPeer(intf, intf.peer.port, false) } }) } diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index ed2edf2e..9c7a91f7 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -136,14 +136,19 @@ func (x *switchLocator) isAncestorOf(y *switchLocator) bool { // Information about a peer, used by the switch to build the tree and eventually make routing decisions. type peerInfo struct { - key crypto.SigPubKey // ID of this peer - locator switchLocator // Should be able to respond with signatures upon request - degree uint64 // Self-reported degree - time time.Time // Time this node was last seen - faster map[switchPort]uint64 // Counter of how often a node is faster than the current parent, penalized extra if slower - port switchPort // Interface number of this peer - msg switchMsg // The wire switchMsg used - blocked bool // True if the link is blocked, used to avoid parenting a blocked link + key crypto.SigPubKey // ID of this peer + locator switchLocator // Should be able to respond with signatures upon request + degree uint64 // Self-reported degree + time time.Time // Time this node was last seen + faster map[switchPort]uint64 // Counter of how often a node is faster than the current parent, penalized extra if slower + port switchPort // Interface number of this peer + msg switchMsg // The wire switchMsg used + readBlock bool // True if the link notified us of a read that blocked too long + writeBlock bool // True of the link notified us of a write that blocked too long +} + +func (pinfo *peerInfo) blocked() bool { + return pinfo.readBlock || pinfo.writeBlock } // This is just a uint64 with a named type for clarity reasons. @@ -250,15 +255,19 @@ func (t *switchTable) _cleanRoot() { } // Blocks and, if possible, unparents a peer -func (t *switchTable) blockPeer(from phony.Actor, port switchPort) { +func (t *switchTable) blockPeer(from phony.Actor, port switchPort, isWrite bool) { t.Act(from, func() { peer, isIn := t.data.peers[port] - if !isIn || peer.blocked { + switch { + case isIn && !isWrite && !peer.readBlock: + peer.readBlock = true + case isIn && isWrite && !peer.writeBlock: + peer.writeBlock = true + default: return } - peer.blocked = true t.data.peers[port] = peer - t._updateTable() + defer t._updateTable() if port != t.parent { return } @@ -273,13 +282,17 @@ func (t *switchTable) blockPeer(from phony.Actor, port switchPort) { }) } -func (t *switchTable) unblockPeer(from phony.Actor, port switchPort) { +func (t *switchTable) unblockPeer(from phony.Actor, port switchPort, isWrite bool) { t.Act(from, func() { peer, isIn := t.data.peers[port] - if !isIn || !peer.blocked { + switch { + case isIn && !isWrite && peer.readBlock: + peer.readBlock = false + case isIn && isWrite && peer.writeBlock: + peer.writeBlock = false + default: return } - peer.blocked = false t.data.peers[port] = peer t._updateTable() }) @@ -422,7 +435,8 @@ func (t *switchTable) _handleMsg(msg *switchMsg, fromPort switchPort, reprocessi if reprocessing { sender.faster = oldSender.faster sender.time = oldSender.time - sender.blocked = oldSender.blocked + sender.readBlock = oldSender.readBlock + sender.writeBlock = oldSender.writeBlock } else { sender.faster = make(map[switchPort]uint64, len(oldSender.faster)) for port, peer := range t.data.peers { @@ -445,7 +459,7 @@ func (t *switchTable) _handleMsg(msg *switchMsg, fromPort switchPort, reprocessi } } } - if sender.blocked != oldSender.blocked { + if sender.blocked() != oldSender.blocked() { doUpdate = true } // Update sender @@ -485,10 +499,10 @@ func (t *switchTable) _handleMsg(msg *switchMsg, fromPort switchPort, reprocessi case sender.faster[t.parent] >= switch_faster_threshold: // The is reliably faster than the current parent. updateRoot = true - case !sender.blocked && oldParent.blocked: + case !sender.blocked() && oldParent.blocked(): // Replace a blocked parent updateRoot = true - case reprocessing && sender.blocked && !oldParent.blocked: + case reprocessing && sender.blocked() && !oldParent.blocked(): // Don't replace an unblocked parent when reprocessing case reprocessing && sender.faster[t.parent] > oldParent.faster[sender.port]: // The sender seems to be reliably faster than the current parent, so switch to them instead. @@ -546,7 +560,7 @@ func (t *switchTable) _updateTable() { } newTable._init() for _, pinfo := range t.data.peers { - if pinfo.blocked || pinfo.locator.root != newTable.self.root { + if pinfo.blocked() || pinfo.locator.root != newTable.self.root { continue } loc := pinfo.locator.clone() From 48f008a8e2ef2b36280e7dcbfac89bbc18d503f8 Mon Sep 17 00:00:00 2001 From: George <57254463+zhoreeq@users.noreply.github.com> Date: Mon, 6 Jul 2020 09:14:34 -0400 Subject: [PATCH 0607/1109] Implement Core.RemovePeer method (#699) (#709) Co-authored-by: George --- src/admin/admin.go | 27 ++++++++++++++++++++++++--- src/yggdrasil/api.go | 29 ++++++++++++++++++++++++----- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/admin/admin.go b/src/admin/admin.go index 8028bcc6..5ea5ab89 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -189,22 +189,43 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { }, errors.New("Failed to add peer") } }) - a.AddHandler("removePeer", []string{"port"}, func(in Info) (Info, error) { + a.AddHandler("disconnectPeer", []string{"port"}, func(in Info) (Info, error) { port, err := strconv.ParseInt(fmt.Sprint(in["port"]), 10, 64) if err != nil { return Info{}, err } if a.core.DisconnectPeer(uint64(port)) == nil { return Info{ - "removed": []string{ + "disconnected": []string{ fmt.Sprint(port), }, }, nil } else { return Info{ - "not_removed": []string{ + "not_disconnected": []string{ fmt.Sprint(port), }, + }, errors.New("Failed to disconnect peer") + } + }) + a.AddHandler("removePeer", []string{"uri", "[interface]"}, func(in Info) (Info, error) { + // Set sane defaults + intf := "" + // Has interface been specified? + if itf, ok := in["interface"]; ok { + intf = itf.(string) + } + if a.core.RemovePeer(in["uri"].(string), intf) == nil { + return Info{ + "removed": []string{ + in["uri"].(string), + }, + }, nil + } else { + return Info{ + "not_removed": []string{ + in["uri"].(string), + }, }, errors.New("Failed to remove peer") } }) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 82d0aa93..be905cbc 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -468,12 +468,31 @@ func (c *Core) AddPeer(addr string, sintf string) error { return nil } -// RemovePeer is not implemented yet. func (c *Core) RemovePeer(addr string, sintf string) error { - // TODO: Implement a reverse of AddPeer, where we look up the port number - // based on the addr and sintf, disconnect it and then remove it from the - // peers list so we don't reconnect to it later - return errors.New("not implemented") + if sintf == "" { + for i, peer := range c.config.Current.Peers { + if peer == addr { + c.config.Current.Peers = append(c.config.Current.Peers[:i], c.config.Current.Peers[i+1:]...) + break + } + } + } else if _, ok := c.config.Current.InterfacePeers[sintf]; ok { + for i, peer := range c.config.Current.InterfacePeers[sintf] { + if peer == addr { + c.config.Current.InterfacePeers[sintf] = append(c.config.Current.InterfacePeers[sintf][:i], c.config.Current.InterfacePeers[sintf][i+1:]...) + break + } + } + } + + ports := c.peers.ports.Load().(map[switchPort]*peer) + for p, peer := range ports { + if addr == peer.intf.name { + c.peers.removePeer(p) + } + } + + return nil } // CallPeer calls a peer once. This should be specified in the peer URI format, From d9fd68f18cd4214004e969b546c8f26e915f8934 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 6 Jul 2020 14:21:28 +0100 Subject: [PATCH 0608/1109] Fix build --- src/yggdrasil/api.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 13f4cb10..c800cb0d 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -465,12 +465,14 @@ func (c *Core) RemovePeer(addr string, sintf string) error { } } - ports := c.peers.ports.Load().(map[switchPort]*peer) - for p, peer := range ports { - if addr == peer.intf.name { - c.peers.removePeer(p) + c.peers.Act(nil, func() { + ports := c.peers.ports + for _, peer := range ports { + if addr == peer.intf.name() { + c.peers._removePeer(peer) + } } - } + }) return nil } From 33e36794587305a578c4ebe83e79bae8c2f7ea77 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 27 Sep 2020 07:16:51 -0500 Subject: [PATCH 0609/1109] multicast, use the prebuilt interface map when checking active listeners (#707) --- src/multicast/multicast.go | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 7de3cfab..33cb29ab 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -259,17 +259,13 @@ func (m *Multicast) _announce() { continue } // Find the interface that matches the listener - if intf, err := net.InterfaceByName(name); err == nil { - if addrs, err := intf.Addrs(); err == nil { - // Loop through the addresses attached to that listener and see if any - // of them match the current address of the listener - for _, addr := range addrs { - if ip, _, err := net.ParseCIDR(addr.String()); err == nil { - // Does the interface address match our listener address? - if ip.Equal(listenaddr.IP) { - found = true - break - } + if info, ok := m._interfaces[name]; ok { + for _, addr := range info.addrs { + if ip, _, err := net.ParseCIDR(addr.String()); err == nil { + // Does the interface address match our listener address? + if ip.Equal(listenaddr.IP) { + found = true + break } } } From fcb6f5ca365e7cb23ff89ee21fad3deb91b926c7 Mon Sep 17 00:00:00 2001 From: Ryan Westlund Date: Sun, 27 Sep 2020 08:22:49 -0400 Subject: [PATCH 0610/1109] Set default conf file on FreeBSD to /usr/local/etc/yggdrasil.conf (#717) --- src/defaults/defaults_freebsd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/defaults/defaults_freebsd.go b/src/defaults/defaults_freebsd.go index b08d80d0..a57be2fa 100644 --- a/src/defaults/defaults_freebsd.go +++ b/src/defaults/defaults_freebsd.go @@ -10,7 +10,7 @@ func GetDefaults() platformDefaultParameters { DefaultAdminListen: "unix:///var/run/yggdrasil.sock", // Configuration (used for yggdrasilctl) - DefaultConfigFile: "/etc/yggdrasil.conf", + DefaultConfigFile: "/usr/local/etc/yggdrasil.conf", // Multicast interfaces DefaultMulticastInterfaces: []string{ From 7588a55e84c81a4d152e0f438db54511961028fa Mon Sep 17 00:00:00 2001 From: asymmetric Date: Sun, 27 Sep 2020 15:24:19 +0300 Subject: [PATCH 0611/1109] README: mention Nix package (#689) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dcc9bd91..aa4a46c1 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ some of the below: - Linux - `.deb` and `.rpm` packages are built by CI for Debian and Red Hat-based distributions - - Void and Arch packages also available within their respective repositories + - Arch, Nix, Void packages also available within their respective repositories - macOS - `.pkg` packages are built by CI - Ubiquiti EdgeOS From e09ca6a089cbb138e7743931f34fe766bf062bd6 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 6 Jul 2020 14:21:28 +0100 Subject: [PATCH 0612/1109] Fix build --- src/yggdrasil/api.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index be905cbc..4232f3c4 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -485,12 +485,14 @@ func (c *Core) RemovePeer(addr string, sintf string) error { } } - ports := c.peers.ports.Load().(map[switchPort]*peer) - for p, peer := range ports { - if addr == peer.intf.name { - c.peers.removePeer(p) + c.peers.Act(nil, func() { + ports := c.peers.ports + for _, peer := range ports { + if addr == peer.intf.name() { + c.peers._removePeer(peer) + } } - } + }) return nil } From 48bf0ce2109542d2fa010d6878bcd4a3f40307ba Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 27 Sep 2020 13:28:13 +0100 Subject: [PATCH 0613/1109] Revert "Fix build" This reverts commit e09ca6a089cbb138e7743931f34fe766bf062bd6. --- src/yggdrasil/api.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 4232f3c4..be905cbc 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -485,14 +485,12 @@ func (c *Core) RemovePeer(addr string, sintf string) error { } } - c.peers.Act(nil, func() { - ports := c.peers.ports - for _, peer := range ports { - if addr == peer.intf.name() { - c.peers._removePeer(peer) - } + ports := c.peers.ports.Load().(map[switchPort]*peer) + for p, peer := range ports { + if addr == peer.intf.name { + c.peers.removePeer(p) } - }) + } return nil } From 1492738c9e05ad77accea0dbbdd275bc98d7d4b8 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 27 Sep 2020 14:28:25 +0100 Subject: [PATCH 0614/1109] golangci-lint in CI (#733) * golangci-lint in CI * Put CI in own job * Run verify job * Use go get * Fix typo * Name lint instead of verify * Read the config * Use debug tag * Tweaks --- .circleci/config.yml | 26 ++++++++- .golangci.yml | 10 ++++ contrib/config/yggdrasilconf.go | 97 --------------------------------- misc/sim/treesim.go | 28 ++++++---- 4 files changed, 49 insertions(+), 112 deletions(-) create mode 100644 .golangci.yml delete mode 100644 contrib/config/yggdrasilconf.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 3f555df5..c1c8cf4a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,6 +3,19 @@ # Check https://circleci.com/docs/2.0/language-go/ for more details version: 2.1 jobs: + lint: + docker: + - image: circleci/golang:1.14.1 + + steps: + - checkout + + - run: + name: Run golangci-lint + command: | + go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.31.0 + golangci-lint run + build-linux: docker: - image: circleci/golang:1.14.1 @@ -201,9 +214,16 @@ workflows: version: 2.1 build: jobs: - - build-linux - - build-macos - - build-other + - lint + - build-linux: + requires: + - lint + - build-macos: + requires: + - lint + - build-other: + requires: + - lint - upload: requires: - build-linux diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000..c35edee4 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,10 @@ +run: + build-tags: + - lint + issues-exit-code: 0 # TODO: change this to 1 when we want it to fail builds + skip-dirs: + - contrib/ + - misc/ +linters: + disable: + - gocyclo \ No newline at end of file diff --git a/contrib/config/yggdrasilconf.go b/contrib/config/yggdrasilconf.go deleted file mode 100644 index ad55e163..00000000 --- a/contrib/config/yggdrasilconf.go +++ /dev/null @@ -1,97 +0,0 @@ -package main - -/* -This is a small utility that is designed to accompany the vyatta-yggdrasil -package. It takes a HJSON configuration file, makes changes to it based on -the command line arguments, and then spits out an updated file. -*/ - -import ( - "bytes" - "encoding/json" - "flag" - "fmt" - "io/ioutil" - "strconv" - - "github.com/hjson/hjson-go" - "golang.org/x/text/encoding/unicode" - - "github.com/yggdrasil-network/yggdrasil-go/src/config" -) - -type nodeConfig = config.NodeConfig - -func main() { - useconffile := flag.String("useconffile", "/etc/yggdrasil.conf", "update config at specified file path") - flag.Parse() - cfg := nodeConfig{} - var config []byte - var err error - config, err = ioutil.ReadFile(*useconffile) - if err != nil { - panic(err) - } - if bytes.Compare(config[0:2], []byte{0xFF, 0xFE}) == 0 || - bytes.Compare(config[0:2], []byte{0xFE, 0xFF}) == 0 { - utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM) - decoder := utf.NewDecoder() - config, err = decoder.Bytes(config) - if err != nil { - panic(err) - } - } - var dat map[string]interface{} - if err := hjson.Unmarshal(config, &dat); err != nil { - panic(err) - } - confJson, err := json.Marshal(dat) - if err != nil { - panic(err) - } - json.Unmarshal(confJson, &cfg) - switch flag.Arg(0) { - case "setMTU": - cfg.IfMTU, err = strconv.Atoi(flag.Arg(1)) - if err != nil { - cfg.IfMTU = 1280 - } - if mtu, _ := strconv.Atoi(flag.Arg(1)); mtu < 1280 { - cfg.IfMTU = 1280 - } - case "setIfName": - cfg.IfName = flag.Arg(1) - case "setListen": - cfg.Listen = flag.Arg(1) - case "setAdminListen": - cfg.AdminListen = flag.Arg(1) - case "setIfTapMode": - if flag.Arg(1) == "true" { - cfg.IfTAPMode = true - } else { - cfg.IfTAPMode = false - } - case "addPeer": - found := false - for _, v := range cfg.Peers { - if v == flag.Arg(1) { - found = true - } - } - if !found { - cfg.Peers = append(cfg.Peers, flag.Arg(1)) - } - case "removePeer": - for k, v := range cfg.Peers { - if v == flag.Arg(1) { - cfg.Peers = append(cfg.Peers[:k], cfg.Peers[k+1:]...) - } - } - } - bs, err := hjson.Marshal(cfg) - if err != nil { - panic(err) - } - fmt.Println(string(bs)) - return -} diff --git a/misc/sim/treesim.go b/misc/sim/treesim.go index 2b155151..22cf881f 100644 --- a/misc/sim/treesim.go +++ b/misc/sim/treesim.go @@ -1,20 +1,24 @@ +// +build !lint + package main -import "fmt" -import "bufio" -import "os" -import "strings" -import "strconv" -import "time" +import ( + "bufio" + "flag" + "fmt" + "os" + "runtime" + "runtime/pprof" + "strconv" + "strings" + "time" -import "runtime" -import "runtime/pprof" -import "flag" + "github.com/gologme/log" -import "github.com/gologme/log" + . "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" -import . "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" -import . "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + . "github.com/yggdrasil-network/yggdrasil-go/src/crypto" +) //////////////////////////////////////////////////////////////////////////////// From d6d2d9c19a9fddad0987252f33b471a25c4a7746 Mon Sep 17 00:00:00 2001 From: Ryan Westlund Date: Sun, 27 Sep 2020 09:42:46 -0400 Subject: [PATCH 0615/1109] Accept some golint suggestions (#690) * Fixed some linter issues * Simplified isBetter method * Accept some linter suggestions * Fix typo Co-authored-by: klesomik Co-authored-by: Neil Alexander --- cmd/genkeys/main.go | 21 +++++++-------- cmd/yggdrasil/main.go | 10 +++---- cmd/yggdrasilctl/main.go | 4 +-- src/admin/admin.go | 56 +++++++++++++++++++-------------------- src/crypto/crypto.go | 2 +- src/tuntap/admin.go | 12 +++------ src/tuntap/iface.go | 1 - src/util/cancellation.go | 9 +++---- src/util/util.go | 3 +-- src/yggdrasil/conn.go | 8 +++--- src/yggdrasil/link.go | 19 +++++++------ src/yggdrasil/nodeinfo.go | 6 ++--- src/yggdrasil/switch.go | 4 +-- src/yggdrasil/tcp.go | 8 +++--- src/yggdrasil/version.go | 6 ++--- 15 files changed, 75 insertions(+), 94 deletions(-) diff --git a/cmd/genkeys/main.go b/cmd/genkeys/main.go index 7b48cdc7..cfab7ec1 100644 --- a/cmd/genkeys/main.go +++ b/cmd/genkeys/main.go @@ -51,7 +51,7 @@ func main() { for { newKey := <-newKeys - if isBetter(currentBest[:], newKey.id[:]) || len(currentBest) == 0 { + if isBetter(currentBest, newKey.id[:]) || len(currentBest) == 0 { currentBest = newKey.id for _, channel := range threadChannels { select { @@ -61,13 +61,13 @@ func main() { fmt.Println("--------------------------------------------------------------------------------") switch { case *doSig: - fmt.Println("sigPriv:", hex.EncodeToString(newKey.priv[:])) - fmt.Println("sigPub:", hex.EncodeToString(newKey.pub[:])) - fmt.Println("TreeID:", hex.EncodeToString(newKey.id[:])) + fmt.Println("sigPriv:", hex.EncodeToString(newKey.priv)) + fmt.Println("sigPub:", hex.EncodeToString(newKey.pub)) + fmt.Println("TreeID:", hex.EncodeToString(newKey.id)) default: - fmt.Println("boxPriv:", hex.EncodeToString(newKey.priv[:])) - fmt.Println("boxPub:", hex.EncodeToString(newKey.pub[:])) - fmt.Println("NodeID:", hex.EncodeToString(newKey.id[:])) + fmt.Println("boxPriv:", hex.EncodeToString(newKey.priv)) + fmt.Println("boxPub:", hex.EncodeToString(newKey.pub)) + fmt.Println("NodeID:", hex.EncodeToString(newKey.id)) fmt.Println("IP:", newKey.ip) } } @@ -76,11 +76,8 @@ func main() { func isBetter(oldID, newID []byte) bool { for idx := range oldID { - if newID[idx] > oldID[idx] { - return true - } - if newID[idx] < oldID[idx] { - return false + if newID[idx] != oldID[idx] { + return newID[idx] > oldID[idx] } } return false diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 813e950b..14b76c34 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -60,8 +60,8 @@ func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *config // throwing everywhere when it's converting things into UTF-16 for the hell // of it - remove it and decode back down into UTF-8. This is necessary // because hjson doesn't know what to do with UTF-16 and will panic - if bytes.Compare(conf[0:2], []byte{0xFF, 0xFE}) == 0 || - bytes.Compare(conf[0:2], []byte{0xFE, 0xFF}) == 0 { + if bytes.Equal(conf[0:2], []byte{0xFF, 0xFE}) || + bytes.Equal(conf[0:2], []byte{0xFE, 0xFF}) { utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM) decoder := utf.NewDecoder() conf, err = decoder.Bytes(conf) @@ -222,7 +222,7 @@ func main() { getNodeID := func() *crypto.NodeID { if pubkey, err := hex.DecodeString(cfg.EncryptionPublicKey); err == nil { var box crypto.BoxPubKey - copy(box[:], pubkey[:]) + copy(box[:], pubkey) return crypto.GetNodeID(&box) } return nil @@ -328,9 +328,9 @@ func main() { // deferred Stop function above will run which will shut down TUN/TAP. for { select { - case _ = <-c: + case <-c: goto exit - case _ = <-r: + case <-r: if *useconffile != "" { cfg = readConfig(useconf, useconffile, normaliseconf) logger.Infoln("Reloading configuration from", *useconffile) diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index 697a3faa..38d935a6 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -78,8 +78,8 @@ func run() int { if *server == endpoint { if config, err := ioutil.ReadFile(defaults.GetDefaults().DefaultConfigFile); err == nil { - if bytes.Compare(config[0:2], []byte{0xFF, 0xFE}) == 0 || - bytes.Compare(config[0:2], []byte{0xFE, 0xFF}) == 0 { + if bytes.Equal(config[0:2], []byte{0xFF, 0xFE}) || + bytes.Equal(config[0:2], []byte{0xFE, 0xFF}) { utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM) decoder := utf.NewDecoder() config, err = decoder.Bytes(config) diff --git a/src/admin/admin.go b/src/admin/admin.go index 5ea5ab89..bdb7cd3a 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -53,7 +53,7 @@ func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc func(In return nil } -// 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{}) error { a.core = c a.log = log @@ -181,13 +181,12 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { in["uri"].(string), }, }, nil - } else { - return Info{ - "not_added": []string{ - in["uri"].(string), - }, - }, errors.New("Failed to add peer") } + return Info{ + "not_added": []string{ + in["uri"].(string), + }, + }, errors.New("Failed to add peer") }) a.AddHandler("disconnectPeer", []string{"port"}, func(in Info) (Info, error) { port, err := strconv.ParseInt(fmt.Sprint(in["port"]), 10, 64) @@ -228,6 +227,11 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { }, }, errors.New("Failed to remove peer") } + return Info{ + "not_removed": []string{ + in["uri"].(string), + }, + }, errors.New("Failed to remove peer") }) a.AddHandler("getAllowedEncryptionPublicKeys", []string{}, func(in Info) (Info, error) { return Info{"allowed_box_pubs": a.core.GetAllowedEncryptionPublicKeys()}, nil @@ -239,13 +243,12 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { in["box_pub_key"].(string), }, }, nil - } else { - return Info{ - "not_added": []string{ - in["box_pub_key"].(string), - }, - }, errors.New("Failed to add allowed key") } + return Info{ + "not_added": []string{ + in["box_pub_key"].(string), + }, + }, errors.New("Failed to add allowed key") }) a.AddHandler("removeAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) { if a.core.RemoveAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { @@ -254,13 +257,12 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { in["box_pub_key"].(string), }, }, nil - } else { - return Info{ - "not_removed": []string{ - in["box_pub_key"].(string), - }, - }, errors.New("Failed to remove allowed key") } + return Info{ + "not_removed": []string{ + in["box_pub_key"].(string), + }, + }, errors.New("Failed to remove allowed key") }) a.AddHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in Info) (Info, error) { var reserr error @@ -271,10 +273,10 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { coords := util.DecodeCoordString(in["coords"].(string)) var boxPubKey crypto.BoxPubKey if b, err := hex.DecodeString(in["box_pub_key"].(string)); err == nil { - copy(boxPubKey[:], b[:]) + copy(boxPubKey[:], b) if n, err := hex.DecodeString(in["target"].(string)); err == nil { var targetNodeID crypto.NodeID - copy(targetNodeID[:], n[:]) + copy(targetNodeID[:], n) result, reserr = a.core.DHTPing(boxPubKey, coords, &targetNodeID) } else { result, reserr = a.core.DHTPing(boxPubKey, coords, nil) @@ -308,14 +310,13 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { var jsoninfo interface{} if err := json.Unmarshal(nodeinfo, &jsoninfo); err != nil { return Info{}, err - } else { - return Info{"nodeinfo": jsoninfo}, nil } + return Info{"nodeinfo": jsoninfo}, nil } else if in["box_pub_key"] == nil || in["coords"] == nil { return Info{}, errors.New("Expecting both box_pub_key and coords") } else { if b, err := hex.DecodeString(in["box_pub_key"].(string)); err == nil { - copy(boxPubKey[:], b[:]) + copy(boxPubKey[:], b) } else { return Info{}, err } @@ -326,12 +327,10 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { var m map[string]interface{} if err = json.Unmarshal(result, &m); err == nil { return Info{"nodeinfo": m}, nil - } else { - return Info{}, err } - } else { return Info{}, err } + return Info{}, err }) } @@ -354,9 +353,8 @@ func (a *AdminSocket) Stop() error { if a.listener != nil { a.started = false return a.listener.Close() - } else { - return nil } + return nil } // listen is run by start and manages API connections. diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go index 211a0e54..dd4013a7 100644 --- a/src/crypto/crypto.go +++ b/src/crypto/crypto.go @@ -272,7 +272,7 @@ func (n *BoxNonce) Increment() { n[len(n)-1] += 2 for i := len(n) - 2; i >= 0; i-- { if n[i+1] < oldNonce[i+1] { - n[i] += 1 + n[i]++ } } } diff --git a/src/tuntap/admin.go b/src/tuntap/admin.go index c7fc20b0..eb1eb69b 100644 --- a/src/tuntap/admin.go +++ b/src/tuntap/admin.go @@ -68,16 +68,14 @@ func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { a.AddHandler("addLocalSubnet", []string{"subnet"}, func(in admin.Info) (admin.Info, error) { if err := t.ckr.addLocalSubnet(in["subnet"].(string)); err == nil { return admin.Info{"added": []string{in["subnet"].(string)}}, nil - } else { - return admin.Info{"not_added": []string{in["subnet"].(string)}}, errors.New("Failed to add source subnet") } + return admin.Info{"not_added": []string{in["subnet"].(string)}}, errors.New("Failed to add source subnet") }) a.AddHandler("addRemoteSubnet", []string{"subnet", "box_pub_key"}, func(in admin.Info) (admin.Info, error) { if err := t.ckr.addRemoteSubnet(in["subnet"].(string), in["box_pub_key"].(string)); err == nil { return admin.Info{"added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil - } else { - return admin.Info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to add route") } + return admin.Info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to add route") }) a.AddHandler("getSourceSubnets", []string{}, func(in admin.Info) (admin.Info, error) { var subnets []string @@ -104,15 +102,13 @@ func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { a.AddHandler("removeLocalSubnet", []string{"subnet"}, func(in admin.Info) (admin.Info, error) { if err := t.ckr.removeLocalSubnet(in["subnet"].(string)); err == nil { return admin.Info{"removed": []string{in["subnet"].(string)}}, nil - } else { - return admin.Info{"not_removed": []string{in["subnet"].(string)}}, errors.New("Failed to remove source subnet") } + return admin.Info{"not_removed": []string{in["subnet"].(string)}}, errors.New("Failed to remove source subnet") }) a.AddHandler("removeRemoteSubnet", []string{"subnet", "box_pub_key"}, func(in admin.Info) (admin.Info, error) { if err := t.ckr.removeRemoteSubnet(in["subnet"].(string), in["box_pub_key"].(string)); err == nil { return admin.Info{"removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil - } else { - return admin.Info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route") } + return admin.Info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route") }) } diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 1e5902e8..bd849c7f 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -199,7 +199,6 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { tc.writeFrom(nil, packet) } }) - return }() } } diff --git a/src/util/cancellation.go b/src/util/cancellation.go index 1f6d1658..7fc83147 100644 --- a/src/util/cancellation.go +++ b/src/util/cancellation.go @@ -54,12 +54,11 @@ func (c *cancellation) Cancel(err error) error { defer c.mutex.Unlock() if c.done { return c.err - } else { - c.err = err - c.done = true - close(c.cancel) - return nil } + c.err = err + c.done = true + close(c.cancel) + return nil } // Error returns the error provided to Cancel, or nil if no error has been provided. diff --git a/src/util/util.go b/src/util/util.go index c20421d5..d7a7443d 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -30,9 +30,8 @@ func UnlockThread() { func ResizeBytes(bs []byte, length int) []byte { if cap(bs) >= length { return bs[:length] - } else { - return make([]byte, length) } + return make([]byte, length) } // TimerStop stops a timer and makes sure the channel is drained, returns true if the timer was stopped before firing. diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 4cd8b205..1b7a5a1e 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -167,10 +167,9 @@ func (c *Conn) _getDeadlineCancellation(t *time.Time) (util.Cancellation, bool) // A deadline is set, so return a Cancellation that uses it c := util.CancellationWithDeadline(c.session.cancel, *t) return c, true - } else { - // No deadline was set, so just return the existing cancellation and a dummy value - return c.session.cancel, false } + // No deadline was set, so just return the existing cancellation and a dummy value + return c.session.cancel, false } // SetReadCallback allows you to specify a function that will be called whenever @@ -225,9 +224,8 @@ func (c *Conn) readNoCopy() ([]byte, error) { case <-cancel.Finished(): if cancel.Error() == util.CancellationTimeoutError { return nil, ConnError{errors.New("read timeout"), true, false, false, 0} - } else { - return nil, ConnError{errors.New("session closed"), false, false, true, 0} } + return nil, ConnError{errors.New("session closed"), false, false, true, 0} case bs := <-c.readBuffer: return bs, nil } diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 0b37eaa2..01d4aff4 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -253,17 +253,16 @@ func (intf *linkInterface) handler() error { <-oldIntf.closed } return nil - } else { - intf.closed = make(chan struct{}) - intf.link.interfaces[intf.info] = intf - defer func() { - intf.link.mutex.Lock() - delete(intf.link.interfaces, intf.info) - intf.link.mutex.Unlock() - close(intf.closed) - }() - intf.link.core.log.Debugln("DEBUG: registered interface for", intf.name) } + intf.closed = make(chan struct{}) + intf.link.interfaces[intf.info] = intf + defer func() { + intf.link.mutex.Lock() + delete(intf.link.interfaces, intf.info) + intf.link.mutex.Unlock() + close(intf.closed) + }() + intf.link.core.log.Debugln("DEBUG: registered interface for", intf.name) intf.link.mutex.Unlock() // Create peer shared := crypto.GetSharedKey(myLinkPriv, &meta.link) diff --git a/src/yggdrasil/nodeinfo.go b/src/yggdrasil/nodeinfo.go index fc6250d6..8d502f41 100644 --- a/src/yggdrasil/nodeinfo.go +++ b/src/yggdrasil/nodeinfo.go @@ -136,15 +136,15 @@ func (m *nodeinfo) _setNodeInfo(given interface{}, privacy bool) error { newnodeinfo[key] = value } } - if newjson, err := json.Marshal(newnodeinfo); err == nil { + newjson, err := json.Marshal(newnodeinfo) + if err == nil { if len(newjson) > 16384 { return errors.New("NodeInfo exceeds max length of 16384 bytes") } m.myNodeInfo = newjson return nil - } else { - return err } + return err } // Add nodeinfo into the cache for a node diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 5e4d3e92..23298e76 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -532,7 +532,6 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep if true || doUpdate { t.updater.Store(&sync.Once{}) } - return } //////////////////////////////////////////////////////////////////////////////// @@ -615,9 +614,8 @@ func (t *switchTable) portIsCloser(dest []byte, port switchPort) bool { theirDist := info.locator.dist(dest) myDist := table.self.dist(dest) return theirDist < myDist - } else { - return false } + return false } // Get the coords of a packet without decoding diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 129fc0ee..2bc81de1 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -205,10 +205,9 @@ func (t *tcp) listener(l *TcpListener, listenaddr string) { t.mutex.Unlock() l.Listener.Close() return - } else { - t.listeners[listenaddr] = l - t.mutex.Unlock() } + t.listeners[listenaddr] = l + t.mutex.Unlock() // And here we go! defer func() { t.link.core.log.Infoln("Stopping TCP listener on:", l.Listener.Addr().String()) @@ -375,9 +374,8 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options tcpOptions) { if sock, err = options.upgrade.upgrade(sock); err != nil { t.link.core.log.Errorln("TCP handler upgrade failed:", err) return - } else { - upgraded = true } + upgraded = true } stream := stream{} stream.init(sock) diff --git a/src/yggdrasil/version.go b/src/yggdrasil/version.go index 91fcc825..e0cb38e3 100644 --- a/src/yggdrasil/version.go +++ b/src/yggdrasil/version.go @@ -28,11 +28,11 @@ func version_getBaseMetadata() version_metadata { } } -// Gest the length of the metadata for this version, used to know how many bytes to read from the start of a connection. +// Gets the length of the metadata for this version, used to know how many bytes to read from the start of a connection. func version_getMetaLength() (mlen int) { mlen += 4 // meta - mlen += 1 // ver, as long as it's < 127, which it is in this version - mlen += 1 // minorVer, as long as it's < 127, which it is in this version + mlen++ // ver, as long as it's < 127, which it is in this version + mlen++ // minorVer, as long as it's < 127, which it is in this version mlen += crypto.BoxPubKeyLen // box mlen += crypto.SigPubKeyLen // sig mlen += crypto.BoxPubKeyLen // link From ba7be10a2f958ccd34f42bba21dde8b34d4042ae Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 27 Sep 2020 15:05:14 +0100 Subject: [PATCH 0616/1109] Update changelog --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b0216eb..1017e8d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,27 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> +## [0.3.15] - 2020-09-27 +### Added +- Support for pinning remote public keys in peering strings has been added, e.g. + - By signing public key: `tcp://host:port?ed25519=key` + - By encryption public key: `tcp://host:port?curve25519=key` + - By both: `tcp://host:port?ed25519=key&curve25519=key` + - By multiple, in case of DNS round-robin or similar: `tcp://host:port?curve25519=key&curve25519=key&ed25519=key&ed25519=key` +- Some checks to prevent Yggdrasil-over-Yggdrasil peerings have been added +- Added support for SOCKS proxy authentication, e.g. `socks://user@password:host/...` + +### Fixed +- Some bugs in the multicast code that could cause unnecessary CPU usage have been fixed +- A possible multicast deadlock on macOS when enumerating interfaces has been fixed +- A deadlock in the connection code has been fixed +- Updated HJSON dependency that caused some build problems + +### Changed +- `DisconnectPeer` and `RemovePeer` have been separated and implemented properly now +- Less nodes are stored in the DHT now, reducing ambient network traffic and possible instability +- Default config file for FreeBSD is now at `/usr/local/etc/yggdrasil.conf` instead of `/etc/yggdrasil.conf` + ## [0.3.14] - 2020-03-28 ### Fixed - Fixes a memory leak that may occur if packets are incorrectly never removed from a switch queue From e90d40a49e30c601c1df1d5174d0341939ca51e1 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 11 Oct 2020 16:41:40 +0100 Subject: [PATCH 0617/1109] Don't require lint in pipeline --- .circleci/config.yml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c1c8cf4a..2f4552de 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -215,15 +215,9 @@ workflows: build: jobs: - lint - - build-linux: - requires: - - lint - - build-macos: - requires: - - lint - - build-other: - requires: - - lint + - build-linux + - build-macos + - build-other - upload: requires: - build-linux From 1d1c6efa1fab9d444bfdfe40cf9bdd50216a4942 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 18 Oct 2020 11:01:18 -0500 Subject: [PATCH 0618/1109] attempt to keep TCP listener alive if there's a temporary error (e.g. too many open files), just pause and retry later --- src/multicast/multicast.go | 4 +++- src/yggdrasil/tcp.go | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 33cb29ab..430e9374 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -191,7 +191,9 @@ func (m *Multicast) getAllowedInterfaces() map[string]net.Interface { // Ask the system for network interfaces allifaces, err := net.Interfaces() if err != nil { - panic(err) + // Don't panic, since this may be from e.g. too many open files (from too much connection spam) + // TODO? log something + return nil } // Work out which interfaces to announce on for _, iface := range allifaces { diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 15d70470..ad9d872e 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -226,7 +226,13 @@ func (t *tcp) listener(l *TcpListener, listenaddr string) { sock, err := l.Listener.Accept() if err != nil { t.links.core.log.Errorln("Failed to accept connection:", err) - return + select { + case <-l.stop: + return + default: + } + time.Sleep(time.Second) // So we don't busy loop + continue } t.waitgroup.Add(1) options := tcpOptions{ From 92dbb48eda4f412831d34f206e9b36d5c0f4ddb5 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 7 Nov 2020 06:18:09 -0600 Subject: [PATCH 0619/1109] add (but don't use) offset field for (protocol) traffic packets --- src/yggdrasil/packetqueue.go | 3 ++- src/yggdrasil/peer.go | 9 +-------- src/yggdrasil/version.go | 2 +- src/yggdrasil/wire.go | 18 ++++++++++++++++++ 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/yggdrasil/packetqueue.go b/src/yggdrasil/packetqueue.go index 6273e6c8..61f3cfd2 100644 --- a/src/yggdrasil/packetqueue.go +++ b/src/yggdrasil/packetqueue.go @@ -54,7 +54,8 @@ func (q *packetQueue) drop() bool { } func (q *packetQueue) push(packet []byte) { - id := pqStreamID(peer_getPacketCoords(packet)) // just coords for now + _, coords := wire_getTrafficOffsetAndCoords(packet) + id := pqStreamID(coords) // just coords for now info := pqPacketInfo{packet: packet, time: time.Now()} for idx := range q.streams { if q.streams[idx].id == id { diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index f04ab280..1d1b8054 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -236,13 +236,6 @@ func (p *peer) _handlePacket(packet []byte) { } } -// Get the coords of a packet without decoding -func peer_getPacketCoords(packet []byte) []byte { - _, pTypeLen := wire_decode_uint64(packet) - coords, _ := wire_decode_coords(packet[pTypeLen:]) - return coords -} - // Called to handle traffic or protocolTraffic packets. // In either case, this reads from the coords of the packet header, does a switch lookup, and forwards to the next node. func (p *peer) _handleTraffic(packet []byte) { @@ -250,7 +243,7 @@ func (p *peer) _handleTraffic(packet []byte) { // Drop traffic if the peer isn't in the switch return } - coords := peer_getPacketCoords(packet) + _, coords := wire_getTrafficOffsetAndCoords(packet) next := p.table.lookup(coords) if nPeer, isIn := p.ports[next]; isIn { nPeer.sendPacketFrom(p, packet) diff --git a/src/yggdrasil/version.go b/src/yggdrasil/version.go index e0cb38e3..f8ce85b8 100644 --- a/src/yggdrasil/version.go +++ b/src/yggdrasil/version.go @@ -24,7 +24,7 @@ func version_getBaseMetadata() version_metadata { return version_metadata{ meta: [4]byte{'m', 'e', 't', 'a'}, ver: 0, - minorVer: 2, + minorVer: 0, } } diff --git a/src/yggdrasil/wire.go b/src/yggdrasil/wire.go index 9746d46e..9c871031 100644 --- a/src/yggdrasil/wire.go +++ b/src/yggdrasil/wire.go @@ -222,6 +222,7 @@ func wire_chop_uint64(toUInt64 *uint64, fromSlice *[]byte) bool { // The wire format for ordinary IPv6 traffic encapsulated by the network. type wire_trafficPacket struct { + Offset uint64 Coords []byte Handle crypto.Handle Nonce crypto.BoxNonce @@ -233,6 +234,7 @@ type wire_trafficPacket struct { func (p *wire_trafficPacket) encode() []byte { bs := pool_getBytes(0) bs = wire_put_uint64(wire_Traffic, bs) + bs = wire_put_uint64(p.Offset, bs) bs = wire_put_coords(p.Coords, bs) bs = append(bs, p.Handle[:]...) bs = append(bs, p.Nonce[:]...) @@ -250,6 +252,8 @@ func (p *wire_trafficPacket) decode(bs []byte) bool { return false case pType != wire_Traffic: return false + case !wire_chop_uint64(&p.Offset, &bs): + return false case !wire_chop_coords(&p.Coords, &bs): return false case !wire_chop_slice(p.Handle[:], &bs): @@ -263,6 +267,7 @@ func (p *wire_trafficPacket) decode(bs []byte) bool { // The wire format for protocol traffic, such as dht req/res or session ping/pong packets. type wire_protoTrafficPacket struct { + Offset uint64 Coords []byte ToKey crypto.BoxPubKey FromKey crypto.BoxPubKey @@ -274,6 +279,7 @@ type wire_protoTrafficPacket struct { func (p *wire_protoTrafficPacket) encode() []byte { coords := wire_encode_coords(p.Coords) bs := wire_encode_uint64(wire_ProtocolTraffic) + bs = wire_put_uint64(p.Offset, bs) bs = append(bs, coords...) bs = append(bs, p.ToKey[:]...) bs = append(bs, p.FromKey[:]...) @@ -290,6 +296,8 @@ func (p *wire_protoTrafficPacket) decode(bs []byte) bool { return false case pType != wire_ProtocolTraffic: return false + case !wire_chop_uint64(&p.Offset, &bs): + return false case !wire_chop_coords(&p.Coords, &bs): return false case !wire_chop_slice(p.ToKey[:], &bs): @@ -303,6 +311,16 @@ func (p *wire_protoTrafficPacket) decode(bs []byte) bool { return true } +// Get the offset and coord slices of a (protocol) traffic packet without decoding +func wire_getTrafficOffsetAndCoords(packet []byte) ([]byte, []byte) { + _, offsetBegin := wire_decode_uint64(packet) + _, offsetLen := wire_decode_uint64(packet[offsetBegin:]) + offsetEnd := offsetBegin + offsetLen + offset := packet[offsetBegin:offsetEnd] + coords, _ := wire_decode_coords(packet[offsetEnd:]) + return offset, coords +} + // The wire format for link protocol traffic, namely switchMsg. // There's really two layers of this, with the outer layer using permanent keys, and the inner layer using ephemeral keys. // The keys themselves are exchanged as part of the connection setup, and then omitted from the packets. From 36e4ce4b0b2748696b6d0e61d3baeb98ad9e64a8 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 7 Nov 2020 07:10:13 -0600 Subject: [PATCH 0620/1109] WIP rough implementation of the source routed part of hybrid routing, does not work if coord length is too long (>127 hops) --- src/yggdrasil/peer.go | 21 +++++++++++++++-- src/yggdrasil/switch.go | 50 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 1d1b8054..b3629311 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -243,8 +243,25 @@ func (p *peer) _handleTraffic(packet []byte) { // Drop traffic if the peer isn't in the switch return } - _, coords := wire_getTrafficOffsetAndCoords(packet) - next := p.table.lookup(coords) + obs, coords := wire_getTrafficOffsetAndCoords(packet) + offset, _ := wire_decode_uint64(obs) + ports := p.table.getPorts(coords) + if offset == 0 { + offset = p.table.getOffset(ports) + } + var next switchPort + if offset == 0 { + // Greedy routing, find the best next hop + next = p.table.lookup(ports) + } else { + // Source routing, read next hop from coords and update offset/obs + if int(offset) < len(ports) { + next = ports[offset] + offset += 1 + // FIXME this breaks if offset is > 127, it's just for testing + wire_put_uint64(offset, obs[:0]) + } + } if nPeer, isIn := p.ports[next]; isIn { nPeer.sendPacketFrom(p, packet) } diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 2f4f3194..4873d8b4 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -631,13 +631,11 @@ func (t *switchTable) start() error { return nil } -func (t *lookupTable) lookup(coords []byte) switchPort { - var offset int +func (t *lookupTable) lookup(ports []switchPort) switchPort { here := &t._start - for offset < len(coords) { - port, l := wire_decode_uint64(coords[offset:]) - offset += l - if next, ok := here.next[switchPort(port)]; ok { + for idx := range ports { + port := ports[idx] + if next, ok := here.next[port]; ok { here = next } else { break @@ -645,3 +643,43 @@ func (t *lookupTable) lookup(coords []byte) switchPort { } return here.port } + +func (t *lookupTable) getPorts(coords []byte) []switchPort { + var ports []switchPort + var offset int + for offset < len(coords) { + port, l := wire_decode_uint64(coords[offset:]) + offset += l + ports = append(ports, switchPort(port)) + } + return ports +} + +func (t *lookupTable) isDescendant(ports []switchPort) bool { + // Note that this returns true for anyone in the subtree that starts at us + // That includes ourself, so we are our own descendant by this logic... + if len(t.self.coords) >= len(ports) { + // Our coords are longer, so they can't be our descendant + return false + } + for idx := range t.self.coords { + if ports[idx] != t.self.coords[idx] { + return false + } + } + return true +} + +func (t *lookupTable) getOffset(ports []switchPort) uint64 { + // If they're our descendant, this returns the length of our coords, used as an offset for source routing + // If they're not our descendant, this returns 0 + var offset uint64 + for idx := range t.self.coords { + if idx < len(ports) && ports[idx] == t.self.coords[idx] { + offset += 1 + } else { + return 0 + } + } + return offset +} From e2521de94d0743800ae53a45dcb666200b5ece21 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 7 Nov 2020 09:44:34 -0600 Subject: [PATCH 0621/1109] add path information to (protocol) traffic packets as they flow through the network, and a field for a reply path --- src/yggdrasil/peer.go | 1 + src/yggdrasil/stream.go | 3 +- src/yggdrasil/wire.go | 63 +++++++++++++++++++++++++---------------- 3 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index b3629311..9f367695 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -262,6 +262,7 @@ func (p *peer) _handleTraffic(packet []byte) { wire_put_uint64(offset, obs[:0]) } } + packet = wire_put_uint64(uint64(next), packet) if nPeer, isIn := p.ports[next]; isIn { nPeer.sendPacketFrom(p, packet) } diff --git a/src/yggdrasil/stream.go b/src/yggdrasil/stream.go index afa97c76..ab6336f1 100644 --- a/src/yggdrasil/stream.go +++ b/src/yggdrasil/stream.go @@ -113,7 +113,8 @@ func (s *stream) readMsgFromBuffer() ([]byte, error) { if msgLen > streamMsgSize { return nil, errors.New("oversized message") } - msg := pool_getBytes(int(msgLen)) + msg := pool_getBytes(int(msgLen + 10)) // Extra padding for up to 1 more switchPort + msg = msg[msgLen:] _, err = io.ReadFull(s.inputBuffer, msg) return msg, err } diff --git a/src/yggdrasil/wire.go b/src/yggdrasil/wire.go index 9c871031..6746cd74 100644 --- a/src/yggdrasil/wire.go +++ b/src/yggdrasil/wire.go @@ -96,9 +96,9 @@ func wire_encode_coords(coords []byte) []byte { // Puts a length prefix and the coords into bs, returns the wire formatted coords. // Useful in hot loops where we don't want to allocate and we know the rest of the later parts of the slice are safe to overwrite. -func wire_put_coords(coords []byte, bs []byte) []byte { - bs = wire_put_uint64(uint64(len(coords)), bs) - bs = append(bs, coords...) +func wire_put_vslice(slice []byte, bs []byte) []byte { + bs = wire_put_uint64(uint64(len(slice)), bs) + bs = append(bs, slice...) return bs } @@ -194,14 +194,14 @@ func wire_chop_slice(toSlice []byte, fromSlice *[]byte) bool { return true } -// A utility function to extract coords from a slice and advance the source slices, returning true if successful. -func wire_chop_coords(toCoords *[]byte, fromSlice *[]byte) bool { - coords, coordLen := wire_decode_coords(*fromSlice) - if coordLen == 0 { +// A utility function to extract a length-prefixed slice (such as coords) from a slice and advance the source slices, returning true if successful. +func wire_chop_vslice(toSlice *[]byte, fromSlice *[]byte) bool { + slice, sliceLen := wire_decode_coords(*fromSlice) + if sliceLen == 0 { // sliceLen is length-prefix size + slice size, in bytes return false } - *toCoords = append((*toCoords)[:0], coords...) - *fromSlice = (*fromSlice)[coordLen:] + *toSlice = append((*toSlice)[:0], slice...) + *fromSlice = (*fromSlice)[sliceLen:] return true } @@ -227,6 +227,8 @@ type wire_trafficPacket struct { Handle crypto.Handle Nonce crypto.BoxNonce Payload []byte + RPath []byte + Path []byte } // Encodes a wire_trafficPacket into its wire format. @@ -235,10 +237,12 @@ func (p *wire_trafficPacket) encode() []byte { bs := pool_getBytes(0) bs = wire_put_uint64(wire_Traffic, bs) bs = wire_put_uint64(p.Offset, bs) - bs = wire_put_coords(p.Coords, bs) + bs = wire_put_vslice(p.Coords, bs) bs = append(bs, p.Handle[:]...) bs = append(bs, p.Nonce[:]...) - bs = append(bs, p.Payload...) + bs = wire_put_vslice(p.Payload, bs) + bs = wire_put_vslice(p.RPath, bs) + bs = append(bs, p.Path...) return bs } @@ -254,14 +258,18 @@ func (p *wire_trafficPacket) decode(bs []byte) bool { return false case !wire_chop_uint64(&p.Offset, &bs): return false - case !wire_chop_coords(&p.Coords, &bs): + case !wire_chop_vslice(&p.Coords, &bs): return false case !wire_chop_slice(p.Handle[:], &bs): return false case !wire_chop_slice(p.Nonce[:], &bs): return false + case !wire_chop_vslice(&p.Payload, &bs): + return false + case !wire_chop_vslice(&p.RPath, &bs): + return false } - p.Payload = append(p.Payload, bs...) + p.Path = bs return true } @@ -273,18 +281,21 @@ type wire_protoTrafficPacket struct { FromKey crypto.BoxPubKey Nonce crypto.BoxNonce Payload []byte + RPath []byte + Path []byte } // Encodes a wire_protoTrafficPacket into its wire format. func (p *wire_protoTrafficPacket) encode() []byte { - coords := wire_encode_coords(p.Coords) bs := wire_encode_uint64(wire_ProtocolTraffic) bs = wire_put_uint64(p.Offset, bs) - bs = append(bs, coords...) + bs = wire_put_vslice(p.Coords, bs) bs = append(bs, p.ToKey[:]...) bs = append(bs, p.FromKey[:]...) bs = append(bs, p.Nonce[:]...) - bs = append(bs, p.Payload...) + bs = wire_put_vslice(p.Payload, bs) + bs = wire_put_vslice(p.RPath, bs) + bs = append(bs, p.Path...) return bs } @@ -298,7 +309,7 @@ func (p *wire_protoTrafficPacket) decode(bs []byte) bool { return false case !wire_chop_uint64(&p.Offset, &bs): return false - case !wire_chop_coords(&p.Coords, &bs): + case !wire_chop_vslice(&p.Coords, &bs): return false case !wire_chop_slice(p.ToKey[:], &bs): return false @@ -306,8 +317,12 @@ func (p *wire_protoTrafficPacket) decode(bs []byte) bool { return false case !wire_chop_slice(p.Nonce[:], &bs): return false + case !wire_chop_vslice(&p.Payload, &bs): + return false + case !wire_chop_vslice(&p.RPath, &bs): + return false } - p.Payload = bs + p.Path = bs return true } @@ -391,7 +406,7 @@ func (p *sessionPing) decode(bs []byte) bool { return false case !wire_chop_uint64(&tstamp, &bs): return false - case !wire_chop_coords(&p.Coords, &bs): + case !wire_chop_vslice(&p.Coords, &bs): return false case !wire_chop_uint64(&mtu, &bs): mtu = 1280 @@ -415,7 +430,7 @@ func (p *nodeinfoReqRes) encode() []byte { pTypeVal = wire_NodeInfoRequest } bs := wire_encode_uint64(pTypeVal) - bs = wire_put_coords(p.SendCoords, bs) + bs = wire_put_vslice(p.SendCoords, bs) if pTypeVal == wire_NodeInfoResponse { bs = append(bs, p.NodeInfo...) } @@ -430,7 +445,7 @@ func (p *nodeinfoReqRes) decode(bs []byte) bool { return false case pType != wire_NodeInfoRequest && pType != wire_NodeInfoResponse: return false - case !wire_chop_coords(&p.SendCoords, &bs): + case !wire_chop_vslice(&p.SendCoords, &bs): return false } if p.IsResponse = pType == wire_NodeInfoResponse; p.IsResponse { @@ -464,7 +479,7 @@ func (r *dhtReq) decode(bs []byte) bool { return false case pType != wire_DHTLookupRequest: return false - case !wire_chop_coords(&r.Coords, &bs): + case !wire_chop_vslice(&r.Coords, &bs): return false case !wire_chop_slice(r.Dest[:], &bs): return false @@ -495,7 +510,7 @@ func (r *dhtRes) decode(bs []byte) bool { return false case pType != wire_DHTLookupResponse: return false - case !wire_chop_coords(&r.Coords, &bs): + case !wire_chop_vslice(&r.Coords, &bs): return false case !wire_chop_slice(r.Dest[:], &bs): return false @@ -505,7 +520,7 @@ func (r *dhtRes) decode(bs []byte) bool { switch { case !wire_chop_slice(info.key[:], &bs): return false - case !wire_chop_coords(&info.coords, &bs): + case !wire_chop_vslice(&info.coords, &bs): return false } r.Infos = append(r.Infos, &info) From b5cd40b801a45baf9eedeba30c2c725014a1a8f9 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 7 Nov 2020 10:50:55 -0600 Subject: [PATCH 0622/1109] WIP very simple insecure proof-of-concept for pathfinding and source routing --- src/yggdrasil/session.go | 19 ++++++++++++++++++- src/yggdrasil/stream.go | 2 +- src/yggdrasil/wire.go | 4 ++-- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 360f2a1b..6a9589fa 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -50,6 +50,8 @@ type sessionInfo struct { conn *Conn // The associated Conn object callbacks []chan func() // Finished work from crypto workers table *lookupTable // table.self is a locator where we get our coords + path []byte // Path from self to destination + rpath []byte // Path from destination to self } // Represents a session ping/pong packet, and includes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU. @@ -328,6 +330,10 @@ func (sinfo *sessionInfo) _sendPingPong(isPong bool) { if sinfo.pingTime.Before(sinfo.time) { sinfo.pingTime = time.Now() } + // Sending a ping may happen when we don't know if our path info is good anymore... + // Reset paths just to be safe... + sinfo.path = nil + sinfo.rpath = nil } func (sinfo *sessionInfo) setConn(from phony.Actor, conn *Conn) { @@ -468,6 +474,8 @@ func (sinfo *sessionInfo) _recvPacket(p *wire_trafficPacket) { sinfo._updateNonce(&p.Nonce) sinfo.bytesRecvd += uint64(len(bs)) sinfo.conn.recvMsg(sinfo, bs) + sinfo.path = append(sinfo.path[:0], p.RPath...) + sinfo.rpath = append(sinfo.rpath[:0], p.Path...) } ch <- callback sinfo.checkCallbacks() @@ -483,15 +491,24 @@ func (sinfo *sessionInfo) _send(msg FlowKeyMessage) { return } sinfo.bytesSent += uint64(len(msg.Message)) - coords := append([]byte(nil), sinfo.coords...) + var coords []byte + var offset uint64 + if len(sinfo.path) > 0 && len(sinfo.path) <= len(sinfo.rpath) { + coords = append([]byte{0}, sinfo.path...) + offset += 1 + } else { + coords = append([]byte(nil), sinfo.coords...) + } if msg.FlowKey != 0 { coords = append(coords, 0) coords = append(coords, wire_encode_uint64(msg.FlowKey)...) } p := wire_trafficPacket{ + Offset: offset, Coords: coords, Handle: sinfo.theirHandle, Nonce: sinfo.myNonce, + RPath: sinfo.rpath, } sinfo.myNonce.Increment() k := sinfo.sharedSesKey diff --git a/src/yggdrasil/stream.go b/src/yggdrasil/stream.go index ab6336f1..ef09c8e7 100644 --- a/src/yggdrasil/stream.go +++ b/src/yggdrasil/stream.go @@ -114,7 +114,7 @@ func (s *stream) readMsgFromBuffer() ([]byte, error) { return nil, errors.New("oversized message") } msg := pool_getBytes(int(msgLen + 10)) // Extra padding for up to 1 more switchPort - msg = msg[msgLen:] + msg = msg[:msgLen] _, err = io.ReadFull(s.inputBuffer, msg) return msg, err } diff --git a/src/yggdrasil/wire.go b/src/yggdrasil/wire.go index 6746cd74..e0c2614a 100644 --- a/src/yggdrasil/wire.go +++ b/src/yggdrasil/wire.go @@ -269,7 +269,7 @@ func (p *wire_trafficPacket) decode(bs []byte) bool { case !wire_chop_vslice(&p.RPath, &bs): return false } - p.Path = bs + p.Path = append(p.Path[:0], bs...) return true } @@ -322,7 +322,7 @@ func (p *wire_protoTrafficPacket) decode(bs []byte) bool { case !wire_chop_vslice(&p.RPath, &bs): return false } - p.Path = bs + p.Path = append(p.Path[:0], bs...) return true } From 994c26e5f71a605ab0b83c195abefce856859453 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 7 Nov 2020 12:08:01 -0600 Subject: [PATCH 0623/1109] simplify pathfinder --- src/yggdrasil/peer.go | 4 ++-- src/yggdrasil/session.go | 14 +++++++++++--- src/yggdrasil/switch.go | 2 +- src/yggdrasil/wire.go | 16 ++++------------ 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 9f367695..74e1023f 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -245,7 +245,7 @@ func (p *peer) _handleTraffic(packet []byte) { } obs, coords := wire_getTrafficOffsetAndCoords(packet) offset, _ := wire_decode_uint64(obs) - ports := p.table.getPorts(coords) + ports := switch_getPorts(coords) if offset == 0 { offset = p.table.getOffset(ports) } @@ -262,7 +262,7 @@ func (p *peer) _handleTraffic(packet []byte) { wire_put_uint64(offset, obs[:0]) } } - packet = wire_put_uint64(uint64(next), packet) + packet = wire_put_uint64(uint64(p.port), packet) if nPeer, isIn := p.ports[next]; isIn { nPeer.sendPacketFrom(p, packet) } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 6a9589fa..4a06beed 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -474,8 +474,16 @@ func (sinfo *sessionInfo) _recvPacket(p *wire_trafficPacket) { sinfo._updateNonce(&p.Nonce) sinfo.bytesRecvd += uint64(len(bs)) sinfo.conn.recvMsg(sinfo, bs) - sinfo.path = append(sinfo.path[:0], p.RPath...) - sinfo.rpath = append(sinfo.rpath[:0], p.Path...) + a := switch_getPorts(p.RPath) + for i := len(a)/2 - 1; i >= 0; i-- { + opp := len(a) - 1 - i + a[i], a[opp] = a[opp], a[i] + } + sinfo.path = sinfo.path[:0] + for _, sPort := range a { + sinfo.path = wire_put_uint64(uint64(sPort), sinfo.path) + } + //sinfo.rpath = append(sinfo.rpath[:0], p.Path...) } ch <- callback sinfo.checkCallbacks() @@ -493,7 +501,7 @@ func (sinfo *sessionInfo) _send(msg FlowKeyMessage) { sinfo.bytesSent += uint64(len(msg.Message)) var coords []byte var offset uint64 - if len(sinfo.path) > 0 && len(sinfo.path) <= len(sinfo.rpath) { + if len(sinfo.path) > 0 { coords = append([]byte{0}, sinfo.path...) offset += 1 } else { diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 4873d8b4..c291d82d 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -644,7 +644,7 @@ func (t *lookupTable) lookup(ports []switchPort) switchPort { return here.port } -func (t *lookupTable) getPorts(coords []byte) []switchPort { +func switch_getPorts(coords []byte) []switchPort { var ports []switchPort var offset int for offset < len(coords) { diff --git a/src/yggdrasil/wire.go b/src/yggdrasil/wire.go index e0c2614a..e8e9bf09 100644 --- a/src/yggdrasil/wire.go +++ b/src/yggdrasil/wire.go @@ -228,7 +228,6 @@ type wire_trafficPacket struct { Nonce crypto.BoxNonce Payload []byte RPath []byte - Path []byte } // Encodes a wire_trafficPacket into its wire format. @@ -241,8 +240,7 @@ func (p *wire_trafficPacket) encode() []byte { bs = append(bs, p.Handle[:]...) bs = append(bs, p.Nonce[:]...) bs = wire_put_vslice(p.Payload, bs) - bs = wire_put_vslice(p.RPath, bs) - bs = append(bs, p.Path...) + bs = append(bs, p.RPath...) return bs } @@ -266,10 +264,8 @@ func (p *wire_trafficPacket) decode(bs []byte) bool { return false case !wire_chop_vslice(&p.Payload, &bs): return false - case !wire_chop_vslice(&p.RPath, &bs): - return false } - p.Path = append(p.Path[:0], bs...) + p.RPath = append(p.RPath[:0], bs...) return true } @@ -282,7 +278,6 @@ type wire_protoTrafficPacket struct { Nonce crypto.BoxNonce Payload []byte RPath []byte - Path []byte } // Encodes a wire_protoTrafficPacket into its wire format. @@ -294,8 +289,7 @@ func (p *wire_protoTrafficPacket) encode() []byte { bs = append(bs, p.FromKey[:]...) bs = append(bs, p.Nonce[:]...) bs = wire_put_vslice(p.Payload, bs) - bs = wire_put_vslice(p.RPath, bs) - bs = append(bs, p.Path...) + bs = append(bs, p.RPath...) return bs } @@ -319,10 +313,8 @@ func (p *wire_protoTrafficPacket) decode(bs []byte) bool { return false case !wire_chop_vslice(&p.Payload, &bs): return false - case !wire_chop_vslice(&p.RPath, &bs): - return false } - p.Path = append(p.Path[:0], bs...) + p.RPath = append(p.RPath[:0], bs...) return true } From e19e938f642ae530e0d94119d590a403ae3cb0b6 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 7 Nov 2020 15:19:09 -0600 Subject: [PATCH 0624/1109] safer pathfinding behavior --- src/yggdrasil/router.go | 12 +++--- src/yggdrasil/session.go | 79 ++++++++++++++++++++-------------------- src/yggdrasil/switch.go | 13 +++++++ 3 files changed, 59 insertions(+), 45 deletions(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index d387346e..f89b26f9 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -196,9 +196,9 @@ func (r *router) _handleProto(packet []byte) { } switch bsType { case wire_SessionPing: - r._handlePing(bs, &p.FromKey) + r._handlePing(bs, &p.FromKey, p.RPath) case wire_SessionPong: - r._handlePong(bs, &p.FromKey) + r._handlePong(bs, &p.FromKey, p.RPath) case wire_NodeInfoRequest: fallthrough case wire_NodeInfoResponse: @@ -212,18 +212,18 @@ func (r *router) _handleProto(packet []byte) { } // Decodes session pings from wire format and passes them to sessions.handlePing where they either create or update a session. -func (r *router) _handlePing(bs []byte, fromKey *crypto.BoxPubKey) { +func (r *router) _handlePing(bs []byte, fromKey *crypto.BoxPubKey, rpath []byte) { ping := sessionPing{} if !ping.decode(bs) { return } ping.SendPermPub = *fromKey - r.sessions.handlePing(&ping) + r.sessions.handlePing(&ping, rpath) } // Handles session pongs (which are really pings with an extra flag to prevent acknowledgement). -func (r *router) _handlePong(bs []byte, fromKey *crypto.BoxPubKey) { - r._handlePing(bs, fromKey) +func (r *router) _handlePong(bs []byte, fromKey *crypto.BoxPubKey, rpath []byte) { + r._handlePing(bs, fromKey, rpath) } // Decodes dht requests and passes them to dht.handleReq to trigger a lookup/response. diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 4a06beed..181a199a 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -51,7 +51,6 @@ type sessionInfo struct { callbacks []chan func() // Finished work from crypto workers table *lookupTable // table.self is a locator where we get our coords path []byte // Path from self to destination - rpath []byte // Path from destination to self } // Represents a session ping/pong packet, and includes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU. @@ -67,41 +66,46 @@ type sessionPing struct { // Updates session info in response to a ping, after checking that the ping is OK. // Returns true if the session was updated, or false otherwise. -func (s *sessionInfo) _update(p *sessionPing) bool { - if !(p.Tstamp > s.tstamp) { +func (sinfo *sessionInfo) _update(p *sessionPing, rpath []byte) bool { + if !(p.Tstamp > sinfo.tstamp) { // To protect against replay attacks return false } - if p.SendPermPub != s.theirPermPub { + if p.SendPermPub != sinfo.theirPermPub { // Should only happen if two sessions got the same handle // That shouldn't be allowed anyway, but if it happens then let one time out return false } - if p.SendSesPub != s.theirSesPub { - s.theirSesPub = p.SendSesPub - s.theirHandle = p.Handle - s.sharedSesKey = *crypto.GetSharedKey(&s.mySesPriv, &s.theirSesPub) - s.theirNonce = crypto.BoxNonce{} + if p.SendSesPub != sinfo.theirSesPub { + sinfo.path = nil + sinfo.theirSesPub = p.SendSesPub + sinfo.theirHandle = p.Handle + sinfo.sharedSesKey = *crypto.GetSharedKey(&sinfo.mySesPriv, &sinfo.theirSesPub) + sinfo.theirNonce = crypto.BoxNonce{} } if p.MTU >= 1280 || p.MTU == 0 { - s.theirMTU = p.MTU - if s.conn != nil { - s.conn.setMTU(s, s._getMTU()) + sinfo.theirMTU = p.MTU + if sinfo.conn != nil { + sinfo.conn.setMTU(sinfo, sinfo._getMTU()) } } - if !bytes.Equal(s.coords, p.Coords) { + if !bytes.Equal(sinfo.coords, p.Coords) { // allocate enough space for additional coords - s.coords = append(make([]byte, 0, len(p.Coords)+11), p.Coords...) + sinfo.coords = append(make([]byte, 0, len(p.Coords)+11), p.Coords...) } - s.time = time.Now() - s.tstamp = p.Tstamp - s.reset = false + sinfo.time = time.Now() + sinfo.tstamp = p.Tstamp + if p.IsPong && sinfo.path == nil { + path := switch_reverseCoordBytes(rpath) + sinfo.path = append(sinfo.path[:0], path...) + } + sinfo.reset = false defer func() { recover() }() // Recover if the below panics select { - case <-s.init: + case <-sinfo.init: default: // Unblock anything waiting for the session to initialize - close(s.init) + close(sinfo.init) } return true } @@ -306,13 +310,13 @@ func (ss *sessions) getSharedKey(myPriv *crypto.BoxPrivKey, // Sends a session ping by calling sendPingPong in ping mode. func (sinfo *sessionInfo) ping(from phony.Actor) { sinfo.Act(from, func() { - sinfo._sendPingPong(false) + sinfo._sendPingPong(false, nil) }) } // Calls getPing, sets the appropriate ping/pong flag, encodes to wire format, and send it. // Updates the time the last ping was sent in the session info. -func (sinfo *sessionInfo) _sendPingPong(isPong bool) { +func (sinfo *sessionInfo) _sendPingPong(isPong bool, path []byte) { ping := sinfo._getPing() ping.IsPong = isPong bs := ping.encode() @@ -324,16 +328,21 @@ func (sinfo *sessionInfo) _sendPingPong(isPong bool) { Nonce: *nonce, Payload: payload, } + if path != nil { + p.Coords = append([]byte{0}, path...) + p.Offset += 1 + } packet := p.encode() // TODO rewrite the below if/when the peer struct becomes an actor, to not go through the router first sinfo.sessions.router.Act(sinfo, func() { sinfo.sessions.router.out(packet) }) - if sinfo.pingTime.Before(sinfo.time) { + if !isPong && sinfo.pingTime.Before(sinfo.time) { sinfo.pingTime = time.Now() } - // Sending a ping may happen when we don't know if our path info is good anymore... - // Reset paths just to be safe... - sinfo.path = nil - sinfo.rpath = nil + if !isPong { + // Sending a ping may happen when we don't know if our path info is good anymore... + // Reset paths just to be safe... + sinfo.path = nil + } } func (sinfo *sessionInfo) setConn(from phony.Actor, conn *Conn) { @@ -345,7 +354,7 @@ func (sinfo *sessionInfo) setConn(from phony.Actor, conn *Conn) { // Handles a session ping, creating a session if needed and calling update, then possibly responding with a pong if the ping was in ping mode and the update was successful. // If the session has a packet cached (common when first setting up a session), it will be sent. -func (ss *sessions) handlePing(ping *sessionPing) { +func (ss *sessions) handlePing(ping *sessionPing, rpath []byte) { // Get the corresponding session (or create a new session) sinfo, isIn := ss.getByTheirPerm(&ping.SendPermPub) switch { @@ -374,11 +383,11 @@ func (ss *sessions) handlePing(ping *sessionPing) { if sinfo != nil { sinfo.Act(ss.router, func() { // Update the session - if !sinfo._update(ping) { /*panic("Should not happen in testing")*/ + if !sinfo._update(ping, rpath) { /*panic("Should not happen in testing")*/ return } if !ping.IsPong { - sinfo._sendPingPong(true) + sinfo._sendPingPong(true, switch_reverseCoordBytes(rpath)) } }) } @@ -474,16 +483,9 @@ func (sinfo *sessionInfo) _recvPacket(p *wire_trafficPacket) { sinfo._updateNonce(&p.Nonce) sinfo.bytesRecvd += uint64(len(bs)) sinfo.conn.recvMsg(sinfo, bs) - a := switch_getPorts(p.RPath) - for i := len(a)/2 - 1; i >= 0; i-- { - opp := len(a) - 1 - i - a[i], a[opp] = a[opp], a[i] + if sinfo.path == nil { + sinfo._sendPingPong(false, nil) } - sinfo.path = sinfo.path[:0] - for _, sPort := range a { - sinfo.path = wire_put_uint64(uint64(sPort), sinfo.path) - } - //sinfo.rpath = append(sinfo.rpath[:0], p.Path...) } ch <- callback sinfo.checkCallbacks() @@ -516,7 +518,6 @@ func (sinfo *sessionInfo) _send(msg FlowKeyMessage) { Coords: coords, Handle: sinfo.theirHandle, Nonce: sinfo.myNonce, - RPath: sinfo.rpath, } sinfo.myNonce.Increment() k := sinfo.sharedSesKey diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index c291d82d..a245560e 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -655,6 +655,19 @@ func switch_getPorts(coords []byte) []switchPort { return ports } +func switch_reverseCoordBytes(coords []byte) []byte { + a := switch_getPorts(coords) + for i := len(a)/2 - 1; i >= 0; i-- { + opp := len(a) - 1 - i + a[i], a[opp] = a[opp], a[i] + } + var reversed []byte + for _, sPort := range a { + reversed = wire_put_uint64(uint64(sPort), reversed) + } + return reversed +} + func (t *lookupTable) isDescendant(ports []switchPort) bool { // Note that this returns true for anyone in the subtree that starts at us // That includes ourself, so we are our own descendant by this logic... From 0ac203b007f0427cba34be40aa5bc79ccfb1c304 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 8 Nov 2020 05:39:30 -0600 Subject: [PATCH 0625/1109] adjust how sessions learn source routes, try to recover faster if coords change (but assume the old path still works until we get a ping through that gives us a new path) --- src/yggdrasil/session.go | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 181a199a..9a69db94 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -95,7 +95,7 @@ func (sinfo *sessionInfo) _update(p *sessionPing, rpath []byte) bool { } sinfo.time = time.Now() sinfo.tstamp = p.Tstamp - if p.IsPong && sinfo.path == nil { + if p.IsPong { path := switch_reverseCoordBytes(rpath) sinfo.path = append(sinfo.path[:0], path...) } @@ -335,13 +335,8 @@ func (sinfo *sessionInfo) _sendPingPong(isPong bool, path []byte) { packet := p.encode() // TODO rewrite the below if/when the peer struct becomes an actor, to not go through the router first sinfo.sessions.router.Act(sinfo, func() { sinfo.sessions.router.out(packet) }) - if !isPong && sinfo.pingTime.Before(sinfo.time) { - sinfo.pingTime = time.Now() - } if !isPong { - // Sending a ping may happen when we don't know if our path info is good anymore... - // Reset paths just to be safe... - sinfo.path = nil + sinfo.pingTime = time.Now() } } @@ -483,9 +478,6 @@ func (sinfo *sessionInfo) _recvPacket(p *wire_trafficPacket) { sinfo._updateNonce(&p.Nonce) sinfo.bytesRecvd += uint64(len(bs)) sinfo.conn.recvMsg(sinfo, bs) - if sinfo.path == nil { - sinfo._sendPingPong(false, nil) - } } ch <- callback sinfo.checkCallbacks() @@ -529,6 +521,9 @@ func (sinfo *sessionInfo) _send(msg FlowKeyMessage) { sinfo.sessions.router.Act(sinfo, func() { sinfo.sessions.router.out(packet) }) + if time.Since(sinfo.pingTime) > 3*time.Second { + sinfo._sendPingPong(false, nil) + } } ch <- callback sinfo.checkCallbacks() From 144d42c773ad072e6d2311e76ad86c0fa98d7d85 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 8 Nov 2020 06:09:55 -0600 Subject: [PATCH 0626/1109] send dht responses via reverse path (fixes some possible DDoS issues with the old coord approach) --- src/yggdrasil/dht.go | 10 ++++++---- src/yggdrasil/router.go | 6 +++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index bbc31546..a68ba2bd 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -185,7 +185,7 @@ func dht_ordered(first, second, third *crypto.NodeID) bool { // Reads a request, performs a lookup, and responds. // Update info about the node that sent the request. -func (t *dht) handleReq(req *dhtReq) { +func (t *dht) handleReq(req *dhtReq, rpath []byte) { // Send them what they asked for res := dhtRes{ Key: t.router.core.boxPub, @@ -193,7 +193,7 @@ func (t *dht) handleReq(req *dhtReq) { Dest: req.Dest, Infos: t.lookup(&req.Dest, false), } - t.sendRes(&res, req) + t.sendRes(&res, req, rpath) // Also add them to our DHT info := dhtInfo{ key: req.Key, @@ -213,13 +213,15 @@ func (t *dht) handleReq(req *dhtReq) { } // Sends a lookup response to the specified node. -func (t *dht) sendRes(res *dhtRes, req *dhtReq) { +func (t *dht) sendRes(res *dhtRes, req *dhtReq, rpath []byte) { // Send a reply for a dhtReq bs := res.encode() shared := t.router.sessions.getSharedKey(&t.router.core.boxPriv, &req.Key) payload, nonce := crypto.BoxSeal(shared, bs, nil) + path := append([]byte{0}, switch_reverseCoordBytes(rpath)...) p := wire_protoTrafficPacket{ - Coords: req.Coords, + Offset: 1, + Coords: path, ToKey: req.Key, FromKey: t.router.core.boxPub, Nonce: *nonce, diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index f89b26f9..089c49e7 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -204,7 +204,7 @@ func (r *router) _handleProto(packet []byte) { case wire_NodeInfoResponse: r._handleNodeInfo(bs, &p.FromKey) case wire_DHTLookupRequest: - r._handleDHTReq(bs, &p.FromKey) + r._handleDHTReq(bs, &p.FromKey, p.RPath) case wire_DHTLookupResponse: r._handleDHTRes(bs, &p.FromKey) default: @@ -227,13 +227,13 @@ func (r *router) _handlePong(bs []byte, fromKey *crypto.BoxPubKey, rpath []byte) } // Decodes dht requests and passes them to dht.handleReq to trigger a lookup/response. -func (r *router) _handleDHTReq(bs []byte, fromKey *crypto.BoxPubKey) { +func (r *router) _handleDHTReq(bs []byte, fromKey *crypto.BoxPubKey, rpath []byte) { req := dhtReq{} if !req.decode(bs) { return } req.Key = *fromKey - r.dht.handleReq(&req) + r.dht.handleReq(&req, rpath) } // Decodes dht responses and passes them to dht.handleRes to update the DHT table and further pass them to the search code (if applicable). From 428789f24c62ac5a936e363a9f4b7488b5ebcd41 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 9 Nov 2020 19:01:11 -0600 Subject: [PATCH 0627/1109] simplify switch parent selection and minor source routing improvements --- src/yggdrasil/dht.go | 1 - src/yggdrasil/session.go | 12 ++++--- src/yggdrasil/switch.go | 73 ++++++---------------------------------- 3 files changed, 19 insertions(+), 67 deletions(-) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index a68ba2bd..21ff8472 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -94,7 +94,6 @@ func (t *dht) reset() { t.ping(info, nil) } } - t.reqs = make(map[dhtReqKey]time.Time) t.table = make(map[crypto.NodeID]*dhtInfo) t.imp = nil } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 9a69db94..8fd7cd5b 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -92,13 +92,15 @@ func (sinfo *sessionInfo) _update(p *sessionPing, rpath []byte) bool { if !bytes.Equal(sinfo.coords, p.Coords) { // allocate enough space for additional coords sinfo.coords = append(make([]byte, 0, len(p.Coords)+11), p.Coords...) - } - sinfo.time = time.Now() - sinfo.tstamp = p.Tstamp - if p.IsPong { + path := switch_reverseCoordBytes(rpath) + sinfo.path = append(sinfo.path[:0], path...) + defer sinfo._sendPingPong(false, nil) + } else if p.IsPong { path := switch_reverseCoordBytes(rpath) sinfo.path = append(sinfo.path[:0], path...) } + sinfo.time = time.Now() + sinfo.tstamp = p.Tstamp sinfo.reset = false defer func() { recover() }() // Recover if the below panics select { @@ -423,6 +425,8 @@ func (ss *sessions) reset() { sinfo := _sinfo // So we can safely put it in a closure sinfo.Act(ss.router, func() { sinfo.reset = true + sinfo._sendPingPong(false, sinfo.path) + sinfo._sendPingPong(false, nil) }) } } diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index a245560e..6cab5bca 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -20,10 +20,9 @@ import ( ) const ( - switch_timeout = time.Minute - switch_updateInterval = switch_timeout / 2 - switch_throttle = switch_updateInterval / 2 - switch_faster_threshold = 240 //Number of switch updates before switching to a faster parent + switch_timeout = time.Minute + switch_updateInterval = switch_timeout / 2 + switch_throttle = switch_updateInterval / 2 ) // The switch locator represents the topology and network state dependent info about a node, minus the signatures that go with it. @@ -136,15 +135,14 @@ func (x *switchLocator) isAncestorOf(y *switchLocator) bool { // Information about a peer, used by the switch to build the tree and eventually make routing decisions. type peerInfo struct { - key crypto.SigPubKey // ID of this peer - locator switchLocator // Should be able to respond with signatures upon request - degree uint64 // Self-reported degree - time time.Time // Time this node was last seen - faster map[switchPort]uint64 // Counter of how often a node is faster than the current parent, penalized extra if slower - port switchPort // Interface number of this peer - msg switchMsg // The wire switchMsg used - readBlock bool // True if the link notified us of a read that blocked too long - writeBlock bool // True of the link notified us of a write that blocked too long + key crypto.SigPubKey // ID of this peer + locator switchLocator // Should be able to respond with signatures upon request + degree uint64 // Self-reported degree + time time.Time // Time this node was last seen + port switchPort // Interface number of this peer + msg switchMsg // The wire switchMsg used + readBlock bool // True if the link notified us of a read that blocked too long + writeBlock bool // True of the link notified us of a write that blocked too long } func (pinfo *peerInfo) blocked() bool { @@ -427,37 +425,12 @@ func (t *switchTable) _handleMsg(msg *switchMsg, fromPort switchPort, reprocessi doUpdate := false oldSender := t.data.peers[fromPort] if !equiv(&sender.locator, &oldSender.locator) { - // Reset faster info, we'll start refilling it right after this - sender.faster = nil doUpdate = true } - // Update the matrix of peer "faster" thresholds if reprocessing { - sender.faster = oldSender.faster sender.time = oldSender.time sender.readBlock = oldSender.readBlock sender.writeBlock = oldSender.writeBlock - } else { - sender.faster = make(map[switchPort]uint64, len(oldSender.faster)) - for port, peer := range t.data.peers { - if port == fromPort { - continue - } else if sender.locator.root != peer.locator.root || sender.locator.tstamp > peer.locator.tstamp { - // We were faster than this node, so increment, as long as we don't overflow because of it - if oldSender.faster[peer.port] < switch_faster_threshold { - sender.faster[port] = oldSender.faster[peer.port] + 1 - } else { - sender.faster[port] = switch_faster_threshold - } - } else { - // Slower than this node, penalize (more than the reward amount) - if oldSender.faster[port] > 1 { - sender.faster[port] = oldSender.faster[peer.port] - 2 - } else { - sender.faster[port] = 0 - } - } - } } if sender.blocked() != oldSender.blocked() { doUpdate = true @@ -496,35 +469,11 @@ func (t *switchTable) _handleMsg(msg *switchMsg, fromPort switchPort, reprocessi case noParent: // We currently have no working parent, and at this point in the switch statement, anything is better than nothing. updateRoot = true - case sender.faster[t.parent] >= switch_faster_threshold: - // The is reliably faster than the current parent. - updateRoot = true case !sender.blocked() && oldParent.blocked(): // Replace a blocked parent updateRoot = true case reprocessing && sender.blocked() && !oldParent.blocked(): // Don't replace an unblocked parent when reprocessing - case reprocessing && sender.faster[t.parent] > oldParent.faster[sender.port]: - // The sender seems to be reliably faster than the current parent, so switch to them instead. - updateRoot = true - case sender.port != t.parent: - // Ignore further cases if the sender isn't our parent. - case !reprocessing && !equiv(&sender.locator, &t.data.locator): - // Special case: - // If coords changed, then we need to penalize this node somehow, to prevent flapping. - // First, reset all faster-related info to 0. - // Then, de-parent the node and reprocess all messages to find a new parent. - t.parent = 0 - for _, peer := range t.data.peers { - if peer.port == sender.port { - continue - } - t._handleMsg(&peer.msg, peer.port, true) - } - // Process the sender last, to avoid keeping them as a parent if at all possible. - t._handleMsg(&sender.msg, sender.port, true) - case now.Sub(t.time) < switch_throttle: - // We've already gotten an update from this root recently, so ignore this one to avoid flooding. case sender.locator.tstamp > t.data.locator.tstamp: // The timestamp was updated, so we need to update locally and send to our peers. updateRoot = true From 04e890fcc3a3044ec8b1f2cb6d6afba32a06e94e Mon Sep 17 00:00:00 2001 From: octeep <74378430+octeep@users.noreply.github.com> Date: Fri, 13 Nov 2020 06:38:27 +0000 Subject: [PATCH 0628/1109] Change DefaultIfName from "/dev/tun0" to "tun0" Specifying the full path to the interface in OpenBSD would result in: panic: Interface name must be tun[0-9]* Therefore, DefaultIfName should be changed to tun0 in order to make yggdrasil work out of the box. --- src/defaults/defaults_openbsd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/defaults/defaults_openbsd.go b/src/defaults/defaults_openbsd.go index 20741ee8..6700eb8f 100644 --- a/src/defaults/defaults_openbsd.go +++ b/src/defaults/defaults_openbsd.go @@ -20,6 +20,6 @@ func GetDefaults() platformDefaultParameters { // TUN/TAP MaximumIfMTU: 16384, DefaultIfMTU: 16384, - DefaultIfName: "/dev/tun0", + DefaultIfName: "tun0", } } From 939ffb02f8dfcfe3ba27cf11be52eee9ababfb1a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 14 Nov 2020 15:05:02 -0600 Subject: [PATCH 0629/1109] adjust when dht reqs are reset --- src/yggdrasil/dht.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 21ff8472..1f74f4d5 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -89,6 +89,7 @@ func (t *dht) reconfigure() { // Resets the DHT in response to coord changes. // This empties all info from the DHT and drops outstanding requests. func (t *dht) reset() { + t.reqs = make(map[dhtReqKey]time.Time) for _, info := range t.table { if t.isImportant(info) { t.ping(info, nil) From ea58a0f1812e2d2613f7bfa3a4dbed8ee3496229 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 15 Nov 2020 13:32:28 +0000 Subject: [PATCH 0630/1109] Clean go.mod/go.sum --- go.mod | 1 - go.sum | 2 -- 2 files changed, 3 deletions(-) diff --git a/go.mod b/go.mod index cea62b2b..bf8e7571 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,6 @@ require ( github.com/mitchellh/mapstructure v1.1.2 github.com/vishvananda/netlink v1.0.0 github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect - github.com/yggdrasil-network/yggdrasil-extras v0.0.0-20200525205615-6c8a4a2e8855 // indirect golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d golang.org/x/net v0.0.0-20200301022130-244492dfa37a golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 diff --git a/go.sum b/go.sum index 013abbeb..f67b6806 100644 --- a/go.sum +++ b/go.sum @@ -31,8 +31,6 @@ github.com/vishvananda/netlink v1.0.0 h1:bqNY2lgheFIu1meHUFSH3d7vG93AFyqg3oGbJCO github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f h1:nBX3nTcmxEtHSERBJaIo1Qa26VwRaopnZmfDQUXsF4I= github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= -github.com/yggdrasil-network/yggdrasil-extras v0.0.0-20200525205615-6c8a4a2e8855 h1:xLQihK8bAKOEDii/Z39dHTgSJzetm2TQ1YKRPRX87R4= -github.com/yggdrasil-network/yggdrasil-extras v0.0.0-20200525205615-6c8a4a2e8855/go.mod h1:xQdsh08Io6nV4WRnOVTe6gI8/2iTvfLDQ0CYa5aMt+I= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw= From df1239b054aee0c124193daff5e6f3f0b231af34 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 25 Nov 2020 02:44:13 -0600 Subject: [PATCH 0631/1109] attempting to debug/fix a possible goroutine leak --- src/yggdrasil/link.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 5222644b..779c435d 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -460,9 +460,6 @@ func (intf *link) notifyStalled() { // reset the close timer func (intf *link) notifyReading() { intf.Act(&intf.reader, func() { - if intf.closeTimer != nil { - intf.closeTimer.Stop() - } intf.closeTimer = time.AfterFunc(closeTime, func() { intf.msgIO.close() }) }) } @@ -470,6 +467,7 @@ func (intf *link) notifyReading() { // wake up the link if it was stalled, and (if size > 0) prepare to send keep-alive traffic func (intf *link) notifyRead(size int) { intf.Act(&intf.reader, func() { + intf.closeTimer.Stop() if intf.stallTimer != nil { intf.stallTimer.Stop() intf.stallTimer = nil From b9f35c55306df6027cf3fb4fe22e9090bf774ecd Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 6 Dec 2020 19:47:25 +0000 Subject: [PATCH 0632/1109] Return ICMPv6 Destination Unreachable for unknown destinations (#748) * Return ICMPv6 Destination Unreachable for unknown destinations * Update go.mod/go.sum for yggdrasil-extras * go mod tidy --- src/tuntap/iface.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 5e693ed8..86c3f838 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -5,6 +5,9 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" + "golang.org/x/net/icmp" + "golang.org/x/net/ipv6" + "github.com/Arceliar/phony" ) @@ -147,6 +150,16 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { } if addrlen != 16 || (!dstAddr.IsValid() && !dstSnet.IsValid()) { // Couldn't find this node's ygg IP + dlen := len(bs) + if dlen > 900 { + dlen = 900 + } + ptb := &icmp.DstUnreach{ + Data: bs[:dlen], + } + if packet, err := CreateICMPv6(bs[8:24], bs[24:40], ipv6.ICMPTypeDestinationUnreachable, 0, ptb); err == nil { + tun.writer.writeFrom(nil, packet) + } return } // Do we have an active connection for this node address? From 709ea6976c339663626dda11a93512a287aeffc3 Mon Sep 17 00:00:00 2001 From: rany Date: Sun, 6 Dec 2020 22:52:10 +0200 Subject: [PATCH 0633/1109] apparmor: allow yggdrasil to resolve hostnames (#739) The apparmor profile in it's current state won't allow resolving hostnames. We need `` because we simply can't just allow `/etc/resolv.conf`. This is because systemd-resolved, resolvconf, and others rely on symbolic links to `/etc/resolv.conf` which would make this extremely complicated. `` deals with this complexity to allow every single one of those packages (systemd-resolved, resolvconf, ... ). ``` network inet stream, network inet dgram, network inet6 dgram, network inet6 stream, network netlink raw, ``` was removed because it's already included in ``. Some permissions that are no longer needed in newer yggdrasil versions were also removed. `owner /sys/kernel/mm/transparent_hugepage/hpage_pmd_size r,` was changed to `/sys/kernel/mm/transparent_hugepage/hpage_pmd_size r,` because there is no guarantee that yggdrasil will always be run as root. (`owner` makes sure that the process's user and the file have the same owner, in that case, root. This might not always be the case so `owner` was removed) --- contrib/apparmor/usr.bin.yggdrasil | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/contrib/apparmor/usr.bin.yggdrasil b/contrib/apparmor/usr.bin.yggdrasil index 3dea142c..b305199f 100644 --- a/contrib/apparmor/usr.bin.yggdrasil +++ b/contrib/apparmor/usr.bin.yggdrasil @@ -1,25 +1,17 @@ -# Last Modified: Tue Mar 10 16:38:14 2020 +# Last Modified: Fri Oct 30 11:33:31 2020 #include /usr/bin/yggdrasil { #include + #include capability net_admin, capability net_raw, - network inet stream, - network inet dgram, - network inet6 dgram, - network inet6 stream, - network netlink raw, - - /lib/@{multiarch}/ld-*.so mr, - /proc/sys/net/core/somaxconn r, - owner /sys/kernel/mm/transparent_hugepage/hpage_pmd_size r, /dev/net/tun rw, + /proc/sys/net/core/somaxconn r, + /sys/kernel/mm/transparent_hugepage/hpage_pmd_size r, - /usr/bin/yggdrasil mr, /etc/yggdrasil.conf rw, /run/yggdrasil.sock rw, - } From 5b326d8bb86b935ca8793068eebbcf7167a91492 Mon Sep 17 00:00:00 2001 From: rany Date: Sun, 6 Dec 2020 22:52:54 +0200 Subject: [PATCH 0634/1109] Update generate.sh (#736) The AppArmor profile in contrib forbids `/usr/bin/yggdrasil` from reading the file in `/var/backups/yggdrasil.conf...`. This works around that restriction by having the shell do the reading of `/var/backups/yggdrasil.conf...` file while providing the same exact functionality without making the AppArmor profile less restrictive. Another change is the safe perms for the `/etc/yggdrasil.conf` (so that config will have 0640 permissions). This is important because if we kept the default of 644 then any user (privileged or unprivileged) will have the ability to read the yggdrasil private key. We use a restrictive umask of 0027 to make this possible. --- contrib/deb/generate.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/deb/generate.sh b/contrib/deb/generate.sh index 1f3186a4..ebe2753a 100644 --- a/contrib/deb/generate.sh +++ b/contrib/deb/generate.sh @@ -83,7 +83,7 @@ then echo "Backing up configuration file to /var/backups/yggdrasil.conf.`date +%Y%m%d`" cp /etc/yggdrasil.conf /var/backups/yggdrasil.conf.`date +%Y%m%d` echo "Normalising and updating /etc/yggdrasil.conf" - /usr/bin/yggdrasil -useconffile /var/backups/yggdrasil.conf.`date +%Y%m%d` -normaliseconf > /etc/yggdrasil.conf + /usr/bin/yggdrasil -useconf -normaliseconf < /var/backups/yggdrasil.conf.`date +%Y%m%d` > /etc/yggdrasil.conf chgrp yggdrasil /etc/yggdrasil.conf if command -v systemctl >/dev/null; then @@ -94,7 +94,7 @@ then else echo "Generating initial configuration file /etc/yggdrasil.conf" echo "Please familiarise yourself with this file before starting Yggdrasil" - /usr/bin/yggdrasil -genconf > /etc/yggdrasil.conf + sh -c 'umask 0027 && /usr/bin/yggdrasil -genconf > /etc/yggdrasil.conf' chgrp yggdrasil /etc/yggdrasil.conf fi EOF From 1daf3e7bd70491f763d45905bcf43ffb1b95426e Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 13 Dec 2020 16:16:14 -0600 Subject: [PATCH 0635/1109] remove link.go block on oldIntf if we already have a connection to the same node, this spams connections, so it's not a good long-term fix if that's where the goroutine leak is --- src/yggdrasil/link.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 779c435d..63c9a309 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -252,16 +252,12 @@ func (intf *link) handler() error { intf.info.box = meta.box intf.info.sig = meta.sig intf.links.mutex.Lock() - if oldIntf, isIn := intf.links.links[intf.info]; isIn { + if _, isIn := intf.links.links[intf.info]; isIn { intf.links.mutex.Unlock() // FIXME we should really return an error and let the caller block instead // That lets them do things like close connections on its own, avoid printing a connection message in the first place, etc. intf.links.core.log.Debugln("DEBUG: found existing interface for", intf.name) intf.msgIO.close() - if !intf.incoming { - // Block outgoing connection attempts until the existing connection closes - <-oldIntf.closed - } return nil } else { intf.closed = make(chan struct{}) From a8810c7ee9bff05e8fd74d68ab83f9b944b5c622 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 13 Dec 2020 16:29:03 -0600 Subject: [PATCH 0636/1109] if the link handler exits early due to an existing connection, then have it return a channel to that connection which closes when the connection is closed, so we can choose to block on that to avoid spamming connection attempts with dial --- src/yggdrasil/link.go | 28 ++++++++++++++-------------- src/yggdrasil/tcp.go | 17 +++++++++++------ 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 63c9a309..2ee2f312 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -187,7 +187,7 @@ func (l *links) stop() error { return nil } -func (intf *link) handler() error { +func (intf *link) handler() (chan struct{}, error) { // TODO split some of this into shorter functions, so it's easier to read, and for the FIXME duplicate peer issue mentioned later go func() { for bss := range intf.writer.worker { @@ -207,38 +207,38 @@ func (intf *link) handler() error { // TODO timeouts on send/recv (goroutine for send/recv, channel select w/ timer) var err error if !util.FuncTimeout(func() { err = intf.msgIO._sendMetaBytes(metaBytes) }, 30*time.Second) { - return errors.New("timeout on metadata send") + return nil, errors.New("timeout on metadata send") } if err != nil { - return err + return nil, err } if !util.FuncTimeout(func() { metaBytes, err = intf.msgIO._recvMetaBytes() }, 30*time.Second) { - return errors.New("timeout on metadata recv") + return nil, errors.New("timeout on metadata recv") } if err != nil { - return err + return nil, err } meta = version_metadata{} if !meta.decode(metaBytes) || !meta.check() { - return errors.New("failed to decode metadata") + return nil, errors.New("failed to decode metadata") } base := version_getBaseMetadata() if meta.ver > base.ver || meta.ver == base.ver && meta.minorVer > base.minorVer { intf.links.core.log.Errorln("Failed to connect to node: " + intf.lname + " version: " + fmt.Sprintf("%d.%d", meta.ver, meta.minorVer)) - return errors.New("failed to connect: wrong version") + return nil, errors.New("failed to connect: wrong version") } // Check if the remote side matches the keys we expected. This is a bit of a weak // check - in future versions we really should check a signature or something like that. if pinned := intf.options.pinnedCurve25519Keys; pinned != nil { if _, allowed := pinned[meta.box]; !allowed { intf.links.core.log.Errorf("Failed to connect to node: %q sent curve25519 key that does not match pinned keys", intf.name) - return fmt.Errorf("failed to connect: host sent curve25519 key that does not match pinned keys") + return nil, fmt.Errorf("failed to connect: host sent curve25519 key that does not match pinned keys") } } if pinned := intf.options.pinnedEd25519Keys; pinned != nil { if _, allowed := pinned[meta.sig]; !allowed { intf.links.core.log.Errorf("Failed to connect to node: %q sent ed25519 key that does not match pinned keys", intf.name) - return fmt.Errorf("failed to connect: host sent ed25519 key that does not match pinned keys") + return nil, fmt.Errorf("failed to connect: host sent ed25519 key that does not match pinned keys") } } // Check if we're authorized to connect to this key / IP @@ -246,19 +246,19 @@ func (intf *link) handler() error { intf.links.core.log.Warnf("%s connection from %s forbidden: AllowedEncryptionPublicKeys does not contain key %s", strings.ToUpper(intf.info.linkType), intf.info.remote, hex.EncodeToString(meta.box[:])) intf.msgIO.close() - return nil + return nil, nil } // Check if we already have a link to this node intf.info.box = meta.box intf.info.sig = meta.sig intf.links.mutex.Lock() - if _, isIn := intf.links.links[intf.info]; isIn { + if oldIntf, isIn := intf.links.links[intf.info]; isIn { intf.links.mutex.Unlock() // FIXME we should really return an error and let the caller block instead // That lets them do things like close connections on its own, avoid printing a connection message in the first place, etc. intf.links.core.log.Debugln("DEBUG: found existing interface for", intf.name) intf.msgIO.close() - return nil + return oldIntf.closed, nil } else { intf.closed = make(chan struct{}) intf.links.links[intf.info] = intf @@ -278,7 +278,7 @@ func (intf *link) handler() error { intf.peer = intf.links.core.peers._newPeer(&meta.box, &meta.sig, shared, intf) }) if intf.peer == nil { - return errors.New("failed to create peer") + return nil, errors.New("failed to create peer") } defer func() { // More cleanup can go here @@ -316,7 +316,7 @@ func (intf *link) handler() error { intf.links.core.log.Infof("Disconnected %s: %s, source %s", strings.ToUpper(intf.info.linkType), themString, intf.info.local) } - return err + return nil, err } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index ad9d872e..10c2b1eb 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -299,7 +299,9 @@ func (t *tcp) call(saddr string, options tcpOptions, sintf string) { } t.waitgroup.Add(1) options.socksPeerAddr = conn.RemoteAddr().String() - t.handler(conn, false, options) + if ch := t.handler(conn, false, options); ch != nil { + <-ch + } } else { dst, err := net.ResolveTCPAddr("tcp", saddr) if err != nil { @@ -365,12 +367,14 @@ func (t *tcp) call(saddr string, options tcpOptions, sintf string) { return } t.waitgroup.Add(1) - t.handler(conn, false, options) + if ch := t.handler(conn, false, options); ch != nil { + <-ch + } } }() } -func (t *tcp) handler(sock net.Conn, incoming bool, options tcpOptions) { +func (t *tcp) handler(sock net.Conn, incoming bool, options tcpOptions) chan struct{} { defer t.waitgroup.Done() // Happens after sock.close defer sock.Close() t.setExtraOptions(sock) @@ -379,7 +383,7 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options tcpOptions) { var err error if sock, err = options.upgrade.upgrade(sock); err != nil { t.links.core.log.Errorln("TCP handler upgrade failed:", err) - return + return nil } upgraded = true } @@ -415,7 +419,7 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options tcpOptions) { // Maybe dial/listen at the application level // Then pass a net.Conn to the core library (after these kinds of checks are done) t.links.core.log.Debugln("Dropping ygg-tunneled connection", local, remote) - return + return nil } } force := net.ParseIP(strings.Split(remote, "%")[0]).IsLinkLocalUnicast() @@ -425,6 +429,7 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options tcpOptions) { panic(err) } t.links.core.log.Debugln("DEBUG: starting handler for", name) - err = link.handler() + ch, err := link.handler() t.links.core.log.Debugln("DEBUG: stopped handler for", name, err) + return ch } From 0ba2ad74fec7d23fcc4aee0788ac8f4de95700be Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 19 Dec 2020 06:03:28 -0600 Subject: [PATCH 0637/1109] use source routes in the dht (when available) --- src/yggdrasil/dht.go | 20 +++++++++++++++++--- src/yggdrasil/router.go | 6 +++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 21ff8472..2ce18576 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -28,7 +28,8 @@ type dhtInfo struct { recv time.Time // When we last received a message pings int // Time out if at least 3 consecutive maintenance pings drop throttle time.Duration - dirty bool // Set to true if we've used this node in ping responses (for queries about someone other than the person doing the asking, i.e. real searches) since the last time we heard from the node + path []byte // source route the destination, learned from response rpath + dirty bool // Set to true if we've used this node in ping responses (for queries about someone other than the person doing the asking, i.e. real searches) since the last time we heard from the node } // Returns the *NodeID associated with dhtInfo.key, calculating it on the fly the first time or from a cache all subsequent times. @@ -91,7 +92,12 @@ func (t *dht) reconfigure() { func (t *dht) reset() { for _, info := range t.table { if t.isImportant(info) { - t.ping(info, nil) + t.ping(info, nil) // This will source route if a path is already known + if info.path != nil { + // In case the source route died, but the dest coords are still OK... + info.path = nil + t.ping(info, nil) + } } } t.table = make(map[crypto.NodeID]*dhtInfo) @@ -116,6 +122,9 @@ func (t *dht) lookup(nodeID *crypto.NodeID, everything bool) []*dhtInfo { results = newRes results = results[:dht_lookup_size] } + for _, info := range results { + info.dirty = true + } return results } @@ -243,7 +252,7 @@ func (t *dht) addCallback(rq *dhtReqKey, callback func(*dhtRes)) { // Reads a lookup response, checks that we had sent a matching request, and processes the response info. // This mainly consists of updating the node we asked in our DHT (they responded, so we know they're still alive), and deciding if we want to do anything with their responses -func (t *dht) handleRes(res *dhtRes) { +func (t *dht) handleRes(res *dhtRes, rpath []byte) { rq := dhtReqKey{res.Key, res.Dest} if callbacks, isIn := t.callbacks[rq]; isIn { for _, callback := range callbacks { @@ -259,6 +268,7 @@ func (t *dht) handleRes(res *dhtRes) { rinfo := dhtInfo{ key: res.Key, coords: res.Coords, + path: switch_reverseCoordBytes(rpath), } if t.isImportant(&rinfo) { t.insert(&rinfo) @@ -290,6 +300,10 @@ func (t *dht) sendReq(req *dhtReq, dest *dhtInfo) { Nonce: *nonce, Payload: payload, } + if dest.path != nil { + p.Coords = append([]byte{0}, dest.path...) + p.Offset += 1 + } packet := p.encode() t.router.out(packet) rq := dhtReqKey{dest.key, req.Dest} diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 089c49e7..db81068a 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -206,7 +206,7 @@ func (r *router) _handleProto(packet []byte) { case wire_DHTLookupRequest: r._handleDHTReq(bs, &p.FromKey, p.RPath) case wire_DHTLookupResponse: - r._handleDHTRes(bs, &p.FromKey) + r._handleDHTRes(bs, &p.FromKey, p.RPath) default: } } @@ -237,13 +237,13 @@ func (r *router) _handleDHTReq(bs []byte, fromKey *crypto.BoxPubKey, rpath []byt } // Decodes dht responses and passes them to dht.handleRes to update the DHT table and further pass them to the search code (if applicable). -func (r *router) _handleDHTRes(bs []byte, fromKey *crypto.BoxPubKey) { +func (r *router) _handleDHTRes(bs []byte, fromKey *crypto.BoxPubKey, rpath []byte) { res := dhtRes{} if !res.decode(bs) { return } res.Key = *fromKey - r.dht.handleRes(&res) + r.dht.handleRes(&res, rpath) } // Decodes nodeinfo request From 0ab2685489f61f0b699d36149cf1d6ef0dd7bc8f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 18 Feb 2021 09:36:45 +0000 Subject: [PATCH 0638/1109] Fix wireguard dependency --- go.mod | 12 ++++++------ go.sum | 51 ++++++++++++++++++++++++++------------------------- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/go.mod b/go.mod index bf8e7571..806914a4 100644 --- a/go.mod +++ b/go.mod @@ -12,10 +12,10 @@ require ( github.com/mitchellh/mapstructure v1.1.2 github.com/vishvananda/netlink v1.0.0 github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect - golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d - golang.org/x/net v0.0.0-20200301022130-244492dfa37a - golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 - golang.org/x/text v0.3.3-0.20191230102452-929e72ca90de - golang.zx2c4.com/wireguard v0.0.20200320 - golang.zx2c4.com/wireguard/windows v0.1.0 + golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad + golang.org/x/net v0.0.0-20210119194325-5f4716e94777 + golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c + golang.org/x/text v0.3.5 + golang.zx2c4.com/wireguard v0.0.0-20210212170059-7a0fb5bbb172 + golang.zx2c4.com/wireguard/windows v0.3.5 ) diff --git a/go.sum b/go.sum index f67b6806..f362f1dd 100644 --- a/go.sum +++ b/go.sum @@ -14,10 +14,8 @@ github.com/hjson/hjson-go v3.0.2-0.20200316202735-d5d0e8b0617d+incompatible h1:v github.com/hjson/hjson-go v3.0.2-0.20200316202735-d5d0e8b0617d+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk8851WTVWlqMC1ecJH07Ctz+Ezxx4u54g= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0/go.mod h1:rUi0/YffDo1oXBOGn1KRq7Fr07LX48XEBecQnmwjsAo= -github.com/lxn/walk v0.0.0-20191128110447-55ccb3a9f5c1 h1:/QwQcwWVOQXcoNuV9tHx30gQ3q7jCE/rKcGjwzsa5tg= -github.com/lxn/walk v0.0.0-20191128110447-55ccb3a9f5c1/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= -github.com/lxn/win v0.0.0-20191128105842-2da648fda5b4 h1:5BmtGkQbch91lglMHQ9JIDGiYCL3kBRBA0ItZTvOcEI= -github.com/lxn/win v0.0.0-20191128105842-2da648fda5b4/go.mod h1:ouWl4wViUNh8tPSIwxTVMuS014WakR1hqvBc2I0bMoA= +github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= +github.com/lxn/win v0.0.0-20201111105847-2a20daff6a55/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -32,31 +30,34 @@ github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJ github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f h1:nBX3nTcmxEtHSERBJaIo1Qa26VwRaopnZmfDQUXsF4I= github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw= -golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200301040627-c5d0d7b4ec88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 h1:/dSxr6gT0FNI1MO5WLJo8mTmItROeOKTkDn+7OwWBos= +golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3-0.20191230102452-929e72ca90de h1:aYKJLPSrddB2N7/6OKyFqJ337SXpo61bBuvO5p1+7iY= -golang.org/x/text v0.3.3-0.20191230102452-929e72ca90de/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.zx2c4.com/wireguard v0.0.20200122-0.20200214175355-9cbcff10dd3e/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4= -golang.zx2c4.com/wireguard v0.0.20200320 h1:1vE6zVeO7fix9cJX1Z9ZQ+ikPIIx7vIyU0o0tLDD88g= -golang.zx2c4.com/wireguard v0.0.20200320/go.mod h1:lDian4Sw4poJ04SgHh35nzMVwGSYlPumkdnHcucAQoY= -golang.zx2c4.com/wireguard/windows v0.1.0 h1:742izt2DAJBpIQT+DvrzN58P9p7fO4BUFOgMzY9qVhw= -golang.zx2c4.com/wireguard/windows v0.1.0/go.mod h1:EK7CxrFnicmYJ0ZCF6crBh2/EMMeSxMlqgLlwN0Kv9s= +golang.zx2c4.com/wireguard v0.0.0-20210203165646-9c7bd73be2cc/go.mod h1:r0ExowOoGFfDoLDxx+M9SYbNVsoZ0xviLL+K4f2mt+A= +golang.zx2c4.com/wireguard v0.0.0-20210212170059-7a0fb5bbb172 h1:g1vFilCRox6yINkkKFt+Dj4T22qjxp63yNzR1BjgmEE= +golang.zx2c4.com/wireguard v0.0.0-20210212170059-7a0fb5bbb172/go.mod h1:r0ExowOoGFfDoLDxx+M9SYbNVsoZ0xviLL+K4f2mt+A= +golang.zx2c4.com/wireguard/windows v0.3.5 h1:nyFx/e63SLnqdLbY8o8+Yqsqqvh/KFWAoOmBW4Pj0rQ= +golang.zx2c4.com/wireguard/windows v0.3.5/go.mod h1:ATrIFNoq3rsK735WJiQzfWYyNFc9xLBhMMjW9DWIvnU= From 7174cfce4031051921d6dbc460c64f66cc872d17 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 7 Mar 2021 08:45:47 +0000 Subject: [PATCH 0639/1109] Move up to Go 1.16, upgrade dependencies (#765) --- .circleci/config.yml | 12 +++--- README.md | 2 +- go.mod | 33 +++++++++------- go.sum | 93 ++++++++++++++++++++++++-------------------- 4 files changed, 76 insertions(+), 64 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2f4552de..7ec96db3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ version: 2.1 jobs: lint: docker: - - image: circleci/golang:1.14.1 + - image: circleci/golang:1.16 steps: - checkout @@ -18,7 +18,7 @@ jobs: build-linux: docker: - - image: circleci/golang:1.14.1 + - image: circleci/golang:1.16 steps: - checkout @@ -119,11 +119,11 @@ jobs: echo -e "Host *\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config - run: - name: Install Go 1.14.1 + name: Install Go 1.16 command: | cd /tmp - curl -LO https://dl.google.com/go/go1.14.1.darwin-amd64.pkg - sudo installer -pkg /tmp/go1.14.1.darwin-amd64.pkg -target / + curl -LO https://dl.google.com/go/go1.16.darwin-amd64.pkg + sudo installer -pkg /tmp/go1.16.darwin-amd64.pkg -target / #- run: # name: Install Gomobile @@ -159,7 +159,7 @@ jobs: build-other: docker: - - image: circleci/golang:1.14.1 + - image: circleci/golang:1.16 steps: - checkout diff --git a/README.md b/README.md index aa4a46c1..d1d254a6 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,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 packages: -1. Install [Go](https://golang.org) (requires Go 1.13 or later) +1. Install [Go](https://golang.org) (requires Go 1.16 or later) 2. Clone this repository 2. Run `./build` diff --git a/go.mod b/go.mod index 806914a4..1e820f63 100644 --- a/go.mod +++ b/go.mod @@ -1,21 +1,24 @@ module github.com/yggdrasil-network/yggdrasil-go -go 1.13 +go 1.16 require ( - github.com/Arceliar/phony v0.0.0-20191006174943-d0c68492aca0 - github.com/cheggaaa/pb/v3 v3.0.4 - github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 + github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 + github.com/cheggaaa/pb/v3 v3.0.6 + github.com/fatih/color v1.10.0 // indirect + github.com/gologme/log v1.2.0 github.com/hashicorp/go-syslog v1.0.0 - github.com/hjson/hjson-go v3.0.2-0.20200316202735-d5d0e8b0617d+incompatible - github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 - github.com/mitchellh/mapstructure v1.1.2 - github.com/vishvananda/netlink v1.0.0 - github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect - golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad - golang.org/x/net v0.0.0-20210119194325-5f4716e94777 - golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c - golang.org/x/text v0.3.5 - golang.zx2c4.com/wireguard v0.0.0-20210212170059-7a0fb5bbb172 - golang.zx2c4.com/wireguard/windows v0.3.5 + github.com/hjson/hjson-go v3.1.0+incompatible + github.com/kardianos/minwinsvc v1.0.0 + github.com/mattn/go-runewidth v0.0.10 // indirect + github.com/mitchellh/mapstructure v1.4.1 + github.com/rivo/uniseg v0.2.0 // indirect + github.com/vishvananda/netlink v1.1.0 + github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect + golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 + golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 + golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b + golang.org/x/text v0.3.6-0.20210220033129-8f690f22cf1c + golang.zx2c4.com/wireguard v0.0.0-20210306175010-7e3b8371a1bf + golang.zx2c4.com/wireguard/windows v0.3.8 ) diff --git a/go.sum b/go.sum index f362f1dd..51498b80 100644 --- a/go.sum +++ b/go.sum @@ -1,63 +1,72 @@ -github.com/Arceliar/phony v0.0.0-20191006174943-d0c68492aca0 h1:p3puK8Sl2xK+2FnnIvY/C0N1aqJo2kbEsdAzU+Tnv48= -github.com/Arceliar/phony v0.0.0-20191006174943-d0c68492aca0/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= +github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= +github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= -github.com/cheggaaa/pb/v3 v3.0.4 h1:QZEPYOj2ix6d5oEg63fbHmpolrnNiwjUsk+h74Yt4bM= -github.com/cheggaaa/pb/v3 v3.0.4/go.mod h1:7rgWxLrAUcFMkvJuv09+DYi7mMUYi8nO9iOWcvGJPfw= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/cheggaaa/pb/v3 v3.0.6 h1:ULPm1wpzvj60FvmCrX7bIaB80UgbhI+zSaQJKRfCbAs= +github.com/cheggaaa/pb/v3 v3.0.6/go.mod h1:X1L61/+36nz9bjIsrDU52qHKOQukUQe2Ge+YvGuquCw= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -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/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= +github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/gologme/log v1.2.0 h1:Ya5Ip/KD6FX7uH0S31QO87nCCSucKtF44TLbTtO7V4c= +github.com/gologme/log v1.2.0/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hjson/hjson-go v3.0.2-0.20200316202735-d5d0e8b0617d+incompatible h1:v6BPcb9q9U6JDVsuizxBr/piVB/2Y1Q5GWoBybvZVWI= -github.com/hjson/hjson-go v3.0.2-0.20200316202735-d5d0e8b0617d+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= -github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk8851WTVWlqMC1ecJH07Ctz+Ezxx4u54g= -github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0/go.mod h1:rUi0/YffDo1oXBOGn1KRq7Fr07LX48XEBecQnmwjsAo= +github.com/hjson/hjson-go v3.1.0+incompatible h1:DY/9yE8ey8Zv22bY+mHV1uk2yRy0h8tKhZ77hEdi0Aw= +github.com/hjson/hjson-go v3.1.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= +github.com/kardianos/minwinsvc v1.0.0 h1:+JfAi8IBJna0jY2dJGZqi7o15z13JelFIklJCAENALA= +github.com/kardianos/minwinsvc v1.0.0/go.mod h1:Bgd0oc+D0Qo3bBytmNtyRKVlp85dAloLKhfxanPFFRc= github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= -github.com/lxn/win v0.0.0-20201111105847-2a20daff6a55/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= -github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= +github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/vishvananda/netlink v1.0.0 h1:bqNY2lgheFIu1meHUFSH3d7vG93AFyqg3oGbJCOJgSM= -github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= -github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f h1:nBX3nTcmxEtHSERBJaIo1Qa26VwRaopnZmfDQUXsF4I= -github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= +github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= +github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA= +github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 h1:/dSxr6gT0FNI1MO5WLJo8mTmItROeOKTkDn+7OwWBos= -golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225014209-683adc9d29d7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305215415-5cdee2b1b5a0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b h1:ggRgirZABFolTmi3sn6Ivd9SipZwLedQ5wR0aAKnFxU= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6-0.20210220033129-8f690f22cf1c h1:SW/oilbeWd6f32u3ZvuYGqZ+wivcp//I3Dy/gByk7Wk= +golang.org/x/text v0.3.6-0.20210220033129-8f690f22cf1c/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.zx2c4.com/wireguard v0.0.0-20210203165646-9c7bd73be2cc/go.mod h1:r0ExowOoGFfDoLDxx+M9SYbNVsoZ0xviLL+K4f2mt+A= -golang.zx2c4.com/wireguard v0.0.0-20210212170059-7a0fb5bbb172 h1:g1vFilCRox6yINkkKFt+Dj4T22qjxp63yNzR1BjgmEE= -golang.zx2c4.com/wireguard v0.0.0-20210212170059-7a0fb5bbb172/go.mod h1:r0ExowOoGFfDoLDxx+M9SYbNVsoZ0xviLL+K4f2mt+A= -golang.zx2c4.com/wireguard/windows v0.3.5 h1:nyFx/e63SLnqdLbY8o8+Yqsqqvh/KFWAoOmBW4Pj0rQ= -golang.zx2c4.com/wireguard/windows v0.3.5/go.mod h1:ATrIFNoq3rsK735WJiQzfWYyNFc9xLBhMMjW9DWIvnU= +golang.zx2c4.com/wireguard v0.0.0-20210225140808-70b7b7158fc9/go.mod h1:39ZQQ95hUxDxT7opsWy/rtfgvXXc8s30qfZ02df69Fo= +golang.zx2c4.com/wireguard v0.0.0-20210306175010-7e3b8371a1bf h1:AtdIMfzvVNPXN4kVY/yWS8mvpQogSwtCRJk2y/LBPpg= +golang.zx2c4.com/wireguard v0.0.0-20210306175010-7e3b8371a1bf/go.mod h1:ojGPy+9W6ZSM8anL+xC67fvh8zPQJwA6KpFOHyDWLX4= +golang.zx2c4.com/wireguard/windows v0.3.8 h1:FvfBEhdZZTwthLuPHdyP6zpivYL3enopxd4XpggAufM= +golang.zx2c4.com/wireguard/windows v0.3.8/go.mod h1:lm7dxHcBuzMNq706Ge1tZKZKw4+19vG9dLOhoDX05HQ= From f0a5cd542cbf995ead8f3683adf427bf77978b60 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 7 Mar 2021 14:03:34 +0000 Subject: [PATCH 0640/1109] Build MSIs for Windows using CircleCI (#766) * Try to build MSIs from CircleCI using wixl/msitools * Upload msis * Change condition * Update Platform * Update Platform * Don't build ARM, it's apparently not well supported * Don't build ARM, it's apparently not well supported * Remove appveyor config * Update comments * newline --- .circleci/config.yml | 46 ++++++++++++++++++++---- appveyor.yml | 20 ----------- contrib/msi/build-msi.sh | 78 ++++++++++++++-------------------------- 3 files changed, 66 insertions(+), 78 deletions(-) delete mode 100644 appveyor.yml diff --git a/.circleci/config.yml b/.circleci/config.yml index 7ec96db3..8a486c38 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -157,6 +157,43 @@ jobs: paths: - upload + build-windows: + docker: + - image: circleci/golang:1.16 + + steps: + - checkout + + - run: + name: Create artifact upload directory and set variables + command: | + mkdir /tmp/upload + echo 'export CINAME=$(sh contrib/semver/name.sh)' >> $BASH_ENV + echo 'export CIVERSION=$(sh contrib/semver/version.sh --bare)' >> $BASH_ENV + git config --global user.email "$(git log --format='%ae' HEAD -1)"; + git config --global user.name "$(git log --format='%an' HEAD -1)"; + + - run: + name: Install tools + command: | + sudo apt-get update + sudo apt-get -y install msitools wixl + + - run: + name: Build for Windows + command: | + rm -f {yggdrasil,yggdrasilctl} + GOOS=windows GOARCH=amd64 ./build && mv yggdrasil.exe /tmp/upload/$CINAME-$CIVERSION-windows-amd64.exe && mv yggdrasilctl.exe /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-windows-amd64.exe; + GOOS=windows GOARCH=386 ./build && mv yggdrasil.exe /tmp/upload/$CINAME-$CIVERSION-windows-i386.exe && mv yggdrasilctl.exe /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-windows-i386.exe; + bash contrib/msi/build-msi.sh x64 + bash contrib/msi/build-msi.sh x86 + mv *.msi /tmp/upload + + - persist_to_workspace: + root: /tmp + paths: + - upload + build-other: docker: - image: circleci/golang:1.16 @@ -187,13 +224,6 @@ jobs: GOOS=freebsd GOARCH=amd64 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-freebsd-amd64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-freebsd-amd64; GOOS=freebsd GOARCH=386 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-freebsd-i386 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-freebsd-i386; - - run: - name: Build for Windows - command: | - rm -f {yggdrasil,yggdrasilctl} - GOOS=windows GOARCH=amd64 ./build && mv yggdrasil.exe /tmp/upload/$CINAME-$CIVERSION-windows-amd64.exe && mv yggdrasilctl.exe /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-windows-amd64.exe; - GOOS=windows GOARCH=386 ./build && mv yggdrasil.exe /tmp/upload/$CINAME-$CIVERSION-windows-i386.exe && mv yggdrasilctl.exe /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-windows-i386.exe; - - persist_to_workspace: root: /tmp paths: @@ -217,9 +247,11 @@ workflows: - lint - build-linux - build-macos + - build-windows - build-other - upload: requires: - build-linux - build-macos + - build-windows - build-other diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 58724a24..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,20 +0,0 @@ -version: '{build}' -pull_requests: - do_not_increment_build_number: true -os: Visual Studio 2017 -shallow_clone: false - -environment: - MSYS2_PATH_TYPE: inherit - CHERE_INVOKING: enabled_from_arguments - -build_script: -- cmd: >- - cd %APPVEYOR_BUILD_FOLDER% -- c:\msys64\usr\bin\bash -lc "./contrib/msi/build-msi.sh x64" -- c:\msys64\usr\bin\bash -lc "./contrib/msi/build-msi.sh x86" - -test: off - -artifacts: -- path: '*.msi' diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index 421481cd..57d4e81d 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -1,9 +1,7 @@ -#!/bin/sh +#!/bin/bash # This script generates an MSI file for Yggdrasil for a given architecture. It -# needs to run on Windows within MSYS2 and Go 1.13 or later must be installed on -# the system and within the PATH. This is ran currently by Appveyor (see -# appveyor.yml in the repository root) for both x86 and x64. +# needs to run on Linux or macOS with Go 1.16, wixl and msitools installed. # # Author: Neil Alexander @@ -11,7 +9,7 @@ PKGARCH=$1 if [ "${PKGARCH}" == "" ]; then - echo "tell me the architecture: x86 or x64" + echo "tell me the architecture: x86, x64 or arm" exit 1 fi @@ -28,28 +26,11 @@ then git checkout ${APPVEYOR_REPO_BRANCH} fi -# Install prerequisites within MSYS2 -pacman -S --needed --noconfirm unzip git curl - -# Download the wix tools! -if [ ! -d wixbin ]; -then - curl -LO https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip - if [ `md5sum wix311-binaries.zip | cut -f 1 -d " "` != "47a506f8ab6666ee3cc502fb07d0ee2a" ]; - then - echo "wix package didn't match expected checksum" - exit 1 - fi - mkdir -p wixbin - unzip -o wix311-binaries.zip -d wixbin || ( - echo "failed to unzip WiX" - exit 1 - ) -fi - # Build Yggdrasil! -[ "${PKGARCH}" == "x64" ] && GOOS=windows GOARCH=amd64 CGO_ENABLED=0 ./build -[ "${PKGARCH}" == "x86" ] && GOOS=windows GOARCH=386 CGO_ENABLED=0 ./build +[ "${PKGARCH}" == "x64" ] && GOOS=windows GOARCH=amd64 CGO_ENABLED=0 ./build -p -l "-aslr" +[ "${PKGARCH}" == "x86" ] && GOOS=windows GOARCH=386 CGO_ENABLED=0 ./build -p -l "-aslr" +[ "${PKGARCH}" == "arm" ] && GOOS=windows GOARCH=arm CGO_ENABLED=0 ./build -p -l "-aslr" +#[ "${PKGARCH}" == "arm64" ] && GOOS=windows GOARCH=arm64 CGO_ENABLED=0 ./build # Create the postinstall script cat > updateconfig.bat << EOF @@ -58,7 +39,9 @@ if not exist %ALLUSERSPROFILE%\\Yggdrasil ( ) if not exist %ALLUSERSPROFILE%\\Yggdrasil\\yggdrasil.conf ( if exist yggdrasil.exe ( - yggdrasil.exe -genconf > %ALLUSERSPROFILE%\\Yggdrasil\\yggdrasil.conf + if not exist %ALLUSERSPROFILE%\\Yggdrasil\\yggdrasil.conf ( + yggdrasil.exe -genconf > %ALLUSERSPROFILE%\\Yggdrasil\\yggdrasil.conf + ) ) ) EOF @@ -72,12 +55,16 @@ PKGVERSIONMS=$(echo $PKGVERSION | tr - .) PKGGUID="54a3294e-a441-4322-aefb-3bb40dd022bb" PKGINSTFOLDER="ProgramFilesFolder" # Download the Wintun driver +curl -o wintun.zip https://www.wintun.net/builds/wintun-0.10.2.zip +unzip wintun.zip if [ $PKGARCH = "x64" ]; then - PKGMSMNAME=wintun-x64.msm - curl -o ${PKGMSMNAME} https://www.wintun.net/builds/wintun-amd64-0.7.msm || (echo "couldn't get wintun"; exit 1) + PKGWINTUNDLL=wintun/bin/amd64/wintun.dll elif [ $PKGARCH = "x86" ]; then - PKGMSMNAME=wintun-x86.msm - curl -o ${PKGMSMNAME} https://www.wintun.net/builds/wintun-x86-0.7.msm || (echo "couldn't get wintun"; exit 1) + PKGWINTUNDLL=wintun/bin/x86/wintun.dll +elif [ $PKGARCH = "arm" ]; then + PKGWINTUNDLL=wintun/bin/arm/wintun.dll +#elif [ $PKGARCH = "arm64" ]; then +# PKGWINTUNDLL=wintun/bin/arm64/wintun.dll else echo "wasn't sure which architecture to get wintun for" exit 1 @@ -100,6 +87,7 @@ cat > wix.xml << EOF Language="1033" Codepage="1252" Version="${PKGVERSIONMS}" + Platform="${PKGARCH}" Manufacturer="github.com/yggdrasil-network"> wix.xml << EOF Source="yggdrasil.exe" KeyPath="yes" /> + + wix.xml << EOF - - @@ -190,13 +178,6 @@ cat > wix.xml << EOF - - - UPGRADINGPRODUCTCODE - - - - wix.xml << EOF - NOT Installed AND NOT REMOVE - + Before="StartServices" /> @@ -218,7 +197,4 @@ cat > wix.xml << EOF EOF # Generate the MSI -CANDLEFLAGS="-nologo" -LIGHTFLAGS="-nologo -spdb -sice:ICE71 -sice:ICE61" -wixbin/candle $CANDLEFLAGS -out ${PKGNAME}-${PKGVERSION}-${PKGARCH}.wixobj -arch ${PKGARCH} wix.xml && \ -wixbin/light $LIGHTFLAGS -ext WixUtilExtension.dll -out ${PKGNAME}-${PKGVERSION}-${PKGARCH}.msi ${PKGNAME}-${PKGVERSION}-${PKGARCH}.wixobj +wixl -v wix.xml -a ${PKGARCH} -o ${PKGNAME}-${PKGVERSION}-${PKGARCH}.msi From ac375917c952a840363122c2670b4cee5c60414c Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 18 Mar 2021 13:58:20 -0500 Subject: [PATCH 0641/1109] Update changelog for v0.3.16 release (#769) * draft of changelog * more changelog --- CHANGELOG.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1017e8d8..856f1cea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,31 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> +## [0.3.16] - 2021-03-18 +### Added +- New simulation code under `cmd/yggdrasilsim` (work-in-progress) + +### Changed +- Multi-threading in the switch + - Swich lookups happen independently for each (incoming) peer connection, instead of being funneled to a single dedicated switch worker + - Packets are queued for each (outgoing) peer connection, instead of being handled by a single dedicated switch worker +- Queue logic rewritten + - Heap structure per peer that traffic is routed to, with one FIFO queue per traffic flow + - The total size of each heap is configured automatically (we basically queue packets until we think we're blocked on a socket write) + - When adding to a full heap, the oldest packet from the largest queue is dropped + - Packets are popped from the queue in FIFO order (oldest packet from among all queues in the heap) to prevent packet reordering at the session level +- Removed global `sync.Pool` of `[]byte` + - Local `sync.Pool`s are used in the hot loops, but not exported, to avoid memory corruption if libraries are reused by other projects + - This may increase allocations (and slightly reduce speed in CPU-bound benchmarks) when interacting with the tun/tap device, but traffic forwarded at the switch layer should be unaffected +- Upgrade dependencies +- Upgrade build to Go 1.16 + +### Fixed +- Fixed a bug where the connection listener could exit prematurely due to resoruce exhaustion (if e.g. too many connections were opened) +- Fixed DefaultIfName for OpenBSD (`/dev/tun0` -> `tun0`) +- Fixed an issue where a peer could sometimes never be added to the switch +- Fixed a goroutine leak that could occur if a peer with an open connection continued to spam additional connection attempts + ## [0.3.15] - 2020-09-27 ### Added - Support for pinning remote public keys in peering strings has been added, e.g. From 9b67eb7ef2b3f13f247e376f8726eadc0a0b4d12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christer=20War=C3=A9n?= Date: Wed, 24 Mar 2021 15:39:55 +0200 Subject: [PATCH 0642/1109] Update Dockerfile Removing personal information --- contrib/docker/Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile index fd2ae981..15129b16 100644 --- a/contrib/docker/Dockerfile +++ b/contrib/docker/Dockerfile @@ -8,7 +8,6 @@ ENV CGO_ENABLED=0 RUN apk add git && ./build && go build -o /src/genkeys cmd/genkeys/main.go FROM docker.io/alpine -LABEL maintainer="Christer Waren/CWINFO " COPY --from=builder /src/yggdrasil /usr/bin/yggdrasil COPY --from=builder /src/yggdrasilctl /usr/bin/yggdrasilctl From ace7b43b6d16c7d53e96d96986f6a109875f1730 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 8 May 2021 07:25:53 -0500 Subject: [PATCH 0643/1109] (broken state) WIP address migration --- cmd/yggdrasil/main.go | 6 ++- src/address/address.go | 114 +++++++++------------------------------ src/admin/admin.go | 15 ++++-- src/tuntap/iface.go | 14 ++--- src/tuntap/tun.go | 16 +++--- src/yggdrasil/api.go | 16 +++--- src/yggdrasil/link.go | 4 +- src/yggdrasil/router.go | 4 +- src/yggdrasil/session.go | 4 +- 9 files changed, 73 insertions(+), 120 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 14b76c34..8d3e00e8 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -230,14 +230,16 @@ func main() { switch { case *getaddr: if nodeid := getNodeID(); nodeid != nil { - addr := *address.AddrForNodeID(nodeid) + panic("TODO") + addr := new(address.Address) //*address.AddrForNodeID(nodeid) ip := net.IP(addr[:]) fmt.Println(ip.String()) } return case *getsnet: if nodeid := getNodeID(); nodeid != nil { - snet := *address.SubnetForNodeID(nodeid) + panic("TODO") + snet := new(address.Address) //*address.SubnetForNodeID(nodeid) ipnet := net.IPNet{ IP: append(snet[:], 0, 0, 0, 0, 0, 0, 0, 0), Mask: net.CIDRMask(len(snet)*8, 128), diff --git a/src/address/address.go b/src/address/address.go index 0569af00..ff2d2120 100644 --- a/src/address/address.go +++ b/src/address/address.go @@ -3,9 +3,7 @@ package address import ( - "fmt" - - "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "crypto/ed25519" ) // Address represents an IPv6 address in the yggdrasil address range. @@ -45,25 +43,34 @@ func (s *Subnet) IsValid() bool { return (*s)[l-1] == prefix[l-1]|0x01 } -// AddrForNodeID takes a *NodeID as an argument and returns an *Address. +// AddrForKey takes an ed25519.PublicKey as an argument and returns an *Address. +// This function returns nil if the key length is not ed25519.PublicKeySize. // This address begins with the contents of GetPrefix(), with the last bit set to 0 to indicate an address. -// The following 8 bits are set to the number of leading 1 bits in the NodeID. -// The NodeID, excluding the leading 1 bits and the first leading 0 bit, is truncated to the appropriate length and makes up the remainder of the address. -func AddrForNodeID(nid *crypto.NodeID) *Address { +// The following 8 bits are set to the number of leading 1 bits in the bitwise inverse of the public key. +// The bitwise inverse of the key, excluding the leading 1 bits and the first leading 0 bit, is truncated to the appropriate length and makes up the remainder of the address. +func AddrForKey(publicKey ed25519.PublicKey) *Address { // 128 bit address // Begins with prefix // Next bit is a 0 // Next 7 bits, interpreted as a uint, are # of leading 1s in the NodeID // Leading 1s and first leading 0 of the NodeID are truncated off // The rest is appended to the IPv6 address (truncated to 128 bits total) + if len(publicKey) != ed25519.PublicKeySize { + return nil + } + var buf [ed25519.PublicKeySize]byte + copy(buf[:], publicKey) + for idx := range buf { + buf[idx] = ^buf[idx] + } var addr Address var temp []byte done := false ones := byte(0) bits := byte(0) nBits := 0 - for idx := 0; idx < 8*len(nid); idx++ { - bit := (nid[idx/8] & (0x80 >> byte(idx%8))) >> byte(7-(idx%8)) + for idx := 0; idx < 8*len(buf); idx++ { + bit := (buf[idx/8] & (0x80 >> byte(idx%8))) >> byte(7-(idx%8)) if !done && bit != 0 { ones++ continue @@ -86,91 +93,22 @@ func AddrForNodeID(nid *crypto.NodeID) *Address { return &addr } -// SubnetForNodeID takes a *NodeID as an argument and returns an *Address. -// This subnet begins with the address prefix, with the last bit set to 1 to indicate a prefix. -// The following 8 bits are set to the number of leading 1 bits in the NodeID. -// The NodeID, excluding the leading 1 bits and the first leading 0 bit, is truncated to the appropriate length and makes up the remainder of the subnet. -func SubnetForNodeID(nid *crypto.NodeID) *Subnet { +// SubnetForKey takes an ed25519.PublicKey as an argument and returns a *Subnet. +// This function returns nil if the key length is not ed25519.PublicKeySize. +// The subnet begins with the address prefix, with the last bit set to 1 to indicate a prefix. +// The following 8 bits are set to the number of leading 1 bits in the bitwise inverse of the key. +// The bitwise inverse of the key, excluding the leading 1 bits and the first leading 0 bit, is truncated to the appropriate length and makes up the remainder of the subnet. +func SubnetForKey(publicKey ed25519.PublicKey) *Subnet { // Exactly as the address version, with two exceptions: // 1) The first bit after the fixed prefix is a 1 instead of a 0 // 2) It's truncated to a subnet prefix length instead of 128 bits - addr := *AddrForNodeID(nid) + addr := AddrForKey(publicKey) + if addr == nil { + return nil + } var snet Subnet copy(snet[:], addr[:]) prefix := GetPrefix() snet[len(prefix)-1] |= 0x01 return &snet } - -// GetNodeIDandMask returns two *NodeID. -// The first is a NodeID with all the bits known from the Address set to their correct values. -// The second is a bitmask with 1 bit set for each bit that was known from the Address. -// This is used to look up NodeIDs in the DHT and tell if they match an Address. -func (a *Address) GetNodeIDandMask() (*crypto.NodeID, *crypto.NodeID) { - // Mask is a bitmask to mark the bits visible from the address - // This means truncated leading 1s, first leading 0, and visible part of addr - var nid crypto.NodeID - var mask crypto.NodeID - prefix := GetPrefix() - ones := int(a[len(prefix)]) - for idx := 0; idx < ones; idx++ { - nid[idx/8] |= 0x80 >> byte(idx%8) - } - nidOffset := ones + 1 - addrOffset := 8*len(prefix) + 8 - for idx := addrOffset; idx < 8*len(a); idx++ { - bits := a[idx/8] & (0x80 >> byte(idx%8)) - bits <<= byte(idx % 8) - nidIdx := nidOffset + (idx - addrOffset) - bits >>= byte(nidIdx % 8) - nid[nidIdx/8] |= bits - } - maxMask := 8*(len(a)-len(prefix)-1) + ones + 1 - for idx := 0; idx < maxMask; idx++ { - mask[idx/8] |= 0x80 >> byte(idx%8) - } - 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. -// 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. -// This is used to look up NodeIDs in the DHT and tell if they match a Subnet. -func (s *Subnet) GetNodeIDandMask() (*crypto.NodeID, *crypto.NodeID) { - // As with the address version, but visible parts of the subnet prefix instead - var nid crypto.NodeID - var mask crypto.NodeID - prefix := GetPrefix() - ones := int(s[len(prefix)]) - for idx := 0; idx < ones; idx++ { - nid[idx/8] |= 0x80 >> byte(idx%8) - } - nidOffset := ones + 1 - addrOffset := 8*len(prefix) + 8 - for idx := addrOffset; idx < 8*len(s); idx++ { - bits := s[idx/8] & (0x80 >> byte(idx%8)) - bits <<= byte(idx % 8) - nidIdx := nidOffset + (idx - addrOffset) - bits >>= byte(nidIdx % 8) - nid[nidIdx/8] |= bits - } - maxMask := 8*(len(s)-len(prefix)-1) + ones + 1 - for idx := 0; idx < maxMask; idx++ { - mask[idx/8] |= 0x80 >> byte(idx%8) - } - 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) -} diff --git a/src/admin/admin.go b/src/admin/admin.go index bdb7cd3a..dbf973e9 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -100,7 +100,8 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { a.AddHandler("getPeers", []string{}, func(in Info) (Info, error) { peers := make(Info) for _, p := range a.core.GetPeers() { - addr := *address.AddrForNodeID(crypto.GetNodeID(&p.PublicKey)) + panic("TODO") + addr := new(address.Address) // TODO *address.AddrForNodeID(crypto.GetNodeID(&p.PublicKey)) so := net.IP(addr[:]).String() peers[so] = Info{ "port": p.Port, @@ -117,7 +118,8 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { a.AddHandler("getSwitchPeers", []string{}, func(in Info) (Info, error) { switchpeers := make(Info) for _, s := range a.core.GetSwitchPeers() { - addr := *address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey)) + panic("TODO") + addr := new(address.Address) // TODO *address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey)) so := fmt.Sprint(s.Port) switchpeers[so] = Info{ "ip": net.IP(addr[:]).String(), @@ -141,7 +143,8 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { a.AddHandler("getDHT", []string{}, func(in Info) (Info, error) { dht := make(Info) for _, d := range a.core.GetDHT() { - addr := *address.AddrForNodeID(crypto.GetNodeID(&d.PublicKey)) + panic("TODO") + addr := new(address.Address) // TODO *address.AddrForNodeID(crypto.GetNodeID(&d.PublicKey)) so := net.IP(addr[:]).String() dht[so] = Info{ "coords": fmt.Sprintf("%v", d.Coords), @@ -154,7 +157,8 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { a.AddHandler("getSessions", []string{}, func(in Info) (Info, error) { sessions := make(Info) for _, s := range a.core.GetSessions() { - addr := *address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey)) + panic("TODO") + addr := new(address.Address) //*address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey)) so := net.IP(addr[:]).String() sessions[so] = Info{ "coords": fmt.Sprintf("%v", s.Coords), @@ -293,7 +297,8 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { "box_pub_key": hex.EncodeToString(dinfo.PublicKey[:]), "coords": fmt.Sprintf("%v", dinfo.Coords), } - addr := net.IP(address.AddrForNodeID(crypto.GetNodeID(&dinfo.PublicKey))[:]).String() + panic("TODO") + addr := "" //net.IP(address.AddrForNodeID(crypto.GetNodeID(&dinfo.PublicKey))[:]).String() infos[addr] = info } return Info{"nodes": infos}, nil diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 86c3f838..da6d8e24 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -139,11 +139,12 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { } if tun.ckr.isEnabled() { if addrlen != 16 || (!dstAddr.IsValid() && !dstSnet.IsValid()) { - if key, err := tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { + if /*key*/ _, err := tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { // A public key was found, get the node ID for the search - dstNodeID := crypto.GetNodeID(&key) - dstAddr = *address.AddrForNodeID(dstNodeID) - dstSnet = *address.SubnetForNodeID(dstNodeID) + panic("TODO") + //dstNodeID := crypto.GetNodeID(&key) + //dstAddr = *address.AddrForNodeID(dstNodeID) + //dstSnet = *address.SubnetForNodeID(dstNodeID) addrlen = 16 } } @@ -170,10 +171,11 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { if !isIn || session == nil { // Neither an address nor a subnet mapping matched, therefore populate // the node ID and mask to commence a search + panic("TODO") if dstAddr.IsValid() { - dstString = dstAddr.GetNodeIDLengthString() + //dstString = dstAddr.GetNodeIDLengthString() } else { - dstString = dstSnet.GetNodeIDLengthString() + //dstString = dstSnet.GetNodeIDLengthString() } } } diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 656ecca7..06f609ae 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -154,9 +154,10 @@ func (tun *TunAdapter) _start() error { return err } copy(boxPub[:], boxPubHex) - nodeID := crypto.GetNodeID(&boxPub) - tun.addr = *address.AddrForNodeID(nodeID) - tun.subnet = *address.SubnetForNodeID(nodeID) + panic("TODO") + //nodeID := crypto.GetNodeID(&boxPub) + //tun.addr = *address.AddrForNodeID(nodeID) + //tun.subnet = *address.SubnetForNodeID(nodeID) addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1) if current.IfName == "none" || current.IfName == "dummy" { tun.log.Debugln("Not starting TUN as ifname is none or dummy") @@ -251,10 +252,11 @@ func (tun *TunAdapter) _wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { } c = &s // Get the remote address and subnet of the other side - remotePubKey := conn.RemoteAddr().(*crypto.BoxPubKey) - remoteNodeID := crypto.GetNodeID(remotePubKey) - s.addr = *address.AddrForNodeID(remoteNodeID) - s.snet = *address.SubnetForNodeID(remoteNodeID) + panic("TODO") + //remotePubKey := conn.RemoteAddr().(*crypto.BoxPubKey) + //remoteNodeID := crypto.GetNodeID(remotePubKey) + //s.addr = *address.AddrForNodeID(remoteNodeID) + //s.snet = *address.SubnetForNodeID(remoteNodeID) // Work out if this is already a destination we already know about atc, aok := tun.addrToConn[s.addr] stc, sok := tun.subnetToConn[s.snet] diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index c800cb0d..9d0634ce 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -9,7 +9,7 @@ import ( "time" "github.com/gologme/log" - "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/Arceliar/phony" @@ -314,8 +314,10 @@ func (c *Core) Coords() []uint64 { // that application also implements either VPN functionality or deals with IP // packets specifically. func (c *Core) Address() net.IP { - address := net.IP(address.AddrForNodeID(c.NodeID())[:]) - return address + panic("TODO") + return nil + //address := net.IP(address.AddrForNodeID(c.NodeID())[:]) + //return address } // Subnet gets the routed IPv6 subnet of the Yggdrasil node. This is always a @@ -324,9 +326,11 @@ func (c *Core) Address() net.IP { // that application also implements either VPN functionality or deals with IP // packets specifically. func (c *Core) Subnet() net.IPNet { - subnet := address.SubnetForNodeID(c.NodeID())[:] - subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0) - return net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)} + panic("TODO") + return net.IPNet{} + //subnet := address.SubnetForNodeID(c.NodeID())[:] + //subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0) + //return net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)} } // MyNodeInfo gets the currently configured nodeinfo. NodeInfo is typically diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 2ee2f312..9ecc98ff 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -13,7 +13,7 @@ import ( //"sync/atomic" "time" - "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/util" "golang.org/x/net/proxy" @@ -287,7 +287,7 @@ func (intf *link) handler() (chan struct{}, error) { intf.peer.Act(intf, intf.peer._removeSelf) }) }() - themAddr := address.AddrForNodeID(crypto.GetNodeID(&intf.info.box)) + themAddr := make([]byte, 16) // TODO address.AddrForNodeID(crypto.GetNodeID(&intf.info.box)) themAddrString := net.IP(themAddr[:]).String() themString := fmt.Sprintf("%s@%s", themAddrString, intf.info.remote) intf.links.core.log.Infof("Connected %s: %s, source %s", diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index db81068a..924d285a 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -53,8 +53,8 @@ type router struct { // Initializes the router struct, which includes setting up channels to/from the adapter. func (r *router) init(core *Core) { r.core = core - r.addr = *address.AddrForNodeID(&r.dht.nodeID) - r.subnet = *address.SubnetForNodeID(&r.dht.nodeID) + // TODO r.addr = *address.AddrForNodeID(&r.dht.nodeID) + // TODO r.subnet = *address.SubnetForNodeID(&r.dht.nodeID) r.intf.router = r phony.Block(&r.core.peers, func() { // FIXME don't block here! diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 8fd7cd5b..2d64cac4 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -221,8 +221,8 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.myNonce[len(sinfo.myNonce)-1] &= 0xfe } sinfo.myHandle = *crypto.NewHandle() - sinfo.theirAddr = *address.AddrForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) - sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) + // TODO sinfo.theirAddr = *address.AddrForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) + // TODO sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) sinfo.table = ss.router.table ss.sinfos[sinfo.myHandle] = &sinfo ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle From f1c37f84406c897f0795f4ac86156ab66dbfe1d5 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 8 May 2021 08:35:58 -0500 Subject: [PATCH 0644/1109] (broken state) WIP rewriting core to use ironwood --- go.mod | 5 +- go.sum | 4 + src/util/util.go | 2 +- src/yggdrasil/api.go | 86 +++-- src/yggdrasil/conn.go | 397 --------------------- src/yggdrasil/core.go | 70 ++-- src/yggdrasil/dht.go | 463 ------------------------- src/yggdrasil/dialer.go | 120 ------- src/yggdrasil/link.go | 354 +++---------------- src/yggdrasil/listener.go | 45 --- src/yggdrasil/nodeinfo.go | 209 ----------- src/yggdrasil/packetqueue.go | 119 ------- src/yggdrasil/peer.go | 447 ------------------------ src/yggdrasil/pool.go | 20 -- src/yggdrasil/router.go | 289 ---------------- src/yggdrasil/search.go | 271 --------------- src/yggdrasil/session.go | 551 ----------------------------- src/yggdrasil/simlink.go | 91 ----- src/yggdrasil/stream.go | 120 ------- src/yggdrasil/switch.go | 647 ----------------------------------- src/yggdrasil/tcp.go | 4 +- src/yggdrasil/tls.go | 4 +- src/yggdrasil/version.go | 46 +-- src/yggdrasil/wire.go | 521 ---------------------------- 24 files changed, 162 insertions(+), 4723 deletions(-) delete mode 100644 src/yggdrasil/conn.go delete mode 100644 src/yggdrasil/dht.go delete mode 100644 src/yggdrasil/dialer.go delete mode 100644 src/yggdrasil/listener.go delete mode 100644 src/yggdrasil/nodeinfo.go delete mode 100644 src/yggdrasil/packetqueue.go delete mode 100644 src/yggdrasil/peer.go delete mode 100644 src/yggdrasil/pool.go delete mode 100644 src/yggdrasil/router.go delete mode 100644 src/yggdrasil/search.go delete mode 100644 src/yggdrasil/session.go delete mode 100644 src/yggdrasil/simlink.go delete mode 100644 src/yggdrasil/stream.go delete mode 100644 src/yggdrasil/switch.go delete mode 100644 src/yggdrasil/wire.go diff --git a/go.mod b/go.mod index 1e820f63..491d5992 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.16 require ( + github.com/Arceliar/ironwood v0.0.0-20210508094446-74a68e4f5970 // indirect github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 github.com/cheggaaa/pb/v3 v3.0.6 github.com/fatih/color v1.10.0 // indirect @@ -15,10 +16,12 @@ require ( github.com/rivo/uniseg v0.2.0 // indirect github.com/vishvananda/netlink v1.1.0 github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect - golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 + golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b golang.org/x/text v0.3.6-0.20210220033129-8f690f22cf1c golang.zx2c4.com/wireguard v0.0.0-20210306175010-7e3b8371a1bf golang.zx2c4.com/wireguard/windows v0.3.8 ) + +replace github.com/Arceliar/ironwood => ../ironwood diff --git a/go.sum b/go.sum index 51498b80..5613fafe 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/Arceliar/ironwood v0.0.0-20210508094446-74a68e4f5970 h1:sKiz18LynwInybi9BIhM8tdvZlSurnT6rM/ZUEqMgzU= +github.com/Arceliar/ironwood v0.0.0-20210508094446-74a68e4f5970/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= @@ -39,6 +41,8 @@ github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= diff --git a/src/util/util.go b/src/util/util.go index d7a7443d..06c2d2d4 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -46,7 +46,7 @@ func TimerStop(t *time.Timer) bool { // FuncTimeout runs the provided function in a separate goroutine, and returns true if the function finishes executing before the timeout passes, or false if the timeout passes. // It includes no mechanism to stop the function if the timeout fires, so the user is expected to do so on their own (such as with a Cancellation or a context). -func FuncTimeout(f func(), timeout time.Duration) bool { +func FuncTimeout(timeout time.Duration, f func()) bool { success := make(chan struct{}) go func() { defer close(success) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 9d0634ce..c8f7900f 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -1,18 +1,17 @@ package yggdrasil import ( - "encoding/hex" + //"encoding/hex" "errors" - "fmt" + //"fmt" "net" - "sort" + //"sort" "time" "github.com/gologme/log" - //"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/Arceliar/phony" + //"github.com/Arceliar/phony" ) // Peer represents a single peer object. This contains information from the @@ -95,13 +94,13 @@ type SwitchQueue struct { // Note that sessions will automatically be closed by Yggdrasil if no traffic is // exchanged for around two minutes. type Session struct { - PublicKey crypto.BoxPubKey // The public key of the remote node - Coords []uint64 // The coordinates of the remote node - BytesSent uint64 // Bytes sent to the session - BytesRecvd uint64 // Bytes received from the session - MTU MTU // The maximum supported message size of the session - Uptime time.Duration // How long this session has been active for - WasMTUFixed bool // This field is no longer used + PublicKey crypto.BoxPubKey // The public key of the remote node + Coords []uint64 // The coordinates of the remote node + BytesSent uint64 // Bytes sent to the session + BytesRecvd uint64 // Bytes received from the session + //MTU MTU // The maximum supported message size of the session + Uptime time.Duration // How long this session has been active for + WasMTUFixed bool // This field is no longer used } // GetPeers returns one or more Peer objects containing information about active @@ -109,6 +108,7 @@ type Session struct { // includes information about the current node (with a port number of 0). If // there is exactly one entry then this node is not connected to any other nodes // and is therefore isolated. +/* TODO func (c *Core) GetPeers() []Peer { var ports map[switchPort]*peer phony.Block(&c.peers, func() { ports = c.peers.ports }) @@ -136,12 +136,14 @@ func (c *Core) GetPeers() []Peer { } return peers } +*/ // GetSwitchPeers returns zero or more SwitchPeer objects containing information // about switch port connections with other Yggdrasil nodes. Note that, unlike // GetPeers, GetSwitchPeers does not include information about the current node, // therefore it is possible for this to return zero elements if the node is // isolated or not connected to any peers. +/* TODO func (c *Core) GetSwitchPeers() []SwitchPeer { var switchpeers []SwitchPeer var table *lookupTable @@ -172,9 +174,11 @@ func (c *Core) GetSwitchPeers() []SwitchPeer { } return switchpeers } +*/ // GetDHT returns zero or more entries as stored in the DHT, cached primarily // from searches that have already taken place. +/* TODO func (c *Core) GetDHT() []DHTEntry { var dhtentries []DHTEntry getDHT := func() { @@ -198,8 +202,10 @@ func (c *Core) GetDHT() []DHTEntry { phony.Block(&c.router, getDHT) return dhtentries } +*/ // GetSessions returns a list of open sessions from this node to other nodes. +/* TODO func (c *Core) GetSessions() []Session { var sessions []Session getSessions := func() { @@ -224,11 +230,13 @@ func (c *Core) GetSessions() []Session { phony.Block(&c.router, getSessions) return sessions } +*/ // ConnListen returns a listener for Yggdrasil session connections. You can only // call this function once as each Yggdrasil node can only have a single // ConnListener. Make sure to keep the reference to this for as long as it is // needed. +/* TODO? func (c *Core) ConnListen() (*Listener, error) { c.router.sessions.listenerMutex.Lock() defer c.router.sessions.listenerMutex.Unlock() @@ -242,16 +250,19 @@ func (c *Core) ConnListen() (*Listener, error) { } return c.router.sessions.listener, nil } +*/ // ConnDialer returns a dialer for Yggdrasil session connections. Since // ConnDialers are stateless, you can request as many dialers as you like, // although ideally you should request only one and keep the reference to it for // as long as it is needed. +/* TODO? func (c *Core) ConnDialer() (*Dialer, error) { return &Dialer{ core: c, }, nil } +*/ // ListenTCP starts a new TCP listener. The input URI should match that of the // "Listen" configuration item, e.g. @@ -270,26 +281,34 @@ func (c *Core) ListenTLS(uri string) (*TcpListener, error) { // NodeID gets the node ID. This is derived from your router encryption keys. // Remote nodes wanting to open connections to your node will need to know your // node ID. +/* TODO? func (c *Core) NodeID() *crypto.NodeID { return crypto.GetNodeID(&c.boxPub) } +*/ // TreeID gets the tree ID. This is derived from your switch signing keys. There // is typically no need to share this key. +/* TODO? func (c *Core) TreeID() *crypto.TreeID { return crypto.GetTreeID(&c.sigPub) } +*/ // SigningPublicKey gets the node's signing public key, as used by the switch. +/* TODO? func (c *Core) SigningPublicKey() string { return hex.EncodeToString(c.sigPub[:]) } +*/ // EncryptionPublicKey gets the node's encryption public key, as used by the // router. +/* TODO? func (c *Core) EncryptionPublicKey() string { return hex.EncodeToString(c.boxPub[:]) } +*/ // Coords returns the current coordinates of the node. Note that these can // change at any time for a number of reasons, not limited to but including @@ -300,6 +319,7 @@ func (c *Core) EncryptionPublicKey() string { // you are the root of the network that you are connected to, or you are not // connected to any other nodes (effectively making you the root of a // single-node network). +/* TODO? func (c *Core) Coords() []uint64 { var coords []byte phony.Block(&c.router, func() { @@ -307,6 +327,7 @@ func (c *Core) Coords() []uint64 { }) return wire_coordsBytestoUint64s(coords) } +*/ // Address gets the IPv6 address of the Yggdrasil node. This is always a /128 // address. The IPv6 address is only relevant when the node is operating as an @@ -314,10 +335,8 @@ func (c *Core) Coords() []uint64 { // that application also implements either VPN functionality or deals with IP // packets specifically. func (c *Core) Address() net.IP { - panic("TODO") - return nil - //address := net.IP(address.AddrForNodeID(c.NodeID())[:]) - //return address + addr := net.IP(address.AddrForKey(c.public)[:]) + return addr } // Subnet gets the routed IPv6 subnet of the Yggdrasil node. This is always a @@ -326,28 +345,31 @@ func (c *Core) Address() net.IP { // that application also implements either VPN functionality or deals with IP // packets specifically. func (c *Core) Subnet() net.IPNet { - panic("TODO") - return net.IPNet{} - //subnet := address.SubnetForNodeID(c.NodeID())[:] - //subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0) - //return net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)} + subnet := address.SubnetForKey(c.public)[:] + subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0) + return net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)} } // MyNodeInfo gets the currently configured nodeinfo. NodeInfo is typically // specified through the "NodeInfo" option in the node configuration or using // the SetNodeInfo function, although it may also contain other built-in values // such as "buildname", "buildversion" etc. +/* TODO? func (c *Core) MyNodeInfo() NodeInfoPayload { return c.router.nodeinfo.getNodeInfo() } +*/ // SetNodeInfo sets the local nodeinfo. Note that nodeinfo can be any value or // struct, it will be serialised into JSON automatically. +/* TODO? func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) { c.router.nodeinfo.setNodeInfo(nodeinfo, nodeinfoprivacy) } +*/ // GetMaximumSessionMTU returns the maximum allowed session MTU size. +/* TODO? func (c *Core) GetMaximumSessionMTU() MTU { var mtu MTU phony.Block(&c.router, func() { @@ -355,10 +377,12 @@ func (c *Core) GetMaximumSessionMTU() MTU { }) return mtu } +*/ // SetMaximumSessionMTU sets the maximum allowed session MTU size. The default // value is 65535 bytes. Session pings will be sent to update all open sessions // if the MTU has changed. +/* TODO? func (c *Core) SetMaximumSessionMTU(mtu MTU) { phony.Block(&c.router, func() { if c.router.sessions.myMaximumMTU != mtu { @@ -367,11 +391,13 @@ func (c *Core) SetMaximumSessionMTU(mtu MTU) { } }) } +*/ // GetNodeInfo requests nodeinfo from a remote node, as specified by the public // key and coordinates specified. The third parameter specifies whether a cached // result is acceptable - this results in less traffic being generated than is // necessary when, e.g. crawling the network. +/* TODO? func (c *Core) GetNodeInfo(key crypto.BoxPubKey, coords []uint64, nocache bool) (NodeInfoPayload, error) { response := make(chan *NodeInfoPayload, 1) c.router.nodeinfo.addCallback(key, func(nodeinfo *NodeInfoPayload) { @@ -390,6 +416,7 @@ func (c *Core) GetNodeInfo(key crypto.BoxPubKey, coords []uint64, nocache bool) } return NodeInfoPayload{}, fmt.Errorf("getNodeInfo timeout: %s", hex.EncodeToString(key[:])) } +*/ // SetSessionGatekeeper allows you to configure a handler function for deciding // whether a session should be allowed or not. The default session firewall is @@ -397,12 +424,14 @@ func (c *Core) GetNodeInfo(key crypto.BoxPubKey, coords []uint64, nocache bool) // side and a boolean which is true if we initiated the session or false if we // received an incoming session request. The function should return true to // allow the session or false to reject it. +/* TODO? func (c *Core) SetSessionGatekeeper(f func(pubkey *crypto.BoxPubKey, initiator bool) bool) { c.router.sessions.isAllowedMutex.Lock() defer c.router.sessions.isAllowedMutex.Unlock() c.router.sessions.isAllowedHandler = f } +*/ // SetLogger sets the output logger of the Yggdrasil node after startup. This // may be useful if you want to redirect the output later. Note that this @@ -469,6 +498,8 @@ func (c *Core) RemovePeer(addr string, sintf string) error { } } + panic("TODO") + /* TODO? c.peers.Act(nil, func() { ports := c.peers.ports for _, peer := range ports { @@ -477,6 +508,7 @@ func (c *Core) RemovePeer(addr string, sintf string) error { } } }) + */ return nil } @@ -493,6 +525,7 @@ func (c *Core) CallPeer(addr string, sintf string) error { // DisconnectPeer disconnects a peer once. This should be specified as a port // number. +/* TODO? func (c *Core) DisconnectPeer(port uint64) error { c.peers.Act(nil, func() { if p, isIn := c.peers.ports[switchPort(port)]; isIn { @@ -501,34 +534,42 @@ func (c *Core) DisconnectPeer(port uint64) error { }) return nil } +*/ // GetAllowedEncryptionPublicKeys returns the public keys permitted for incoming // peer connections. If this list is empty then all incoming peer connections // are accepted by default. +/* TODO? func (c *Core) GetAllowedEncryptionPublicKeys() []string { return c.peers.getAllowedEncryptionPublicKeys() } +*/ // AddAllowedEncryptionPublicKey whitelists a key for incoming peer connections. // By default all incoming peer connections are accepted, but adding public keys // to the whitelist using this function enables strict checking from that point // forward. Once the whitelist is enabled, only peer connections from // whitelisted public keys will be accepted. +/* TODO? func (c *Core) AddAllowedEncryptionPublicKey(bstr string) (err error) { c.peers.addAllowedEncryptionPublicKey(bstr) return nil } +*/ // RemoveAllowedEncryptionPublicKey removes a key from the whitelist for // incoming peer connections. If none are set, an empty list permits all // incoming connections. +/* TODO? func (c *Core) RemoveAllowedEncryptionPublicKey(bstr string) (err error) { c.peers.removeAllowedEncryptionPublicKey(bstr) return nil } +*/ // DHTPing sends a DHT ping to the node with the provided key and coords, // optionally looking up the specified target NodeID. +/* NOT TODO!! func (c *Core) DHTPing(key crypto.BoxPubKey, coords []uint64, target *crypto.NodeID) (DHTRes, error) { resCh := make(chan *dhtRes, 1) info := dhtInfo{ @@ -564,3 +605,4 @@ func (c *Core) DHTPing(key crypto.BoxPubKey, coords []uint64, target *crypto.Nod } return DHTRes{}, fmt.Errorf("DHT ping timeout: %s", hex.EncodeToString(key[:])) } +*/ diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go deleted file mode 100644 index 81b5b8d3..00000000 --- a/src/yggdrasil/conn.go +++ /dev/null @@ -1,397 +0,0 @@ -package yggdrasil - -import ( - "errors" - "fmt" - "net" - "time" - - "github.com/yggdrasil-network/yggdrasil-go/src/crypto" - "github.com/yggdrasil-network/yggdrasil-go/src/types" - "github.com/yggdrasil-network/yggdrasil-go/src/util" - - "github.com/Arceliar/phony" -) - -type MTU = types.MTU - -// ConnError implements the net.Error interface -type ConnError struct { - error - timeout bool - temporary bool - closed bool - maxsize int -} - -// Timeout returns true if the error relates to a timeout condition on the -// connection. -func (e *ConnError) Timeout() bool { - return e.timeout -} - -// Temporary return true if the error is temporary or false if it is a permanent -// error condition. -func (e *ConnError) Temporary() bool { - return e.temporary -} - -// PacketTooBig returns in response to sending a packet that is too large, and -// if so, the maximum supported packet size that should be used for the -// connection. -func (e *ConnError) PacketTooBig() bool { - return e.maxsize > 0 -} - -// PacketMaximumSize returns the maximum supported packet size. This will only -// return a non-zero value if ConnError.PacketTooBig() returns true. -func (e *ConnError) PacketMaximumSize() int { - if !e.PacketTooBig() { - return 0 - } - return e.maxsize -} - -// Closed returns if the session is already closed and is now unusable. -func (e *ConnError) Closed() bool { - return e.closed -} - -// The Conn struct is a reference to an active connection session between the -// local node and a remote node. Conn implements the io.ReadWriteCloser -// interface and is used to send and receive traffic with a remote node. -type Conn struct { - phony.Inbox - core *Core - readDeadline *time.Time - writeDeadline *time.Time - nodeID *crypto.NodeID - nodeMask *crypto.NodeID - session *sessionInfo - mtu MTU - readCallback func([]byte) - readBuffer chan []byte -} - -// TODO func NewConn() that initializes additional fields as needed -func newConn(core *Core, nodeID *crypto.NodeID, nodeMask *crypto.NodeID, session *sessionInfo) *Conn { - conn := Conn{ - core: core, - nodeID: nodeID, - nodeMask: nodeMask, - session: session, - readBuffer: make(chan []byte, 1024), - } - return &conn -} - -// String returns a string that uniquely identifies a connection. Currently this -// takes a form similar to "conn=0x0000000", which contains a memory reference -// to the Conn object. While this value should always be unique for each Conn -// object, the format of this is not strictly defined and may change in the -// future. -func (c *Conn) String() string { - var s string - phony.Block(c, func() { s = fmt.Sprintf("conn=%p", c) }) - return s -} - -func (c *Conn) setMTU(from phony.Actor, mtu MTU) { - c.Act(from, func() { c.mtu = mtu }) -} - -// This should never be called from an actor, used in the dial functions -func (c *Conn) search() error { - var err error - done := make(chan struct{}) - phony.Block(&c.core.router, func() { - _, isIn := c.core.router.searches.searches[*c.nodeID] - if !isIn { - searchCompleted := func(sinfo *sessionInfo, e error) { - select { - case <-done: - // Somehow this was called multiple times, TODO don't let that happen - if sinfo != nil { - // Need to clean up to avoid a session leak - sinfo.cancel.Cancel(nil) - sinfo.sessions.removeSession(sinfo) - } - default: - if sinfo != nil { - // Finish initializing the session - 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 - close(done) - } - } - sinfo := c.core.router.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) - sinfo.startSearch() - } else { - err = errors.New("search already exists") - close(done) - } - }) - <-done - if c.session == nil && err == nil { - panic("search failed but returned no error") - } - return err -} - -// Used in session keep-alive traffic -func (c *Conn) _doSearch() { - s := fmt.Sprintf("conn=%p", c) - routerWork := func() { - // Check to see if there is a search already matching the destination - sinfo, isIn := c.core.router.searches.searches[*c.nodeID] - if !isIn { - // Nothing was found, so create a new search - searchCompleted := func(sinfo *sessionInfo, e error) {} - sinfo = c.core.router.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) - c.core.log.Debugf("%s DHT search started: %p", s, sinfo) - // Start the search - sinfo.startSearch() - } - } - c.core.router.Act(c.session, routerWork) -} - -func (c *Conn) _getDeadlineCancellation(t *time.Time) (util.Cancellation, bool) { - if t != nil { - // A deadline is set, so return a Cancellation that uses it - c := util.CancellationWithDeadline(c.session.cancel, *t) - return c, true - } - // No deadline was set, so just return the existing cancellation and a dummy value - return c.session.cancel, false -} - -// SetReadCallback allows you to specify a function that will be called whenever -// a packet is received. This should be used if you wish to implement -// asynchronous patterns for receiving data from the remote node. -// -// Note that if a read callback has been supplied, you should no longer attempt -// to use the synchronous Read function. -func (c *Conn) SetReadCallback(callback func([]byte)) { - c.Act(nil, func() { - c.readCallback = callback - c._drainReadBuffer() - }) -} - -func (c *Conn) _drainReadBuffer() { - if c.readCallback == nil { - return - } - select { - case bs := <-c.readBuffer: - c.readCallback(bs) - c.Act(nil, c._drainReadBuffer) // In case there's more - default: - } -} - -// Called by the session to pass a new message to the Conn -func (c *Conn) recvMsg(from phony.Actor, msg []byte) { - c.Act(from, func() { - if c.readCallback != nil { - c.readCallback(msg) - } else { - select { - case c.readBuffer <- msg: - default: - } - } - }) -} - -// Used internally by Read, the caller is responsible for util.PutBytes when they're done. -func (c *Conn) readNoCopy() ([]byte, error) { - var cancel util.Cancellation - var doCancel bool - phony.Block(c, func() { cancel, doCancel = c._getDeadlineCancellation(c.readDeadline) }) - if doCancel { - defer cancel.Cancel(nil) - } - // Wait for some traffic to come through from the session - select { - case <-cancel.Finished(): - if cancel.Error() == util.CancellationTimeoutError { - return nil, ConnError{errors.New("read timeout"), true, false, false, 0} - } - return nil, ConnError{errors.New("session closed"), false, false, true, 0} - case bs := <-c.readBuffer: - return bs, nil - } -} - -// Read allows you to read from the connection in a synchronous fashion. The -// function will block up until the point that either new data is available, the -// connection has been closed or the read deadline has been reached. If the -// function succeeds, the number of bytes read from the connection will be -// returned. Otherwise, an error condition will be returned. -// -// Note that you can also implement asynchronous reads by using SetReadCallback. -// If you do that, you should no longer attempt to use the Read function. -func (c *Conn) Read(b []byte) (int, error) { - bs, err := c.readNoCopy() - if err != nil { - return 0, err - } - n := len(bs) - if len(bs) > len(b) { - n = len(b) - err = ConnError{errors.New("read buffer too small for entire packet"), false, true, false, 0} - } - // Copy results to the output slice and clean up - copy(b, bs) - // Return the number of bytes copied to the slice, along with any error - return n, err -} - -func (c *Conn) _write(msg FlowKeyMessage) error { - if len(msg.Message) > int(c.mtu) { - return ConnError{errors.New("packet too big"), true, false, false, int(c.mtu)} - } - c.session.Act(c, func() { - // Send the packet - c.session._send(msg) - // Session keep-alive, while we wait for the crypto workers from send - switch { - case time.Since(c.session.time) > 6*time.Second: - if c.session.time.Before(c.session.pingTime) && time.Since(c.session.pingTime) > 6*time.Second { - // TODO double check that the above condition is correct - c._doSearch() - } else { - c.session.ping(c.session) // TODO send from self if this becomes an actor - } - case c.session.reset && c.session.pingTime.Before(c.session.time): - c.session.ping(c.session) // TODO send from self if this becomes an actor - default: // Don't do anything, to keep traffic throttled - } - }) - return nil -} - -// WriteFrom should be called by a phony.Actor, and tells the Conn to send a -// message. This is used internally by Write. If the callback is called with a -// non-nil value, then it is safe to reuse the argument FlowKeyMessage. -func (c *Conn) WriteFrom(from phony.Actor, msg FlowKeyMessage, callback func(error)) { - c.Act(from, func() { - callback(c._write(msg)) - }) -} - -// writeNoCopy is used internally by Write and makes use of WriteFrom under the hood. -// The caller must not reuse the argument FlowKeyMessage when a nil error is returned. -func (c *Conn) writeNoCopy(msg FlowKeyMessage) error { - var cancel util.Cancellation - var doCancel bool - phony.Block(c, func() { cancel, doCancel = c._getDeadlineCancellation(c.writeDeadline) }) - if doCancel { - defer cancel.Cancel(nil) - } - var err error - select { - case <-cancel.Finished(): - if cancel.Error() == util.CancellationTimeoutError { - err = ConnError{errors.New("write timeout"), true, false, false, 0} - } else { - err = ConnError{errors.New("session closed"), false, false, true, 0} - } - default: - done := make(chan struct{}) - callback := func(e error) { err = e; close(done) } - c.WriteFrom(nil, msg, callback) - <-done - } - return err -} - -// Write allows you to write to the connection in a synchronous fashion. This -// function may block until either the write has completed, the connection has -// been closed or the write deadline has been reached. If the function succeeds, -// the number of written bytes is returned. Otherwise, an error condition is -// returned. -func (c *Conn) Write(b []byte) (int, error) { - written := len(b) - bs := make([]byte, 0, len(b)+crypto.BoxOverhead) - bs = append(bs, b...) - msg := FlowKeyMessage{Message: bs} - err := c.writeNoCopy(msg) - if err != nil { - written = 0 - } - return written, err -} - -// Close will close an open connection and any blocking operations on the -// connection will unblock and return. From this point forward, the connection -// can no longer be used and you should no longer attempt to Read or Write to -// the connection. -func (c *Conn) Close() (err error) { - phony.Block(c, func() { - if c.session != nil { - // Close the session, if it hasn't been closed already - if e := c.session.cancel.Cancel(errors.New("connection closed")); e != nil { - err = ConnError{errors.New("close failed, session already closed"), false, false, true, 0} - } else { - c.session.doRemove() - } - } - }) - return -} - -// LocalAddr returns the complete public key of the local side of the -// connection. This is always going to return your own node's public key. -func (c *Conn) LocalAddr() net.Addr { - return &c.core.boxPub -} - -// RemoteAddr returns the complete public key of the remote side of the -// connection. -func (c *Conn) RemoteAddr() net.Addr { - if c.session != nil { - return &c.session.theirPermPub - } - return nil -} - -// SetDeadline is equivalent to calling both SetReadDeadline and -// SetWriteDeadline with the same value, configuring the maximum amount of time -// that synchronous Read and Write operations can block for. If no deadline is -// configured, Read and Write operations can potentially block indefinitely. -func (c *Conn) SetDeadline(t time.Time) error { - c.SetReadDeadline(t) - c.SetWriteDeadline(t) - return nil -} - -// SetReadDeadline configures the maximum amount of time that a synchronous Read -// operation can block for. A Read operation will unblock at the point that the -// read deadline is reached if no other condition (such as data arrival or -// connection closure) happens first. If no deadline is configured, Read -// operations can potentially block indefinitely. -func (c *Conn) SetReadDeadline(t time.Time) error { - // TODO warn that this can block while waiting for the Conn actor to run, so don't call it from other actors... - phony.Block(c, func() { c.readDeadline = &t }) - return nil -} - -// SetWriteDeadline configures the maximum amount of time that a synchronous -// Write operation can block for. A Write operation will unblock at the point -// that the read deadline is reached if no other condition (such as data sending -// or connection closure) happens first. If no deadline is configured, Write -// operations can potentially block indefinitely. -func (c *Conn) SetWriteDeadline(t time.Time) error { - // TODO warn that this can block while waiting for the Conn actor to run, so don't call it from other actors... - phony.Block(c, func() { c.writeDeadline = &t }) - return nil -} diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 4ac678df..351e1904 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -1,16 +1,18 @@ package yggdrasil import ( + "crypto/ed25519" "encoding/hex" "errors" "io/ioutil" "time" + iw "github.com/Arceliar/ironwood/encrypted" "github.com/Arceliar/phony" "github.com/gologme/log" "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/version" ) @@ -21,14 +23,10 @@ type Core struct { // We're going to keep our own copy of the provided config - that way we can // guarantee that it will be covered by the mutex phony.Inbox + *iw.PacketConn config config.NodeState // Config - boxPub crypto.BoxPubKey - boxPriv crypto.BoxPrivKey - sigPub crypto.SigPubKey - sigPriv crypto.SigPrivKey - switchTable switchTable - peers peers - router router + secret ed25519.PrivateKey + public ed25519.PublicKey links links log *log.Logger addPeerTimer *time.Timer @@ -45,40 +43,23 @@ func (c *Core) _init() error { current := c.config.GetCurrent() - boxPrivHex, err := hex.DecodeString(current.EncryptionPrivateKey) + sigPriv, err := hex.DecodeString(current.SigningPrivateKey) if err != nil { return err } - if len(boxPrivHex) < crypto.BoxPrivKeyLen { - return errors.New("EncryptionPrivateKey is incorrect length") - } - - sigPrivHex, err := hex.DecodeString(current.SigningPrivateKey) - if err != nil { - return err - } - if len(sigPrivHex) < crypto.SigPrivKeyLen { + if len(sigPriv) < ed25519.PrivateKeySize { return errors.New("SigningPrivateKey is incorrect length") } - copy(c.boxPriv[:], boxPrivHex) - copy(c.sigPriv[:], sigPrivHex) + c.secret = ed25519.PrivateKey(sigPriv) + sigPub := c.secret.Public() + c.public = sigPub.(ed25519.PublicKey) - boxPub, sigPub := c.boxPriv.Public(), c.sigPriv.Public() - - copy(c.boxPub[:], boxPub[:]) - copy(c.sigPub[:], sigPub[:]) - - if bp := hex.EncodeToString(c.boxPub[:]); current.EncryptionPublicKey != bp { - c.log.Warnln("EncryptionPublicKey in config is incorrect, should be", bp) + pc, err := iw.NewPacketConn(c.secret) + if err != nil { + return err } - if sp := hex.EncodeToString(c.sigPub[:]); current.SigningPublicKey != sp { - c.log.Warnln("SigningPublicKey in config is incorrect, should be", sp) - } - - c.peers.init(c) - c.router.init(c) - c.switchTable.init(c) // TODO move before peers? before router? + c.PacketConn = pc return nil } @@ -126,8 +107,9 @@ func (c *Core) UpdateConfig(config *config.NodeConfig) { c.config.Replace(*config) // Notify the router and switch about the new configuration - c.router.Act(c, c.router.reconfigure) - c.switchTable.Act(c, c.switchTable.reconfigure) + panic("TODO") + //c.router.Act(c, c.router.reconfigure) + //c.switchTable.Act(c, c.switchTable.reconfigure) }) } @@ -170,15 +152,15 @@ func (c *Core) _start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState return nil, err } - if err := c.switchTable.start(); err != nil { - c.log.Errorln("Failed to start switch") - return nil, err - } + //if err := c.switchTable.start(); err != nil { + // c.log.Errorln("Failed to start switch") + // return nil, err + //} - if err := c.router.start(); err != nil { - c.log.Errorln("Failed to start router") - return nil, err - } + //if err := c.router.start(); err != nil { + // c.log.Errorln("Failed to start router") + // return nil, err + //} c.Act(c, c._addPeerLoop) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go deleted file mode 100644 index 4466ffd8..00000000 --- a/src/yggdrasil/dht.go +++ /dev/null @@ -1,463 +0,0 @@ -package yggdrasil - -// A chord-like Distributed Hash Table (DHT). -// Used to look up coords given a NodeID and bitmask (taken from an IPv6 address). -// Keeps track of immediate successor, predecessor, and all peers. -// Also keeps track of other nodes if they're closer in tree space than all other known nodes encountered when heading in either direction to that point, under the hypothesis that, for the kinds of networks we care about, this should probabilistically include the node needed to keep lookups to near O(logn) steps. - -import ( - "sort" - "time" - - "github.com/yggdrasil-network/yggdrasil-go/src/crypto" -) - -const ( - dht_lookup_size = 16 - dht_timeout = 6 * time.Minute - dht_max_delay = 5 * time.Minute - dht_max_delay_dirty = 30 * time.Second -) - -// dhtInfo represents everything we know about a node in the DHT. -// This includes its key, a cache of its NodeID, coords, and timing/ping related info for deciding who/when to ping nodes for maintenance. -type dhtInfo struct { - nodeID_hidden *crypto.NodeID - key crypto.BoxPubKey - coords []byte - recv time.Time // When we last received a message - pings int // Time out if at least 3 consecutive maintenance pings drop - throttle time.Duration - path []byte // source route the destination, learned from response rpath - dirty bool // Set to true if we've used this node in ping responses (for queries about someone other than the person doing the asking, i.e. real searches) since the last time we heard from the node -} - -// Returns the *NodeID associated with dhtInfo.key, calculating it on the fly the first time or from a cache all subsequent times. -func (info *dhtInfo) getNodeID() *crypto.NodeID { - if info.nodeID_hidden == nil { - info.nodeID_hidden = crypto.GetNodeID(&info.key) - } - return info.nodeID_hidden -} - -// Request for a node to do a lookup. -// Includes our key and coords so they can send a response back, and the destination NodeID we want to ask about. -type dhtReq struct { - Key crypto.BoxPubKey // Key of whoever asked - Coords []byte // Coords of whoever asked - Dest crypto.NodeID // NodeID they're asking about -} - -// Response to a DHT lookup. -// Includes the key and coords of the node that's responding, and the destination they were asked about. -// The main part is Infos []*dhtInfo, the lookup response. -type dhtRes struct { - Key crypto.BoxPubKey // key of the sender - Coords []byte // coords of the sender - Dest crypto.NodeID - Infos []*dhtInfo // response -} - -// Parts of a DHT req usable as a key in a map. -type dhtReqKey struct { - key crypto.BoxPubKey - dest crypto.NodeID -} - -// The main DHT struct. -type dht struct { - router *router - nodeID crypto.NodeID - reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests - callbacks map[dhtReqKey][]dht_callbackInfo // Search and admin lookup callbacks - // These next two could be replaced by a single linked list or similar... - table map[crypto.NodeID]*dhtInfo - imp []*dhtInfo -} - -// Initializes the DHT. -func (t *dht) init(r *router) { - t.router = r - t.nodeID = *t.router.core.NodeID() - t.callbacks = make(map[dhtReqKey][]dht_callbackInfo) - t.reset() -} - -func (t *dht) reconfigure() { - // This is where reconfiguration would go, if we had anything to do -} - -// Resets the DHT in response to coord changes. -// This empties all info from the DHT and drops outstanding requests. -func (t *dht) reset() { - t.reqs = make(map[dhtReqKey]time.Time) - for _, info := range t.table { - if t.isImportant(info) { - t.ping(info, nil) // This will source route if a path is already known - if info.path != nil { - // In case the source route died, but the dest coords are still OK... - info.path = nil - t.ping(info, nil) - } - } - } - t.table = make(map[crypto.NodeID]*dhtInfo) - t.imp = nil -} - -// Does a DHT lookup and returns up to dht_lookup_size results. -func (t *dht) lookup(nodeID *crypto.NodeID, everything bool) []*dhtInfo { - results := make([]*dhtInfo, 0, len(t.table)) - for _, info := range t.table { - results = append(results, info) - } - if len(results) > dht_lookup_size { - // Drop the middle part, so we keep some nodes before and after. - // This should help to bootstrap / recover more quickly. - sort.SliceStable(results, func(i, j int) bool { - return dht_ordered(nodeID, results[i].getNodeID(), results[j].getNodeID()) - }) - newRes := make([]*dhtInfo, 0, len(results)) - newRes = append(newRes, results[len(results)-dht_lookup_size/2:]...) - newRes = append(newRes, results[:len(results)-dht_lookup_size/2]...) - results = newRes - results = results[:dht_lookup_size] - } - for _, info := range results { - info.dirty = true - } - return results -} - -// Insert into table, preserving the time we last sent a packet if the node was already in the table, otherwise setting that time to now. -func (t *dht) insert(info *dhtInfo) { - if *info.getNodeID() == t.nodeID { - // This shouldn't happen, but don't add it if it does - return - } - info.recv = time.Now() - if oldInfo, isIn := t.table[*info.getNodeID()]; isIn { - sameCoords := true - if len(info.coords) != len(oldInfo.coords) { - sameCoords = false - } else { - for idx := 0; idx < len(info.coords); idx++ { - if info.coords[idx] != oldInfo.coords[idx] { - sameCoords = false - break - } - } - } - if sameCoords { - info.throttle = oldInfo.throttle - } - } - t.imp = nil // It needs to update to get a pointer to the new info - t.table[*info.getNodeID()] = info -} - -// Insert a peer into the table if it hasn't been pinged lately, to keep peers from dropping -func (t *dht) insertPeer(info *dhtInfo) { - t.insert(info) // FIXME this resets timers / ping counts / etc, so it seems kind of dangerous - t.ping(info, nil) // This is a quick fix to the above, ping them immediately... -} - -// Return true if first/second/third are (partially) ordered correctly. -func dht_ordered(first, second, third *crypto.NodeID) bool { - lessOrEqual := func(first, second *crypto.NodeID) bool { - for idx := 0; idx < crypto.NodeIDLen; idx++ { - if first[idx] > second[idx] { - return false - } - if first[idx] < second[idx] { - return true - } - } - return true - } - firstLessThanSecond := lessOrEqual(first, second) - secondLessThanThird := lessOrEqual(second, third) - thirdLessThanFirst := lessOrEqual(third, first) - switch { - case firstLessThanSecond && secondLessThanThird: - // Nothing wrapped around 0, the easy case - return true - case thirdLessThanFirst && firstLessThanSecond: - // Third wrapped around 0 - return true - case secondLessThanThird && thirdLessThanFirst: - // Second (and third) wrapped around 0 - return true - } - return false -} - -// Reads a request, performs a lookup, and responds. -// Update info about the node that sent the request. -func (t *dht) handleReq(req *dhtReq, rpath []byte) { - // Send them what they asked for - res := dhtRes{ - Key: t.router.core.boxPub, - Coords: t.router.table.self.getCoords(), - Dest: req.Dest, - Infos: t.lookup(&req.Dest, false), - } - t.sendRes(&res, req, rpath) - // Also add them to our DHT - info := dhtInfo{ - key: req.Key, - coords: req.Coords, - } - if _, isIn := t.table[*info.getNodeID()]; !isIn && t.isImportant(&info) { - t.ping(&info, nil) - } - // Maybe mark nodes from lookup as dirty - if req.Dest != *info.getNodeID() { - // This node asked about someone other than themself, so this wasn't just idle traffic. - for _, info := range res.Infos { - // Mark nodes dirty so we're sure to check up on them again later - info.dirty = true - } - } -} - -// Sends a lookup response to the specified node. -func (t *dht) sendRes(res *dhtRes, req *dhtReq, rpath []byte) { - // Send a reply for a dhtReq - bs := res.encode() - shared := t.router.sessions.getSharedKey(&t.router.core.boxPriv, &req.Key) - payload, nonce := crypto.BoxSeal(shared, bs, nil) - path := append([]byte{0}, switch_reverseCoordBytes(rpath)...) - p := wire_protoTrafficPacket{ - Offset: 1, - Coords: path, - ToKey: req.Key, - FromKey: t.router.core.boxPub, - Nonce: *nonce, - Payload: payload, - } - packet := p.encode() - t.router.out(packet) -} - -type dht_callbackInfo struct { - f func(*dhtRes) - time time.Time -} - -// Adds a callback and removes it after some timeout. -func (t *dht) addCallback(rq *dhtReqKey, callback func(*dhtRes)) { - info := dht_callbackInfo{callback, time.Now().Add(6 * time.Second)} - t.callbacks[*rq] = append(t.callbacks[*rq], info) -} - -// Reads a lookup response, checks that we had sent a matching request, and processes the response info. -// This mainly consists of updating the node we asked in our DHT (they responded, so we know they're still alive), and deciding if we want to do anything with their responses -func (t *dht) handleRes(res *dhtRes, rpath []byte) { - rq := dhtReqKey{res.Key, res.Dest} - if callbacks, isIn := t.callbacks[rq]; isIn { - for _, callback := range callbacks { - callback.f(res) - } - delete(t.callbacks, rq) - } - _, isIn := t.reqs[rq] - if !isIn { - return - } - delete(t.reqs, rq) - rinfo := dhtInfo{ - key: res.Key, - coords: res.Coords, - path: switch_reverseCoordBytes(rpath), - } - if t.isImportant(&rinfo) { - t.insert(&rinfo) - } - for _, info := range res.Infos { - if *info.getNodeID() == t.nodeID { - continue - } // Skip self - if _, isIn := t.table[*info.getNodeID()]; isIn { - // TODO? don't skip if coords are different? - continue - } - if t.isImportant(info) { - t.ping(info, nil) - } - } -} - -// Sends a lookup request to the specified node. -func (t *dht) sendReq(req *dhtReq, dest *dhtInfo) { - // Send a dhtReq to the node in dhtInfo - bs := req.encode() - shared := t.router.sessions.getSharedKey(&t.router.core.boxPriv, &dest.key) - payload, nonce := crypto.BoxSeal(shared, bs, nil) - p := wire_protoTrafficPacket{ - Coords: dest.coords, - ToKey: dest.key, - FromKey: t.router.core.boxPub, - Nonce: *nonce, - Payload: payload, - } - if dest.path != nil { - p.Coords = append([]byte{0}, dest.path...) - p.Offset += 1 - } - packet := p.encode() - t.router.out(packet) - rq := dhtReqKey{dest.key, req.Dest} - t.reqs[rq] = time.Now() -} - -// Sends a lookup to this info, looking for the target. -func (t *dht) ping(info *dhtInfo, target *crypto.NodeID) { - // Creates a req for the node at dhtInfo, asking them about the target (if one is given) or themself (if no target is given) - if target == nil { - target = &t.nodeID - } - req := dhtReq{ - Key: t.router.core.boxPub, - Coords: t.router.table.self.getCoords(), - Dest: *target, - } - t.sendReq(&req, info) -} - -// Periodic maintenance work to keep important DHT nodes alive. -func (t *dht) doMaintenance() { - now := time.Now() - newReqs := make(map[dhtReqKey]time.Time, len(t.reqs)) - for key, start := range t.reqs { - if now.Sub(start) < 6*time.Second { - newReqs[key] = start - } - } - t.reqs = newReqs - newCallbacks := make(map[dhtReqKey][]dht_callbackInfo, len(t.callbacks)) - for key, cs := range t.callbacks { - for _, c := range cs { - if now.Before(c.time) { - newCallbacks[key] = append(newCallbacks[key], c) - } else { - // Signal failure - c.f(nil) - } - } - } - t.callbacks = newCallbacks - for infoID, info := range t.table { - switch { - case info.pings > 6: - // It failed to respond to too many pings - fallthrough - case now.Sub(info.recv) > dht_timeout: - // It's too old - fallthrough - case info.dirty && now.Sub(info.recv) > dht_max_delay_dirty && !t.isImportant(info): - // We won't ping it to refresh it, so just drop it - delete(t.table, infoID) - t.imp = nil - } - } - for _, info := range t.getImportant() { - switch { - case now.Sub(info.recv) > info.throttle: - info.throttle *= 2 - if info.throttle < time.Second { - info.throttle = time.Second - } else if info.throttle > dht_max_delay { - info.throttle = dht_max_delay - } - fallthrough - case info.dirty && now.Sub(info.recv) > dht_max_delay_dirty: - t.ping(info, nil) - info.pings++ - } - } -} - -// Gets a list of important nodes, used by isImportant. -func (t *dht) getImportant() []*dhtInfo { - if t.imp == nil { - // Get a list of all known nodes - infos := make([]*dhtInfo, 0, len(t.table)) - for _, info := range t.table { - infos = append(infos, info) - } - // Sort them by increasing order in distance along the ring - sort.SliceStable(infos, func(i, j int) bool { - // Sort in order of predecessors (!), reverse from chord normal, because it plays nicer with zero bits for unknown parts of target addresses - return dht_ordered(infos[j].getNodeID(), infos[i].getNodeID(), &t.nodeID) - }) - // Keep the ones that are no further than the closest seen so far - minDist := ^uint64(0) - loc := t.router.table.self - important := infos[:0] - for _, info := range infos { - dist := uint64(loc.dist(info.coords)) - if dist < minDist { - minDist = dist - important = append(important, info) - } else if len(important) < 2 { - important = append(important, info) - } - } - var temp []*dhtInfo - minDist = ^uint64(0) - for idx := len(infos) - 1; idx >= 0; idx-- { - info := infos[idx] - dist := uint64(loc.dist(info.coords)) - if dist < minDist { - minDist = dist - temp = append(temp, info) - } else if len(temp) < 2 { - temp = append(temp, info) - } - } - for idx := len(temp) - 1; idx >= 0; idx-- { - important = append(important, temp[idx]) - } - t.imp = important - } - return t.imp -} - -// Returns true if this is a node we need to keep track of for the DHT to work. -func (t *dht) isImportant(ninfo *dhtInfo) bool { - if ninfo.key == t.router.core.boxPub { - return false - } - important := t.getImportant() - // Check if ninfo is of equal or greater importance to what we already know - loc := t.router.table.self - ndist := uint64(loc.dist(ninfo.coords)) - minDist := ^uint64(0) - for _, info := range important { - if (*info.getNodeID() == *ninfo.getNodeID()) || - (ndist < minDist && dht_ordered(info.getNodeID(), ninfo.getNodeID(), &t.nodeID)) { - // Either the same node, or a better one - return true - } - dist := uint64(loc.dist(info.coords)) - if dist < minDist { - minDist = dist - } - } - minDist = ^uint64(0) - for idx := len(important) - 1; idx >= 0; idx-- { - info := important[idx] - if (*info.getNodeID() == *ninfo.getNodeID()) || - (ndist < minDist && dht_ordered(&t.nodeID, ninfo.getNodeID(), info.getNodeID())) { - // Either the same node, or a better one - return true - } - dist := uint64(loc.dist(info.coords)) - if dist < minDist { - minDist = dist - } - } - // We didn't find any important node that ninfo is better than - return false -} diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go deleted file mode 100644 index 9f58d305..00000000 --- a/src/yggdrasil/dialer.go +++ /dev/null @@ -1,120 +0,0 @@ -package yggdrasil - -import ( - "context" - "encoding/hex" - "errors" - "net" - "strconv" - "strings" - "time" - - "github.com/yggdrasil-network/yggdrasil-go/src/crypto" -) - -// Dialer represents an Yggdrasil connection dialer. -type Dialer struct { - core *Core -} - -// Dial opens a session to the given node. The first parameter should be -// "curve25519" or "nodeid" and the second parameter should contain a -// hexadecimal representation of the target. It uses DialContext internally. -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 when -// the network is "nodeid", or DialByPublicKey when the network is "curve25519". -func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { - var nodeID crypto.NodeID - var nodeMask crypto.NodeID - // Process - switch network { - case "curve25519": - dest, err := hex.DecodeString(address) - if err != nil { - return nil, err - } - if len(dest) != crypto.BoxPubKeyLen { - return nil, errors.New("invalid key length supplied") - } - var pubKey crypto.BoxPubKey - copy(pubKey[:], dest) - return d.DialByPublicKey(ctx, &pubKey) - case "nodeid": - // A node ID was provided - we don't need to do anything special with it - if tokens := strings.Split(address, "/"); len(tokens) == 2 { - l, err := strconv.Atoi(tokens[1]) - if err != nil { - return nil, err - } - dest, err := hex.DecodeString(tokens[0]) - if err != nil { - return nil, err - } - copy(nodeID[:], dest) - for idx := 0; idx < l; idx++ { - nodeMask[idx/8] |= 0x80 >> byte(idx%8) - } - } else { - dest, err := hex.DecodeString(tokens[0]) - if err != nil { - return nil, err - } - copy(nodeID[:], dest) - for i := range nodeMask { - nodeMask[i] = 0xFF - } - } - return d.DialByNodeIDandMask(ctx, &nodeID, &nodeMask) - default: - // An unexpected address type was given, so give up - return nil, errors.New("unexpected address type") - } -} - -// DialByNodeIDandMask opens a session to the given node based on raw NodeID -// parameters. 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(ctx context.Context, nodeID, nodeMask *crypto.NodeID) (net.Conn, error) { - startDial := time.Now() - conn := newConn(d.core, nodeID, nodeMask, nil) - if err := conn.search(); err != nil { - // TODO: make searches take a context, so they can be cancelled early - conn.Close() - return nil, err - } - endSearch := time.Now() - d.core.log.Debugln("Dial searched for:", nodeID, "in time:", endSearch.Sub(startDial)) - conn.session.setConn(nil, conn) - var cancel context.CancelFunc - if ctx == nil { - ctx = context.Background() - } - ctx, cancel = context.WithTimeout(ctx, 6*time.Second) - defer cancel() - select { - case <-conn.session.init: - endInit := time.Now() - d.core.log.Debugln("Dial initialized session for:", nodeID, "in time:", endInit.Sub(endSearch)) - d.core.log.Debugln("Finished dial for:", nodeID, "in time:", endInit.Sub(startDial)) - return conn, nil - case <-ctx.Done(): - conn.Close() - return nil, errors.New("session handshake timeout") - } -} - -// DialByPublicKey opens a session to the given node based on the public key. 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) DialByPublicKey(ctx context.Context, pubKey *crypto.BoxPubKey) (net.Conn, error) { - nodeID := crypto.GetNodeID(pubKey) - var nodeMask crypto.NodeID - for i := range nodeMask { - nodeMask[i] = 0xFF - } - return d.DialByNodeIDandMask(ctx, nodeID, &nodeMask) -} diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 9ecc98ff..aff40549 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -1,6 +1,7 @@ package yggdrasil import ( + "crypto/ed25519" "encoding/hex" "errors" "fmt" @@ -17,8 +18,7 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/util" "golang.org/x/net/proxy" - - "github.com/Arceliar/phony" + //"github.com/Arceliar/phony" // TODO? use instead of mutexes ) type links struct { @@ -30,48 +30,27 @@ type links struct { // TODO timeout (to remove from switch), read from config.ReadTimeout } +// linkInfo is used as a map key type linkInfo struct { - box crypto.BoxPubKey // Their encryption key - sig crypto.SigPubKey // Their signing key - linkType string // Type of link, e.g. TCP, AWDL - local string // Local name or address - remote string // Remote name or address -} - -type linkMsgIO interface { - readMsg() ([]byte, error) - writeMsgs([][]byte) (int, error) - close() error - // These are temporary workarounds to stream semantics - _sendMetaBytes([]byte) error - _recvMetaBytes() ([]byte, error) + key crypto.SigPubKey + linkType string // Type of link, e.g. TCP, AWDL + local string // Local name or address + remote string // Remote name or address } type link struct { - lname string - links *links - peer *peer - options linkOptions - msgIO linkMsgIO - info linkInfo - incoming bool - force bool - closed chan struct{} - reader linkReader // Reads packets, notifies this link, passes packets to switch - writer linkWriter // Writes packets, notifies this link - phony.Inbox // Protects the below - sendTimer *time.Timer // Fires to signal that sending is blocked - keepAliveTimer *time.Timer // Fires to send keep-alive traffic - stallTimer *time.Timer // Fires to signal that no incoming traffic (including keep-alive) has been seen - closeTimer *time.Timer // Fires when the link has been idle so long we need to close it - readUnblocked bool // True if we've sent a read message unblocking this peer in the switch - writeUnblocked bool // True if we've sent a write message unblocking this peer in the swithc - shutdown bool // True if we're shutting down, avoids sending some messages that could race with new peers being crated in the same port + lname string + links *links + conn net.Conn + options linkOptions + info linkInfo + incoming bool + force bool + closed chan struct{} } type linkOptions struct { - pinnedCurve25519Keys map[crypto.BoxPubKey]struct{} - pinnedEd25519Keys map[crypto.SigPubKey]struct{} + pinnedEd25519Keys map[crypto.SigPubKey]struct{} } func (l *links) init(c *Core) error { @@ -100,16 +79,6 @@ func (l *links) call(uri string, sintf string) error { } pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/") tcpOpts := tcpOptions{} - if pubkeys, ok := u.Query()["curve25519"]; ok && len(pubkeys) > 0 { - tcpOpts.pinnedCurve25519Keys = make(map[crypto.BoxPubKey]struct{}) - for _, pubkey := range pubkeys { - if boxPub, err := hex.DecodeString(pubkey); err == nil { - var boxPubKey crypto.BoxPubKey - copy(boxPubKey[:], boxPub) - tcpOpts.pinnedCurve25519Keys[boxPubKey] = struct{}{} - } - } - } if pubkeys, ok := u.Query()["ed25519"]; ok && len(pubkeys) > 0 { tcpOpts.pinnedEd25519Keys = make(map[crypto.SigPubKey]struct{}) for _, pubkey := range pubkeys { @@ -157,13 +126,13 @@ func (l *links) listen(uri string) error { } } -func (l *links) create(msgIO linkMsgIO, name, linkType, local, remote string, incoming, force bool, options linkOptions) (*link, error) { +func (l *links) create(conn net.Conn, name, linkType, local, remote string, incoming, force bool, options linkOptions) (*link, error) { // Technically anything unique would work for names, but let's pick something human readable, just for debugging intf := link{ + conn: conn, lname: name, links: l, options: options, - msgIO: msgIO, info: linkInfo{ linkType: linkType, local: local, @@ -172,10 +141,6 @@ func (l *links) create(msgIO linkMsgIO, name, linkType, local, remote string, in incoming: incoming, force: force, } - intf.writer.intf = &intf - intf.writer.worker = make(chan [][]byte, 1) - intf.reader.intf = &intf - intf.reader.err = make(chan error) return &intf, nil } @@ -189,30 +154,31 @@ func (l *links) stop() error { func (intf *link) handler() (chan struct{}, error) { // TODO split some of this into shorter functions, so it's easier to read, and for the FIXME duplicate peer issue mentioned later - go func() { - for bss := range intf.writer.worker { - intf.msgIO.writeMsgs(bss) - } - }() - defer intf.writer.Act(nil, func() { - intf.writer.closed = true - close(intf.writer.worker) - }) - myLinkPub, myLinkPriv := crypto.NewBoxKeys() + defer intf.conn.Close() meta := version_getBaseMetadata() - meta.box = intf.links.core.boxPub - meta.sig = intf.links.core.sigPub - meta.link = *myLinkPub + meta.key = intf.links.core.public metaBytes := meta.encode() // TODO timeouts on send/recv (goroutine for send/recv, channel select w/ timer) var err error - if !util.FuncTimeout(func() { err = intf.msgIO._sendMetaBytes(metaBytes) }, 30*time.Second) { + if !util.FuncTimeout(30*time.Second, func() { + var n int + n, err = intf.conn.Write(metaBytes) + if err == nil && n != len(metaBytes) { + err = errors.New("incomplete metadata send") + } + }) { return nil, errors.New("timeout on metadata send") } if err != nil { return nil, err } - if !util.FuncTimeout(func() { metaBytes, err = intf.msgIO._recvMetaBytes() }, 30*time.Second) { + if !util.FuncTimeout(30*time.Second, func() { + var n int + n, err = io.ReadFull(intf.conn, metaBytes) + if err == nil && n != len(metaBytes) { + err = errors.New("incomplete metadata recv") + } + }) { return nil, errors.New("timeout on metadata recv") } if err != nil { @@ -229,35 +195,31 @@ func (intf *link) handler() (chan struct{}, error) { } // Check if the remote side matches the keys we expected. This is a bit of a weak // check - in future versions we really should check a signature or something like that. - if pinned := intf.options.pinnedCurve25519Keys; pinned != nil { - if _, allowed := pinned[meta.box]; !allowed { - intf.links.core.log.Errorf("Failed to connect to node: %q sent curve25519 key that does not match pinned keys", intf.name) - return nil, fmt.Errorf("failed to connect: host sent curve25519 key that does not match pinned keys") - } - } if pinned := intf.options.pinnedEd25519Keys; pinned != nil { - if _, allowed := pinned[meta.sig]; !allowed { + var key crypto.SigPubKey + copy(key[:], meta.key) + if _, allowed := pinned[key]; !allowed { intf.links.core.log.Errorf("Failed to connect to node: %q sent ed25519 key that does not match pinned keys", intf.name) return nil, fmt.Errorf("failed to connect: host sent ed25519 key that does not match pinned keys") } } // Check if we're authorized to connect to this key / IP + /* TODO check allowed public keys if intf.incoming && !intf.force && !intf.links.core.peers.isAllowedEncryptionPublicKey(&meta.box) { intf.links.core.log.Warnf("%s connection from %s forbidden: AllowedEncryptionPublicKeys does not contain key %s", strings.ToUpper(intf.info.linkType), intf.info.remote, hex.EncodeToString(meta.box[:])) intf.msgIO.close() return nil, nil } + */ // Check if we already have a link to this node - intf.info.box = meta.box - intf.info.sig = meta.sig + copy(intf.info.key[:], meta.key) intf.links.mutex.Lock() if oldIntf, isIn := intf.links.links[intf.info]; isIn { intf.links.mutex.Unlock() // FIXME we should really return an error and let the caller block instead // That lets them do things like close connections on its own, avoid printing a connection message in the first place, etc. intf.links.core.log.Debugln("DEBUG: found existing interface for", intf.name) - intf.msgIO.close() return oldIntf.closed, nil } else { intf.closed = make(chan struct{}) @@ -271,43 +233,13 @@ func (intf *link) handler() (chan struct{}, error) { intf.links.core.log.Debugln("DEBUG: registered interface for", intf.name) } intf.links.mutex.Unlock() - // Create peer - shared := crypto.GetSharedKey(myLinkPriv, &meta.link) - phony.Block(&intf.links.core.peers, func() { - // FIXME don't use phony.Block, it's bad practice, even if it's safe here - intf.peer = intf.links.core.peers._newPeer(&meta.box, &meta.sig, shared, intf) - }) - if intf.peer == nil { - return nil, errors.New("failed to create peer") - } - defer func() { - // More cleanup can go here - intf.Act(nil, func() { - intf.shutdown = true - intf.peer.Act(intf, intf.peer._removeSelf) - }) - }() themAddr := make([]byte, 16) // TODO address.AddrForNodeID(crypto.GetNodeID(&intf.info.box)) themAddrString := net.IP(themAddr[:]).String() themString := fmt.Sprintf("%s@%s", themAddrString, intf.info.remote) intf.links.core.log.Infof("Connected %s: %s, source %s", strings.ToUpper(intf.info.linkType), themString, intf.info.local) - // Start things - go intf.peer.start() - intf.Act(nil, intf._notifyIdle) - intf.reader.Act(nil, intf.reader._read) - // Wait for the reader to finish - // TODO find a way to do this without keeping live goroutines around - done := make(chan struct{}) - defer close(done) - go func() { - select { - case <-intf.links.stopped: - intf.msgIO.close() - case <-done: - } - }() - err = <-intf.reader.err + // Run the handler + err = intf.links.core.PacketConn.HandleConn(ed25519.PublicKey(intf.info.key[:]), intf.conn) // TODO don't report an error if it's just a 'use of closed network connection' if err != nil { intf.links.core.log.Infof("Disconnected %s: %s, source %s; error: %s", @@ -319,43 +251,8 @@ func (intf *link) handler() (chan struct{}, error) { return nil, err } -//////////////////////////////////////////////////////////////////////////////// - -// link needs to match the linkInterface type needed by the peers - -type linkInterface interface { - out([][]byte) - linkOut([]byte) - close() - // These next ones are only used by the API - name() string - local() string - remote() string - interfaceType() string -} - -func (intf *link) out(bss [][]byte) { - intf.Act(nil, func() { - // nil to prevent it from blocking if the link is somehow frozen - // this is safe because another packet won't be sent until the link notifies - // the peer that it's ready for one - intf.writer.sendFrom(nil, bss) - }) -} - -func (intf *link) linkOut(bs []byte) { - intf.Act(nil, func() { - // nil to prevent it from blocking if the link is somehow frozen - // FIXME this is hypothetically not safe, the peer shouldn't be sending - // additional packets until this one finishes, otherwise this could leak - // memory if writing happens slower than link packets are generated... - // that seems unlikely, so it's a lesser evil than deadlocking for now - intf.writer.sendFrom(nil, [][]byte{bs}) - }) -} - func (intf *link) close() { - intf.Act(nil, func() { intf.msgIO.close() }) + intf.conn.Close() } func (intf *link) name() string { @@ -373,168 +270,3 @@ func (intf *link) remote() string { func (intf *link) interfaceType() string { return intf.info.linkType } - -//////////////////////////////////////////////////////////////////////////////// -const ( - sendTime = 1 * time.Second // How long to wait before deciding a send is blocked - keepAliveTime = 2 * time.Second // How long to wait before sending a keep-alive response if we have no real traffic to send - stallTime = 6 * time.Second // How long to wait for response traffic before deciding the connection has stalled - closeTime = 2 * switch_timeout // How long to wait before closing the link -) - -// notify the intf that we're currently sending -func (intf *link) notifySending(size int) { - intf.Act(&intf.writer, func() { - intf.sendTimer = time.AfterFunc(sendTime, intf.notifyBlockedSend) - if intf.keepAliveTimer != nil { - intf.keepAliveTimer.Stop() - intf.keepAliveTimer = nil - } - intf.peer.notifyBlocked(intf) - }) -} - -// This gets called from a time.AfterFunc, and notifies the switch that we appear -// to have gotten blocked on a write, so the switch should start routing traffic -// through other links, if alternatives exist -func (intf *link) notifyBlockedSend() { - intf.Act(nil, func() { - if intf.sendTimer != nil { - //As far as we know, we're still trying to send, and the timer fired. - intf.sendTimer.Stop() - intf.sendTimer = nil - if !intf.shutdown && intf.writeUnblocked { - intf.writeUnblocked = false - intf.links.core.switchTable.blockPeer(intf, intf.peer.port, true) - } - } - }) -} - -// notify the intf that we've finished sending, returning the peer to the switch -func (intf *link) notifySent(size int) { - intf.Act(&intf.writer, func() { - if intf.sendTimer != nil { - intf.sendTimer.Stop() - intf.sendTimer = nil - } - if intf.keepAliveTimer != nil { - // TODO? unset this when we start sending, not when we finish... - intf.keepAliveTimer.Stop() - intf.keepAliveTimer = nil - } - intf._notifyIdle() - if size > 0 && intf.stallTimer == nil { - intf.stallTimer = time.AfterFunc(stallTime, intf.notifyStalled) - } - if !intf.shutdown && !intf.writeUnblocked { - intf.writeUnblocked = true - intf.links.core.switchTable.unblockPeer(intf, intf.peer.port, true) - } - }) -} - -// Notify the peer that we're ready for more traffic -func (intf *link) _notifyIdle() { - intf.peer.Act(intf, intf.peer._handleIdle) -} - -// Set the peer as stalled, to prevent them from returning to the switch until a read succeeds -func (intf *link) notifyStalled() { - intf.Act(nil, func() { // Sent from a time.AfterFunc - if intf.stallTimer != nil { - intf.stallTimer.Stop() - intf.stallTimer = nil - if !intf.shutdown && intf.readUnblocked { - intf.readUnblocked = false - intf.links.core.switchTable.blockPeer(intf, intf.peer.port, false) - } - } - }) -} - -// reset the close timer -func (intf *link) notifyReading() { - intf.Act(&intf.reader, func() { - intf.closeTimer = time.AfterFunc(closeTime, func() { intf.msgIO.close() }) - }) -} - -// wake up the link if it was stalled, and (if size > 0) prepare to send keep-alive traffic -func (intf *link) notifyRead(size int) { - intf.Act(&intf.reader, func() { - intf.closeTimer.Stop() - if intf.stallTimer != nil { - intf.stallTimer.Stop() - intf.stallTimer = nil - } - if size > 0 && intf.keepAliveTimer == nil { - intf.keepAliveTimer = time.AfterFunc(keepAliveTime, intf.notifyDoKeepAlive) - } - if !intf.shutdown && !intf.readUnblocked { - intf.readUnblocked = true - intf.links.core.switchTable.unblockPeer(intf, intf.peer.port, false) - } - }) -} - -// We need to send keep-alive traffic now -func (intf *link) notifyDoKeepAlive() { - intf.Act(nil, func() { // Sent from a time.AfterFunc - if intf.keepAliveTimer != nil { - intf.keepAliveTimer.Stop() - intf.keepAliveTimer = nil - intf.writer.sendFrom(nil, [][]byte{nil}) // Empty keep-alive traffic - } - }) -} - -//////////////////////////////////////////////////////////////////////////////// - -type linkWriter struct { - phony.Inbox - intf *link - worker chan [][]byte - closed bool -} - -func (w *linkWriter) sendFrom(from phony.Actor, bss [][]byte) { - w.Act(from, func() { - if w.closed { - return - } - var size int - for _, bs := range bss { - size += len(bs) - } - w.intf.notifySending(size) - w.worker <- bss - w.intf.notifySent(size) - }) -} - -//////////////////////////////////////////////////////////////////////////////// - -type linkReader struct { - phony.Inbox - intf *link - err chan error -} - -func (r *linkReader) _read() { - r.intf.notifyReading() - msg, err := r.intf.msgIO.readMsg() - r.intf.notifyRead(len(msg)) - if len(msg) > 0 { - r.intf.peer.handlePacketFrom(r, msg) - } - if err != nil { - if err != io.EOF { - r.err <- err - } - close(r.err) - return - } - // Now try to read again - r.Act(nil, r._read) -} diff --git a/src/yggdrasil/listener.go b/src/yggdrasil/listener.go deleted file mode 100644 index 74ef3e88..00000000 --- a/src/yggdrasil/listener.go +++ /dev/null @@ -1,45 +0,0 @@ -package yggdrasil - -import ( - "errors" - "net" -) - -// Listener waits for incoming sessions -type Listener struct { - core *Core - conn chan *Conn - close chan interface{} -} - -// Accept blocks until a new incoming session is received -func (l *Listener) Accept() (net.Conn, error) { - select { - case c, ok := <-l.conn: - if !ok { - return nil, errors.New("listener closed") - } - return c, nil - case <-l.close: - return nil, errors.New("listener closed") - } -} - -// Close will stop the listener -func (l *Listener) Close() (err error) { - defer func() { - recover() - err = errors.New("already closed") - }() - if l.core.router.sessions.listener == l { - l.core.router.sessions.listener = nil - } - close(l.close) - close(l.conn) - return nil -} - -// Addr returns the address of the listener -func (l *Listener) Addr() net.Addr { - return &l.core.boxPub -} diff --git a/src/yggdrasil/nodeinfo.go b/src/yggdrasil/nodeinfo.go deleted file mode 100644 index 30db03c5..00000000 --- a/src/yggdrasil/nodeinfo.go +++ /dev/null @@ -1,209 +0,0 @@ -package yggdrasil - -import ( - "encoding/json" - "errors" - "runtime" - "strings" - "time" - - "github.com/Arceliar/phony" - "github.com/yggdrasil-network/yggdrasil-go/src/crypto" - "github.com/yggdrasil-network/yggdrasil-go/src/version" -) - -type nodeinfo struct { - phony.Inbox - core *Core - myNodeInfo NodeInfoPayload - callbacks map[crypto.BoxPubKey]nodeinfoCallback - cache map[crypto.BoxPubKey]nodeinfoCached - table *lookupTable -} - -type nodeinfoCached struct { - payload NodeInfoPayload - created time.Time -} - -type nodeinfoCallback struct { - call func(nodeinfo *NodeInfoPayload) - created time.Time -} - -// Represents a session nodeinfo packet. -type nodeinfoReqRes struct { - SendPermPub crypto.BoxPubKey // Sender's permanent key - SendCoords []byte // Sender's coords - IsResponse bool - NodeInfo NodeInfoPayload -} - -// Initialises the nodeinfo cache/callback maps, and starts a goroutine to keep -// the cache/callback maps clean of stale entries -func (m *nodeinfo) init(core *Core) { - m.Act(nil, func() { - m._init(core) - }) -} - -func (m *nodeinfo) _init(core *Core) { - m.core = core - m.callbacks = make(map[crypto.BoxPubKey]nodeinfoCallback) - m.cache = make(map[crypto.BoxPubKey]nodeinfoCached) - - m._cleanup() -} - -func (m *nodeinfo) _cleanup() { - for boxPubKey, callback := range m.callbacks { - if time.Since(callback.created) > time.Minute { - delete(m.callbacks, boxPubKey) - } - } - for boxPubKey, cache := range m.cache { - if time.Since(cache.created) > time.Hour { - delete(m.cache, boxPubKey) - } - } - time.AfterFunc(time.Second*30, func() { - m.Act(nil, m._cleanup) - }) -} - -// Add a callback for a nodeinfo lookup -func (m *nodeinfo) addCallback(sender crypto.BoxPubKey, call func(nodeinfo *NodeInfoPayload)) { - m.Act(nil, func() { - m._addCallback(sender, call) - }) -} - -func (m *nodeinfo) _addCallback(sender crypto.BoxPubKey, call func(nodeinfo *NodeInfoPayload)) { - m.callbacks[sender] = nodeinfoCallback{ - created: time.Now(), - call: call, - } -} - -// Handles the callback, if there is one -func (m *nodeinfo) _callback(sender crypto.BoxPubKey, nodeinfo NodeInfoPayload) { - if callback, ok := m.callbacks[sender]; ok { - callback.call(&nodeinfo) - delete(m.callbacks, sender) - } -} - -// Get the current node's nodeinfo -func (m *nodeinfo) getNodeInfo() (p NodeInfoPayload) { - phony.Block(m, func() { - p = m._getNodeInfo() - }) - return -} - -func (m *nodeinfo) _getNodeInfo() NodeInfoPayload { - return m.myNodeInfo -} - -// Set the current node's nodeinfo -func (m *nodeinfo) setNodeInfo(given interface{}, privacy bool) (err error) { - phony.Block(m, func() { - err = m._setNodeInfo(given, privacy) - }) - return -} - -func (m *nodeinfo) _setNodeInfo(given interface{}, privacy bool) error { - defaults := map[string]interface{}{ - "buildname": version.BuildName(), - "buildversion": version.BuildVersion(), - "buildplatform": runtime.GOOS, - "buildarch": runtime.GOARCH, - } - newnodeinfo := make(map[string]interface{}) - if !privacy { - for k, v := range defaults { - newnodeinfo[k] = v - } - } - if nodeinfomap, ok := given.(map[string]interface{}); ok { - for key, value := range nodeinfomap { - if _, ok := defaults[key]; ok { - if strvalue, strok := value.(string); strok && strings.EqualFold(strvalue, "null") || value == nil { - delete(newnodeinfo, key) - } - continue - } - newnodeinfo[key] = value - } - } - newjson, err := json.Marshal(newnodeinfo) - if err == nil { - if len(newjson) > 16384 { - return errors.New("NodeInfo exceeds max length of 16384 bytes") - } - m.myNodeInfo = newjson - return nil - } - return err -} - -// Add nodeinfo into the cache for a node -func (m *nodeinfo) _addCachedNodeInfo(key crypto.BoxPubKey, payload NodeInfoPayload) { - m.cache[key] = nodeinfoCached{ - created: time.Now(), - payload: payload, - } -} - -// Get a nodeinfo entry from the cache -func (m *nodeinfo) _getCachedNodeInfo(key crypto.BoxPubKey) (NodeInfoPayload, error) { - if nodeinfo, ok := m.cache[key]; ok { - return nodeinfo.payload, nil - } - return NodeInfoPayload{}, errors.New("No cache entry found") -} - -// Handles a nodeinfo request/response - called from the router -func (m *nodeinfo) handleNodeInfo(from phony.Actor, nodeinfo *nodeinfoReqRes) { - m.Act(from, func() { - m._handleNodeInfo(nodeinfo) - }) -} - -func (m *nodeinfo) _handleNodeInfo(nodeinfo *nodeinfoReqRes) { - if nodeinfo.IsResponse { - m._callback(nodeinfo.SendPermPub, nodeinfo.NodeInfo) - m._addCachedNodeInfo(nodeinfo.SendPermPub, nodeinfo.NodeInfo) - } else { - m._sendNodeInfo(nodeinfo.SendPermPub, nodeinfo.SendCoords, true) - } -} - -// Send nodeinfo request or response - called from the router -func (m *nodeinfo) sendNodeInfo(key crypto.BoxPubKey, coords []byte, isResponse bool) { - m.Act(nil, func() { - m._sendNodeInfo(key, coords, isResponse) - }) -} - -func (m *nodeinfo) _sendNodeInfo(key crypto.BoxPubKey, coords []byte, isResponse bool) { - loc := m.table.self - nodeinfo := nodeinfoReqRes{ - SendCoords: loc.getCoords(), - IsResponse: isResponse, - NodeInfo: m._getNodeInfo(), - } - bs := nodeinfo.encode() - shared := m.core.router.sessions.getSharedKey(&m.core.boxPriv, &key) - payload, nonce := crypto.BoxSeal(shared, bs, nil) - p := wire_protoTrafficPacket{ - Coords: coords, - ToKey: key, - FromKey: m.core.boxPub, - Nonce: *nonce, - Payload: payload, - } - packet := p.encode() - m.core.router.out(packet) -} diff --git a/src/yggdrasil/packetqueue.go b/src/yggdrasil/packetqueue.go deleted file mode 100644 index 61f3cfd2..00000000 --- a/src/yggdrasil/packetqueue.go +++ /dev/null @@ -1,119 +0,0 @@ -package yggdrasil - -import ( - "container/heap" - "time" -) - -// TODO separate queues per e.g. traffic flow -// For now, we put everything in queue - -type pqStreamID string - -type pqPacketInfo struct { - packet []byte - time time.Time -} - -type pqStream struct { - id pqStreamID - infos []pqPacketInfo - size uint64 -} - -type packetQueue struct { - streams []pqStream - size uint64 -} - -// drop will remove a packet from the queue, returning it to the pool -// returns true if a packet was removed, false otherwise -func (q *packetQueue) drop() bool { - if q.size == 0 { - return false - } - var longestIdx int - for idx := range q.streams { - if q.streams[idx].size > q.streams[longestIdx].size { - longestIdx = idx - } - } - stream := q.streams[longestIdx] - info := stream.infos[0] - if len(stream.infos) > 1 { - stream.infos = stream.infos[1:] - stream.size -= uint64(len(info.packet)) - q.streams[longestIdx] = stream - q.size -= uint64(len(info.packet)) - heap.Fix(q, longestIdx) - } else { - heap.Remove(q, longestIdx) - } - pool_putBytes(info.packet) - return true -} - -func (q *packetQueue) push(packet []byte) { - _, coords := wire_getTrafficOffsetAndCoords(packet) - id := pqStreamID(coords) // just coords for now - info := pqPacketInfo{packet: packet, time: time.Now()} - for idx := range q.streams { - if q.streams[idx].id == id { - q.streams[idx].infos = append(q.streams[idx].infos, info) - q.streams[idx].size += uint64(len(packet)) - q.size += uint64(len(packet)) - return - } - } - stream := pqStream{id: id, size: uint64(len(packet))} - stream.infos = append(stream.infos, info) - heap.Push(q, stream) -} - -func (q *packetQueue) pop() ([]byte, bool) { - if q.size > 0 { - stream := q.streams[0] - info := stream.infos[0] - if len(stream.infos) > 1 { - stream.infos = stream.infos[1:] - stream.size -= uint64(len(info.packet)) - q.streams[0] = stream - q.size -= uint64(len(info.packet)) - heap.Fix(q, 0) - } else { - heap.Remove(q, 0) - } - return info.packet, true - } - return nil, false -} - -//////////////////////////////////////////////////////////////////////////////// - -// Interface methods for packetQueue to satisfy heap.Interface - -func (q *packetQueue) Len() int { - return len(q.streams) -} - -func (q *packetQueue) Less(i, j int) bool { - return q.streams[i].infos[0].time.Before(q.streams[j].infos[0].time) -} - -func (q *packetQueue) Swap(i, j int) { - q.streams[i], q.streams[j] = q.streams[j], q.streams[i] -} - -func (q *packetQueue) Push(x interface{}) { - stream := x.(pqStream) - q.streams = append(q.streams, stream) - q.size += stream.size -} - -func (q *packetQueue) Pop() interface{} { - idx := len(q.streams) - 1 - stream := q.streams[idx] - q.streams = q.streams[:idx] - q.size -= stream.size - return stream -} diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go deleted file mode 100644 index 74e1023f..00000000 --- a/src/yggdrasil/peer.go +++ /dev/null @@ -1,447 +0,0 @@ -package yggdrasil - -// TODO cleanup, this file is kind of a mess -// Commented code should be removed -// Live code should be better commented - -import ( - "encoding/hex" - "time" - - "github.com/yggdrasil-network/yggdrasil-go/src/crypto" - - "github.com/Arceliar/phony" -) - -// The peers struct represents peers with an active connection. -// Incoming packets are passed to the corresponding peer, which handles them somehow. -// In most cases, this involves passing the packet to the handler for outgoing traffic to another peer. -// In other cases, its link protocol traffic is used to build the spanning tree, in which case this checks signatures and passes the message along to the switch. -type peers struct { - phony.Inbox - core *Core - ports map[switchPort]*peer // use CoW semantics, share updated version with each peer - table *lookupTable // Sent from switch, share updated version with each peer -} - -// Initializes the peers struct. -func (ps *peers) init(c *Core) { - ps.core = c - ps.ports = make(map[switchPort]*peer) - ps.table = new(lookupTable) -} - -func (ps *peers) reconfigure() { - // This is where reconfiguration would go, if we had anything to do -} - -// Returns true if an incoming peer connection to a key is allowed, either -// because the key is in the whitelist or because the whitelist is empty. -func (ps *peers) isAllowedEncryptionPublicKey(box *crypto.BoxPubKey) bool { - boxstr := hex.EncodeToString(box[:]) - ps.core.config.Mutex.RLock() - defer ps.core.config.Mutex.RUnlock() - for _, v := range ps.core.config.Current.AllowedEncryptionPublicKeys { - if v == boxstr { - return true - } - } - return len(ps.core.config.Current.AllowedEncryptionPublicKeys) == 0 -} - -// Adds a key to the whitelist. -func (ps *peers) addAllowedEncryptionPublicKey(box string) { - ps.core.config.Mutex.RLock() - defer ps.core.config.Mutex.RUnlock() - ps.core.config.Current.AllowedEncryptionPublicKeys = - append(ps.core.config.Current.AllowedEncryptionPublicKeys, box) -} - -// Removes a key from the whitelist. -func (ps *peers) removeAllowedEncryptionPublicKey(box string) { - ps.core.config.Mutex.RLock() - defer ps.core.config.Mutex.RUnlock() - for k, v := range ps.core.config.Current.AllowedEncryptionPublicKeys { - if v == box { - ps.core.config.Current.AllowedEncryptionPublicKeys = - append(ps.core.config.Current.AllowedEncryptionPublicKeys[:k], - ps.core.config.Current.AllowedEncryptionPublicKeys[k+1:]...) - } - } -} - -// Gets the whitelist of allowed keys for incoming connections. -func (ps *peers) getAllowedEncryptionPublicKeys() []string { - ps.core.config.Mutex.RLock() - defer ps.core.config.Mutex.RUnlock() - return ps.core.config.Current.AllowedEncryptionPublicKeys -} - -// Information known about a peer, including their box/sig keys, precomputed shared keys (static and ephemeral) and a handler for their outgoing traffic -type peer struct { - phony.Inbox - core *Core - intf linkInterface - port switchPort - box crypto.BoxPubKey - sig crypto.SigPubKey - shared crypto.BoxSharedKey - linkShared crypto.BoxSharedKey - endpoint string - firstSeen time.Time // To track uptime for getPeers - dinfo *dhtInfo // used to keep the DHT working - // The below aren't actually useful internally, they're just gathered for getPeers statistics - bytesSent uint64 - bytesRecvd uint64 - ports map[switchPort]*peer - table *lookupTable - queue packetQueue - max uint64 - seq uint64 // this and idle are used to detect when to drop packets from queue - idle bool - drop bool // set to true if we're dropping packets from the queue -} - -func (ps *peers) updateTables(from phony.Actor, table *lookupTable) { - ps.Act(from, func() { - ps.table = table - ps._updatePeers() - }) -} - -func (ps *peers) _updatePeers() { - ports := ps.ports - table := ps.table - for _, peer := range ps.ports { - p := peer // peer is mutated during iteration - p.Act(ps, func() { - p.ports = ports - p.table = table - }) - } -} - -// Creates a new peer with the specified box, sig, and linkShared keys, using the lowest unoccupied port number. -func (ps *peers) _newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShared *crypto.BoxSharedKey, intf linkInterface) *peer { - now := time.Now() - p := peer{box: *box, - core: ps.core, - intf: intf, - sig: *sig, - shared: *crypto.GetSharedKey(&ps.core.boxPriv, box), - linkShared: *linkShared, - firstSeen: now, - } - oldPorts := ps.ports - newPorts := make(map[switchPort]*peer) - for k, v := range oldPorts { - newPorts[k] = v - } - for idx := switchPort(0); true; idx++ { - if _, isIn := newPorts[idx]; !isIn { - p.port = switchPort(idx) - newPorts[p.port] = &p - break - } - } - ps.ports = newPorts - ps._updatePeers() - return &p -} - -func (p *peer) _removeSelf() { - p.core.peers.Act(p, func() { - p.core.peers._removePeer(p) - }) -} - -// Removes a peer for a given port, if one exists. -func (ps *peers) _removePeer(p *peer) { - if q := ps.ports[p.port]; p.port == 0 || q != p { - return - } // Can't remove self peer or nonexistant peer - ps.core.switchTable.forgetPeer(ps, p.port) - oldPorts := ps.ports - newPorts := make(map[switchPort]*peer) - for k, v := range oldPorts { - newPorts[k] = v - } - delete(newPorts, p.port) - p.intf.close() - ps.ports = newPorts - ps._updatePeers() -} - -// If called, sends a notification to each peer that they should send a new switch message. -// Mainly called by the switch after an update. -func (ps *peers) sendSwitchMsgs(from phony.Actor) { - ps.Act(from, func() { - for _, peer := range ps.ports { - p := peer - if p.port == 0 { - continue - } - p.Act(ps, p._sendSwitchMsg) - } - }) -} - -func (ps *peers) updateDHT(from phony.Actor) { - ps.Act(from, func() { - for _, peer := range ps.ports { - p := peer - if p.port == 0 { - continue - } - p.Act(ps, p._updateDHT) - } - }) -} - -// This must be launched in a separate goroutine by whatever sets up the peer struct. -func (p *peer) start() { - // Just for good measure, immediately send a switch message to this peer when we start - p.Act(nil, p._sendSwitchMsg) -} - -func (p *peer) _updateDHT() { - if p.dinfo != nil { - p.core.router.insertPeer(p, p.dinfo) - } -} - -func (p *peer) handlePacketFrom(from phony.Actor, packet []byte) { - p.Act(from, func() { - p._handlePacket(packet) - }) -} - -// Called to handle incoming packets. -// Passes the packet to a handler for that packet type. -func (p *peer) _handlePacket(packet []byte) { - // FIXME this is off by stream padding and msg length overhead, should be done in tcp.go - p.bytesRecvd += uint64(len(packet)) - pType, pTypeLen := wire_decode_uint64(packet) - if pTypeLen == 0 { - return - } - switch pType { - case wire_Traffic: - p._handleTraffic(packet) - case wire_ProtocolTraffic: - p._handleTraffic(packet) - case wire_LinkProtocolTraffic: - p._handleLinkTraffic(packet) - default: - } -} - -// Called to handle traffic or protocolTraffic packets. -// In either case, this reads from the coords of the packet header, does a switch lookup, and forwards to the next node. -func (p *peer) _handleTraffic(packet []byte) { - if _, isIn := p.table.elems[p.port]; !isIn && p.port != 0 { - // Drop traffic if the peer isn't in the switch - return - } - obs, coords := wire_getTrafficOffsetAndCoords(packet) - offset, _ := wire_decode_uint64(obs) - ports := switch_getPorts(coords) - if offset == 0 { - offset = p.table.getOffset(ports) - } - var next switchPort - if offset == 0 { - // Greedy routing, find the best next hop - next = p.table.lookup(ports) - } else { - // Source routing, read next hop from coords and update offset/obs - if int(offset) < len(ports) { - next = ports[offset] - offset += 1 - // FIXME this breaks if offset is > 127, it's just for testing - wire_put_uint64(offset, obs[:0]) - } - } - packet = wire_put_uint64(uint64(p.port), packet) - if nPeer, isIn := p.ports[next]; isIn { - nPeer.sendPacketFrom(p, packet) - } - //p.core.switchTable.packetInFrom(p, packet) -} - -func (p *peer) sendPacketFrom(from phony.Actor, packet []byte) { - p.Act(from, func() { - p._sendPacket(packet) - }) -} - -func (p *peer) _sendPacket(packet []byte) { - p.queue.push(packet) - if p.idle { - p.idle = false - p._handleIdle() - } else if p.drop { - for p.queue.size > p.max { - p.queue.drop() - } - } -} - -func (p *peer) _handleIdle() { - var packets [][]byte - var size uint64 - for { - if packet, success := p.queue.pop(); success { - packets = append(packets, packet) - size += uint64(len(packet)) - } else { - break - } - } - p.seq++ - if len(packets) > 0 { - p.bytesSent += uint64(size) - p.intf.out(packets) - p.max = p.queue.size - } else { - p.idle = true - } - p.drop = false -} - -func (p *peer) notifyBlocked(from phony.Actor) { - p.Act(from, func() { - seq := p.seq - p.Act(nil, func() { - if seq == p.seq { - p.drop = true - p.max = 2*p.queue.size + streamMsgSize - } - }) - }) -} - -// This wraps the packet in the inner (ephemeral) and outer (permanent) crypto layers. -// It sends it to p.linkOut, which bypasses the usual packet queues. -func (p *peer) _sendLinkPacket(packet []byte) { - innerPayload, innerNonce := crypto.BoxSeal(&p.linkShared, packet, nil) - innerLinkPacket := wire_linkProtoTrafficPacket{ - Nonce: *innerNonce, - Payload: innerPayload, - } - outerPayload := innerLinkPacket.encode() - bs, nonce := crypto.BoxSeal(&p.shared, outerPayload, nil) - linkPacket := wire_linkProtoTrafficPacket{ - Nonce: *nonce, - Payload: bs, - } - packet = linkPacket.encode() - p.intf.linkOut(packet) -} - -// Decrypts the outer (permanent) and inner (ephemeral) crypto layers on link traffic. -// Identifies the link traffic type and calls the appropriate handler. -func (p *peer) _handleLinkTraffic(bs []byte) { - packet := wire_linkProtoTrafficPacket{} - if !packet.decode(bs) { - return - } - outerPayload, isOK := crypto.BoxOpen(&p.shared, packet.Payload, &packet.Nonce) - if !isOK { - return - } - innerPacket := wire_linkProtoTrafficPacket{} - if !innerPacket.decode(outerPayload) { - return - } - payload, isOK := crypto.BoxOpen(&p.linkShared, innerPacket.Payload, &innerPacket.Nonce) - if !isOK { - return - } - pType, pTypeLen := wire_decode_uint64(payload) - if pTypeLen == 0 { - return - } - switch pType { - case wire_SwitchMsg: - p._handleSwitchMsg(payload) - default: - } -} - -// Gets a switchMsg from the switch, adds signed next-hop info for this peer, and sends it to them. -func (p *peer) _sendSwitchMsg() { - msg := p.table.getMsg() - if msg == nil { - return - } - bs := getBytesForSig(&p.sig, msg) - msg.Hops = append(msg.Hops, switchMsgHop{ - Port: p.port, - Next: p.sig, - Sig: *crypto.Sign(&p.core.sigPriv, bs), - }) - packet := msg.encode() - p._sendLinkPacket(packet) -} - -// Handles a switchMsg from the peer, checking signatures and passing good messages to the switch. -// Also creates a dhtInfo struct and arranges for it to be added to the dht (this is how dht bootstrapping begins). -func (p *peer) _handleSwitchMsg(packet []byte) { - var msg switchMsg - if !msg.decode(packet) { - return - } - if len(msg.Hops) < 1 { - p._removeSelf() - return - } - var loc switchLocator - prevKey := msg.Root - for idx, hop := range msg.Hops { - // Check signatures and collect coords for dht - sigMsg := msg - sigMsg.Hops = msg.Hops[:idx] - loc.coords = append(loc.coords, hop.Port) - bs := getBytesForSig(&hop.Next, &sigMsg) - if !crypto.Verify(&prevKey, bs, &hop.Sig) { - p._removeSelf() - return - } - prevKey = hop.Next - } - p.core.switchTable.Act(p, func() { - if !p.core.switchTable._checkRoot(&msg) { - // Bad switch message - p.Act(&p.core.switchTable, func() { - p.dinfo = nil - }) - } else { - // handle the message - p.core.switchTable._handleMsg(&msg, p.port, false) - p.Act(&p.core.switchTable, func() { - // Pass a message to the dht informing it that this peer (still) exists - loc.coords = loc.coords[:len(loc.coords)-1] - p.dinfo = &dhtInfo{ - key: p.box, - coords: loc.getCoords(), - } - p._updateDHT() - }) - } - }) -} - -// This generates the bytes that we sign or check the signature of for a switchMsg. -// It begins with the next node's key, followed by the root and the timestamp, followed by coords being advertised to the next node. -func getBytesForSig(next *crypto.SigPubKey, msg *switchMsg) []byte { - var loc switchLocator - for _, hop := range msg.Hops { - loc.coords = append(loc.coords, hop.Port) - } - bs := append([]byte(nil), next[:]...) - bs = append(bs, msg.Root[:]...) - bs = append(bs, wire_encode_uint64(wire_intToUint(msg.TStamp))...) - bs = append(bs, wire_encode_coords(loc.getCoords())...) - return bs -} diff --git a/src/yggdrasil/pool.go b/src/yggdrasil/pool.go deleted file mode 100644 index e95236a5..00000000 --- a/src/yggdrasil/pool.go +++ /dev/null @@ -1,20 +0,0 @@ -package yggdrasil - -import "sync" - -// Used internally to reduce allocations in the hot loop -// I.e. packets being switched or between the crypto and the switch -// For safety reasons, these must not escape this package -var pool = sync.Pool{New: func() interface{} { return []byte(nil) }} - -func pool_getBytes(size int) []byte { - bs := pool.Get().([]byte) - if cap(bs) < size { - bs = make([]byte, size) - } - return bs[:size] -} - -func pool_putBytes(bs []byte) { - pool.Put(bs) -} diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go deleted file mode 100644 index 924d285a..00000000 --- a/src/yggdrasil/router.go +++ /dev/null @@ -1,289 +0,0 @@ -package yggdrasil - -// This part does most of the work to handle packets to/from yourself -// It also manages crypto and dht info -// TODO clean up old/unused code, maybe improve comments on whatever is left - -// Send: -// Receive a packet from the adapter -// Look up session (if none exists, trigger a search) -// Hand off to session (which encrypts, etc) -// Session will pass it back to router.out, which hands it off to the self peer -// The self peer triggers a lookup to find which peer to send to next -// And then passes it to that's peer's peer.out function -// The peer.out function sends it over the wire to the matching peer - -// Recv: -// A packet comes in off the wire, and goes to a peer.handlePacket -// The peer does a lookup, sees no better peer than the self -// Hands it to the self peer.out, which passes it to router.in -// If it's dht/seach/etc. traffic, the router passes it to that part -// If it's an encapsulated IPv6 packet, the router looks up the session for it -// The packet is passed to the session, which decrypts it, router.recvPacket -// The router then runs some sanity checks before passing it to the adapter - -import ( - //"bytes" - - "time" - - "github.com/yggdrasil-network/yggdrasil-go/src/address" - "github.com/yggdrasil-network/yggdrasil-go/src/crypto" - - "github.com/Arceliar/phony" -) - -// The router struct has channels to/from the adapter device and a self peer (0), which is how messages are passed between this node and the peers/switch layer. -// The router's phony.Inbox goroutine is responsible for managing all information related to the dht, searches, and crypto sessions. -type router struct { - phony.Inbox - core *Core - addr address.Address - subnet address.Subnet - out func([]byte) // packets we're sending to the network, link to peer's "in" - dht dht - nodeinfo nodeinfo - searches searches - sessions sessions - intf routerInterface - peer *peer - table *lookupTable // has a copy of our locator -} - -// Initializes the router struct, which includes setting up channels to/from the adapter. -func (r *router) init(core *Core) { - r.core = core - // TODO r.addr = *address.AddrForNodeID(&r.dht.nodeID) - // TODO r.subnet = *address.SubnetForNodeID(&r.dht.nodeID) - r.intf.router = r - phony.Block(&r.core.peers, func() { - // FIXME don't block here! - r.peer = r.core.peers._newPeer(&r.core.boxPub, &r.core.sigPub, &crypto.BoxSharedKey{}, &r.intf) - }) - r.peer.Act(r, r.peer._handleIdle) - r.out = func(bs []byte) { - r.peer.handlePacketFrom(r, bs) - } - r.nodeinfo.init(r.core) - r.core.config.Mutex.RLock() - r.nodeinfo.setNodeInfo(r.core.config.Current.NodeInfo, r.core.config.Current.NodeInfoPrivacy) - r.core.config.Mutex.RUnlock() - r.dht.init(r) - r.searches.init(r) - r.sessions.init(r) -} - -func (r *router) updateTable(from phony.Actor, table *lookupTable) { - r.Act(from, func() { - r.table = table - r.nodeinfo.Act(r, func() { - r.nodeinfo.table = table - }) - for _, ses := range r.sessions.sinfos { - sinfo := ses - sinfo.Act(r, func() { - sinfo.table = table - }) - } - }) -} - -// Reconfigures the router and any child modules. This should only ever be run -// by the router actor. -func (r *router) reconfigure() { - // Reconfigure the router - current := r.core.config.GetCurrent() - r.core.log.Println("Reloading NodeInfo...") - if err := r.nodeinfo.setNodeInfo(current.NodeInfo, current.NodeInfoPrivacy); err != nil { - r.core.log.Errorln("Error reloading NodeInfo:", err) - } else { - r.core.log.Infoln("NodeInfo updated") - } - // Reconfigure children - r.dht.reconfigure() - r.searches.reconfigure() - r.sessions.reconfigure() -} - -// Starts the tickerLoop goroutine. -func (r *router) start() error { - r.core.log.Infoln("Starting router") - go r.doMaintenance() - return nil -} - -// Insert a peer info into the dht, TODO? make the dht a separate actor -func (r *router) insertPeer(from phony.Actor, info *dhtInfo) { - r.Act(from, func() { - r.dht.insertPeer(info) - }) -} - -// Reset sessions and DHT after the switch sees our coords change -func (r *router) reset(from phony.Actor) { - r.Act(from, func() { - r.sessions.reset() - r.dht.reset() - }) -} - -// TODO remove reconfigure so this is just a ticker loop -// and then find something better than a ticker loop to schedule things... -func (r *router) doMaintenance() { - phony.Block(r, func() { - // Any periodic maintenance stuff goes here - r.core.switchTable.doMaintenance(r) - r.dht.doMaintenance() - r.sessions.cleanup() - }) - time.AfterFunc(time.Second, r.doMaintenance) -} - -// Checks incoming traffic type and passes it to the appropriate handler. -func (r *router) _handlePacket(packet []byte) { - pType, pTypeLen := wire_decode_uint64(packet) - if pTypeLen == 0 { - return - } - switch pType { - case wire_Traffic: - r._handleTraffic(packet) - case wire_ProtocolTraffic: - r._handleProto(packet) - default: - } -} - -// Handles incoming traffic, i.e. encapuslated ordinary IPv6 packets. -// Passes them to the crypto session worker to be decrypted and sent to the adapter. -func (r *router) _handleTraffic(packet []byte) { - p := wire_trafficPacket{} - if !p.decode(packet) { - return - } - sinfo, isIn := r.sessions.getSessionForHandle(&p.Handle) - if !isIn { - return - } - sinfo.recv(r, &p) -} - -// Handles protocol traffic by decrypting it, checking its type, and passing it to the appropriate handler for that traffic type. -func (r *router) _handleProto(packet []byte) { - // First parse the packet - p := wire_protoTrafficPacket{} - if !p.decode(packet) { - return - } - // Now try to open the payload - var sharedKey *crypto.BoxSharedKey - if p.ToKey == r.core.boxPub { - // Try to open using our permanent key - sharedKey = r.sessions.getSharedKey(&r.core.boxPriv, &p.FromKey) - } else { - return - } - bs, isOK := crypto.BoxOpen(sharedKey, p.Payload, &p.Nonce) - if !isOK { - return - } - // Now do something with the bytes in bs... - // send dht messages to dht, sessionRefresh to sessions, data to adapter... - // For data, should check that key and IP match... - bsType, bsTypeLen := wire_decode_uint64(bs) - if bsTypeLen == 0 { - return - } - switch bsType { - case wire_SessionPing: - r._handlePing(bs, &p.FromKey, p.RPath) - case wire_SessionPong: - r._handlePong(bs, &p.FromKey, p.RPath) - case wire_NodeInfoRequest: - fallthrough - case wire_NodeInfoResponse: - r._handleNodeInfo(bs, &p.FromKey) - case wire_DHTLookupRequest: - r._handleDHTReq(bs, &p.FromKey, p.RPath) - case wire_DHTLookupResponse: - r._handleDHTRes(bs, &p.FromKey, p.RPath) - default: - } -} - -// Decodes session pings from wire format and passes them to sessions.handlePing where they either create or update a session. -func (r *router) _handlePing(bs []byte, fromKey *crypto.BoxPubKey, rpath []byte) { - ping := sessionPing{} - if !ping.decode(bs) { - return - } - ping.SendPermPub = *fromKey - r.sessions.handlePing(&ping, rpath) -} - -// Handles session pongs (which are really pings with an extra flag to prevent acknowledgement). -func (r *router) _handlePong(bs []byte, fromKey *crypto.BoxPubKey, rpath []byte) { - r._handlePing(bs, fromKey, rpath) -} - -// Decodes dht requests and passes them to dht.handleReq to trigger a lookup/response. -func (r *router) _handleDHTReq(bs []byte, fromKey *crypto.BoxPubKey, rpath []byte) { - req := dhtReq{} - if !req.decode(bs) { - return - } - req.Key = *fromKey - r.dht.handleReq(&req, rpath) -} - -// Decodes dht responses and passes them to dht.handleRes to update the DHT table and further pass them to the search code (if applicable). -func (r *router) _handleDHTRes(bs []byte, fromKey *crypto.BoxPubKey, rpath []byte) { - res := dhtRes{} - if !res.decode(bs) { - return - } - res.Key = *fromKey - r.dht.handleRes(&res, rpath) -} - -// Decodes nodeinfo request -func (r *router) _handleNodeInfo(bs []byte, fromKey *crypto.BoxPubKey) { - req := nodeinfoReqRes{} - if !req.decode(bs) { - return - } - req.SendPermPub = *fromKey - r.nodeinfo.handleNodeInfo(r, &req) -} - -//////////////////////////////////////////////////////////////////////////////// - -// routerInterface is a helper that implements linkInterface -type routerInterface struct { - router *router -} - -func (intf *routerInterface) out(bss [][]byte) { - // Note that this is run in the peer's goroutine - intf.router.Act(intf.router.peer, func() { - for _, bs := range bss { - intf.router._handlePacket(bs) - } - }) - // This should now immediately make the peer idle again - // So the self-peer shouldn't end up buffering anything - // We let backpressure act as a throttle instead - intf.router.peer._handleIdle() -} - -func (intf *routerInterface) linkOut(_ []byte) {} - -func (intf *routerInterface) close() {} - -func (intf *routerInterface) name() string { return "(self)" } - -func (intf *routerInterface) local() string { return "(self)" } - -func (intf *routerInterface) remote() string { return "(self)" } - -func (intf *routerInterface) interfaceType() string { return "self" } diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go deleted file mode 100644 index febde3d8..00000000 --- a/src/yggdrasil/search.go +++ /dev/null @@ -1,271 +0,0 @@ -package yggdrasil - -// This thing manages search packets - -// The basic idea is as follows: -// We may know a NodeID (with a mask) and want to connect -// We begin a search by sending a dht lookup to ourself -// Each time a node responds, we sort the results and filter to only include useful nodes -// We then periodically send a packet to the first node from the list (after re-filtering) -// This happens in parallel for each node that replies -// Meanwhile, we keep a list of the (up to) 16 closest nodes to the destination that we've visited -// We only consider an unvisited node useful if either the list isn't full or the unvisited node is closer to the destination than the furthest node on the list -// That gives the search some chance to recover if it hits a dead end where a node doesn't know everyone it should - -import ( - "errors" - "sort" - "time" - - "github.com/yggdrasil-network/yggdrasil-go/src/crypto" -) - -// This defines the time after which we time out a search (so it can restart). -const search_RETRY_TIME = 3 * time.Second -const search_STEP_TIME = time.Second -const search_MAX_RESULTS = dht_lookup_size - -// Information about an ongoing search. -// Includes the target NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited. -type searchInfo struct { - searches *searches - dest crypto.NodeID - mask crypto.NodeID - time time.Time - visited []*crypto.NodeID // Closest addresses visited so far - callback func(*sessionInfo, error) - // TODO context.Context for timeout and cancellation - send uint64 // log number of requests sent - recv uint64 // log number of responses received -} - -// This stores a map of active searches. -type searches struct { - router *router - searches map[crypto.NodeID]*searchInfo -} - -// Initializes the searches struct. -func (s *searches) init(r *router) { - s.router = r - s.searches = make(map[crypto.NodeID]*searchInfo) -} - -func (s *searches) reconfigure() { - // This is where reconfiguration would go, if we had anything to do -} - -// Creates a new search info, adds it to the searches struct, and returns a pointer to the info. -func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo { - info := searchInfo{ - searches: s, - dest: *dest, - mask: *mask, - time: time.Now(), - callback: callback, - } - s.searches[*dest] = &info - return &info -} - -//////////////////////////////////////////////////////////////////////////////// - -// Checks if there's an ongoing search related to a dhtRes. -// If there is, it adds the response info to the search and triggers a new search step. -// If there's no ongoing search, or we if the dhtRes finished the search (it was from the target node), then don't do anything more. -func (sinfo *searchInfo) handleDHTRes(res *dhtRes) { - if nfo := sinfo.searches.searches[sinfo.dest]; nfo != sinfo { - return // already done - } - if res != nil { - sinfo.recv++ - if sinfo.checkDHTRes(res) { - return // Search finished successfully - } - // Use results to start an additional search thread - infos := append([]*dhtInfo(nil), res.Infos...) - infos = sinfo.getAllowedInfos(infos) - if len(infos) > 0 { - sinfo.continueSearch(infos) - } - } -} - -// If there has been no response in too long, then this cleans up the search. -// Otherwise, it pops the closest node to the destination (in keyspace) off of the toVisit list and sends a dht ping. -func (sinfo *searchInfo) doSearchStep(infos []*dhtInfo) { - if len(infos) > 0 { - // Send to the next search target - next := infos[0] - rq := dhtReqKey{next.key, sinfo.dest} - sinfo.searches.router.dht.addCallback(&rq, sinfo.handleDHTRes) - sinfo.searches.router.dht.ping(next, &sinfo.dest) - sinfo.send++ - } -} - -// Get a list of search targets that are close enough to the destination to try -// Requires an initial list as input -func (sinfo *searchInfo) getAllowedInfos(infos []*dhtInfo) []*dhtInfo { - var temp []*dhtInfo - for _, info := range infos { - if false && len(sinfo.visited) < search_MAX_RESULTS { - // We're not full on results yet, so don't block anything yet - } else if !dht_ordered(&sinfo.dest, info.getNodeID(), sinfo.visited[len(sinfo.visited)-1]) { - // Too far away - continue - } - var known bool - for _, nfo := range sinfo.visited { - if *nfo == *info.getNodeID() { - known = true - break - } - } - if !known { - temp = append(temp, info) - } - } - infos = append(infos[:0], temp...) // restrict to only the allowed infos - sort.SliceStable(infos, func(i, j int) bool { - // Should return true if i is closer to the destination than j - return dht_ordered(&sinfo.dest, infos[i].getNodeID(), infos[j].getNodeID()) - }) // Sort infos to start with the closest - if len(infos) > search_MAX_RESULTS { - infos = infos[:search_MAX_RESULTS] // Limit max number of infos - } - return infos -} - -// Run doSearchStep and schedule another continueSearch to happen after search_RETRY_TIME. -// Must not be called with an empty list of infos -func (sinfo *searchInfo) continueSearch(infos []*dhtInfo) { - sinfo.doSearchStep(infos) - infos = infos[1:] // Remove the node we just tried - // In case there's no response, try the next node in infos later - time.AfterFunc(search_STEP_TIME, func() { - sinfo.searches.router.Act(nil, func() { - // FIXME this keeps the search alive forever if not for the searches map, fix that - newSearchInfo := sinfo.searches.searches[sinfo.dest] - if newSearchInfo != sinfo { - return - } - // Get good infos here instead of at the top, to make sure we can always start things off with a continueSearch call to ourself - infos = sinfo.getAllowedInfos(infos) - if len(infos) > 0 { - sinfo.continueSearch(infos) - } - }) - }) -} - -// Initially start a search -func (sinfo *searchInfo) startSearch() { - var infos []*dhtInfo - infos = append(infos, &dhtInfo{ - key: sinfo.searches.router.core.boxPub, - coords: sinfo.searches.router.table.self.getCoords(), - }) - // Start the search by asking ourself, useful if we're the destination - sinfo.continueSearch(infos) - // Start a timer to clean up the search if everything times out - var cleanupFunc func() - cleanupFunc = func() { - sinfo.searches.router.Act(nil, func() { - // FIXME this keeps the search alive forever if not for the searches map, fix that - newSearchInfo := sinfo.searches.searches[sinfo.dest] - if newSearchInfo != sinfo { - return - } - elapsed := time.Since(sinfo.time) - if elapsed > search_RETRY_TIME { - // cleanup - delete(sinfo.searches.searches, sinfo.dest) - sinfo.searches.router.core.log.Debugln("search timeout:", &sinfo.dest, sinfo.send, sinfo.recv) - sinfo.callback(nil, errors.New("search reached dead end")) - return - } - time.AfterFunc(search_RETRY_TIME-elapsed, cleanupFunc) - }) - } - time.AfterFunc(search_RETRY_TIME, cleanupFunc) -} - -// Calls create search, and initializes the iterative search parts of the struct before returning it. -func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo { - sinfo := s.createSearch(dest, mask, callback) - sinfo.visited = append(sinfo.visited, &s.router.dht.nodeID) - return sinfo -} - -// Checks if a dhtRes is good (called by handleDHTRes). -// If the response is from the target, get/create a session, trigger a session ping, and return true. -// Otherwise return false. -func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { - from := dhtInfo{key: res.Key, coords: res.Coords} - them := from.getNodeID() - var known bool - for _, v := range sinfo.visited { - if *v == *them { - known = true - break - } - } - if !known { - if len(sinfo.visited) < search_MAX_RESULTS || dht_ordered(&sinfo.dest, them, sinfo.visited[len(sinfo.visited)-1]) { - // Closer to the destination than the threshold, so update visited - sinfo.searches.router.core.log.Debugln("Updating search:", &sinfo.dest, them, sinfo.send, sinfo.recv) - sinfo.visited = append(sinfo.visited, them) - sort.SliceStable(sinfo.visited, func(i, j int) bool { - // Should return true if i is closer to the destination than j - return dht_ordered(&sinfo.dest, sinfo.visited[i], sinfo.visited[j]) - }) // Sort infos to start with the closest - if len(sinfo.visited) > search_MAX_RESULTS { - sinfo.visited = sinfo.visited[:search_MAX_RESULTS] - } - sinfo.time = time.Now() - } - } - var destMasked crypto.NodeID - var themMasked crypto.NodeID - for idx := 0; idx < crypto.NodeIDLen; idx++ { - destMasked[idx] = sinfo.dest[idx] & sinfo.mask[idx] - themMasked[idx] = them[idx] & sinfo.mask[idx] - } - if themMasked != destMasked { - return false - } - finishSearch := func(sess *sessionInfo, err error) { - if sess != nil { - // FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)? - sess.Act(sinfo.searches.router, func() { sess.coords = res.Coords }) - sess.ping(sinfo.searches.router) - } - if err != nil { - sinfo.callback(nil, err) - } else { - sinfo.callback(sess, nil) - } - // Cleanup - if _, isIn := sinfo.searches.searches[sinfo.dest]; isIn { - sinfo.searches.router.core.log.Debugln("Finished search:", &sinfo.dest, sinfo.send, sinfo.recv) - delete(sinfo.searches.searches, res.Dest) - } - } - // They match, so create a session and send a sessionRequest - var err error - sess, isIn := sinfo.searches.router.sessions.getByTheirPerm(&res.Key) - if !isIn { - // Don't already have a session - sess = sinfo.searches.router.sessions.createSession(&res.Key) - if sess == nil { - err = errors.New("session not allowed") - } else if _, isIn := sinfo.searches.router.sessions.getByTheirPerm(&res.Key); !isIn { - panic("This should never happen") - } - } else { - err = errors.New("session already exists") - } - finishSearch(sess, err) - return true -} diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go deleted file mode 100644 index 2d64cac4..00000000 --- a/src/yggdrasil/session.go +++ /dev/null @@ -1,551 +0,0 @@ -package yggdrasil - -// This is the session manager -// It's responsible for keeping track of open sessions to other nodes -// The session information consists of crypto keys and coords - -import ( - "bytes" - "sync" - "time" - - "github.com/yggdrasil-network/yggdrasil-go/src/address" - "github.com/yggdrasil-network/yggdrasil-go/src/crypto" - "github.com/yggdrasil-network/yggdrasil-go/src/util" - - "github.com/Arceliar/phony" -) - -// All the information we know about an active session. -// This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API. -type sessionInfo struct { - phony.Inbox // Protects all of the below, use it any time you read/change the contents of a session - sessions *sessions // - theirAddr address.Address // - theirSubnet address.Subnet // - theirPermPub crypto.BoxPubKey // - theirSesPub crypto.BoxPubKey // - mySesPub crypto.BoxPubKey // - mySesPriv crypto.BoxPrivKey // - sharedPermKey crypto.BoxSharedKey // used for session pings - sharedSesKey crypto.BoxSharedKey // derived from session keys - theirHandle crypto.Handle // - myHandle crypto.Handle // - theirNonce crypto.BoxNonce // - myNonce crypto.BoxNonce // - theirMTU MTU // - myMTU MTU // - wasMTUFixed bool // Was the MTU fixed by a receive error? - timeOpened time.Time // Time the session was opened - time time.Time // Time we last received a packet - mtuTime time.Time // time myMTU was last changed - pingTime time.Time // time the first ping was sent since the last received packet - coords []byte // coords of destination - reset bool // reset if coords change - tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation - bytesSent uint64 // Bytes of real traffic sent in this session - bytesRecvd uint64 // Bytes of real traffic received in this session - init chan struct{} // Closed when the first session pong arrives, used to signal that the session is ready for initial use - cancel util.Cancellation // Used to terminate workers - conn *Conn // The associated Conn object - callbacks []chan func() // Finished work from crypto workers - table *lookupTable // table.self is a locator where we get our coords - path []byte // Path from self to destination -} - -// Represents a session ping/pong packet, and includes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU. -type sessionPing struct { - SendPermPub crypto.BoxPubKey // Sender's permanent key - Handle crypto.Handle // Random number to ID session - SendSesPub crypto.BoxPubKey // Session key to use - Coords []byte // - Tstamp int64 // unix time, but the only real requirement is that it increases - IsPong bool // - MTU MTU // -} - -// Updates session info in response to a ping, after checking that the ping is OK. -// Returns true if the session was updated, or false otherwise. -func (sinfo *sessionInfo) _update(p *sessionPing, rpath []byte) bool { - if !(p.Tstamp > sinfo.tstamp) { - // To protect against replay attacks - return false - } - if p.SendPermPub != sinfo.theirPermPub { - // Should only happen if two sessions got the same handle - // That shouldn't be allowed anyway, but if it happens then let one time out - return false - } - if p.SendSesPub != sinfo.theirSesPub { - sinfo.path = nil - sinfo.theirSesPub = p.SendSesPub - sinfo.theirHandle = p.Handle - sinfo.sharedSesKey = *crypto.GetSharedKey(&sinfo.mySesPriv, &sinfo.theirSesPub) - sinfo.theirNonce = crypto.BoxNonce{} - } - if p.MTU >= 1280 || p.MTU == 0 { - sinfo.theirMTU = p.MTU - if sinfo.conn != nil { - sinfo.conn.setMTU(sinfo, sinfo._getMTU()) - } - } - if !bytes.Equal(sinfo.coords, p.Coords) { - // allocate enough space for additional coords - sinfo.coords = append(make([]byte, 0, len(p.Coords)+11), p.Coords...) - path := switch_reverseCoordBytes(rpath) - sinfo.path = append(sinfo.path[:0], path...) - defer sinfo._sendPingPong(false, nil) - } else if p.IsPong { - path := switch_reverseCoordBytes(rpath) - sinfo.path = append(sinfo.path[:0], path...) - } - sinfo.time = time.Now() - sinfo.tstamp = p.Tstamp - sinfo.reset = false - defer func() { recover() }() // Recover if the below panics - select { - case <-sinfo.init: - default: - // Unblock anything waiting for the session to initialize - close(sinfo.init) - } - return true -} - -// Struct of all active sessions. -// Sessions are indexed by handle. -// Additionally, stores maps of address/subnet onto keys, and keys onto handles. -type sessions struct { - router *router - listener *Listener - listenerMutex sync.Mutex - lastCleanup time.Time - isAllowedHandler func(pubkey *crypto.BoxPubKey, initiator bool) bool // Returns true or false if session setup is allowed - isAllowedMutex sync.RWMutex // Protects the above - myMaximumMTU MTU // Maximum allowed session MTU - permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey // Maps known permanent keys to their shared key, used by DHT a lot - sinfos map[crypto.Handle]*sessionInfo // Maps handle onto session info - byTheirPerm map[crypto.BoxPubKey]*crypto.Handle // Maps theirPermPub onto handle -} - -// Initializes the session struct. -func (ss *sessions) init(r *router) { - ss.router = r - ss.permShared = make(map[crypto.BoxPubKey]*crypto.BoxSharedKey) - ss.sinfos = make(map[crypto.Handle]*sessionInfo) - ss.byTheirPerm = make(map[crypto.BoxPubKey]*crypto.Handle) - ss.lastCleanup = time.Now() - ss.myMaximumMTU = 65535 -} - -func (ss *sessions) reconfigure() { - ss.router.Act(nil, func() { - for _, session := range ss.sinfos { - sinfo, mtu := session, ss.myMaximumMTU - sinfo.Act(ss.router, func() { - sinfo.myMTU = mtu - }) - session.ping(ss.router) - } - }) -} - -// Determines whether the session with a given publickey is allowed based on -// session firewall rules. -func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) bool { - ss.isAllowedMutex.RLock() - defer ss.isAllowedMutex.RUnlock() - - if ss.isAllowedHandler == nil { - return true - } - - return ss.isAllowedHandler(pubkey, initiator) -} - -// Gets the session corresponding to a given handle. -func (ss *sessions) getSessionForHandle(handle *crypto.Handle) (*sessionInfo, bool) { - sinfo, isIn := ss.sinfos[*handle] - return sinfo, isIn -} - -// Gets a session corresponding to a permanent key used by the remote node. -func (ss *sessions) getByTheirPerm(key *crypto.BoxPubKey) (*sessionInfo, bool) { - h, isIn := ss.byTheirPerm[*key] - if !isIn { - return nil, false - } - sinfo, isIn := ss.getSessionForHandle(h) - return sinfo, isIn -} - -// Creates a new session and lazily cleans up old existing sessions. This -// includes initializing session info to sane defaults (e.g. lowest supported -// MTU). -func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { - // TODO: this check definitely needs to be moved - if !ss.isSessionAllowed(theirPermKey, true) { - return nil - } - sinfo := sessionInfo{} - sinfo.sessions = ss - sinfo.theirPermPub = *theirPermKey - sinfo.sharedPermKey = *ss.getSharedKey(&ss.router.core.boxPriv, &sinfo.theirPermPub) - pub, priv := crypto.NewBoxKeys() - sinfo.mySesPub = *pub - sinfo.mySesPriv = *priv - sinfo.myNonce = *crypto.NewBoxNonce() - sinfo.theirMTU = 1280 - sinfo.myMTU = ss.myMaximumMTU - now := time.Now() - sinfo.timeOpened = now - sinfo.time = now - sinfo.mtuTime = now - sinfo.pingTime = now - sinfo.init = make(chan struct{}) - sinfo.cancel = util.NewCancellation() - higher := false - for idx := range ss.router.core.boxPub { - if ss.router.core.boxPub[idx] > sinfo.theirPermPub[idx] { - higher = true - break - } else if ss.router.core.boxPub[idx] < sinfo.theirPermPub[idx] { - break - } - } - if higher { - // higher => odd nonce - sinfo.myNonce[len(sinfo.myNonce)-1] |= 0x01 - } else { - // lower => even nonce - sinfo.myNonce[len(sinfo.myNonce)-1] &= 0xfe - } - sinfo.myHandle = *crypto.NewHandle() - // TODO sinfo.theirAddr = *address.AddrForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) - // TODO sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub)) - sinfo.table = ss.router.table - ss.sinfos[sinfo.myHandle] = &sinfo - ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle - return &sinfo -} - -func (ss *sessions) cleanup() { - // Time thresholds almost certainly could use some adjusting - for k := range ss.permShared { - // Delete a key, to make sure this eventually shrinks to 0 - delete(ss.permShared, k) - break - } - if time.Since(ss.lastCleanup) < time.Minute { - return - } - permShared := make(map[crypto.BoxPubKey]*crypto.BoxSharedKey, len(ss.permShared)) - for k, v := range ss.permShared { - permShared[k] = v - } - ss.permShared = permShared - sinfos := make(map[crypto.Handle]*sessionInfo, len(ss.sinfos)) - for k, v := range ss.sinfos { - sinfos[k] = v - } - ss.sinfos = sinfos - byTheirPerm := make(map[crypto.BoxPubKey]*crypto.Handle, len(ss.byTheirPerm)) - for k, v := range ss.byTheirPerm { - byTheirPerm[k] = v - } - ss.byTheirPerm = byTheirPerm - ss.lastCleanup = time.Now() -} - -func (sinfo *sessionInfo) doRemove() { - sinfo.sessions.router.Act(nil, func() { - sinfo.sessions.removeSession(sinfo) - }) -} - -// Closes a session, removing it from sessions maps. -func (ss *sessions) removeSession(sinfo *sessionInfo) { - if s := sinfo.sessions.sinfos[sinfo.myHandle]; s == sinfo { - delete(sinfo.sessions.sinfos, sinfo.myHandle) - delete(sinfo.sessions.byTheirPerm, sinfo.theirPermPub) - } -} - -// Returns a session ping appropriate for the given session info. -func (sinfo *sessionInfo) _getPing() sessionPing { - coords := sinfo.table.self.getCoords() - ping := sessionPing{ - SendPermPub: sinfo.sessions.router.core.boxPub, - Handle: sinfo.myHandle, - SendSesPub: sinfo.mySesPub, - Tstamp: time.Now().Unix(), - Coords: coords, - MTU: sinfo.myMTU, - } - sinfo.myNonce.Increment() - return ping -} - -// Gets the shared key for a pair of box keys. -// Used to cache recently used shared keys for protocol traffic. -// This comes up with dht req/res and session ping/pong traffic. -func (ss *sessions) getSharedKey(myPriv *crypto.BoxPrivKey, - theirPub *crypto.BoxPubKey) *crypto.BoxSharedKey { - return crypto.GetSharedKey(myPriv, theirPub) - // FIXME concurrency issues with the below, so for now we just burn the CPU every time - if skey, isIn := ss.permShared[*theirPub]; isIn { - return skey - } - // First do some cleanup - const maxKeys = 1024 - for key := range ss.permShared { - // Remove a random key until the store is small enough - if len(ss.permShared) < maxKeys { - break - } - delete(ss.permShared, key) - } - ss.permShared[*theirPub] = crypto.GetSharedKey(myPriv, theirPub) - return ss.permShared[*theirPub] -} - -// Sends a session ping by calling sendPingPong in ping mode. -func (sinfo *sessionInfo) ping(from phony.Actor) { - sinfo.Act(from, func() { - sinfo._sendPingPong(false, nil) - }) -} - -// Calls getPing, sets the appropriate ping/pong flag, encodes to wire format, and send it. -// Updates the time the last ping was sent in the session info. -func (sinfo *sessionInfo) _sendPingPong(isPong bool, path []byte) { - ping := sinfo._getPing() - ping.IsPong = isPong - bs := ping.encode() - payload, nonce := crypto.BoxSeal(&sinfo.sharedPermKey, bs, nil) - p := wire_protoTrafficPacket{ - Coords: sinfo.coords, - ToKey: sinfo.theirPermPub, - FromKey: sinfo.sessions.router.core.boxPub, - Nonce: *nonce, - Payload: payload, - } - if path != nil { - p.Coords = append([]byte{0}, path...) - p.Offset += 1 - } - packet := p.encode() - // TODO rewrite the below if/when the peer struct becomes an actor, to not go through the router first - sinfo.sessions.router.Act(sinfo, func() { sinfo.sessions.router.out(packet) }) - if !isPong { - sinfo.pingTime = time.Now() - } -} - -func (sinfo *sessionInfo) setConn(from phony.Actor, conn *Conn) { - sinfo.Act(from, func() { - sinfo.conn = conn - sinfo.conn.setMTU(sinfo, sinfo._getMTU()) - }) -} - -// Handles a session ping, creating a session if needed and calling update, then possibly responding with a pong if the ping was in ping mode and the update was successful. -// If the session has a packet cached (common when first setting up a session), it will be sent. -func (ss *sessions) handlePing(ping *sessionPing, rpath []byte) { - // Get the corresponding session (or create a new session) - sinfo, isIn := ss.getByTheirPerm(&ping.SendPermPub) - switch { - case ping.IsPong: // This is a response, not an initial ping, so ignore it. - case isIn: // Session already exists - case !ss.isSessionAllowed(&ping.SendPermPub, false): // Session is not allowed - default: - ss.listenerMutex.Lock() - if ss.listener != nil { - // This is a ping from an allowed node for which no session exists, and we have a listener ready to handle sessions. - // We need to create a session and pass it to the listener. - sinfo = ss.createSession(&ping.SendPermPub) - if s, _ := ss.getByTheirPerm(&ping.SendPermPub); s != sinfo { - panic("This should not happen") - } - conn := newConn(ss.router.core, crypto.GetNodeID(&sinfo.theirPermPub), &crypto.NodeID{}, sinfo) - for i := range conn.nodeMask { - conn.nodeMask[i] = 0xFF - } - sinfo.setConn(ss.router, conn) - c := ss.listener.conn - go func() { c <- conn }() - } - ss.listenerMutex.Unlock() - } - if sinfo != nil { - sinfo.Act(ss.router, func() { - // Update the session - if !sinfo._update(ping, rpath) { /*panic("Should not happen in testing")*/ - return - } - if !ping.IsPong { - sinfo._sendPingPong(true, switch_reverseCoordBytes(rpath)) - } - }) - } -} - -// Get the MTU of the session. -// Will be equal to the smaller of this node's MTU or the remote node's MTU. -// If sending over links with a maximum message size (this was a thing with the old UDP code), it could be further lowered, to a minimum of 1280. -func (sinfo *sessionInfo) _getMTU() MTU { - if sinfo.theirMTU == 0 || sinfo.myMTU == 0 { - return 0 - } - if sinfo.theirMTU < sinfo.myMTU { - return sinfo.theirMTU - } - return sinfo.myMTU -} - -// Checks if a packet's nonce is newer than any previously received -func (sinfo *sessionInfo) _nonceIsOK(theirNonce *crypto.BoxNonce) bool { - return theirNonce.Minus(&sinfo.theirNonce) > 0 -} - -// Updates the nonce mask by (possibly) shifting the bitmask and setting the bit corresponding to this nonce to 1, and then updating the most recent nonce -func (sinfo *sessionInfo) _updateNonce(theirNonce *crypto.BoxNonce) { - if theirNonce.Minus(&sinfo.theirNonce) > 0 { - // This nonce is the newest we've seen, so make a note of that - sinfo.theirNonce = *theirNonce - sinfo.time = time.Now() - } -} - -// Resets all sessions to an uninitialized state. -// Called after coord changes, so attempts to use a session will trigger a new ping and notify the remote end of the coord change. -// Only call this from the router actor. -func (ss *sessions) reset() { - for _, _sinfo := range ss.sinfos { - sinfo := _sinfo // So we can safely put it in a closure - sinfo.Act(ss.router, func() { - sinfo.reset = true - sinfo._sendPingPong(false, sinfo.path) - sinfo._sendPingPong(false, nil) - }) - } -} - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////// Worker Functions Below //////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -type sessionCryptoManager struct { - phony.Inbox -} - -func (m *sessionCryptoManager) workerGo(from phony.Actor, f func()) { - m.Act(from, func() { - util.WorkerGo(f) - }) -} - -var manager = sessionCryptoManager{} - -type FlowKeyMessage struct { - FlowKey uint64 - Message []byte -} - -func (sinfo *sessionInfo) recv(from phony.Actor, packet *wire_trafficPacket) { - sinfo.Act(from, func() { - sinfo._recvPacket(packet) - }) -} - -func (sinfo *sessionInfo) _recvPacket(p *wire_trafficPacket) { - select { - case <-sinfo.init: - default: - return - } - if !sinfo._nonceIsOK(&p.Nonce) { - return - } - k := sinfo.sharedSesKey - var isOK bool - var bs []byte - ch := make(chan func(), 1) - poolFunc := func() { - bs, isOK = crypto.BoxOpen(&k, p.Payload, &p.Nonce) - callback := func() { - if !isOK || k != sinfo.sharedSesKey || !sinfo._nonceIsOK(&p.Nonce) { - // Either we failed to decrypt, or the session was updated, or we - // received this packet in the mean time - return - } - sinfo._updateNonce(&p.Nonce) - sinfo.bytesRecvd += uint64(len(bs)) - sinfo.conn.recvMsg(sinfo, bs) - } - ch <- callback - sinfo.checkCallbacks() - } - sinfo.callbacks = append(sinfo.callbacks, ch) - manager.workerGo(sinfo, poolFunc) -} - -func (sinfo *sessionInfo) _send(msg FlowKeyMessage) { - select { - case <-sinfo.init: - default: - return - } - sinfo.bytesSent += uint64(len(msg.Message)) - var coords []byte - var offset uint64 - if len(sinfo.path) > 0 { - coords = append([]byte{0}, sinfo.path...) - offset += 1 - } else { - coords = append([]byte(nil), sinfo.coords...) - } - if msg.FlowKey != 0 { - coords = append(coords, 0) - coords = append(coords, wire_encode_uint64(msg.FlowKey)...) - } - p := wire_trafficPacket{ - Offset: offset, - Coords: coords, - Handle: sinfo.theirHandle, - Nonce: sinfo.myNonce, - } - sinfo.myNonce.Increment() - k := sinfo.sharedSesKey - ch := make(chan func(), 1) - poolFunc := func() { - p.Payload, _ = crypto.BoxSeal(&k, msg.Message, &p.Nonce) - packet := p.encode() - callback := func() { - sinfo.sessions.router.Act(sinfo, func() { - sinfo.sessions.router.out(packet) - }) - if time.Since(sinfo.pingTime) > 3*time.Second { - sinfo._sendPingPong(false, nil) - } - } - ch <- callback - sinfo.checkCallbacks() - } - sinfo.callbacks = append(sinfo.callbacks, ch) - manager.workerGo(sinfo, poolFunc) -} - -func (sinfo *sessionInfo) checkCallbacks() { - sinfo.Act(nil, func() { - if len(sinfo.callbacks) > 0 { - select { - case callback := <-sinfo.callbacks[0]: - sinfo.callbacks = sinfo.callbacks[1:] - callback() - sinfo.checkCallbacks() - default: - } - } - }) -} diff --git a/src/yggdrasil/simlink.go b/src/yggdrasil/simlink.go deleted file mode 100644 index 9be6b306..00000000 --- a/src/yggdrasil/simlink.go +++ /dev/null @@ -1,91 +0,0 @@ -package yggdrasil - -import ( - "errors" - "github.com/Arceliar/phony" -) - -type Simlink struct { - phony.Inbox - rch chan []byte - dest *Simlink - link *link - started bool -} - -func (s *Simlink) readMsg() ([]byte, error) { - bs, ok := <-s.rch - if !ok { - return nil, errors.New("read from closed Simlink") - } - return bs, nil -} - -func (s *Simlink) _recvMetaBytes() ([]byte, error) { - return s.readMsg() -} - -func (s *Simlink) _sendMetaBytes(bs []byte) error { - _, err := s.writeMsgs([][]byte{bs}) - return err -} - -func (s *Simlink) close() error { - defer func() { recover() }() - close(s.rch) - return nil -} - -func (s *Simlink) writeMsgs(msgs [][]byte) (int, error) { - if s.dest == nil { - return 0, errors.New("write to unpaired Simlink") - } - var size int - for _, msg := range msgs { - size += len(msg) - bs := append([]byte(nil), msg...) - phony.Block(s, func() { - s.dest.Act(s, func() { - defer func() { recover() }() - s.dest.rch <- bs - }) - }) - } - return size, nil -} - -func (c *Core) NewSimlink() *Simlink { - s := &Simlink{rch: make(chan []byte, 1)} - n := "Simlink" - var err error - s.link, err = c.links.create(s, n, n, n, n, false, true, linkOptions{}) - if err != nil { - panic(err) - } - return s -} - -func (s *Simlink) SetDestination(dest *Simlink) error { - var err error - phony.Block(s, func() { - if s.dest != nil { - err = errors.New("destination already set") - } else { - s.dest = dest - } - }) - return err -} - -func (s *Simlink) Start() error { - var err error - phony.Block(s, func() { - if s.started { - err = errors.New("already started") - } else { - s.started = true - go s.link.handler() - } - }) - return err -} diff --git a/src/yggdrasil/stream.go b/src/yggdrasil/stream.go deleted file mode 100644 index ef09c8e7..00000000 --- a/src/yggdrasil/stream.go +++ /dev/null @@ -1,120 +0,0 @@ -package yggdrasil - -import ( - "bufio" - "errors" - "fmt" - "io" - "net" -) - -// Test that this matches the interface we expect -var _ = linkMsgIO(&stream{}) - -type stream struct { - rwc io.ReadWriteCloser - inputBuffer *bufio.Reader - outputBuffer net.Buffers -} - -func (s *stream) close() error { - return s.rwc.Close() -} - -const streamMsgSize = 2048 + 65535 - -var streamMsg = [...]byte{0xde, 0xad, 0xb1, 0x75} // "dead bits" - -func (s *stream) init(rwc io.ReadWriteCloser) { - // TODO have this also do the metadata handshake and create the peer struct - s.rwc = rwc - // TODO call something to do the metadata exchange - s.inputBuffer = bufio.NewReaderSize(s.rwc, 2*streamMsgSize) -} - -// writeMsg writes a message with stream padding, and is *not* thread safe. -func (s *stream) writeMsgs(bss [][]byte) (int, error) { - buf := s.outputBuffer[:0] - var written int - for _, bs := range bss { - buf = append(buf, streamMsg[:]) - buf = append(buf, wire_encode_uint64(uint64(len(bs)))) - buf = append(buf, bs) - written += len(bs) - } - s.outputBuffer = buf[:0] // So we can reuse the same underlying array later - _, err := buf.WriteTo(s.rwc) - for _, bs := range bss { - pool_putBytes(bs) - } - // TODO only include number of bytes from bs *successfully* written? - return written, err -} - -// readMsg reads a message from the stream, accounting for stream padding, and is *not* thread safe. -func (s *stream) readMsg() ([]byte, error) { - for { - bs, err := s.readMsgFromBuffer() - if err != nil { - return nil, fmt.Errorf("message error: %v", err) - } - return bs, err - } -} - -// Writes metadata bytes without stream padding, meant to be temporary -func (s *stream) _sendMetaBytes(metaBytes []byte) error { - var written int - for written < len(metaBytes) { - n, err := s.rwc.Write(metaBytes) - written += n - if err != nil { - return err - } - } - return nil -} - -// Reads metadata bytes without stream padding, meant to be temporary -func (s *stream) _recvMetaBytes() ([]byte, error) { - var meta version_metadata - frag := meta.encode() - metaBytes := make([]byte, 0, len(frag)) - for len(metaBytes) < len(frag) { - n, err := s.rwc.Read(frag) - if err != nil { - return nil, err - } - metaBytes = append(metaBytes, frag[:n]...) - } - return metaBytes, nil -} - -// Reads bytes from the underlying rwc and returns 1 full message -func (s *stream) readMsgFromBuffer() ([]byte, error) { - pad := streamMsg // Copy - _, err := io.ReadFull(s.inputBuffer, pad[:]) - if err != nil { - return nil, err - } else if pad != streamMsg { - return nil, errors.New("bad message") - } - lenSlice := make([]byte, 0, 10) - // FIXME this nextByte stuff depends on wire.go format, kind of ugly to have it here - nextByte := byte(0xff) - for nextByte > 127 { - nextByte, err = s.inputBuffer.ReadByte() - if err != nil { - return nil, err - } - lenSlice = append(lenSlice, nextByte) - } - msgLen, _ := wire_decode_uint64(lenSlice) - if msgLen > streamMsgSize { - return nil, errors.New("oversized message") - } - msg := pool_getBytes(int(msgLen + 10)) // Extra padding for up to 1 more switchPort - msg = msg[:msgLen] - _, err = io.ReadFull(s.inputBuffer, msg) - return msg, err -} diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go deleted file mode 100644 index 6cab5bca..00000000 --- a/src/yggdrasil/switch.go +++ /dev/null @@ -1,647 +0,0 @@ -package yggdrasil - -// This part constructs a spanning tree of the network -// It routes packets based on distance on the spanning tree -// In general, this is *not* equivalent to routing on the tree -// It falls back to the tree in the worst case, but it can take shortcuts too -// This is the part that makes routing reasonably efficient on scale-free graphs - -// TODO document/comment everything in a lot more detail - -// TODO? use a pre-computed lookup table (python version had this) -// A little annoying to do with constant changes from backpressure - -import ( - "time" - - "github.com/yggdrasil-network/yggdrasil-go/src/crypto" - - "github.com/Arceliar/phony" -) - -const ( - switch_timeout = time.Minute - switch_updateInterval = switch_timeout / 2 - switch_throttle = switch_updateInterval / 2 -) - -// The switch locator represents the topology and network state dependent info about a node, minus the signatures that go with it. -// Nodes will pick the best root they see, provided that the root continues to push out updates with new timestamps. -// The coords represent a path from the root to a node. -// This path is generally part of a spanning tree, except possibly the last hop (it can loop when sending coords to your parent, but they see this and know not to use a looping path). -type switchLocator struct { - root crypto.SigPubKey - tstamp int64 - coords []switchPort -} - -// Returns true if the first sigPubKey has a higher TreeID. -func firstIsBetter(first, second *crypto.SigPubKey) bool { - // Higher TreeID is better - ftid := crypto.GetTreeID(first) - stid := crypto.GetTreeID(second) - for idx := 0; idx < len(ftid); idx++ { - if ftid[idx] == stid[idx] { - continue - } - return ftid[idx] > stid[idx] - } - // Edge case, when comparing identical IDs - return false -} - -// Returns a copy of the locator which can safely be mutated. -func (l *switchLocator) clone() switchLocator { - // Used to create a deep copy for use in messages - // Copy required because we need to mutate coords before sending - // (By appending the port from us to the destination) - loc := *l - loc.coords = make([]switchPort, len(l.coords), len(l.coords)+1) - copy(loc.coords, l.coords) - return loc -} - -// Gets the distance a locator is from the provided destination coords, with the coords provided in []byte format (used to compress integers sent over the wire). -func (l *switchLocator) dist(dest []byte) int { - // Returns distance (on the tree) from these coords - offset := 0 - fdc := 0 - for { - if fdc >= len(l.coords) { - break - } - coord, length := wire_decode_uint64(dest[offset:]) - if length == 0 { - break - } - if l.coords[fdc] != switchPort(coord) { - break - } - fdc++ - offset += length - } - dist := len(l.coords[fdc:]) - for { - _, length := wire_decode_uint64(dest[offset:]) - if length == 0 { - break - } - dist++ - offset += length - } - return dist -} - -func (l *switchLocator) ldist(sl *switchLocator) int { - lca := -1 - for idx := 0; idx < len(l.coords); idx++ { - if idx >= len(sl.coords) { - break - } - if l.coords[idx] != sl.coords[idx] { - break - } - lca = idx - } - return len(l.coords) + len(sl.coords) - 2*(lca+1) -} - -// Gets coords in wire encoded format, with *no* length prefix. -func (l *switchLocator) getCoords() []byte { - bs := make([]byte, 0, len(l.coords)) - for _, coord := range l.coords { - c := wire_encode_uint64(uint64(coord)) - bs = append(bs, c...) - } - return bs -} - -// Returns true if this locator represents an ancestor of the locator given as an argument. -// Ancestor means that it's the parent node, or the parent of parent, and so on... -func (x *switchLocator) isAncestorOf(y *switchLocator) bool { - if x.root != y.root { - return false - } - if len(x.coords) > len(y.coords) { - return false - } - for idx := range x.coords { - if x.coords[idx] != y.coords[idx] { - return false - } - } - return true -} - -// Information about a peer, used by the switch to build the tree and eventually make routing decisions. -type peerInfo struct { - key crypto.SigPubKey // ID of this peer - locator switchLocator // Should be able to respond with signatures upon request - degree uint64 // Self-reported degree - time time.Time // Time this node was last seen - port switchPort // Interface number of this peer - msg switchMsg // The wire switchMsg used - readBlock bool // True if the link notified us of a read that blocked too long - writeBlock bool // True of the link notified us of a write that blocked too long -} - -func (pinfo *peerInfo) blocked() bool { - return pinfo.readBlock || pinfo.writeBlock -} - -// This is just a uint64 with a named type for clarity reasons. -type switchPort uint64 - -// This is the subset of the information about a peer needed to make routing decisions, and it stored separately in an atomically accessed table, which gets hammered in the "hot loop" of the routing logic (see: peer.handleTraffic in peers.go). -type tableElem struct { - port switchPort - locator switchLocator - time time.Time - next map[switchPort]*tableElem -} - -// This is the subset of the information about all peers needed to make routing decisions, and it stored separately in an atomically accessed table, which gets hammered in the "hot loop" of the routing logic (see: peer.handleTraffic in peers.go). -type lookupTable struct { - self switchLocator - elems map[switchPort]tableElem // all switch peers, just for sanity checks + API/debugging - _start tableElem // used for lookups - _msg switchMsg -} - -// This is switch information which is mutable and needs to be modified by other goroutines, but is not accessed atomically. -// Use the switchTable functions to access it safely using the RWMutex for synchronization. -type switchData struct { - // All data that's mutable and used by exported Table methods - // To be read/written with atomic.Value Store/Load calls - locator switchLocator - peers map[switchPort]peerInfo - msg *switchMsg -} - -// All the information stored by the switch. -type switchTable struct { - core *Core - key crypto.SigPubKey // Our own key - phony.Inbox // Owns the below - time time.Time // Time when locator.tstamp was last updated - drop map[crypto.SigPubKey]int64 // Tstamp associated with a dropped root - parent switchPort // Port of whatever peer is our parent, or self if we're root - data switchData // -} - -// Minimum allowed total size of switch queues. -const SwitchQueueTotalMinSize = 4 * 1024 * 1024 - -// Initializes the switchTable struct. -func (t *switchTable) init(core *Core) { - now := time.Now() - t.core = core - t.key = t.core.sigPub - locator := switchLocator{root: t.key, tstamp: now.Unix()} - peers := make(map[switchPort]peerInfo) - t.data = switchData{locator: locator, peers: peers} - t.drop = make(map[crypto.SigPubKey]int64) - phony.Block(t, t._updateTable) -} - -func (t *switchTable) reconfigure() { - // This is where reconfiguration would go, if we had anything useful to do. - t.core.links.reconfigure() - t.core.peers.reconfigure() -} - -// Regular maintenance to possibly timeout/reset the root and similar. -func (t *switchTable) doMaintenance(from phony.Actor) { - t.Act(from, func() { - // Periodic maintenance work to keep things internally consistent - t._cleanRoot() - t._cleanDropped() - }) -} - -// Updates the root periodically if it is ourself, or promotes ourself to root if we're better than the current root or if the current root has timed out. -func (t *switchTable) _cleanRoot() { - // TODO rethink how this is done?... - // Get rid of the root if it looks like its timed out - now := time.Now() - doUpdate := false - if now.Sub(t.time) > switch_timeout { - dropped := t.data.peers[t.parent] - dropped.time = t.time - t.drop[t.data.locator.root] = t.data.locator.tstamp - doUpdate = true - } - // Or, if we're better than our root, root ourself - if firstIsBetter(&t.key, &t.data.locator.root) { - doUpdate = true - } - // Or, if we are the root, possibly update our timestamp - if t.data.locator.root == t.key && - now.Sub(t.time) > switch_updateInterval { - doUpdate = true - } - if doUpdate { - t.parent = switchPort(0) - t.time = now - if t.data.locator.root != t.key { - defer t.core.router.reset(nil) - } - t.data.locator = switchLocator{root: t.key, tstamp: now.Unix()} - t._updateTable() // updates base copy of switch msg in lookupTable - t.core.peers.sendSwitchMsgs(t) - } -} - -// Blocks and, if possible, unparents a peer -func (t *switchTable) blockPeer(from phony.Actor, port switchPort, isWrite bool) { - t.Act(from, func() { - peer, isIn := t.data.peers[port] - switch { - case isIn && !isWrite && !peer.readBlock: - peer.readBlock = true - case isIn && isWrite && !peer.writeBlock: - peer.writeBlock = true - default: - return - } - t.data.peers[port] = peer - defer t._updateTable() - if port != t.parent { - return - } - t.parent = 0 - for _, info := range t.data.peers { - if info.port == port { - continue - } - t._handleMsg(&info.msg, info.port, true) - } - t._handleMsg(&peer.msg, peer.port, true) - }) -} - -func (t *switchTable) unblockPeer(from phony.Actor, port switchPort, isWrite bool) { - t.Act(from, func() { - peer, isIn := t.data.peers[port] - switch { - case isIn && !isWrite && peer.readBlock: - peer.readBlock = false - case isIn && isWrite && peer.writeBlock: - peer.writeBlock = false - default: - return - } - t.data.peers[port] = peer - t._updateTable() - }) -} - -// Removes a peer. -// Must be called by the router actor with a lambda that calls this. -// If the removed peer was this node's parent, it immediately tries to find a new parent. -func (t *switchTable) forgetPeer(from phony.Actor, port switchPort) { - t.Act(from, func() { - delete(t.data.peers, port) - defer t._updateTable() - if port != t.parent { - return - } - t.parent = 0 - for _, info := range t.data.peers { - t._handleMsg(&info.msg, info.port, true) - } - }) -} - -// Dropped is a list of roots that are better than the current root, but stopped sending new timestamps. -// If we switch to a new root, and that root is better than an old root that previously timed out, then we can clean up the old dropped root infos. -// This function is called periodically to do that cleanup. -func (t *switchTable) _cleanDropped() { - // TODO? only call this after root changes, not periodically - for root := range t.drop { - if !firstIsBetter(&root, &t.data.locator.root) { - delete(t.drop, root) - } - } -} - -// A switchMsg contains the root node's sig key, timestamp, and signed per-hop information about a path from the root node to some other node in the network. -// This is exchanged with peers to construct the spanning tree. -// A subset of this information, excluding the signatures, is used to construct locators that are used elsewhere in the code. -type switchMsg struct { - Root crypto.SigPubKey - TStamp int64 - Hops []switchMsgHop -} - -// This represents the signed information about the path leading from the root the Next node, via the Port specified here. -type switchMsgHop struct { - Port switchPort - Next crypto.SigPubKey - Sig crypto.SigBytes -} - -// This returns a *switchMsg to a copy of this node's current switchMsg, which can safely have additional information appended to Hops and sent to a peer. -func (t *switchTable) _getMsg() *switchMsg { - if t.parent == 0 { - return &switchMsg{Root: t.key, TStamp: t.data.locator.tstamp} - } else if parent, isIn := t.data.peers[t.parent]; isIn { - msg := parent.msg - msg.Hops = append([]switchMsgHop(nil), msg.Hops...) - return &msg - } else { - return nil - } -} - -func (t *lookupTable) getMsg() *switchMsg { - msg := t._msg - msg.Hops = append([]switchMsgHop(nil), t._msg.Hops...) - return &msg -} - -// This function checks that the root information in a switchMsg is OK. -// In particular, that the root is better, or else the same as the current root but with a good timestamp, and that this root+timestamp haven't been dropped due to timeout. -func (t *switchTable) _checkRoot(msg *switchMsg) bool { - // returns false if it's a dropped root, not a better root, or has an older timestamp - // returns true otherwise - // used elsewhere to keep inserting peers into the dht only if root info is OK - dropTstamp, isIn := t.drop[msg.Root] - switch { - case isIn && dropTstamp >= msg.TStamp: - return false - case firstIsBetter(&msg.Root, &t.data.locator.root): - return true - case t.data.locator.root != msg.Root: - return false - case t.data.locator.tstamp > msg.TStamp: - return false - default: - return true - } -} - -// This updates the switch with information about a peer. -// Then the tricky part, it decides if it should update our own locator as a result. -// That happens if this node is already our parent, or is advertising a better root, or is advertising a better path to the same root, etc... -// There are a lot of very delicate order sensitive checks here, so its' best to just read the code if you need to understand what it's doing. -// It's very important to not change the order of the statements in the case function unless you're absolutely sure that it's safe, including safe if used alongside nodes that used the previous order. -// Set the third arg to true if you're reprocessing an old message, e.g. to find a new parent after one disconnects, to avoid updating some timing related things. -func (t *switchTable) _handleMsg(msg *switchMsg, fromPort switchPort, reprocessing bool) { - // TODO directly use a switchMsg instead of switchMessage + sigs - now := time.Now() - // Set up the sender peerInfo - var sender peerInfo - sender.locator.root = msg.Root - sender.locator.tstamp = msg.TStamp - prevKey := msg.Root - for _, hop := range msg.Hops { - // Build locator - sender.locator.coords = append(sender.locator.coords, hop.Port) - sender.key = prevKey - prevKey = hop.Next - } - if sender.key == t.key { - return // Don't peer with ourself via different interfaces - } - sender.msg = *msg - sender.port = fromPort - sender.time = now - // Decide what to do - equiv := func(x *switchLocator, y *switchLocator) bool { - if x.root != y.root { - return false - } - if len(x.coords) != len(y.coords) { - return false - } - for idx := range x.coords { - if x.coords[idx] != y.coords[idx] { - return false - } - } - return true - } - doUpdate := false - oldSender := t.data.peers[fromPort] - if !equiv(&sender.locator, &oldSender.locator) { - doUpdate = true - } - if reprocessing { - sender.time = oldSender.time - sender.readBlock = oldSender.readBlock - sender.writeBlock = oldSender.writeBlock - } - if sender.blocked() != oldSender.blocked() { - doUpdate = true - } - // Update sender - t.data.peers[fromPort] = sender - // Decide if we should also update our root info to make the sender our parent - updateRoot := false - oldParent, isIn := t.data.peers[t.parent] - noParent := !isIn - noLoop := func() bool { - for idx := 0; idx < len(msg.Hops)-1; idx++ { - if msg.Hops[idx].Next == t.core.sigPub { - return false - } - } - if sender.locator.root == t.core.sigPub { - return false - } - return true - }() - dropTstamp, isIn := t.drop[sender.locator.root] - // Decide if we need to update info about the root or change parents. - switch { - case !noLoop: - // This route loops, so we can't use the sender as our parent. - case isIn && dropTstamp >= sender.locator.tstamp: - // This is a known root with a timestamp older than a known timeout, so we can't trust it to be a new announcement. - case firstIsBetter(&sender.locator.root, &t.data.locator.root): - // This is a better root than what we're currently using, so we should update. - updateRoot = true - case t.data.locator.root != sender.locator.root: - // This is not the same root, and it's apparently not better (from the above), so we should ignore it. - case t.data.locator.tstamp > sender.locator.tstamp: - // This timetsamp is older than the most recently seen one from this root, so we should ignore it. - case noParent: - // We currently have no working parent, and at this point in the switch statement, anything is better than nothing. - updateRoot = true - case !sender.blocked() && oldParent.blocked(): - // Replace a blocked parent - updateRoot = true - case reprocessing && sender.blocked() && !oldParent.blocked(): - // Don't replace an unblocked parent when reprocessing - case sender.locator.tstamp > t.data.locator.tstamp: - // The timestamp was updated, so we need to update locally and send to our peers. - updateRoot = true - } - // Note that we depend on the LIFO order of the stack of defers here... - if updateRoot { - doUpdate = true - if !equiv(&sender.locator, &t.data.locator) { - defer t.core.router.reset(t) - } - if t.data.locator.tstamp != sender.locator.tstamp { - t.time = now - } - t.data.locator = sender.locator - t.parent = sender.port - defer t.core.peers.sendSwitchMsgs(t) - } - if doUpdate { - t._updateTable() - } -} - -//////////////////////////////////////////////////////////////////////////////// - -// The rest of these are related to the switch lookup table - -func (t *switchTable) _updateTable() { - newTable := lookupTable{ - self: t.data.locator.clone(), - elems: make(map[switchPort]tableElem, len(t.data.peers)), - _msg: *t._getMsg(), - } - newTable._init() - for _, pinfo := range t.data.peers { - if pinfo.blocked() || pinfo.locator.root != newTable.self.root { - continue - } - loc := pinfo.locator.clone() - loc.coords = loc.coords[:len(loc.coords)-1] // Remove the them->self link - elem := tableElem{ - locator: loc, - port: pinfo.port, - time: pinfo.time, - } - newTable._insert(&elem) - newTable.elems[pinfo.port] = elem - } - t.core.peers.updateTables(t, &newTable) - t.core.router.updateTable(t, &newTable) -} - -func (t *lookupTable) _init() { - // WARNING: this relies on the convention that the self port is 0 - self := tableElem{locator: t.self} // create self elem - t._start = self // initialize _start to self - t._insert(&self) // insert self into table -} - -func (t *lookupTable) _insert(elem *tableElem) { - // This is a helper that should only be run during _updateTable - here := &t._start - for idx := 0; idx <= len(elem.locator.coords); idx++ { - refLoc := here.locator - refLoc.coords = refLoc.coords[:idx] // Note that this is length idx (starts at length 0) - oldDist := refLoc.ldist(&here.locator) - newDist := refLoc.ldist(&elem.locator) - var update bool - switch { - case newDist < oldDist: // new elem is closer to this point in the tree - update = true - case newDist > oldDist: // new elem is too far - case elem.locator.tstamp > refLoc.tstamp: // new elem has a closer timestamp - update = true - case elem.locator.tstamp < refLoc.tstamp: // new elem's timestamp is too old - case elem.time.Before(here.time): // same dist+timestamp, but new elem delivered it faster - update = true - } - if update { - here.port = elem.port - here.locator = elem.locator - here.time = elem.time - // Problem: here is a value, so this doesn't actually update anything... - } - if idx < len(elem.locator.coords) { - if here.next == nil { - here.next = make(map[switchPort]*tableElem) - } - var next *tableElem - var ok bool - if next, ok = here.next[elem.locator.coords[idx]]; !ok { - nextVal := *elem - next = &nextVal - here.next[next.locator.coords[idx]] = next - } - here = next - } - } -} - -// Starts the switch worker -func (t *switchTable) start() error { - t.core.log.Infoln("Starting switch") - // There's actually nothing to do to start it... - return nil -} - -func (t *lookupTable) lookup(ports []switchPort) switchPort { - here := &t._start - for idx := range ports { - port := ports[idx] - if next, ok := here.next[port]; ok { - here = next - } else { - break - } - } - return here.port -} - -func switch_getPorts(coords []byte) []switchPort { - var ports []switchPort - var offset int - for offset < len(coords) { - port, l := wire_decode_uint64(coords[offset:]) - offset += l - ports = append(ports, switchPort(port)) - } - return ports -} - -func switch_reverseCoordBytes(coords []byte) []byte { - a := switch_getPorts(coords) - for i := len(a)/2 - 1; i >= 0; i-- { - opp := len(a) - 1 - i - a[i], a[opp] = a[opp], a[i] - } - var reversed []byte - for _, sPort := range a { - reversed = wire_put_uint64(uint64(sPort), reversed) - } - return reversed -} - -func (t *lookupTable) isDescendant(ports []switchPort) bool { - // Note that this returns true for anyone in the subtree that starts at us - // That includes ourself, so we are our own descendant by this logic... - if len(t.self.coords) >= len(ports) { - // Our coords are longer, so they can't be our descendant - return false - } - for idx := range t.self.coords { - if ports[idx] != t.self.coords[idx] { - return false - } - } - return true -} - -func (t *lookupTable) getOffset(ports []switchPort) uint64 { - // If they're our descendant, this returns the length of our coords, used as an offset for source routing - // If they're not our descendant, this returns 0 - var offset uint64 - for idx := range t.self.coords { - if idx < len(ports) && ports[idx] == t.self.coords[idx] { - offset += 1 - } else { - return 0 - } - } - return offset -} diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 10c2b1eb..59ca2ace 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -387,8 +387,6 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options tcpOptions) chan str } upgraded = true } - stream := stream{} - stream.init(sock) var name, proto, local, remote string if options.socksProxyAddr != "" { name = "socks://" + sock.RemoteAddr().String() + "/" + options.socksPeerAddr @@ -423,7 +421,7 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options tcpOptions) chan str } } force := net.ParseIP(strings.Split(remote, "%")[0]).IsLinkLocalUnicast() - link, err := t.links.create(&stream, name, proto, local, remote, incoming, force, options.linkOptions) + link, err := t.links.create(sock, name, proto, local, remote, incoming, force, options.linkOptions) if err != nil { t.links.core.log.Println(err) panic(err) diff --git a/src/yggdrasil/tls.go b/src/yggdrasil/tls.go index e2861aca..fd8a291c 100644 --- a/src/yggdrasil/tls.go +++ b/src/yggdrasil/tls.go @@ -34,7 +34,7 @@ func (t *tcptls) init(tcp *tcp) { } edpriv := make(ed25519.PrivateKey, ed25519.PrivateKeySize) - copy(edpriv[:], tcp.links.core.sigPriv[:]) + copy(edpriv[:], tcp.links.core.secret[:]) certBuf := &bytes.Buffer{} @@ -42,7 +42,7 @@ func (t *tcptls) init(tcp *tcp) { pubtemp := x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{ - CommonName: hex.EncodeToString(tcp.links.core.sigPub[:]), + CommonName: hex.EncodeToString(tcp.links.core.public[:]), }, NotBefore: time.Now(), NotAfter: time.Now().Add(time.Hour * 24 * 365), diff --git a/src/yggdrasil/version.go b/src/yggdrasil/version.go index f8ce85b8..a3c9bce5 100644 --- a/src/yggdrasil/version.go +++ b/src/yggdrasil/version.go @@ -4,19 +4,17 @@ package yggdrasil // Used in the initial connection setup and key exchange // Some of this could arguably go in wire.go instead -import "github.com/yggdrasil-network/yggdrasil-go/src/crypto" +import "crypto/ed25519" // This is the version-specific metadata exchanged at the start of a connection. // It must always begin with the 4 bytes "meta" and a wire formatted uint64 major version number. // The current version also includes a minor version number, and the box/sig/link keys that need to be exchanged to open a connection. type version_metadata struct { meta [4]byte - ver uint64 // 1 byte in this version + ver uint8 // 1 byte in this version // Everything after this point potentially depends on the version number, and is subject to change in future versions - minorVer uint64 // 1 byte in this version - box crypto.BoxPubKey - sig crypto.SigPubKey - link crypto.BoxPubKey + minorVer uint8 // 1 byte in this version + key ed25519.PublicKey } // Gets a base metadata with no keys set, but with the correct version numbers. @@ -30,12 +28,10 @@ func version_getBaseMetadata() version_metadata { // Gets the length of the metadata for this version, used to know how many bytes to read from the start of a connection. func version_getMetaLength() (mlen int) { - mlen += 4 // meta - mlen++ // ver, as long as it's < 127, which it is in this version - mlen++ // minorVer, as long as it's < 127, which it is in this version - mlen += crypto.BoxPubKeyLen // box - mlen += crypto.SigPubKeyLen // sig - mlen += crypto.BoxPubKeyLen // link + mlen += 4 // meta + mlen++ // ver, as long as it's < 127, which it is in this version + mlen++ // minorVer, as long as it's < 127, which it is in this version + mlen += ed25519.PublicKeySize // key return } @@ -43,11 +39,9 @@ func version_getMetaLength() (mlen int) { func (m *version_metadata) encode() []byte { bs := make([]byte, 0, version_getMetaLength()) bs = append(bs, m.meta[:]...) - bs = append(bs, wire_encode_uint64(m.ver)...) - bs = append(bs, wire_encode_uint64(m.minorVer)...) - bs = append(bs, m.box[:]...) - bs = append(bs, m.sig[:]...) - bs = append(bs, m.link[:]...) + bs = append(bs, m.ver) + bs = append(bs, m.minorVer) + bs = append(bs, m.key[:]...) if len(bs) != version_getMetaLength() { panic("Inconsistent metadata length") } @@ -56,20 +50,14 @@ func (m *version_metadata) encode() []byte { // Decodes version metadata from its wire format into the struct. func (m *version_metadata) decode(bs []byte) bool { - switch { - case !wire_chop_slice(m.meta[:], &bs): - return false - case !wire_chop_uint64(&m.ver, &bs): - return false - case !wire_chop_uint64(&m.minorVer, &bs): - return false - case !wire_chop_slice(m.box[:], &bs): - return false - case !wire_chop_slice(m.sig[:], &bs): - return false - case !wire_chop_slice(m.link[:], &bs): + if len(bs) != version_getMetaLength() { return false } + offset := 0 + offset += copy(m.meta[:], bs[offset:]) + m.ver, offset = bs[offset], offset+1 + m.minorVer, offset = bs[offset], offset+1 + m.key = append([]byte(nil), bs[offset:]...) return true } diff --git a/src/yggdrasil/wire.go b/src/yggdrasil/wire.go deleted file mode 100644 index e8e9bf09..00000000 --- a/src/yggdrasil/wire.go +++ /dev/null @@ -1,521 +0,0 @@ -package yggdrasil - -// Wire formatting tools -// These are all ugly and probably not very secure - -// TODO clean up unused/commented code, and add better comments to whatever is left - -// Packet types, as wire_encode_uint64(type) at the start of each packet - -import ( - "github.com/yggdrasil-network/yggdrasil-go/src/crypto" -) - -const ( - wire_Traffic = iota // data being routed somewhere, handle for crypto - wire_ProtocolTraffic // protocol traffic, pub keys for crypto - wire_LinkProtocolTraffic // link proto traffic, pub keys for crypto - wire_SwitchMsg // inside link protocol traffic header - wire_SessionPing // inside protocol traffic header - wire_SessionPong // inside protocol traffic header - wire_DHTLookupRequest // inside protocol traffic header - wire_DHTLookupResponse // inside protocol traffic header - wire_NodeInfoRequest // inside protocol traffic header - wire_NodeInfoResponse // inside protocol traffic header -) - -// Calls wire_put_uint64 on a nil slice. -func wire_encode_uint64(elem uint64) []byte { - return wire_put_uint64(elem, nil) -} - -// Encode uint64 using a variable length scheme. -// Similar to binary.Uvarint, but big-endian. -func wire_put_uint64(e uint64, out []byte) []byte { - var b [10]byte - i := len(b) - 1 - b[i] = byte(e & 0x7f) - for e >>= 7; e != 0; e >>= 7 { - i-- - b[i] = byte(e | 0x80) - } - return append(out, b[i:]...) -} - -// Returns the length of a wire encoded uint64 of this value. -func wire_uint64_len(elem uint64) int { - l := 1 - for e := elem >> 7; e > 0; e >>= 7 { - l++ - } - return l -} - -// Decode uint64 from a []byte slice. -// Returns the decoded uint64 and the number of bytes used. -func wire_decode_uint64(bs []byte) (uint64, int) { - length := 0 - elem := uint64(0) - for _, b := range bs { - elem <<= 7 - elem |= uint64(b & 0x7f) - length++ - if b&0x80 == 0 { - break - } - } - return elem, length -} - -// Converts an int64 into uint64 so it can be written to the wire. -// Non-negative integers are mapped to even integers: 0 -> 0, 1 -> 2, etc. -// Negative integers are mapped to odd integers: -1 -> 1, -2 -> 3, etc. -// This means the least significant bit is a sign bit. -// This is known as zigzag encoding. -func wire_intToUint(i int64) uint64 { - // signed arithmetic shift - return uint64((i >> 63) ^ (i << 1)) -} - -// Converts uint64 back to int64, genreally when being read from the wire. -func wire_intFromUint(u uint64) int64 { - // non-arithmetic shift - return int64((u >> 1) ^ -(u & 1)) -} - -//////////////////////////////////////////////////////////////////////////////// - -// Takes coords, returns coords prefixed with encoded coord length. -func wire_encode_coords(coords []byte) []byte { - coordLen := wire_encode_uint64(uint64(len(coords))) - bs := make([]byte, 0, len(coordLen)+len(coords)) - bs = append(bs, coordLen...) - bs = append(bs, coords...) - return bs -} - -// Puts a length prefix and the coords into bs, returns the wire formatted coords. -// Useful in hot loops where we don't want to allocate and we know the rest of the later parts of the slice are safe to overwrite. -func wire_put_vslice(slice []byte, bs []byte) []byte { - bs = wire_put_uint64(uint64(len(slice)), bs) - bs = append(bs, slice...) - return bs -} - -// Takes a slice that begins with coords (starting with coord length). -// Returns a slice of coords and the number of bytes read. -// Used as part of various decode() functions for structs. -func wire_decode_coords(packet []byte) ([]byte, int) { - coordLen, coordBegin := wire_decode_uint64(packet) - coordEnd := coordBegin + int(coordLen) - if coordBegin == 0 || coordEnd > len(packet) { - return nil, 0 - } - return packet[coordBegin:coordEnd], coordEnd -} - -// Converts a []uint64 set of coords to a []byte set of coords. -func wire_coordsUint64stoBytes(in []uint64) (out []byte) { - for _, coord := range in { - c := wire_encode_uint64(coord) - out = append(out, c...) - } - return out -} - -// Converts a []byte set of coords to a []uint64 set of coords. -func wire_coordsBytestoUint64s(in []byte) (out []uint64) { - offset := 0 - for { - coord, length := wire_decode_uint64(in[offset:]) - if length == 0 { - break - } - out = append(out, coord) - offset += length - } - return out -} - -//////////////////////////////////////////////////////////////////////////////// - -// Encodes a swtichMsg into its wire format. -func (m *switchMsg) encode() []byte { - bs := wire_encode_uint64(wire_SwitchMsg) - bs = append(bs, m.Root[:]...) - bs = append(bs, wire_encode_uint64(wire_intToUint(m.TStamp))...) - for _, hop := range m.Hops { - bs = append(bs, wire_encode_uint64(uint64(hop.Port))...) - bs = append(bs, hop.Next[:]...) - bs = append(bs, hop.Sig[:]...) - } - return bs -} - -// Decodes a wire formatted switchMsg into the struct, returns true if successful. -func (m *switchMsg) decode(bs []byte) bool { - var pType uint64 - var tstamp uint64 - switch { - case !wire_chop_uint64(&pType, &bs): - return false - case pType != wire_SwitchMsg: - return false - case !wire_chop_slice(m.Root[:], &bs): - return false - case !wire_chop_uint64(&tstamp, &bs): - return false - } - m.TStamp = wire_intFromUint(tstamp) - for len(bs) > 0 { - var hop switchMsgHop - switch { - case !wire_chop_uint64((*uint64)(&hop.Port), &bs): - return false - case !wire_chop_slice(hop.Next[:], &bs): - return false - case !wire_chop_slice(hop.Sig[:], &bs): - return false - } - m.Hops = append(m.Hops, hop) - } - return true -} - -//////////////////////////////////////////////////////////////////////////////// - -// A utility function used to copy bytes into a slice and advance the beginning of the source slice, returns true if successful. -func wire_chop_slice(toSlice []byte, fromSlice *[]byte) bool { - if len(*fromSlice) < len(toSlice) { - return false - } - copy(toSlice, *fromSlice) - *fromSlice = (*fromSlice)[len(toSlice):] - return true -} - -// A utility function to extract a length-prefixed slice (such as coords) from a slice and advance the source slices, returning true if successful. -func wire_chop_vslice(toSlice *[]byte, fromSlice *[]byte) bool { - slice, sliceLen := wire_decode_coords(*fromSlice) - if sliceLen == 0 { // sliceLen is length-prefix size + slice size, in bytes - return false - } - *toSlice = append((*toSlice)[:0], slice...) - *fromSlice = (*fromSlice)[sliceLen:] - return true -} - -// A utility function to extract a wire encoded uint64 into the provided pointer while advancing the start of the source slice, returning true if successful. -func wire_chop_uint64(toUInt64 *uint64, fromSlice *[]byte) bool { - dec, decLen := wire_decode_uint64(*fromSlice) - if decLen == 0 { - return false - } - *toUInt64 = dec - *fromSlice = (*fromSlice)[decLen:] - return true -} - -//////////////////////////////////////////////////////////////////////////////// - -// Wire traffic packets - -// The wire format for ordinary IPv6 traffic encapsulated by the network. -type wire_trafficPacket struct { - Offset uint64 - Coords []byte - Handle crypto.Handle - Nonce crypto.BoxNonce - Payload []byte - RPath []byte -} - -// Encodes a wire_trafficPacket into its wire format. -// The returned slice was taken from the pool. -func (p *wire_trafficPacket) encode() []byte { - bs := pool_getBytes(0) - bs = wire_put_uint64(wire_Traffic, bs) - bs = wire_put_uint64(p.Offset, bs) - bs = wire_put_vslice(p.Coords, bs) - bs = append(bs, p.Handle[:]...) - bs = append(bs, p.Nonce[:]...) - bs = wire_put_vslice(p.Payload, bs) - bs = append(bs, p.RPath...) - return bs -} - -// Decodes an encoded wire_trafficPacket into the struct, returning true if successful. -// Either way, the argument slice is added to the pool. -func (p *wire_trafficPacket) decode(bs []byte) bool { - defer pool_putBytes(bs) - var pType uint64 - switch { - case !wire_chop_uint64(&pType, &bs): - return false - case pType != wire_Traffic: - return false - case !wire_chop_uint64(&p.Offset, &bs): - return false - case !wire_chop_vslice(&p.Coords, &bs): - return false - case !wire_chop_slice(p.Handle[:], &bs): - return false - case !wire_chop_slice(p.Nonce[:], &bs): - return false - case !wire_chop_vslice(&p.Payload, &bs): - return false - } - p.RPath = append(p.RPath[:0], bs...) - return true -} - -// The wire format for protocol traffic, such as dht req/res or session ping/pong packets. -type wire_protoTrafficPacket struct { - Offset uint64 - Coords []byte - ToKey crypto.BoxPubKey - FromKey crypto.BoxPubKey - Nonce crypto.BoxNonce - Payload []byte - RPath []byte -} - -// Encodes a wire_protoTrafficPacket into its wire format. -func (p *wire_protoTrafficPacket) encode() []byte { - bs := wire_encode_uint64(wire_ProtocolTraffic) - bs = wire_put_uint64(p.Offset, bs) - bs = wire_put_vslice(p.Coords, bs) - bs = append(bs, p.ToKey[:]...) - bs = append(bs, p.FromKey[:]...) - bs = append(bs, p.Nonce[:]...) - bs = wire_put_vslice(p.Payload, bs) - bs = append(bs, p.RPath...) - return bs -} - -// Decodes an encoded wire_protoTrafficPacket into the struct, returning true if successful. -func (p *wire_protoTrafficPacket) decode(bs []byte) bool { - var pType uint64 - switch { - case !wire_chop_uint64(&pType, &bs): - return false - case pType != wire_ProtocolTraffic: - return false - case !wire_chop_uint64(&p.Offset, &bs): - return false - case !wire_chop_vslice(&p.Coords, &bs): - return false - case !wire_chop_slice(p.ToKey[:], &bs): - return false - case !wire_chop_slice(p.FromKey[:], &bs): - return false - case !wire_chop_slice(p.Nonce[:], &bs): - return false - case !wire_chop_vslice(&p.Payload, &bs): - return false - } - p.RPath = append(p.RPath[:0], bs...) - return true -} - -// Get the offset and coord slices of a (protocol) traffic packet without decoding -func wire_getTrafficOffsetAndCoords(packet []byte) ([]byte, []byte) { - _, offsetBegin := wire_decode_uint64(packet) - _, offsetLen := wire_decode_uint64(packet[offsetBegin:]) - offsetEnd := offsetBegin + offsetLen - offset := packet[offsetBegin:offsetEnd] - coords, _ := wire_decode_coords(packet[offsetEnd:]) - return offset, coords -} - -// The wire format for link protocol traffic, namely switchMsg. -// There's really two layers of this, with the outer layer using permanent keys, and the inner layer using ephemeral keys. -// The keys themselves are exchanged as part of the connection setup, and then omitted from the packets. -// The two layer logic is handled in peers.go, but it's kind of ugly. -type wire_linkProtoTrafficPacket struct { - Nonce crypto.BoxNonce - Payload []byte -} - -// Encodes a wire_linkProtoTrafficPacket into its wire format. -func (p *wire_linkProtoTrafficPacket) encode() []byte { - bs := wire_encode_uint64(wire_LinkProtocolTraffic) - bs = append(bs, p.Nonce[:]...) - bs = append(bs, p.Payload...) - return bs -} - -// Decodes an encoded wire_linkProtoTrafficPacket into the struct, returning true if successful. -func (p *wire_linkProtoTrafficPacket) decode(bs []byte) bool { - var pType uint64 - switch { - case !wire_chop_uint64(&pType, &bs): - return false - case pType != wire_LinkProtocolTraffic: - return false - case !wire_chop_slice(p.Nonce[:], &bs): - return false - } - p.Payload = bs - return true -} - -//////////////////////////////////////////////////////////////////////////////// - -// Encodes a sessionPing into its wire format. -func (p *sessionPing) encode() []byte { - var pTypeVal uint64 - if p.IsPong { - pTypeVal = wire_SessionPong - } else { - pTypeVal = wire_SessionPing - } - bs := wire_encode_uint64(pTypeVal) - //p.sendPermPub used in top level (crypto), so skipped here - bs = append(bs, p.Handle[:]...) - bs = append(bs, p.SendSesPub[:]...) - bs = append(bs, wire_encode_uint64(wire_intToUint(p.Tstamp))...) - coords := wire_encode_coords(p.Coords) - bs = append(bs, coords...) - bs = append(bs, wire_encode_uint64(uint64(p.MTU))...) - return bs -} - -// Decodes an encoded sessionPing into the struct, returning true if successful. -func (p *sessionPing) decode(bs []byte) bool { - var pType uint64 - var tstamp uint64 - var mtu uint64 - switch { - case !wire_chop_uint64(&pType, &bs): - return false - case pType != wire_SessionPing && pType != wire_SessionPong: - return false - //p.sendPermPub used in top level (crypto), so skipped here - case !wire_chop_slice(p.Handle[:], &bs): - return false - case !wire_chop_slice(p.SendSesPub[:], &bs): - return false - case !wire_chop_uint64(&tstamp, &bs): - return false - case !wire_chop_vslice(&p.Coords, &bs): - return false - case !wire_chop_uint64(&mtu, &bs): - mtu = 1280 - } - p.Tstamp = wire_intFromUint(tstamp) - if pType == wire_SessionPong { - p.IsPong = true - } - p.MTU = MTU(mtu) - return true -} - -//////////////////////////////////////////////////////////////////////////////// - -// Encodes a nodeinfoReqRes into its wire format. -func (p *nodeinfoReqRes) encode() []byte { - var pTypeVal uint64 - if p.IsResponse { - pTypeVal = wire_NodeInfoResponse - } else { - pTypeVal = wire_NodeInfoRequest - } - bs := wire_encode_uint64(pTypeVal) - bs = wire_put_vslice(p.SendCoords, bs) - if pTypeVal == wire_NodeInfoResponse { - bs = append(bs, p.NodeInfo...) - } - return bs -} - -// Decodes an encoded nodeinfoReqRes into the struct, returning true if successful. -func (p *nodeinfoReqRes) decode(bs []byte) bool { - var pType uint64 - switch { - case !wire_chop_uint64(&pType, &bs): - return false - case pType != wire_NodeInfoRequest && pType != wire_NodeInfoResponse: - return false - case !wire_chop_vslice(&p.SendCoords, &bs): - return false - } - if p.IsResponse = pType == wire_NodeInfoResponse; p.IsResponse { - if len(bs) == 0 { - return false - } - p.NodeInfo = make(NodeInfoPayload, len(bs)) - if !wire_chop_slice(p.NodeInfo[:], &bs) { - return false - } - } - return true -} - -//////////////////////////////////////////////////////////////////////////////// - -// Encodes a dhtReq into its wire format. -func (r *dhtReq) encode() []byte { - coords := wire_encode_coords(r.Coords) - bs := wire_encode_uint64(wire_DHTLookupRequest) - bs = append(bs, coords...) - bs = append(bs, r.Dest[:]...) - return bs -} - -// Decodes an encoded dhtReq into the struct, returning true if successful. -func (r *dhtReq) decode(bs []byte) bool { - var pType uint64 - switch { - case !wire_chop_uint64(&pType, &bs): - return false - case pType != wire_DHTLookupRequest: - return false - case !wire_chop_vslice(&r.Coords, &bs): - return false - case !wire_chop_slice(r.Dest[:], &bs): - return false - default: - return true - } -} - -// Encodes a dhtRes into its wire format. -func (r *dhtRes) encode() []byte { - coords := wire_encode_coords(r.Coords) - bs := wire_encode_uint64(wire_DHTLookupResponse) - bs = append(bs, coords...) - bs = append(bs, r.Dest[:]...) - for _, info := range r.Infos { - coords = wire_encode_coords(info.coords) - bs = append(bs, info.key[:]...) - bs = append(bs, coords...) - } - return bs -} - -// Decodes an encoded dhtRes into the struct, returning true if successful. -func (r *dhtRes) decode(bs []byte) bool { - var pType uint64 - switch { - case !wire_chop_uint64(&pType, &bs): - return false - case pType != wire_DHTLookupResponse: - return false - case !wire_chop_vslice(&r.Coords, &bs): - return false - case !wire_chop_slice(r.Dest[:], &bs): - return false - } - for len(bs) > 0 { - info := dhtInfo{} - switch { - case !wire_chop_slice(info.key[:], &bs): - return false - case !wire_chop_vslice(&info.coords, &bs): - return false - } - r.Infos = append(r.Infos, &info) - } - return true -} From 0cff56fcc17d1acaf5297a7024477a9ca1bd3590 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 8 May 2021 10:39:07 -0500 Subject: [PATCH 0645/1109] (broken state) WIP on tuntap --- src/address/address.go | 32 ++++ src/admin/admin.go | 363 +++++++++++++++++++++-------------------- src/tuntap/conn.go | 227 -------------------------- src/tuntap/iface.go | 251 ++++++++++++++-------------- src/tuntap/keystore.go | 152 +++++++++++++++++ src/tuntap/tun.go | 46 ++++-- 6 files changed, 524 insertions(+), 547 deletions(-) delete mode 100644 src/tuntap/conn.go create mode 100644 src/tuntap/keystore.go diff --git a/src/address/address.go b/src/address/address.go index ff2d2120..7add23ac 100644 --- a/src/address/address.go +++ b/src/address/address.go @@ -112,3 +112,35 @@ func SubnetForKey(publicKey ed25519.PublicKey) *Subnet { snet[len(prefix)-1] |= 0x01 return &snet } + +// GetKet returns the partial ed25519.PublicKey for the Address. +// This is used for key lookup. +func (a *Address) GetKey() ed25519.PublicKey { + var key [ed25519.PublicKeySize]byte + prefix := GetPrefix() + ones := int(a[len(prefix)]) + for idx := 0; idx < ones; idx++ { + key[idx/8] |= 0x80 >> byte(idx%8) + } + keyOffset := ones + 1 + addrOffset := 8*len(prefix) + 8 + for idx := addrOffset; idx < 8*len(a); idx++ { + bits := a[idx/8] & (0x80 >> byte(idx%8)) + bits <<= byte(idx % 8) + keyIdx := keyOffset + (idx - addrOffset) + bits >>= byte(keyIdx % 8) + key[keyIdx/8] |= bits + } + for idx := range key { + key[idx] = ^key[idx] + } + return ed25519.PublicKey(key[:]) +} + +// GetKet returns the partial ed25519.PublicKey for the Subnet. +// This is used for key lookup. +func (s *Subnet) GetKey() ed25519.PublicKey { + var addr Address + copy(addr[:], s[:]) + return addr.GetKey() +} diff --git a/src/admin/admin.go b/src/admin/admin.go index dbf973e9..a87e1248 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -1,24 +1,24 @@ package admin import ( - "encoding/hex" + //"encoding/hex" "encoding/json" "errors" "fmt" "net" "net/url" "os" - "strconv" + //"strconv" "strings" "time" "github.com/gologme/log" - "github.com/yggdrasil-network/yggdrasil-go/src/address" + //"github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/config" - "github.com/yggdrasil-network/yggdrasil-go/src/crypto" - "github.com/yggdrasil-network/yggdrasil-go/src/util" - "github.com/yggdrasil-network/yggdrasil-go/src/version" + //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" + //"github.com/yggdrasil-network/yggdrasil-go/src/util" + //"github.com/yggdrasil-network/yggdrasil-go/src/version" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) @@ -82,13 +82,14 @@ func (a *AdminSocket) UpdateConfig(config *config.NodeConfig) { } func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { + /* TODO a.AddHandler("getSelf", []string{}, func(in Info) (Info, error) { ip := a.core.Address().String() subnet := a.core.Subnet() return Info{ "self": Info{ ip: Info{ - "box_pub_key": a.core.EncryptionPublicKey(), + // TODO"box_pub_key": a.core.EncryptionPublicKey(), "build_name": version.BuildName(), "build_version": version.BuildVersion(), "coords": fmt.Sprintf("%v", a.core.Coords()), @@ -140,203 +141,205 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { return Info{"switchqueues": queues.asMap()}, nil }) */ - a.AddHandler("getDHT", []string{}, func(in Info) (Info, error) { - dht := make(Info) - for _, d := range a.core.GetDHT() { - panic("TODO") - addr := new(address.Address) // TODO *address.AddrForNodeID(crypto.GetNodeID(&d.PublicKey)) - so := net.IP(addr[:]).String() - dht[so] = Info{ - "coords": fmt.Sprintf("%v", d.Coords), - "last_seen": d.LastSeen.Seconds(), - "box_pub_key": hex.EncodeToString(d.PublicKey[:]), + /* + a.AddHandler("getDHT", []string{}, func(in Info) (Info, error) { + dht := make(Info) + for _, d := range a.core.GetDHT() { + panic("TODO") + addr := new(address.Address) // TODO *address.AddrForNodeID(crypto.GetNodeID(&d.PublicKey)) + so := net.IP(addr[:]).String() + dht[so] = Info{ + "coords": fmt.Sprintf("%v", d.Coords), + "last_seen": d.LastSeen.Seconds(), + "box_pub_key": hex.EncodeToString(d.PublicKey[:]), + } } - } - return Info{"dht": dht}, nil - }) - a.AddHandler("getSessions", []string{}, func(in Info) (Info, error) { - sessions := make(Info) - for _, s := range a.core.GetSessions() { - panic("TODO") - addr := new(address.Address) //*address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey)) - so := net.IP(addr[:]).String() - sessions[so] = Info{ - "coords": fmt.Sprintf("%v", s.Coords), - "bytes_sent": s.BytesSent, - "bytes_recvd": s.BytesRecvd, - "mtu": s.MTU, - "uptime": s.Uptime.Seconds(), - "was_mtu_fixed": s.WasMTUFixed, - "box_pub_key": hex.EncodeToString(s.PublicKey[:]), + return Info{"dht": dht}, nil + }) + a.AddHandler("getSessions", []string{}, func(in Info) (Info, error) { + sessions := make(Info) + for _, s := range a.core.GetSessions() { + panic("TODO") + addr := new(address.Address) //*address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey)) + so := net.IP(addr[:]).String() + sessions[so] = Info{ + "coords": fmt.Sprintf("%v", s.Coords), + "bytes_sent": s.BytesSent, + "bytes_recvd": s.BytesRecvd, + "mtu": s.MTU, + "uptime": s.Uptime.Seconds(), + "was_mtu_fixed": s.WasMTUFixed, + "box_pub_key": hex.EncodeToString(s.PublicKey[:]), + } + } + return Info{"sessions": sessions}, nil + }) + a.AddHandler("addPeer", []string{"uri", "[interface]"}, func(in Info) (Info, error) { + // Set sane defaults + intf := "" + // Has interface been specified? + if itf, ok := in["interface"]; ok { + intf = itf.(string) + } + if a.core.AddPeer(in["uri"].(string), intf) == nil { + return Info{ + "added": []string{ + in["uri"].(string), + }, + }, nil } - } - return Info{"sessions": sessions}, nil - }) - a.AddHandler("addPeer", []string{"uri", "[interface]"}, func(in Info) (Info, error) { - // Set sane defaults - intf := "" - // Has interface been specified? - if itf, ok := in["interface"]; ok { - intf = itf.(string) - } - if a.core.AddPeer(in["uri"].(string), intf) == nil { return Info{ - "added": []string{ + "not_added": []string{ in["uri"].(string), }, - }, nil - } - return Info{ - "not_added": []string{ - in["uri"].(string), - }, - }, errors.New("Failed to add peer") - }) - a.AddHandler("disconnectPeer", []string{"port"}, func(in Info) (Info, error) { - port, err := strconv.ParseInt(fmt.Sprint(in["port"]), 10, 64) - if err != nil { - return Info{}, err - } - if a.core.DisconnectPeer(uint64(port)) == nil { - return Info{ - "disconnected": []string{ - fmt.Sprint(port), - }, - }, nil - } else { - return Info{ - "not_disconnected": []string{ - fmt.Sprint(port), - }, - }, errors.New("Failed to disconnect peer") - } - }) - a.AddHandler("removePeer", []string{"uri", "[interface]"}, func(in Info) (Info, error) { - // Set sane defaults - intf := "" - // Has interface been specified? - if itf, ok := in["interface"]; ok { - intf = itf.(string) - } - if a.core.RemovePeer(in["uri"].(string), intf) == nil { - return Info{ - "removed": []string{ - in["uri"].(string), - }, - }, nil - } else { + }, errors.New("Failed to add peer") + }) + a.AddHandler("disconnectPeer", []string{"port"}, func(in Info) (Info, error) { + port, err := strconv.ParseInt(fmt.Sprint(in["port"]), 10, 64) + if err != nil { + return Info{}, err + } + if a.core.DisconnectPeer(uint64(port)) == nil { + return Info{ + "disconnected": []string{ + fmt.Sprint(port), + }, + }, nil + } else { + return Info{ + "not_disconnected": []string{ + fmt.Sprint(port), + }, + }, errors.New("Failed to disconnect peer") + } + }) + a.AddHandler("removePeer", []string{"uri", "[interface]"}, func(in Info) (Info, error) { + // Set sane defaults + intf := "" + // Has interface been specified? + if itf, ok := in["interface"]; ok { + intf = itf.(string) + } + if a.core.RemovePeer(in["uri"].(string), intf) == nil { + return Info{ + "removed": []string{ + in["uri"].(string), + }, + }, nil + } else { + return Info{ + "not_removed": []string{ + in["uri"].(string), + }, + }, errors.New("Failed to remove peer") + } return Info{ "not_removed": []string{ in["uri"].(string), }, }, errors.New("Failed to remove peer") - } - return Info{ - "not_removed": []string{ - in["uri"].(string), - }, - }, errors.New("Failed to remove peer") - }) - a.AddHandler("getAllowedEncryptionPublicKeys", []string{}, func(in Info) (Info, error) { - return Info{"allowed_box_pubs": a.core.GetAllowedEncryptionPublicKeys()}, nil - }) - a.AddHandler("addAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) { - if a.core.AddAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { + }) + a.AddHandler("getAllowedEncryptionPublicKeys", []string{}, func(in Info) (Info, error) { + return Info{"allowed_box_pubs": a.core.GetAllowedEncryptionPublicKeys()}, nil + }) + a.AddHandler("addAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) { + if a.core.AddAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { + return Info{ + "added": []string{ + in["box_pub_key"].(string), + }, + }, nil + } return Info{ - "added": []string{ + "not_added": []string{ in["box_pub_key"].(string), }, - }, nil - } - return Info{ - "not_added": []string{ - in["box_pub_key"].(string), - }, - }, errors.New("Failed to add allowed key") - }) - a.AddHandler("removeAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) { - if a.core.RemoveAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { + }, errors.New("Failed to add allowed key") + }) + a.AddHandler("removeAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) { + if a.core.RemoveAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { + return Info{ + "removed": []string{ + in["box_pub_key"].(string), + }, + }, nil + } return Info{ - "removed": []string{ + "not_removed": []string{ in["box_pub_key"].(string), }, - }, nil - } - return Info{ - "not_removed": []string{ - in["box_pub_key"].(string), - }, - }, errors.New("Failed to remove allowed key") - }) - a.AddHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in Info) (Info, error) { - var reserr error - var result yggdrasil.DHTRes - if in["target"] == nil { - in["target"] = "none" - } - coords := util.DecodeCoordString(in["coords"].(string)) - var boxPubKey crypto.BoxPubKey - if b, err := hex.DecodeString(in["box_pub_key"].(string)); err == nil { - copy(boxPubKey[:], b) - if n, err := hex.DecodeString(in["target"].(string)); err == nil { - var targetNodeID crypto.NodeID - copy(targetNodeID[:], n) - result, reserr = a.core.DHTPing(boxPubKey, coords, &targetNodeID) - } else { - result, reserr = a.core.DHTPing(boxPubKey, coords, nil) + }, errors.New("Failed to remove allowed key") + }) + a.AddHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in Info) (Info, error) { + var reserr error + var result yggdrasil.DHTRes + if in["target"] == nil { + in["target"] = "none" } - } else { - return Info{}, err - } - if reserr != nil { - return Info{}, reserr - } - infos := make(map[string]map[string]string, len(result.Infos)) - for _, dinfo := range result.Infos { - info := map[string]string{ - "box_pub_key": hex.EncodeToString(dinfo.PublicKey[:]), - "coords": fmt.Sprintf("%v", dinfo.Coords), - } - panic("TODO") - addr := "" //net.IP(address.AddrForNodeID(crypto.GetNodeID(&dinfo.PublicKey))[:]).String() - infos[addr] = info - } - return Info{"nodes": infos}, nil - }) - a.AddHandler("getNodeInfo", []string{"[box_pub_key]", "[coords]", "[nocache]"}, func(in Info) (Info, error) { - var nocache bool - if in["nocache"] != nil { - nocache = in["nocache"].(string) == "true" - } - var boxPubKey crypto.BoxPubKey - var coords []uint64 - if in["box_pub_key"] == nil && in["coords"] == nil { - nodeinfo := a.core.MyNodeInfo() - var jsoninfo interface{} - if err := json.Unmarshal(nodeinfo, &jsoninfo); err != nil { - return Info{}, err - } - return Info{"nodeinfo": jsoninfo}, nil - } else if in["box_pub_key"] == nil || in["coords"] == nil { - return Info{}, errors.New("Expecting both box_pub_key and coords") - } else { + coords := util.DecodeCoordString(in["coords"].(string)) + var boxPubKey crypto.BoxPubKey if b, err := hex.DecodeString(in["box_pub_key"].(string)); err == nil { copy(boxPubKey[:], b) + if n, err := hex.DecodeString(in["target"].(string)); err == nil { + var targetNodeID crypto.NodeID + copy(targetNodeID[:], n) + result, reserr = a.core.DHTPing(boxPubKey, coords, &targetNodeID) + } else { + result, reserr = a.core.DHTPing(boxPubKey, coords, nil) + } } else { return Info{}, err } - coords = util.DecodeCoordString(in["coords"].(string)) - } - result, err := a.core.GetNodeInfo(boxPubKey, coords, nocache) - if err == nil { - var m map[string]interface{} - if err = json.Unmarshal(result, &m); err == nil { - return Info{"nodeinfo": m}, nil + if reserr != nil { + return Info{}, reserr + } + infos := make(map[string]map[string]string, len(result.Infos)) + for _, dinfo := range result.Infos { + info := map[string]string{ + "box_pub_key": hex.EncodeToString(dinfo.PublicKey[:]), + "coords": fmt.Sprintf("%v", dinfo.Coords), + } + panic("TODO") + addr := "" //net.IP(address.AddrForNodeID(crypto.GetNodeID(&dinfo.PublicKey))[:]).String() + infos[addr] = info + } + return Info{"nodes": infos}, nil + }) + a.AddHandler("getNodeInfo", []string{"[box_pub_key]", "[coords]", "[nocache]"}, func(in Info) (Info, error) { + var nocache bool + if in["nocache"] != nil { + nocache = in["nocache"].(string) == "true" + } + var boxPubKey crypto.BoxPubKey + var coords []uint64 + if in["box_pub_key"] == nil && in["coords"] == nil { + nodeinfo := a.core.MyNodeInfo() + var jsoninfo interface{} + if err := json.Unmarshal(nodeinfo, &jsoninfo); err != nil { + return Info{}, err + } + return Info{"nodeinfo": jsoninfo}, nil + } else if in["box_pub_key"] == nil || in["coords"] == nil { + return Info{}, errors.New("Expecting both box_pub_key and coords") + } else { + if b, err := hex.DecodeString(in["box_pub_key"].(string)); err == nil { + copy(boxPubKey[:], b) + } else { + return Info{}, err + } + coords = util.DecodeCoordString(in["coords"].(string)) + } + result, err := a.core.GetNodeInfo(boxPubKey, coords, nocache) + if err == nil { + var m map[string]interface{} + if err = json.Unmarshal(result, &m); err == nil { + return Info{"nodeinfo": m}, nil + } + return Info{}, err } return Info{}, err - } - return Info{}, err - }) + }) + */ } // Start runs the admin API socket to listen for / respond to admin API calls. diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go deleted file mode 100644 index ddd89e9b..00000000 --- a/src/tuntap/conn.go +++ /dev/null @@ -1,227 +0,0 @@ -package tuntap - -import ( - "bytes" - "errors" - "time" - - "github.com/Arceliar/phony" - "github.com/yggdrasil-network/yggdrasil-go/src/address" - "github.com/yggdrasil-network/yggdrasil-go/src/crypto" - "github.com/yggdrasil-network/yggdrasil-go/src/util" - "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" - "golang.org/x/net/icmp" - "golang.org/x/net/ipv6" -) - -const tunConnTimeout = 2 * time.Minute - -type tunConn struct { - phony.Inbox - tun *TunAdapter - conn *yggdrasil.Conn - addr address.Address - snet address.Subnet - stop chan struct{} - alive *time.Timer // From calling time.AfterFunc -} - -func (s *tunConn) close() { - s.tun.Act(s, s._close_from_tun) -} - -func (s *tunConn) _close_from_tun() { - go s.conn.Close() // Just in case it blocks on actor operations - delete(s.tun.addrToConn, s.addr) - delete(s.tun.subnetToConn, s.snet) - func() { - defer func() { recover() }() - close(s.stop) // Closes reader/writer goroutines - }() -} - -func (s *tunConn) _read(bs []byte) (err error) { - select { - case <-s.stop: - err = errors.New("session was already closed") - return - default: - } - if len(bs) == 0 { - err = errors.New("read packet with 0 size") - return - } - ipv4 := len(bs) > 20 && bs[0]&0xf0 == 0x40 - ipv6 := len(bs) > 40 && bs[0]&0xf0 == 0x60 - isCGA := true - // Check source addresses - switch { - case ipv6 && bs[8] == 0x02 && bytes.Equal(s.addr[:16], bs[8:24]): // source - case ipv6 && bs[8] == 0x03 && bytes.Equal(s.snet[:8], bs[8:16]): // source - default: - isCGA = false - } - // Check destination addresses - switch { - case ipv6 && bs[24] == 0x02 && bytes.Equal(s.tun.addr[:16], bs[24:40]): // destination - case ipv6 && bs[24] == 0x03 && bytes.Equal(s.tun.subnet[:8], bs[24:32]): // destination - default: - isCGA = false - } - // Decide how to handle the packet - var skip bool - switch { - case isCGA: // Allowed - case s.tun.ckr.isEnabled() && (ipv4 || ipv6): - var srcAddr address.Address - var dstAddr address.Address - var addrlen int - if ipv4 { - copy(srcAddr[:], bs[12:16]) - copy(dstAddr[:], bs[16:20]) - addrlen = 4 - } - if ipv6 { - copy(srcAddr[:], bs[8:24]) - copy(dstAddr[:], bs[24:40]) - addrlen = 16 - } - if !s.tun.ckr.isValidLocalAddress(dstAddr, addrlen) { - // The destination address isn't in our CKR allowed range - skip = true - } else if key, err := s.tun.ckr.getPublicKeyForAddress(srcAddr, addrlen); err == nil { - if *s.conn.RemoteAddr().(*crypto.BoxPubKey) == key { - // This is the one allowed CKR case, where source and destination addresses are both good - } else { - // The CKR key associated with this address doesn't match the sender's NodeID - skip = true - } - } else { - // We have no CKR route for this source address - skip = true - } - default: - skip = true - } - if skip { - err = errors.New("address not allowed") - return - } - s.tun.writer.writeFrom(s, bs) - s.stillAlive() - return -} - -func (s *tunConn) writeFrom(from phony.Actor, bs []byte) { - s.Act(from, func() { - s._write(bs) - }) -} - -func (s *tunConn) _write(bs []byte) (err error) { - select { - case <-s.stop: - err = errors.New("session was already closed") - return - default: - } - v4 := len(bs) > 20 && bs[0]&0xf0 == 0x40 - v6 := len(bs) > 40 && bs[0]&0xf0 == 0x60 - isCGA := true - // Check source addresses - switch { - case v6 && bs[8] == 0x02 && bytes.Equal(s.tun.addr[:16], bs[8:24]): // source - case v6 && bs[8] == 0x03 && bytes.Equal(s.tun.subnet[:8], bs[8:16]): // source - default: - isCGA = false - } - // Check destiantion addresses - switch { - case v6 && bs[24] == 0x02 && bytes.Equal(s.addr[:16], bs[24:40]): // destination - case v6 && bs[24] == 0x03 && bytes.Equal(s.snet[:8], bs[24:32]): // destination - default: - isCGA = false - } - // Decide how to handle the packet - var skip bool - switch { - case isCGA: // Allowed - case s.tun.ckr.isEnabled() && (v4 || v6): - var srcAddr address.Address - var dstAddr address.Address - var addrlen int - if v4 { - copy(srcAddr[:], bs[12:16]) - copy(dstAddr[:], bs[16:20]) - addrlen = 4 - } - if v6 { - copy(srcAddr[:], bs[8:24]) - copy(dstAddr[:], bs[24:40]) - addrlen = 16 - } - if !s.tun.ckr.isValidLocalAddress(srcAddr, addrlen) { - // The source address isn't in our CKR allowed range - skip = true - } else if key, err := s.tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { - if *s.conn.RemoteAddr().(*crypto.BoxPubKey) == key { - // This is the one allowed CKR case, where source and destination addresses are both good - } else { - // The CKR key associated with this address doesn't match the sender's NodeID - skip = true - } - } else { - // We have no CKR route for this destination address... why do we have the packet in the first place? - skip = true - } - default: - skip = true - } - if skip { - err = errors.New("address not allowed") - return - } - msg := yggdrasil.FlowKeyMessage{ - FlowKey: util.GetFlowKey(bs), - Message: bs, - } - s.conn.WriteFrom(s, msg, func(err error) { - if err == nil { - // No point in wasting resources to send back an error if there was none - return - } - s.Act(s.conn, func() { - if e, eok := err.(yggdrasil.ConnError); !eok { - if e.Closed() { - s.tun.log.Debugln(s.conn.String(), "TUN/TAP generic write debug:", err) - } else { - s.tun.log.Errorln(s.conn.String(), "TUN/TAP generic write error:", err) - } - } else if e.PacketTooBig() { - // TODO: This currently isn't aware of IPv4 for CKR - ptb := &icmp.PacketTooBig{ - MTU: int(e.PacketMaximumSize()), - Data: bs[:900], - } - if packet, err := CreateICMPv6(bs[8:24], bs[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb); err == nil { - s.tun.writer.writeFrom(s, packet) - } - } else { - if e.Closed() { - s.tun.log.Debugln(s.conn.String(), "TUN/TAP conn write debug:", err) - } else { - s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn write error:", err) - } - } - }) - }) - s.stillAlive() - return -} - -func (s *tunConn) stillAlive() { - if s.alive != nil { - s.alive.Stop() - } - s.alive = time.AfterFunc(tunConnTimeout, s.close) -} diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index da6d8e24..836a9c61 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -1,12 +1,12 @@ package tuntap 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/yggdrasil" + //"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" - "golang.org/x/net/icmp" - "golang.org/x/net/ipv6" + //"golang.org/x/net/icmp" + //"golang.org/x/net/ipv6" "github.com/Arceliar/phony" ) @@ -84,139 +84,142 @@ func (tun *TunAdapter) handlePacketFrom(from phony.Actor, packet []byte, err err // does the work of reading a packet and sending it to the correct tunConn func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { - if err != nil { - tun.log.Errorln("TUN iface read error:", err) - return - } - // Offset the buffer from now on so that we can ignore ethernet frames if - // they are present - bs := recvd[:] - // Check if the packet is long enough to detect if it's an ICMP packet or not - if len(bs) < 7 { - tun.log.Traceln("TUN iface read undersized unknown packet, length:", len(bs)) - return - } - // From the IP header, work out what our source and destination addresses - // and node IDs are. We will need these in order to work out where to send - // the packet - var dstAddr address.Address - var dstSnet address.Subnet - var addrlen int - n := len(bs) - // Check the IP protocol - if it doesn't match then we drop the packet and - // do nothing with it - if bs[0]&0xf0 == 0x60 { - // Check if we have a fully-sized IPv6 header - if len(bs) < 40 { - tun.log.Traceln("TUN iface read undersized ipv6 packet, length:", len(bs)) + panic("TODO") + /* + if err != nil { + tun.log.Errorln("TUN iface read error:", err) return } - // Check the packet size - if n-tun_IPv6_HEADER_LENGTH != 256*int(bs[4])+int(bs[5]) { + // Offset the buffer from now on so that we can ignore ethernet frames if + // they are present + bs := recvd[:] + // Check if the packet is long enough to detect if it's an ICMP packet or not + if len(bs) < 7 { + tun.log.Traceln("TUN iface read undersized unknown packet, length:", len(bs)) return } - // IPv6 address - addrlen = 16 - copy(dstAddr[:addrlen], bs[24:]) - copy(dstSnet[:addrlen/2], bs[24:]) - } else if bs[0]&0xf0 == 0x40 { - // Check if we have a fully-sized IPv4 header - if len(bs) < 20 { - tun.log.Traceln("TUN iface read undersized ipv4 packet, length:", len(bs)) + // From the IP header, work out what our source and destination addresses + // and node IDs are. We will need these in order to work out where to send + // the packet + var dstAddr address.Address + var dstSnet address.Subnet + var addrlen int + n := len(bs) + // Check the IP protocol - if it doesn't match then we drop the packet and + // do nothing with it + if bs[0]&0xf0 == 0x60 { + // Check if we have a fully-sized IPv6 header + if len(bs) < 40 { + tun.log.Traceln("TUN iface read undersized ipv6 packet, length:", len(bs)) + return + } + // Check the packet size + if n-tun_IPv6_HEADER_LENGTH != 256*int(bs[4])+int(bs[5]) { + return + } + // IPv6 address + addrlen = 16 + copy(dstAddr[:addrlen], bs[24:]) + copy(dstSnet[:addrlen/2], bs[24:]) + } else if bs[0]&0xf0 == 0x40 { + // Check if we have a fully-sized IPv4 header + if len(bs) < 20 { + tun.log.Traceln("TUN iface read undersized ipv4 packet, length:", len(bs)) + return + } + // Check the packet size + if n != 256*int(bs[2])+int(bs[3]) { + return + } + // IPv4 address + addrlen = 4 + copy(dstAddr[:addrlen], bs[16:]) + } else { + // Unknown address length or protocol, so drop the packet and ignore it + tun.log.Traceln("Unknown packet type, dropping") return } - // Check the packet size - if n != 256*int(bs[2])+int(bs[3]) { - return + if tun.ckr.isEnabled() { + if addrlen != 16 || (!dstAddr.IsValid() && !dstSnet.IsValid()) { + if key, err := tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { + // A public key was found, get the node ID for the search + panic("TODO") + //dstNodeID := crypto.GetNodeID(&key) + //dstAddr = *address.AddrForNodeID(dstNodeID) + //dstSnet = *address.SubnetForNodeID(dstNodeID) + addrlen = 16 + } + } } - // IPv4 address - addrlen = 4 - copy(dstAddr[:addrlen], bs[16:]) - } else { - // Unknown address length or protocol, so drop the packet and ignore it - tun.log.Traceln("Unknown packet type, dropping") - return - } - if tun.ckr.isEnabled() { if addrlen != 16 || (!dstAddr.IsValid() && !dstSnet.IsValid()) { - if /*key*/ _, err := tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { - // A public key was found, get the node ID for the search - panic("TODO") - //dstNodeID := crypto.GetNodeID(&key) - //dstAddr = *address.AddrForNodeID(dstNodeID) - //dstSnet = *address.SubnetForNodeID(dstNodeID) - addrlen = 16 + // Couldn't find this node's ygg IP + dlen := len(bs) + if dlen > 900 { + dlen = 900 } + ptb := &icmp.DstUnreach{ + Data: bs[:dlen], + } + if packet, err := CreateICMPv6(bs[8:24], bs[24:40], ipv6.ICMPTypeDestinationUnreachable, 0, ptb); err == nil { + tun.writer.writeFrom(nil, packet) + } + return } - } - if addrlen != 16 || (!dstAddr.IsValid() && !dstSnet.IsValid()) { - // Couldn't find this node's ygg IP - dlen := len(bs) - if dlen > 900 { - dlen = 900 - } - ptb := &icmp.DstUnreach{ - Data: bs[:dlen], - } - if packet, err := CreateICMPv6(bs[8:24], bs[24:40], ipv6.ICMPTypeDestinationUnreachable, 0, ptb); err == nil { - tun.writer.writeFrom(nil, packet) - } - return - } - // Do we have an active connection for this node address? - var dstString string - session, isIn := tun.addrToConn[dstAddr] - if !isIn || session == nil { - session, isIn = tun.subnetToConn[dstSnet] + // Do we have an active connection for this node address? + var dstString string + session, isIn := tun.addrToConn[dstAddr] if !isIn || session == nil { - // Neither an address nor a subnet mapping matched, therefore populate - // the node ID and mask to commence a search - panic("TODO") - if dstAddr.IsValid() { - //dstString = dstAddr.GetNodeIDLengthString() - } else { - //dstString = dstSnet.GetNodeIDLengthString() + session, isIn = tun.subnetToConn[dstSnet] + if !isIn || session == nil { + // Neither an address nor a subnet mapping matched, therefore populate + // the node ID and mask to commence a search + panic("TODO") + if dstAddr.IsValid() { + //dstString = dstAddr.GetNodeIDLengthString() + } else { + //dstString = dstSnet.GetNodeIDLengthString() + } } } - } - // If we don't have a connection then we should open one - if !isIn || session == nil { - // Check we haven't been given empty node ID, really this shouldn't ever - // happen but just to be sure... - if dstString == "" { - panic("Given empty dstString - this shouldn't happen") + // If we don't have a connection then we should open one + if !isIn || session == nil { + // Check we haven't been given empty node ID, really this shouldn't ever + // happen but just to be sure... + if dstString == "" { + panic("Given empty dstString - this shouldn't happen") + } + _, known := tun.dials[dstString] + tun.dials[dstString] = append(tun.dials[dstString], bs) + for len(tun.dials[dstString]) > 32 { + tun.dials[dstString] = tun.dials[dstString][1:] + } + if !known { + go func() { + conn, err := tun.dialer.Dial("nodeid", dstString) + tun.Act(nil, func() { + packets := tun.dials[dstString] + delete(tun.dials, dstString) + if err != nil { + return + } + // We've been given a connection so prepare the session wrapper + var tc *tunConn + if tc, err = tun._wrap(conn.(*yggdrasil.Conn)); err != nil { + // Something went wrong when storing the connection, typically that + // something already exists for this address or subnet + tun.log.Debugln("TUN iface wrap:", err) + return + } + for _, packet := range packets { + tc.writeFrom(nil, packet) + } + }) + }() + } } - _, known := tun.dials[dstString] - tun.dials[dstString] = append(tun.dials[dstString], bs) - for len(tun.dials[dstString]) > 32 { - tun.dials[dstString] = tun.dials[dstString][1:] + // If we have a connection now, try writing to it + if isIn && session != nil { + session.writeFrom(tun, bs) } - if !known { - go func() { - conn, err := tun.dialer.Dial("nodeid", dstString) - tun.Act(nil, func() { - packets := tun.dials[dstString] - delete(tun.dials, dstString) - if err != nil { - return - } - // We've been given a connection so prepare the session wrapper - var tc *tunConn - if tc, err = tun._wrap(conn.(*yggdrasil.Conn)); err != nil { - // Something went wrong when storing the connection, typically that - // something already exists for this address or subnet - tun.log.Debugln("TUN iface wrap:", err) - return - } - for _, packet := range packets { - tc.writeFrom(nil, packet) - } - }) - }() - } - } - // If we have a connection now, try writing to it - if isIn && session != nil { - session.writeFrom(tun, bs) - } + */ } diff --git a/src/tuntap/keystore.go b/src/tuntap/keystore.go new file mode 100644 index 00000000..8901965f --- /dev/null +++ b/src/tuntap/keystore.go @@ -0,0 +1,152 @@ +package tuntap + +import ( + "crypto/ed25519" + "sync" + "time" + + iwt "github.com/Arceliar/ironwood/types" + + "github.com/yggdrasil-network/yggdrasil-go/src/address" +) + +const keyStoreTimeout = 2 * time.Minute + +type keyStore struct { + tun *TunAdapter + mutex sync.Mutex + keyToInfo map[keyArray]*keyInfo + addrToInfo map[address.Address]*keyInfo + addrBuffer map[address.Address]*buffer + subnetToInfo map[address.Subnet]*keyInfo + subnetBuffer map[address.Subnet]*buffer +} + +type keyArray [ed25519.PublicKeySize]byte + +type keyInfo struct { + key keyArray + address address.Address + subnet address.Subnet + mtu MTU // TODO use this + timeout *time.Timer // From calling a time.AfterFunc to do cleanup +} + +type buffer struct { + packets [][]byte + timeout *time.Timer +} + +func (k *keyStore) init(tun *TunAdapter) { + k.tun = tun + k.keyToInfo = make(map[keyArray]*keyInfo) + k.addrToInfo = make(map[address.Address]*keyInfo) + k.addrBuffer = make(map[address.Address]*buffer) + k.subnetToInfo = make(map[address.Subnet]*keyInfo) + k.subnetBuffer = make(map[address.Subnet]*buffer) +} + +func (k *keyStore) sendToAddress(addr address.Address, bs []byte) { + k.mutex.Lock() + defer k.mutex.Unlock() + if info := k.addrToInfo[addr]; info != nil { + k.tun.core.WriteTo(bs, iwt.Addr(info.key[:])) + k.resetTimeout(info) + } else { + var buf *buffer + if buf = k.addrBuffer[addr]; buf == nil { + buf = new(buffer) + k.addrBuffer[addr] = buf + } + msg := append([]byte(nil), bs...) + buf.packets = append(buf.packets, msg) + if buf.timeout != nil { + buf.timeout.Stop() + } + buf.timeout = time.AfterFunc(keyStoreTimeout, func() { + k.mutex.Lock() + defer k.mutex.Unlock() + if nbuf := k.addrBuffer[addr]; nbuf == buf { + delete(k.addrBuffer, addr) + } + }) + } +} + +func (k *keyStore) sendToSubnet(subnet address.Subnet, bs []byte) { + k.mutex.Lock() + defer k.mutex.Unlock() + if info := k.subnetToInfo[subnet]; info != nil { + k.tun.core.WriteTo(bs, iwt.Addr(info.key[:])) + k.resetTimeout(info) + } else { + var buf *buffer + if buf = k.subnetBuffer[subnet]; buf == nil { + buf = new(buffer) + k.subnetBuffer[subnet] = buf + } + msg := append([]byte(nil), bs...) + buf.packets = append(buf.packets, msg) + if buf.timeout != nil { + buf.timeout.Stop() + } + buf.timeout = time.AfterFunc(keyStoreTimeout, func() { + k.mutex.Lock() + defer k.mutex.Unlock() + if nbuf := k.subnetBuffer[subnet]; nbuf == buf { + delete(k.subnetBuffer, subnet) + } + }) + } +} + +func (k *keyStore) update(key ed25519.PublicKey) { + k.mutex.Lock() + defer k.mutex.Unlock() + var kArray keyArray + copy(kArray[:], key) + var info *keyInfo + if info = k.keyToInfo[kArray]; info == nil { + info = new(keyInfo) + info.key = kArray + info.address = *address.AddrForKey(ed25519.PublicKey(info.key[:])) + info.subnet = *address.SubnetForKey(ed25519.PublicKey(info.key[:])) + info.mtu = MTU(^uint16(0)) // TODO + k.keyToInfo[info.key] = info + k.addrToInfo[info.address] = info + k.subnetToInfo[info.subnet] = info + k.resetTimeout(info) + if buf := k.addrBuffer[info.address]; buf != nil { + for _, bs := range buf.packets { + k.tun.core.WriteTo(bs, iwt.Addr(info.key[:])) + } + delete(k.addrBuffer, info.address) + } + if buf := k.subnetBuffer[info.subnet]; buf != nil { + for _, bs := range buf.packets { + k.tun.core.WriteTo(bs, iwt.Addr(info.key[:])) + } + delete(k.subnetBuffer, info.subnet) + } + } + k.resetTimeout(info) +} + +func (k *keyStore) resetTimeout(info *keyInfo) { + if info.timeout != nil { + info.timeout.Stop() + } + info.timeout = time.AfterFunc(keyStoreTimeout, func() { + k.mutex.Lock() + defer k.mutex.Unlock() + if nfo := k.keyToInfo[info.key]; nfo == info { + delete(k.keyToInfo, info.key) + } + if nfo := k.addrToInfo[info.address]; nfo == info { + delete(k.addrToInfo, info.address) + } + if nfo := k.subnetToInfo[info.subnet]; nfo == info { + delete(k.subnetToInfo, info.subnet) + } + }) +} diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 06f609ae..fc1b7eb9 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -9,6 +9,7 @@ package tuntap // TODO: Don't block in reader on writes that are pending searches import ( + "crypto/ed25519" "encoding/hex" "errors" "fmt" @@ -38,13 +39,12 @@ const tun_IPv6_HEADER_LENGTH = 40 // calling yggdrasil.Start(). type TunAdapter struct { core *yggdrasil.Core + store keyStore writer tunWriter reader tunReader config *config.NodeState log *log.Logger reconfigure chan chan error - listener *yggdrasil.Listener - dialer *yggdrasil.Dialer addr address.Address subnet address.Subnet ckr cryptokey @@ -53,15 +53,12 @@ type TunAdapter struct { iface tun.Device phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below //mutex sync.RWMutex // Protects the below - addrToConn map[address.Address]*tunConn - subnetToConn map[address.Subnet]*tunConn - 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 + //Listener *yggdrasil.Listener + //Dialer *yggdrasil.Dialer } // Gets the maximum supported MTU for the platform based on the defaults in @@ -113,20 +110,20 @@ func MaximumMTU() MTU { // Init initialises the TUN module. You must have acquired a Listener from // the Yggdrasil core before this point and it must not be in use elsewhere. func (tun *TunAdapter) Init(core *yggdrasil.Core, config *config.NodeState, log *log.Logger, options interface{}) error { + /* TODO tunoptions, ok := options.(TunOptions) if !ok { return fmt.Errorf("invalid options supplied to TunAdapter module") } + */ tun.core = core + tun.store.init(tun) tun.config = config tun.log = log - tun.listener = tunoptions.Listener - tun.dialer = tunoptions.Dialer - tun.addrToConn = make(map[address.Address]*tunConn) - tun.subnetToConn = make(map[address.Subnet]*tunConn) - tun.dials = make(map[string][][]byte) tun.writer.tun = tun tun.reader.tun = tun + tun.core.SetOutOfBandHandler(tun.oobHandler) + return nil } @@ -145,7 +142,7 @@ func (tun *TunAdapter) _start() error { return errors.New("TUN module is already started") } current := tun.config.GetCurrent() - if tun.config == nil || tun.listener == nil || tun.dialer == nil { + if tun.config == nil { return errors.New("no configuration available to TUN") } var boxPub crypto.BoxPubKey @@ -169,9 +166,9 @@ func (tun *TunAdapter) _start() error { if tun.MTU() != current.IfMTU { tun.log.Warnf("Warning: Interface MTU %d automatically adjusted to %d (supported range is 1280-%d)", current.IfMTU, tun.MTU(), MaximumMTU()) } - tun.core.SetMaximumSessionMTU(tun.MTU()) + // TODO tun.core.SetMaximumSessionMTU(tun.MTU()) tun.isOpen = true - go tun.handler() + // TODO go tun.handler() tun.reader.Act(nil, tun.reader._read) // Start the reader tun.ckr.init(tun) return nil @@ -225,6 +222,7 @@ func (tun *TunAdapter) UpdateConfig(config *config.NodeConfig) { tun.Act(nil, tun.ckr.configure) } +/* func (tun *TunAdapter) handler() error { for { // Accept the incoming connection @@ -283,3 +281,19 @@ func (tun *TunAdapter) _wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { // Return return c, err } +*/ + +func (tun *TunAdapter) oobHandler(fromKey, toKey ed25519.PublicKey, data []byte) { + panic("TODO") + // parse packet + // If it's a lookup then send a response + // If it's a response then (maybe) update the keystore +} + +func (tun *TunAdapter) sendKeyLookup(partial ed25519.PublicKey) { + panic("TODO") +} + +func (tun *TunAdapter) sendKeyResponse(dest ed25519.PublicKey) { + panic("TODO") +} From 5b22392c66e41c174efcb0ddb80693e3b7a82132 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 8 May 2021 11:14:50 -0500 Subject: [PATCH 0646/1109] (broken state) more WIP on tuntap stuff --- src/tuntap/iface.go | 91 +++++++++++++++++++++++++++++++++++++++++- src/tuntap/keystore.go | 5 ++- src/tuntap/tun.go | 2 + 3 files changed, 96 insertions(+), 2 deletions(-) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 836a9c61..50ea6dae 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -1,18 +1,107 @@ package tuntap import ( - //"github.com/yggdrasil-network/yggdrasil-go/src/address" + "crypto/ed25519" + + "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" //"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" //"golang.org/x/net/icmp" //"golang.org/x/net/ipv6" + iwt "github.com/Arceliar/ironwood/types" "github.com/Arceliar/phony" ) const TUN_OFFSET_BYTES = 4 +func (tun *TunAdapter) doRead() { + var buf [TUN_OFFSET_BYTES + 65535]byte + for { + n, err := tun.iface.Read(buf[:], TUN_OFFSET_BYTES) + if n <= TUN_OFFSET_BYTES || err != nil { + tun.log.Errorln("Error reading TUN:", err) + ferr := tun.iface.Flush() + if ferr != nil { + tun.log.Errorln("Unable to flush packets:", ferr) + } + return + } + begin := TUN_OFFSET_BYTES + end := begin + n + bs := buf[begin:end] + if bs[0]&0xf0 != 0x60 { + continue // not IPv6 + } + if len(bs) < 40 { + tun.log.Traceln("TUN iface read undersized ipv6 packet, length:", len(bs)) + continue + } + var srcAddr, dstAddr address.Address + var srcSubnet, dstSubnet address.Subnet + copy(srcAddr[:], bs[8:]) + copy(dstAddr[:], bs[24:]) + copy(srcSubnet[:], bs[8:]) + copy(dstSubnet[:], bs[24:]) + if srcAddr != tun.addr && srcSubnet != tun.subnet { + continue // Wrong soruce address + } + if dstAddr.IsValid() { + tun.store.sendToAddress(dstAddr, bs) + } else if dstSubnet.IsValid() { + tun.store.sendToSubnet(dstSubnet, bs) + } + } +} + +func (tun *TunAdapter) doWrite() { + panic("TODO") + // Read from the packetconn + // Get the info from the store + // Check if addresses / subnets / etc are OK (both sides of connection) + var buf [TUN_OFFSET_BYTES + 65535]byte + for { + bs := buf[TUN_OFFSET_BYTES:] + n, from, err := tun.core.ReadFrom(bs) + if err != nil { + return + } + bs = bs[:n] + if bs[0]&0xf0 != 0x60 { + continue // not IPv6 + } + if len(bs) < 40 { + continue + } + var srcAddr, dstAddr address.Address + var srcSubnet, dstSubnet address.Subnet + copy(srcAddr[:], bs[8:]) + copy(dstAddr[:], bs[24:]) + copy(srcSubnet[:], bs[8:]) + copy(dstSubnet[:], bs[24:]) + if dstAddr != tun.addr && dstSubnet != tun.subnet { + continue // bad local address/subnet + } + info := tun.store.update(ed25519.PublicKey(from.(iwt.Addr))) + if srcAddr != info.address && srcSubnet != info.subnet { + continue // bad remote address/subnet + } + bs = buf[:TUN_OFFSET_BYTES+n] + n, err = tun.iface.Write(bs, TUN_OFFSET_BYTES) + if err != nil { + tun.Act(nil, func() { + if !tun.isOpen { + tun.log.Errorln("TUN iface write error:", err) + } + }) + } + if n != len(bs) { + // TODO some kind of error reporting for a partial write + } + } +} + type tunWriter struct { phony.Inbox tun *TunAdapter diff --git a/src/tuntap/keystore.go b/src/tuntap/keystore.go index 8901965f..c7733a29 100644 --- a/src/tuntap/keystore.go +++ b/src/tuntap/keystore.go @@ -70,6 +70,7 @@ func (k *keyStore) sendToAddress(addr address.Address, bs []byte) { delete(k.addrBuffer, addr) } }) + panic("TODO") // TODO send lookup } } @@ -97,10 +98,11 @@ func (k *keyStore) sendToSubnet(subnet address.Subnet, bs []byte) { delete(k.subnetBuffer, subnet) } }) + panic("TODO") // TODO send lookup } } -func (k *keyStore) update(key ed25519.PublicKey) { +func (k *keyStore) update(key ed25519.PublicKey) *keyInfo { k.mutex.Lock() defer k.mutex.Unlock() var kArray keyArray @@ -130,6 +132,7 @@ func (k *keyStore) update(key ed25519.PublicKey) { } } k.resetTimeout(info) + return info } func (k *keyStore) resetTimeout(info *keyInfo) { diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index fc1b7eb9..7fa85175 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -171,6 +171,8 @@ func (tun *TunAdapter) _start() error { // TODO go tun.handler() tun.reader.Act(nil, tun.reader._read) // Start the reader tun.ckr.init(tun) + go tun.doRead() + go tun.doWrite() return nil } From 0f787364deb17831f4216d48b5f434ceb4429c99 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 8 May 2021 11:32:57 -0500 Subject: [PATCH 0647/1109] (broken state) more tuntap WIP to add out-of-band key lookup --- src/tuntap/tun.go | 101 +++++++++++++++------------------------------- 1 file changed, 33 insertions(+), 68 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 7fa85175..388602c1 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -39,6 +39,7 @@ const tun_IPv6_HEADER_LENGTH = 40 // calling yggdrasil.Start(). type TunAdapter struct { core *yggdrasil.Core + secret ed25519.PrivateKey store keyStore writer tunWriter reader tunReader @@ -109,13 +110,14 @@ func MaximumMTU() MTU { // Init initialises the TUN module. You must have acquired a Listener from // the Yggdrasil core before this point and it must not be in use elsewhere. -func (tun *TunAdapter) Init(core *yggdrasil.Core, config *config.NodeState, log *log.Logger, options interface{}) error { +func (tun *TunAdapter) Init(secret ed25519.PrivateKey, core *yggdrasil.Core, config *config.NodeState, log *log.Logger, options interface{}) error { /* TODO tunoptions, ok := options.(TunOptions) if !ok { return fmt.Errorf("invalid options supplied to TunAdapter module") } */ + tun.secret = secret tun.core = core tun.store.init(tun) tun.config = config @@ -224,78 +226,41 @@ func (tun *TunAdapter) UpdateConfig(config *config.NodeConfig) { tun.Act(nil, tun.ckr.configure) } -/* -func (tun *TunAdapter) handler() error { - for { - // Accept the incoming connection - conn, err := tun.listener.Accept() - if err != nil { - tun.log.Errorln("TUN connection accept error:", err) - return err - } - phony.Block(tun, func() { - if _, err := tun._wrap(conn.(*yggdrasil.Conn)); err != nil { - // Something went wrong when storing the connection, typically that - // something already exists for this address or subnet - tun.log.Debugln("TUN handler wrap:", err) - } - }) - } -} - -func (tun *TunAdapter) _wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { - // Prepare a session wrapper for the given connection - s := tunConn{ - tun: tun, - conn: conn, - stop: make(chan struct{}), - } - c = &s - // Get the remote address and subnet of the other side - panic("TODO") - //remotePubKey := conn.RemoteAddr().(*crypto.BoxPubKey) - //remoteNodeID := crypto.GetNodeID(remotePubKey) - //s.addr = *address.AddrForNodeID(remoteNodeID) - //s.snet = *address.SubnetForNodeID(remoteNodeID) - // Work out if this is already a destination we already know about - atc, aok := tun.addrToConn[s.addr] - stc, sok := tun.subnetToConn[s.snet] - // If we know about a connection for this destination already then assume it - // is no longer valid and close it - if aok { - atc._close_from_tun() - err = errors.New("replaced connection for address") - } else if sok { - stc._close_from_tun() - err = errors.New("replaced connection for subnet") - } - // Save the session wrapper so that we can look it up quickly next time - // we receive a packet through the interface for this address - tun.addrToConn[s.addr] = &s - tun.subnetToConn[s.snet] = &s - // Set the read callback and start the timeout - conn.SetReadCallback(func(bs []byte) { - s.Act(conn, func() { - s._read(bs) - }) - }) - s.Act(nil, s.stillAlive) - // Return - return c, err -} -*/ - func (tun *TunAdapter) oobHandler(fromKey, toKey ed25519.PublicKey, data []byte) { - panic("TODO") - // parse packet - // If it's a lookup then send a response - // If it's a response then (maybe) update the keystore + if len(data) != 1+ed25519.SignatureSize { + return + } + sig := data[1:] + switch data[0] { + case typeKeyLookup: + snet := *address.SubnetForKey(toKey) + if snet == tun.subnet && ed25519.Verify(fromKey, toKey[:], sig) { + // This is looking for at least our subnet (possibly our address) + // Send a response + tun.sendKeyResponse(fromKey) + } + case typeKeyResponse: + // TODO keep a list of something to match against... + // Ignore the response if it doesn't match anything of interest... + if ed25519.Verify(fromKey, toKey[:], sig) { + tun.store.update(fromKey) + } + } } +const ( + typeKeyLookup = 1 + typeKeyResponse = 2 +) + func (tun *TunAdapter) sendKeyLookup(partial ed25519.PublicKey) { - panic("TODO") + sig := ed25519.Sign(tun.secret, partial[:]) + bs := append([]byte{typeKeyLookup}, sig...) + tun.core.SendOutOfBand(partial, bs) } func (tun *TunAdapter) sendKeyResponse(dest ed25519.PublicKey) { - panic("TODO") + sig := ed25519.Sign(tun.secret, dest[:]) + bs := append([]byte{typeKeyResponse}, sig...) + tun.core.SendOutOfBand(dest, bs) } From b345806e3f38acc39442cb8bed48e1942a88e24b Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 8 May 2021 11:35:04 -0500 Subject: [PATCH 0648/1109] (broken state) more WIP (cleanup) --- src/tuntap/iface.go | 223 +------------------------------------------- src/tuntap/tun.go | 10 +- 2 files changed, 6 insertions(+), 227 deletions(-) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 50ea6dae..c7222266 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -4,19 +4,19 @@ import ( "crypto/ed25519" "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/yggdrasil" //"golang.org/x/net/icmp" //"golang.org/x/net/ipv6" iwt "github.com/Arceliar/ironwood/types" - "github.com/Arceliar/phony" + //"github.com/Arceliar/phony" ) const TUN_OFFSET_BYTES = 4 -func (tun *TunAdapter) doRead() { +func (tun *TunAdapter) read() { var buf [TUN_OFFSET_BYTES + 65535]byte for { n, err := tun.iface.Read(buf[:], TUN_OFFSET_BYTES) @@ -55,11 +55,7 @@ func (tun *TunAdapter) doRead() { } } -func (tun *TunAdapter) doWrite() { - panic("TODO") - // Read from the packetconn - // Get the info from the store - // Check if addresses / subnets / etc are OK (both sides of connection) +func (tun *TunAdapter) write() { var buf [TUN_OFFSET_BYTES + 65535]byte for { bs := buf[TUN_OFFSET_BYTES:] @@ -101,214 +97,3 @@ func (tun *TunAdapter) doWrite() { } } } - -type tunWriter struct { - phony.Inbox - tun *TunAdapter - buf [TUN_OFFSET_BYTES + 65536]byte -} - -func (w *tunWriter) writeFrom(from phony.Actor, b []byte) { - w.Act(from, func() { - w._write(b) - }) -} - -// write is pretty loose with the memory safety rules, e.g. it assumes it can -// read w.tun.iface.IsTap() safely -func (w *tunWriter) _write(b []byte) { - var written int - var err error - n := len(b) - if n == 0 { - return - } - temp := append(w.buf[:TUN_OFFSET_BYTES], b...) - written, err = w.tun.iface.Write(temp, TUN_OFFSET_BYTES) - if err != nil { - w.tun.Act(w, func() { - if !w.tun.isOpen { - w.tun.log.Errorln("TUN iface write error:", err) - } - }) - } - if written != n+TUN_OFFSET_BYTES { - // FIXME some platforms return the wrong number of bytes written, causing error spam - //w.tun.log.Errorln("TUN iface write mismatch:", written, "bytes written vs", n+TUN_OFFSET_BYTES, "bytes given") - } -} - -type tunReader struct { - phony.Inbox - tun *TunAdapter - buf [TUN_OFFSET_BYTES + 65536]byte -} - -func (r *tunReader) _read() { - // Get a slice to store the packet in - // Wait for a packet to be delivered to us through the TUN adapter - n, err := r.tun.iface.Read(r.buf[:], TUN_OFFSET_BYTES) - if n <= TUN_OFFSET_BYTES || err != nil { - r.tun.log.Errorln("Error reading TUN:", err) - ferr := r.tun.iface.Flush() - if ferr != nil { - r.tun.log.Errorln("Unable to flush packets:", ferr) - } - } else { - bs := make([]byte, n, n+crypto.BoxOverhead) // extra capacity for later... - copy(bs, r.buf[TUN_OFFSET_BYTES:n+TUN_OFFSET_BYTES]) - r.tun.handlePacketFrom(r, bs, err) - } - if err == nil { - // Now read again - r.Act(nil, r._read) - } -} - -func (tun *TunAdapter) handlePacketFrom(from phony.Actor, packet []byte, err error) { - tun.Act(from, func() { - tun._handlePacket(packet, err) - }) -} - -// does the work of reading a packet and sending it to the correct tunConn -func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { - panic("TODO") - /* - if err != nil { - tun.log.Errorln("TUN iface read error:", err) - return - } - // Offset the buffer from now on so that we can ignore ethernet frames if - // they are present - bs := recvd[:] - // Check if the packet is long enough to detect if it's an ICMP packet or not - if len(bs) < 7 { - tun.log.Traceln("TUN iface read undersized unknown packet, length:", len(bs)) - return - } - // From the IP header, work out what our source and destination addresses - // and node IDs are. We will need these in order to work out where to send - // the packet - var dstAddr address.Address - var dstSnet address.Subnet - var addrlen int - n := len(bs) - // Check the IP protocol - if it doesn't match then we drop the packet and - // do nothing with it - if bs[0]&0xf0 == 0x60 { - // Check if we have a fully-sized IPv6 header - if len(bs) < 40 { - tun.log.Traceln("TUN iface read undersized ipv6 packet, length:", len(bs)) - return - } - // Check the packet size - if n-tun_IPv6_HEADER_LENGTH != 256*int(bs[4])+int(bs[5]) { - return - } - // IPv6 address - addrlen = 16 - copy(dstAddr[:addrlen], bs[24:]) - copy(dstSnet[:addrlen/2], bs[24:]) - } else if bs[0]&0xf0 == 0x40 { - // Check if we have a fully-sized IPv4 header - if len(bs) < 20 { - tun.log.Traceln("TUN iface read undersized ipv4 packet, length:", len(bs)) - return - } - // Check the packet size - if n != 256*int(bs[2])+int(bs[3]) { - return - } - // IPv4 address - addrlen = 4 - copy(dstAddr[:addrlen], bs[16:]) - } else { - // Unknown address length or protocol, so drop the packet and ignore it - tun.log.Traceln("Unknown packet type, dropping") - return - } - if tun.ckr.isEnabled() { - if addrlen != 16 || (!dstAddr.IsValid() && !dstSnet.IsValid()) { - if key, err := tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil { - // A public key was found, get the node ID for the search - panic("TODO") - //dstNodeID := crypto.GetNodeID(&key) - //dstAddr = *address.AddrForNodeID(dstNodeID) - //dstSnet = *address.SubnetForNodeID(dstNodeID) - addrlen = 16 - } - } - } - if addrlen != 16 || (!dstAddr.IsValid() && !dstSnet.IsValid()) { - // Couldn't find this node's ygg IP - dlen := len(bs) - if dlen > 900 { - dlen = 900 - } - ptb := &icmp.DstUnreach{ - Data: bs[:dlen], - } - if packet, err := CreateICMPv6(bs[8:24], bs[24:40], ipv6.ICMPTypeDestinationUnreachable, 0, ptb); err == nil { - tun.writer.writeFrom(nil, packet) - } - return - } - // Do we have an active connection for this node address? - var dstString string - session, isIn := tun.addrToConn[dstAddr] - if !isIn || session == nil { - session, isIn = tun.subnetToConn[dstSnet] - if !isIn || session == nil { - // Neither an address nor a subnet mapping matched, therefore populate - // the node ID and mask to commence a search - panic("TODO") - if dstAddr.IsValid() { - //dstString = dstAddr.GetNodeIDLengthString() - } else { - //dstString = dstSnet.GetNodeIDLengthString() - } - } - } - // If we don't have a connection then we should open one - if !isIn || session == nil { - // Check we haven't been given empty node ID, really this shouldn't ever - // happen but just to be sure... - if dstString == "" { - panic("Given empty dstString - this shouldn't happen") - } - _, known := tun.dials[dstString] - tun.dials[dstString] = append(tun.dials[dstString], bs) - for len(tun.dials[dstString]) > 32 { - tun.dials[dstString] = tun.dials[dstString][1:] - } - if !known { - go func() { - conn, err := tun.dialer.Dial("nodeid", dstString) - tun.Act(nil, func() { - packets := tun.dials[dstString] - delete(tun.dials, dstString) - if err != nil { - return - } - // We've been given a connection so prepare the session wrapper - var tc *tunConn - if tc, err = tun._wrap(conn.(*yggdrasil.Conn)); err != nil { - // Something went wrong when storing the connection, typically that - // something already exists for this address or subnet - tun.log.Debugln("TUN iface wrap:", err) - return - } - for _, packet := range packets { - tc.writeFrom(nil, packet) - } - }) - }() - } - } - // If we have a connection now, try writing to it - if isIn && session != nil { - session.writeFrom(tun, bs) - } - */ -} diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 388602c1..db0a7361 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -41,8 +41,6 @@ type TunAdapter struct { core *yggdrasil.Core secret ed25519.PrivateKey store keyStore - writer tunWriter - reader tunReader config *config.NodeState log *log.Logger reconfigure chan chan error @@ -122,8 +120,6 @@ func (tun *TunAdapter) Init(secret ed25519.PrivateKey, core *yggdrasil.Core, con tun.store.init(tun) tun.config = config tun.log = log - tun.writer.tun = tun - tun.reader.tun = tun tun.core.SetOutOfBandHandler(tun.oobHandler) return nil @@ -170,11 +166,9 @@ func (tun *TunAdapter) _start() error { } // TODO tun.core.SetMaximumSessionMTU(tun.MTU()) tun.isOpen = true - // TODO go tun.handler() - tun.reader.Act(nil, tun.reader._read) // Start the reader tun.ckr.init(tun) - go tun.doRead() - go tun.doWrite() + go tun.read() + go tun.write() return nil } From 8bed79370b56907a5e777c99ff501e747ca7b492 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 8 May 2021 11:52:22 -0500 Subject: [PATCH 0649/1109] (broken state) WIP, compiles and passes the netns ping test --- cmd/yggdrasil/main.go | 35 ++++++++++++++++++++++------------- go.mod | 2 +- go.sum | 3 --- src/tuntap/keystore.go | 4 ++-- src/tuntap/tun.go | 16 +++++++--------- 5 files changed, 32 insertions(+), 28 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 8d3e00e8..464d3c0f 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -281,7 +281,7 @@ func main() { panic(err) } // Register the session firewall gatekeeper function - n.core.SetSessionGatekeeper(n.sessionFirewall) + // TODO n.core.SetSessionGatekeeper(n.sessionFirewall) // Allocate our modules n.admin = &admin.AdminSocket{} n.multicast = &multicast.Multicast{} @@ -299,19 +299,26 @@ func main() { } n.multicast.SetupAdminHandlers(n.admin.(*admin.AdminSocket)) // Start the TUN/TAP interface - if listener, err := n.core.ConnListen(); err == nil { - if dialer, err := n.core.ConnDialer(); err == nil { - n.tuntap.Init(&n.core, n.state, logger, tuntap.TunOptions{Listener: listener, Dialer: dialer}) - if err := n.tuntap.Start(); err != nil { - logger.Errorln("An error occurred starting TUN/TAP:", err) - } - n.tuntap.SetupAdminHandlers(n.admin.(*admin.AdminSocket)) - } else { - logger.Errorln("Unable to get Dialer:", err) - } - } else { - logger.Errorln("Unable to get Listener:", err) + n.tuntap.Init(&n.core, n.state, logger, tuntap.TunOptions{}) + if err := n.tuntap.Start(); err != nil { + logger.Errorln("An error occurred starting TUN/TAP:", err) } + n.tuntap.SetupAdminHandlers(n.admin.(*admin.AdminSocket)) + /* + if listener, err := n.core.ConnListen(); err == nil { + if dialer, err := n.core.ConnDialer(); err == nil { + n.tuntap.Init(&n.core, n.state, logger, tuntap.TunOptions{Listener: listener, Dialer: dialer}) + if err := n.tuntap.Start(); err != nil { + logger.Errorln("An error occurred starting TUN/TAP:", err) + } + n.tuntap.SetupAdminHandlers(n.admin.(*admin.AdminSocket)) + } else { + logger.Errorln("Unable to get Dialer:", err) + } + } else { + logger.Errorln("Unable to get Listener:", err) + } + */ // Make some nice output that tells us what our IPv6 address and subnet are. // This is just logged to stdout for the user. address := n.core.Address() @@ -396,12 +403,14 @@ func (n *node) sessionFirewall(pubkey *crypto.BoxPubKey, initiator bool) bool { // Look and see if the pubkey is that of a direct peer var isDirectPeer bool + /* TODO for _, peer := range n.core.GetPeers() { if peer.PublicKey == *pubkey { isDirectPeer = true break } } + */ // Allow direct peers if appropriate if n.state.Current.SessionFirewall.AllowFromDirect && isDirectPeer { diff --git a/go.mod b/go.mod index 491d5992..a5d98819 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.16 require ( - github.com/Arceliar/ironwood v0.0.0-20210508094446-74a68e4f5970 // indirect + github.com/Arceliar/ironwood v0.0.0-00010101000000-000000000000 github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 github.com/cheggaaa/pb/v3 v3.0.6 github.com/fatih/color v1.10.0 // indirect diff --git a/go.sum b/go.sum index 5613fafe..da4c5c54 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/Arceliar/ironwood v0.0.0-20210508094446-74a68e4f5970 h1:sKiz18LynwInybi9BIhM8tdvZlSurnT6rM/ZUEqMgzU= -github.com/Arceliar/ironwood v0.0.0-20210508094446-74a68e4f5970/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= @@ -39,7 +37,6 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17 github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= diff --git a/src/tuntap/keystore.go b/src/tuntap/keystore.go index c7733a29..4904737b 100644 --- a/src/tuntap/keystore.go +++ b/src/tuntap/keystore.go @@ -70,7 +70,7 @@ func (k *keyStore) sendToAddress(addr address.Address, bs []byte) { delete(k.addrBuffer, addr) } }) - panic("TODO") // TODO send lookup + k.tun.sendKeyLookup(addr.GetKey()) } } @@ -98,7 +98,7 @@ func (k *keyStore) sendToSubnet(subnet address.Subnet, bs []byte) { delete(k.subnetBuffer, subnet) } }) - panic("TODO") // TODO send lookup + k.tun.sendKeyLookup(subnet.GetKey()) } } diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index db0a7361..ba444814 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -39,7 +39,6 @@ const tun_IPv6_HEADER_LENGTH = 40 // calling yggdrasil.Start(). type TunAdapter struct { core *yggdrasil.Core - secret ed25519.PrivateKey store keyStore config *config.NodeState log *log.Logger @@ -108,14 +107,13 @@ func MaximumMTU() MTU { // Init initialises the TUN module. You must have acquired a Listener from // the Yggdrasil core before this point and it must not be in use elsewhere. -func (tun *TunAdapter) Init(secret ed25519.PrivateKey, core *yggdrasil.Core, config *config.NodeState, log *log.Logger, options interface{}) error { +func (tun *TunAdapter) Init(core *yggdrasil.Core, config *config.NodeState, log *log.Logger, options interface{}) error { /* TODO tunoptions, ok := options.(TunOptions) if !ok { return fmt.Errorf("invalid options supplied to TunAdapter module") } */ - tun.secret = secret tun.core = core tun.store.init(tun) tun.config = config @@ -149,10 +147,10 @@ func (tun *TunAdapter) _start() error { return err } copy(boxPub[:], boxPubHex) - panic("TODO") - //nodeID := crypto.GetNodeID(&boxPub) - //tun.addr = *address.AddrForNodeID(nodeID) - //tun.subnet = *address.SubnetForNodeID(nodeID) + sk := tun.core.PrivateKey() + pk := sk.Public().(ed25519.PublicKey) + tun.addr = *address.AddrForKey(pk) + tun.subnet = *address.SubnetForKey(pk) addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1) if current.IfName == "none" || current.IfName == "dummy" { tun.log.Debugln("Not starting TUN as ifname is none or dummy") @@ -248,13 +246,13 @@ const ( ) func (tun *TunAdapter) sendKeyLookup(partial ed25519.PublicKey) { - sig := ed25519.Sign(tun.secret, partial[:]) + sig := ed25519.Sign(tun.core.PrivateKey(), partial[:]) bs := append([]byte{typeKeyLookup}, sig...) tun.core.SendOutOfBand(partial, bs) } func (tun *TunAdapter) sendKeyResponse(dest ed25519.PublicKey) { - sig := ed25519.Sign(tun.secret, dest[:]) + sig := ed25519.Sign(tun.core.PrivateKey(), dest[:]) bs := append([]byte{typeKeyResponse}, sig...) tun.core.SendOutOfBand(dest, bs) } From e6e55fb4d134d37ae8a79eed9776652cb634fb66 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 8 May 2021 11:53:44 -0500 Subject: [PATCH 0650/1109] dependency update --- go.mod | 4 +--- go.sum | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a5d98819..1d894d2c 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.16 require ( - github.com/Arceliar/ironwood v0.0.0-00010101000000-000000000000 + github.com/Arceliar/ironwood v0.0.0-20210508094446-74a68e4f5970 github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 github.com/cheggaaa/pb/v3 v3.0.6 github.com/fatih/color v1.10.0 // indirect @@ -23,5 +23,3 @@ require ( golang.zx2c4.com/wireguard v0.0.0-20210306175010-7e3b8371a1bf golang.zx2c4.com/wireguard/windows v0.3.8 ) - -replace github.com/Arceliar/ironwood => ../ironwood diff --git a/go.sum b/go.sum index da4c5c54..3a4690ab 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/Arceliar/ironwood v0.0.0-20210508094446-74a68e4f5970 h1:sKiz18LynwInybi9BIhM8tdvZlSurnT6rM/ZUEqMgzU= +github.com/Arceliar/ironwood v0.0.0-20210508094446-74a68e4f5970/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= From b4224aa02de105991c84aa0dc6c243e8eeb23241 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 8 May 2021 11:57:54 -0500 Subject: [PATCH 0651/1109] fix ironwood dependency version --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 1d894d2c..c1c1ab6a 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.16 require ( - github.com/Arceliar/ironwood v0.0.0-20210508094446-74a68e4f5970 + github.com/Arceliar/ironwood v0.0.0-20210508165109-414e0b35fc84 github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 github.com/cheggaaa/pb/v3 v3.0.6 github.com/fatih/color v1.10.0 // indirect diff --git a/go.sum b/go.sum index 3a4690ab..233eb143 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/Arceliar/ironwood v0.0.0-20210508094446-74a68e4f5970 h1:sKiz18LynwInybi9BIhM8tdvZlSurnT6rM/ZUEqMgzU= github.com/Arceliar/ironwood v0.0.0-20210508094446-74a68e4f5970/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= +github.com/Arceliar/ironwood v0.0.0-20210508165109-414e0b35fc84 h1:hhgFLxGnMXtr1TwnFQcUudSphe7g8i0qB/xgfNlLmHE= +github.com/Arceliar/ironwood v0.0.0-20210508165109-414e0b35fc84/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= From ed85cf08f23916b9a4deed70570b34ea5c6ee1ab Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 8 May 2021 12:31:26 -0500 Subject: [PATCH 0652/1109] WIP close the ironwood PacketConn when shutting down --- src/yggdrasil/core.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 351e1904..4a55b007 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -175,6 +175,7 @@ func (c *Core) Stop() { // This function is unsafe and should only be ran by the core actor. func (c *Core) _stop() { + c.PacketConn.Close() c.log.Infoln("Stopping...") if c.addPeerTimer != nil { c.addPeerTimer.Stop() From 3bfd891fd4024ff02f1a4584b726344d1a1b0b9a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 9 May 2021 09:20:28 -0500 Subject: [PATCH 0653/1109] reduce time keystore mutex is held and (apparently) fix a deadlock --- src/tuntap/keystore.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/tuntap/keystore.go b/src/tuntap/keystore.go index 4904737b..ddcf50c4 100644 --- a/src/tuntap/keystore.go +++ b/src/tuntap/keystore.go @@ -48,10 +48,10 @@ func (k *keyStore) init(tun *TunAdapter) { func (k *keyStore) sendToAddress(addr address.Address, bs []byte) { k.mutex.Lock() - defer k.mutex.Unlock() if info := k.addrToInfo[addr]; info != nil { - k.tun.core.WriteTo(bs, iwt.Addr(info.key[:])) k.resetTimeout(info) + k.mutex.Unlock() + k.tun.core.WriteTo(bs, iwt.Addr(info.key[:])) } else { var buf *buffer if buf = k.addrBuffer[addr]; buf == nil { @@ -70,16 +70,17 @@ func (k *keyStore) sendToAddress(addr address.Address, bs []byte) { delete(k.addrBuffer, addr) } }) + k.mutex.Unlock() k.tun.sendKeyLookup(addr.GetKey()) } } func (k *keyStore) sendToSubnet(subnet address.Subnet, bs []byte) { k.mutex.Lock() - defer k.mutex.Unlock() if info := k.subnetToInfo[subnet]; info != nil { - k.tun.core.WriteTo(bs, iwt.Addr(info.key[:])) k.resetTimeout(info) + k.mutex.Unlock() + k.tun.core.WriteTo(bs, iwt.Addr(info.key[:])) } else { var buf *buffer if buf = k.subnetBuffer[subnet]; buf == nil { @@ -98,13 +99,13 @@ func (k *keyStore) sendToSubnet(subnet address.Subnet, bs []byte) { delete(k.subnetBuffer, subnet) } }) + k.mutex.Unlock() k.tun.sendKeyLookup(subnet.GetKey()) } } func (k *keyStore) update(key ed25519.PublicKey) *keyInfo { k.mutex.Lock() - defer k.mutex.Unlock() var kArray keyArray copy(kArray[:], key) var info *keyInfo @@ -118,6 +119,7 @@ func (k *keyStore) update(key ed25519.PublicKey) *keyInfo { k.addrToInfo[info.address] = info k.subnetToInfo[info.subnet] = info k.resetTimeout(info) + k.mutex.Unlock() if buf := k.addrBuffer[info.address]; buf != nil { for _, bs := range buf.packets { k.tun.core.WriteTo(bs, iwt.Addr(info.key[:])) @@ -130,8 +132,10 @@ func (k *keyStore) update(key ed25519.PublicKey) *keyInfo { } delete(k.subnetBuffer, info.subnet) } + } else { + k.resetTimeout(info) + k.mutex.Unlock() } - k.resetTimeout(info) return info } From b48962a69a37fc0f74b29a0a1c33525f963c6e0d Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 9 May 2021 11:27:37 -0500 Subject: [PATCH 0654/1109] limit MTU to no more than what the packetconn claims to support --- go.mod | 2 +- go.sum | 4 ++++ src/tuntap/tun.go | 8 ++++++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c1c1ab6a..0f3bce55 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.16 require ( - github.com/Arceliar/ironwood v0.0.0-20210508165109-414e0b35fc84 + github.com/Arceliar/ironwood v0.0.0-20210509162436-68a5d50e0d8f github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 github.com/cheggaaa/pb/v3 v3.0.6 github.com/fatih/color v1.10.0 // indirect diff --git a/go.sum b/go.sum index 233eb143..93e4b3a8 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,10 @@ github.com/Arceliar/ironwood v0.0.0-20210508094446-74a68e4f5970 h1:sKiz18LynwIny github.com/Arceliar/ironwood v0.0.0-20210508094446-74a68e4f5970/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/ironwood v0.0.0-20210508165109-414e0b35fc84 h1:hhgFLxGnMXtr1TwnFQcUudSphe7g8i0qB/xgfNlLmHE= github.com/Arceliar/ironwood v0.0.0-20210508165109-414e0b35fc84/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= +github.com/Arceliar/ironwood v0.0.0-20210509152840-bc3ec5afa1ed h1:LXo+ZsQIx2xI6pbojnhK7PenYNMi3edKI9xSdoX/iSw= +github.com/Arceliar/ironwood v0.0.0-20210509152840-bc3ec5afa1ed/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= +github.com/Arceliar/ironwood v0.0.0-20210509162436-68a5d50e0d8f h1:kjCfDzlGtM3BD9X5kF8SxvFhghCncYyJb/rxA/NZwK4= +github.com/Arceliar/ironwood v0.0.0-20210509162436-68a5d50e0d8f/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index ba444814..68785cc1 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -156,10 +156,14 @@ func (tun *TunAdapter) _start() error { tun.log.Debugln("Not starting TUN as ifname is none or dummy") return nil } - if err := tun.setup(current.IfName, addr, current.IfMTU); err != nil { + mtu := current.IfMTU + if tun.core.MTU() < uint64(mtu) { + mtu = MTU(tun.core.MTU()) + } + if err := tun.setup(current.IfName, addr, mtu); err != nil { return err } - if tun.MTU() != current.IfMTU { + if tun.MTU() != mtu { tun.log.Warnf("Warning: Interface MTU %d automatically adjusted to %d (supported range is 1280-%d)", current.IfMTU, tun.MTU(), MaximumMTU()) } // TODO tun.core.SetMaximumSessionMTU(tun.MTU()) From 6cb958e3dc8acc0ce5fb24f728131a5182c17ba0 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 10 May 2021 05:58:06 -0500 Subject: [PATCH 0655/1109] update genkeys to new address format --- cmd/genkeys/main.go | 108 ++++++++++++-------------------------------- 1 file changed, 29 insertions(+), 79 deletions(-) diff --git a/cmd/genkeys/main.go b/cmd/genkeys/main.go index cfab7ec1..81942446 100644 --- a/cmd/genkeys/main.go +++ b/cmd/genkeys/main.go @@ -13,116 +13,66 @@ This only matters if it's high enough to make you the root of the tree. package main import ( + "crypto/ed25519" "encoding/hex" - "flag" "fmt" "net" "runtime" "github.com/yggdrasil-network/yggdrasil-go/src/address" - "github.com/yggdrasil-network/yggdrasil-go/src/crypto" ) -var doSig = flag.Bool("sig", false, "generate new signing keys instead") - type keySet struct { - priv []byte - pub []byte - id []byte - ip string + priv ed25519.PrivateKey + pub ed25519.PublicKey } func main() { threads := runtime.GOMAXPROCS(0) - var threadChannels []chan []byte - var currentBest []byte + var currentBest ed25519.PublicKey newKeys := make(chan keySet, threads) - flag.Parse() - for i := 0; i < threads; i++ { - threadChannels = append(threadChannels, make(chan []byte, threads)) - switch { - case *doSig: - go doSigKeys(newKeys, threadChannels[i]) - default: - go doBoxKeys(newKeys, threadChannels[i]) - } + go doKeys(newKeys) } - for { newKey := <-newKeys - if isBetter(currentBest, newKey.id[:]) || len(currentBest) == 0 { - currentBest = newKey.id - for _, channel := range threadChannels { - select { - case channel <- newKey.id: - } - } - fmt.Println("--------------------------------------------------------------------------------") - switch { - case *doSig: - fmt.Println("sigPriv:", hex.EncodeToString(newKey.priv)) - fmt.Println("sigPub:", hex.EncodeToString(newKey.pub)) - fmt.Println("TreeID:", hex.EncodeToString(newKey.id)) - default: - fmt.Println("boxPriv:", hex.EncodeToString(newKey.priv)) - fmt.Println("boxPub:", hex.EncodeToString(newKey.pub)) - fmt.Println("NodeID:", hex.EncodeToString(newKey.id)) - fmt.Println("IP:", newKey.ip) - } + if isBetter(currentBest, newKey.pub) || len(currentBest) == 0 { + currentBest = newKey.pub + fmt.Println("-----") + fmt.Println("Priv:", hex.EncodeToString(newKey.priv)) + fmt.Println("Pub:", hex.EncodeToString(newKey.pub)) + addr := address.AddrForKey(newKey.pub) + fmt.Println("IP:", net.IP(addr[:]).String()) } } } -func isBetter(oldID, newID []byte) bool { - for idx := range oldID { - if newID[idx] != oldID[idx] { - return newID[idx] > oldID[idx] +func isBetter(oldPub, newPub ed25519.PublicKey) bool { + for idx := range oldPub { + if newPub[idx] < oldPub[idx] { + return true + } + if newPub[idx] > oldPub[idx] { + break } } return false } -func doBoxKeys(out chan<- keySet, in <-chan []byte) { - var bestID crypto.NodeID - for { - select { - case newBestID := <-in: - if isBetter(bestID[:], newBestID) { - copy(bestID[:], newBestID) - } - default: - pub, priv := crypto.NewBoxKeys() - id := crypto.GetNodeID(pub) - if !isBetter(bestID[:], id[:]) { - continue - } - bestID = *id - ip := net.IP(address.AddrForNodeID(id)[:]).String() - out <- keySet{priv[:], pub[:], id[:], ip} - } - } -} - -func doSigKeys(out chan<- keySet, in <-chan []byte) { - var bestID crypto.TreeID - for idx := range bestID { - bestID[idx] = 0 +func doKeys(out chan<- keySet) { + bestKey := make(ed25519.PublicKey, ed25519.PublicKeySize) + for idx := range bestKey { + bestKey[idx] = 0xff } for { - select { - case newBestID := <-in: - if isBetter(bestID[:], newBestID) { - copy(bestID[:], newBestID) - } - default: + pub, priv, err := ed25519.GenerateKey(nil) + if err != nil { + panic(err) } - pub, priv := crypto.NewSigKeys() - id := crypto.GetTreeID(pub) - if !isBetter(bestID[:], id[:]) { + if !isBetter(bestKey, pub) { continue } - bestID = *id - out <- keySet{priv[:], pub[:], id[:], ""} + bestKey = pub + out <- keySet{priv, pub} } } From bb92e61e68c0db85cd4164c0e2267abccbde0891 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 10 May 2021 22:06:38 +0100 Subject: [PATCH 0656/1109] Remove encryption public key options (they are now derived from ed25519 key conversion in IW), also bump link version number --- cmd/yggdrasil/main.go | 6 +----- src/config/config.go | 15 --------------- src/tuntap/tun.go | 12 +++--------- src/yggdrasil/core.go | 12 +++--------- src/yggdrasil/version.go | 2 +- 5 files changed, 8 insertions(+), 39 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 464d3c0f..bc3e1c40 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -220,11 +220,7 @@ func main() { } // Have we been asked for the node address yet? If so, print it and then stop. getNodeID := func() *crypto.NodeID { - if pubkey, err := hex.DecodeString(cfg.EncryptionPublicKey); err == nil { - var box crypto.BoxPubKey - copy(box[:], pubkey) - return crypto.GetNodeID(&box) - } + // TODO: curve return nil } switch { diff --git a/src/config/config.go b/src/config/config.go index 95d9bbd1..17c2618a 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -68,8 +68,6 @@ type NodeConfig struct { 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. To disable\nthe admin socket, use the value \"none\" instead."` MulticastInterfaces []string `comment:"Regular expressions for which interfaces multicast peer discovery\nshould be enabled on. If none specified, multicast peer discovery is\ndisabled. The default value is .* which uses all interfaces."` AllowedEncryptionPublicKeys []string `comment:"List of peer encryption public keys to allow incoming TCP peering\nconnections from. If left empty/undefined then all connections will\nbe allowed by default. This does not affect outgoing peerings, nor\ndoes it affect link-local peers discovered via multicast."` - EncryptionPublicKey string `comment:"Your public encryption key. Your peers may ask you for this to put\ninto their AllowedEncryptionPublicKeys configuration."` - EncryptionPrivateKey string `comment:"Your private encryption key. DO NOT share this with anyone!"` SigningPublicKey string `comment:"Your public signing key. You should not ordinarily need to share\nthis with anyone."` SigningPrivateKey string `comment:"Your private signing key. DO NOT share this with anyone!"` LinkLocalTCPPort uint16 `comment:"The port number to be used for the link-local TCP listeners for the\nconfigured MulticastInterfaces. This option does not affect listeners\nspecified in the Listen option. Unless you plan to firewall link-local\ntraffic, it is best to leave this as the default value of 0. This\noption cannot currently be changed by reloading config during runtime."` @@ -113,14 +111,11 @@ type SwitchOptions struct { // using -autoconf. func GenerateConfig() *NodeConfig { // Generate encryption keys. - bpub, bpriv := crypto.NewBoxKeys() spub, spriv := crypto.NewSigKeys() // Create a node configuration and populate it. cfg := NodeConfig{} cfg.Listen = []string{} cfg.AdminListen = defaults.GetDefaults().DefaultAdminListen - cfg.EncryptionPublicKey = hex.EncodeToString(bpub[:]) - cfg.EncryptionPrivateKey = hex.EncodeToString(bpriv[:]) cfg.SigningPublicKey = hex.EncodeToString(spub[:]) cfg.SigningPrivateKey = hex.EncodeToString(spriv[:]) cfg.Peers = []string{} @@ -139,16 +134,6 @@ func GenerateConfig() *NodeConfig { return &cfg } -// NewEncryptionKeys replaces the encryption keypair in the NodeConfig with a -// new encryption keypair. The encryption keys are used by the router to encrypt -// traffic and to derive the node ID and IPv6 address/subnet of the node, so -// this is equivalent to discarding the node's identity on the network. -func (cfg *NodeConfig) NewEncryptionKeys() { - bpub, bpriv := crypto.NewBoxKeys() - cfg.EncryptionPublicKey = hex.EncodeToString(bpub[:]) - cfg.EncryptionPrivateKey = hex.EncodeToString(bpriv[:]) -} - // NewSigningKeys replaces the signing keypair in the NodeConfig with a new // signing keypair. The signing keys are used by the switch to derive the // structure of the spanning tree. diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 68785cc1..2e44a017 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -10,7 +10,6 @@ package tuntap import ( "crypto/ed25519" - "encoding/hex" "errors" "fmt" "net" @@ -23,7 +22,6 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/config" - "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" "github.com/yggdrasil-network/yggdrasil-go/src/types" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" @@ -118,7 +116,9 @@ func (tun *TunAdapter) Init(core *yggdrasil.Core, config *config.NodeState, log tun.store.init(tun) tun.config = config tun.log = log - tun.core.SetOutOfBandHandler(tun.oobHandler) + if err := tun.core.SetOutOfBandHandler(tun.oobHandler); err != nil { + return fmt.Errorf("tun.core.SetOutOfBandHander: %w", err) + } return nil } @@ -141,12 +141,6 @@ func (tun *TunAdapter) _start() error { if tun.config == nil { return errors.New("no configuration available to TUN") } - var boxPub crypto.BoxPubKey - boxPubHex, err := hex.DecodeString(current.EncryptionPublicKey) - if err != nil { - return err - } - copy(boxPub[:], boxPubHex) sk := tun.core.PrivateKey() pk := sk.Public().(ed25519.PublicKey) tun.addr = *address.AddrForKey(pk) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 4a55b007..e3c992d3 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -52,16 +52,10 @@ func (c *Core) _init() error { } c.secret = ed25519.PrivateKey(sigPriv) - sigPub := c.secret.Public() - c.public = sigPub.(ed25519.PublicKey) + c.public = c.secret.Public().(ed25519.PublicKey) - pc, err := iw.NewPacketConn(c.secret) - if err != nil { - return err - } - c.PacketConn = pc - - return nil + c.PacketConn, err = iw.NewPacketConn(c.secret) + return err } // If any static peers were provided in the configuration above then we should diff --git a/src/yggdrasil/version.go b/src/yggdrasil/version.go index a3c9bce5..e653e92f 100644 --- a/src/yggdrasil/version.go +++ b/src/yggdrasil/version.go @@ -22,7 +22,7 @@ func version_getBaseMetadata() version_metadata { return version_metadata{ meta: [4]byte{'m', 'e', 't', 'a'}, ver: 0, - minorVer: 0, + minorVer: 1, } } From c20b66f3b60c40c45125cc13c07803998432f8db Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 10 May 2021 22:31:01 +0100 Subject: [PATCH 0657/1109] Metadata/version tweaks --- src/yggdrasil/link.go | 14 +++++++++----- src/yggdrasil/version.go | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index aff40549..3d06fbf1 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -185,13 +185,17 @@ func (intf *link) handler() (chan struct{}, error) { return nil, err } meta = version_metadata{} - if !meta.decode(metaBytes) || !meta.check() { + base := version_getBaseMetadata() + if !meta.decode(metaBytes) { return nil, errors.New("failed to decode metadata") } - base := version_getBaseMetadata() - if meta.ver > base.ver || meta.ver == base.ver && meta.minorVer > base.minorVer { - intf.links.core.log.Errorln("Failed to connect to node: " + intf.lname + " version: " + fmt.Sprintf("%d.%d", meta.ver, meta.minorVer)) - return nil, errors.New("failed to connect: wrong version") + if !meta.check() { + intf.links.core.log.Errorf("Failed to connect to node: %s is incompatible version (local %s, remote %s)", + intf.lname, + fmt.Sprintf("%d.%d", base.ver, base.minorVer), + fmt.Sprintf("%d.%d", meta.ver, meta.minorVer), + ) + return nil, errors.New("remote node is incompatible version") } // Check if the remote side matches the keys we expected. This is a bit of a weak // check - in future versions we really should check a signature or something like that. diff --git a/src/yggdrasil/version.go b/src/yggdrasil/version.go index e653e92f..ad334c8b 100644 --- a/src/yggdrasil/version.go +++ b/src/yggdrasil/version.go @@ -22,7 +22,7 @@ func version_getBaseMetadata() version_metadata { return version_metadata{ meta: [4]byte{'m', 'e', 't', 'a'}, ver: 0, - minorVer: 1, + minorVer: 3, } } From 05caf36f4e4f96a9b7ff557224024cbb75598dd6 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 10 May 2021 22:39:12 +0100 Subject: [PATCH 0658/1109] Fix AllowedPublicKeys --- cmd/yggdrasil/main.go | 4 ++-- src/config/config.go | 46 +++++++++++++++++++++---------------------- src/yggdrasil/link.go | 16 ++++++++++----- 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index bc3e1c40..1760e384 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -369,7 +369,7 @@ func (n *node) sessionFirewall(pubkey *crypto.BoxPubKey, initiator bool) bool { // Prepare for checking whitelist/blacklist var box crypto.BoxPubKey // Reject blacklisted nodes - for _, b := range n.state.Current.SessionFirewall.BlacklistEncryptionPublicKeys { + for _, b := range n.state.Current.SessionFirewall.BlacklistPublicKeys { key, err := hex.DecodeString(b) if err == nil { copy(box[:crypto.BoxPubKeyLen], key) @@ -380,7 +380,7 @@ func (n *node) sessionFirewall(pubkey *crypto.BoxPubKey, initiator bool) bool { } // Allow whitelisted nodes - for _, b := range n.state.Current.SessionFirewall.WhitelistEncryptionPublicKeys { + for _, b := range n.state.Current.SessionFirewall.WhitelistPublicKeys { key, err := hex.DecodeString(b) if err == nil { copy(box[:crypto.BoxPubKeyLen], key) diff --git a/src/config/config.go b/src/config/config.go index 17c2618a..2f1e6878 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -62,32 +62,32 @@ func (s *NodeState) Replace(n NodeConfig) { // options that are necessary for an Yggdrasil node to run. You will need to // supply one of these structs to the Yggdrasil core when starting a node. type NodeConfig struct { - Peers []string `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j. These connections\nwill obey the operating system routing table, therefore you should\nuse this section when you may connect via different interfaces."` - InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ tcp://a.b.c.d:e ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."` - Listen []string `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntcp://0.0.0.0:0 or tcp://[::]:0 to listen on all interfaces."` - 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. To disable\nthe admin socket, use the value \"none\" instead."` - MulticastInterfaces []string `comment:"Regular expressions for which interfaces multicast peer discovery\nshould be enabled on. If none specified, multicast peer discovery is\ndisabled. The default value is .* which uses all interfaces."` - AllowedEncryptionPublicKeys []string `comment:"List of peer encryption public keys to allow incoming TCP peering\nconnections from. If left empty/undefined then all connections will\nbe allowed by default. This does not affect outgoing peerings, nor\ndoes it affect link-local peers discovered via multicast."` - SigningPublicKey string `comment:"Your public signing key. You should not ordinarily need to share\nthis with anyone."` - SigningPrivateKey string `comment:"Your private signing key. DO NOT share this with anyone!"` - LinkLocalTCPPort uint16 `comment:"The port number to be used for the link-local TCP listeners for the\nconfigured MulticastInterfaces. This option does not affect listeners\nspecified in the Listen option. Unless you plan to firewall link-local\ntraffic, it is best to leave this as the default value of 0. This\noption cannot currently be changed by reloading config during runtime."` - IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."` - IfMTU MTU `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` - SessionFirewall SessionFirewall `comment:"The session firewall controls who can send/receive network traffic\nto/from. This is useful if you want to protect this node without\nresorting to using a real firewall. This does not affect traffic\nbeing routed via this node to somewhere else. Rules are prioritised as\nfollows: blacklist, whitelist, always allow outgoing, direct, remote."` - TunnelRouting TunnelRouting `comment:"Allow tunneling non-Yggdrasil traffic over Yggdrasil. This effectively\nallows you to use Yggdrasil to route to, or to bridge other networks,\nsimilar to a VPN tunnel. Tunnelling works between any two nodes and\ndoes not require them to be directly peered."` - SwitchOptions SwitchOptions `comment:"Advanced options for tuning the switch. Normally you will not need\nto edit these options."` - NodeInfoPrivacy bool `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."` - NodeInfo map[string]interface{} `comment:"Optional node info. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."` + Peers []string `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j. These connections\nwill obey the operating system routing table, therefore you should\nuse this section when you may connect via different interfaces."` + InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ tcp://a.b.c.d:e ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."` + Listen []string `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntcp://0.0.0.0:0 or tcp://[::]:0 to listen on all interfaces."` + 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. To disable\nthe admin socket, use the value \"none\" instead."` + MulticastInterfaces []string `comment:"Regular expressions for which interfaces multicast peer discovery\nshould be enabled on. If none specified, multicast peer discovery is\ndisabled. The default value is .* which uses all interfaces."` + AllowedPublicKeys []string `comment:"List of peer encryption public keys to allow incoming TCP peering\nconnections from. If left empty/undefined then all connections will\nbe allowed by default. This does not affect outgoing peerings, nor\ndoes it affect link-local peers discovered via multicast."` + SigningPublicKey string `comment:"Your public signing key. You should not ordinarily need to share\nthis with anyone."` + SigningPrivateKey string `comment:"Your private signing key. DO NOT share this with anyone!"` + LinkLocalTCPPort uint16 `comment:"The port number to be used for the link-local TCP listeners for the\nconfigured MulticastInterfaces. This option does not affect listeners\nspecified in the Listen option. Unless you plan to firewall link-local\ntraffic, it is best to leave this as the default value of 0. This\noption cannot currently be changed by reloading config during runtime."` + IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."` + IfMTU MTU `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` + SessionFirewall SessionFirewall `comment:"The session firewall controls who can send/receive network traffic\nto/from. This is useful if you want to protect this node without\nresorting to using a real firewall. This does not affect traffic\nbeing routed via this node to somewhere else. Rules are prioritised as\nfollows: blacklist, whitelist, always allow outgoing, direct, remote."` + TunnelRouting TunnelRouting `comment:"Allow tunneling non-Yggdrasil traffic over Yggdrasil. This effectively\nallows you to use Yggdrasil to route to, or to bridge other networks,\nsimilar to a VPN tunnel. Tunnelling works between any two nodes and\ndoes not require them to be directly peered."` + SwitchOptions SwitchOptions `comment:"Advanced options for tuning the switch. Normally you will not need\nto edit these options."` + NodeInfoPrivacy bool `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."` + NodeInfo map[string]interface{} `comment:"Optional node info. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."` } // SessionFirewall controls the session firewall configuration. type SessionFirewall struct { - Enable bool `comment:"Enable or disable the session firewall. If disabled, network traffic\nfrom any node will be allowed. If enabled, the below rules apply."` - AllowFromDirect bool `comment:"Allow network traffic from directly connected peers."` - AllowFromRemote bool `comment:"Allow network traffic from remote nodes on the network that you are\nnot directly peered with."` - AlwaysAllowOutbound bool `comment:"Allow outbound network traffic regardless of AllowFromDirect or\nAllowFromRemote. This does allow a remote node to send unsolicited\ntraffic back to you for the length of the session."` - WhitelistEncryptionPublicKeys []string `comment:"List of public keys from which network traffic is always accepted,\nregardless of AllowFromDirect or AllowFromRemote."` - BlacklistEncryptionPublicKeys []string `comment:"List of public keys from which network traffic is always rejected,\nregardless of the whitelist, AllowFromDirect or AllowFromRemote."` + Enable bool `comment:"Enable or disable the session firewall. If disabled, network traffic\nfrom any node will be allowed. If enabled, the below rules apply."` + AllowFromDirect bool `comment:"Allow network traffic from directly connected peers."` + AllowFromRemote bool `comment:"Allow network traffic from remote nodes on the network that you are\nnot directly peered with."` + AlwaysAllowOutbound bool `comment:"Allow outbound network traffic regardless of AllowFromDirect or\nAllowFromRemote. This does allow a remote node to send unsolicited\ntraffic back to you for the length of the session."` + WhitelistPublicKeys []string `comment:"List of public keys from which network traffic is always accepted,\nregardless of AllowFromDirect or AllowFromRemote."` + BlacklistPublicKeys []string `comment:"List of public keys from which network traffic is always rejected,\nregardless of the whitelist, AllowFromDirect or AllowFromRemote."` } // TunnelRouting contains the crypto-key routing tables for tunneling regular @@ -120,7 +120,7 @@ func GenerateConfig() *NodeConfig { cfg.SigningPrivateKey = hex.EncodeToString(spriv[:]) cfg.Peers = []string{} cfg.InterfacePeers = map[string][]string{} - cfg.AllowedEncryptionPublicKeys = []string{} + cfg.AllowedPublicKeys = []string{} cfg.MulticastInterfaces = defaults.GetDefaults().DefaultMulticastInterfaces cfg.IfName = defaults.GetDefaults().DefaultIfName cfg.IfMTU = defaults.GetDefaults().DefaultIfMTU diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 3d06fbf1..aec1533d 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -208,14 +208,20 @@ func (intf *link) handler() (chan struct{}, error) { } } // Check if we're authorized to connect to this key / IP - /* TODO check allowed public keys - if intf.incoming && !intf.force && !intf.links.core.peers.isAllowedEncryptionPublicKey(&meta.box) { + allowed := intf.links.core.config.GetCurrent().AllowedPublicKeys + isallowed := len(allowed) == 0 + for _, k := range allowed { + if k == hex.EncodeToString(meta.key) { // TODO: this is yuck + isallowed = true + break + } + } + if intf.incoming && !intf.force && !isallowed { intf.links.core.log.Warnf("%s connection from %s forbidden: AllowedEncryptionPublicKeys does not contain key %s", - strings.ToUpper(intf.info.linkType), intf.info.remote, hex.EncodeToString(meta.box[:])) - intf.msgIO.close() + strings.ToUpper(intf.info.linkType), intf.info.remote, hex.EncodeToString(meta.key)) + intf.close() return nil, nil } - */ // Check if we already have a link to this node copy(intf.info.key[:], meta.key) intf.links.mutex.Lock() From e12c639c21b8510848d122bfe369440328d303ff Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 10 May 2021 22:42:57 +0100 Subject: [PATCH 0659/1109] Remove obsolete switch options --- src/config/config.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/config/config.go b/src/config/config.go index 2f1e6878..b97ad60a 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -75,7 +75,6 @@ type NodeConfig struct { IfMTU MTU `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` SessionFirewall SessionFirewall `comment:"The session firewall controls who can send/receive network traffic\nto/from. This is useful if you want to protect this node without\nresorting to using a real firewall. This does not affect traffic\nbeing routed via this node to somewhere else. Rules are prioritised as\nfollows: blacklist, whitelist, always allow outgoing, direct, remote."` TunnelRouting TunnelRouting `comment:"Allow tunneling non-Yggdrasil traffic over Yggdrasil. This effectively\nallows you to use Yggdrasil to route to, or to bridge other networks,\nsimilar to a VPN tunnel. Tunnelling works between any two nodes and\ndoes not require them to be directly peered."` - SwitchOptions SwitchOptions `comment:"Advanced options for tuning the switch. Normally you will not need\nto edit these options."` NodeInfoPrivacy bool `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."` NodeInfo map[string]interface{} `comment:"Optional node info. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."` } @@ -100,12 +99,6 @@ type TunnelRouting struct { IPv4LocalSubnets []string `comment:"IPv4 subnets belonging to this node's end of the tunnels. Only traffic\nfrom these ranges will be tunnelled."` } -// SwitchOptions contains tuning options for the switch. These are advanced -// options and shouldn't be changed unless necessary. -type SwitchOptions struct { - MaxTotalQueueSize uint64 `comment:"Maximum size of all switch queues combined (in bytes)."` -} - // Generates default configuration and returns a pointer to the resulting // NodeConfig. This is used when outputting the -genconf parameter and also when // using -autoconf. @@ -128,7 +121,6 @@ func GenerateConfig() *NodeConfig { cfg.SessionFirewall.AllowFromDirect = true cfg.SessionFirewall.AllowFromRemote = true cfg.SessionFirewall.AlwaysAllowOutbound = true - cfg.SwitchOptions.MaxTotalQueueSize = 4 * 1024 * 1024 cfg.NodeInfoPrivacy = false return &cfg From 57ea61b338daadc733c804c7bb7d5338cb62624e Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 10 May 2021 22:47:28 +0100 Subject: [PATCH 0660/1109] Remove reconfiguration on SIGHUP - it didn't work reliably anyway --- cmd/yggdrasil/main.go | 23 +---------------------- src/admin/admin.go | 12 +----------- src/module/module.go | 1 - src/multicast/multicast.go | 26 -------------------------- src/tuntap/tun.go | 20 -------------------- src/yggdrasil/core.go | 17 ----------------- 6 files changed, 2 insertions(+), 97 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 1760e384..e409f4ab 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -323,31 +323,10 @@ func main() { logger.Infof("Your IPv6 subnet is %s", subnet.String()) // Catch interrupts from the operating system to exit gracefully. c := make(chan os.Signal, 1) - r := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) - signal.Notify(r, os.Interrupt, syscall.SIGHUP) // Capture the service being stopped on Windows. minwinsvc.SetOnExit(n.shutdown) - defer n.shutdown() - // Wait for the terminate/interrupt signal. Once a signal is received, the - // deferred Stop function above will run which will shut down TUN/TAP. - for { - select { - case <-c: - goto exit - case <-r: - if *useconffile != "" { - cfg = readConfig(useconf, useconffile, normaliseconf) - logger.Infoln("Reloading configuration from", *useconffile) - n.core.UpdateConfig(cfg) - n.tuntap.UpdateConfig(cfg) - n.multicast.UpdateConfig(cfg) - } else { - logger.Errorln("Reloading config at runtime is only possible with -useconffile") - } - } - } -exit: + n.shutdown() } func (n *node) shutdown() { diff --git a/src/admin/admin.go b/src/admin/admin.go index a87e1248..83f330ed 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -8,6 +8,7 @@ import ( "net" "net/url" "os" + //"strconv" "strings" "time" @@ -70,17 +71,6 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. 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) { /* TODO a.AddHandler("getSelf", []string{}, func(in Info) (Info, error) { diff --git a/src/module/module.go b/src/module/module.go index ab704e70..24854e71 100644 --- a/src/module/module.go +++ b/src/module/module.go @@ -14,7 +14,6 @@ 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 } diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 430e9374..6825776d 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -129,32 +129,6 @@ func (m *Multicast) _stop() error { return nil } -// UpdateConfig updates the multicast module with the provided config.NodeConfig -// and then signals the various module goroutines to reconfigure themselves if -// needed. -func (m *Multicast) UpdateConfig(config *config.NodeConfig) { - m.Act(nil, 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.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") -} - func (m *Multicast) _updateInterfaces() { interfaces := make(map[string]interfaceInfo) intfs := m.getAllowedInterfaces() diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 2e44a017..1618506a 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -40,7 +40,6 @@ type TunAdapter struct { store keyStore config *config.NodeState log *log.Logger - reconfigure chan chan error addr address.Address subnet address.Subnet ckr cryptokey @@ -197,25 +196,6 @@ func (tun *TunAdapter) _stop() error { return nil } -// UpdateConfig updates the TUN module with the provided config.NodeConfig -// and then signals the various module goroutines to reconfigure themselves if -// needed. -func (tun *TunAdapter) UpdateConfig(config *config.NodeConfig) { - tun.log.Debugln("Reloading TUN configuration...") - - // Replace the active configuration with the supplied one - tun.config.Replace(*config) - - // If the MTU has changed in the TUN module then this is where we would - // tell the router so that updated session pings can be sent. However, we - // don't currently update the MTU of the adapter once it has been created so - // this doesn't actually happen in the real world yet. - // tun.core.SetMaximumSessionMTU(...) - - // Notify children about the configuration change - tun.Act(nil, tun.ckr.configure) -} - func (tun *TunAdapter) oobHandler(fromKey, toKey ed25519.PublicKey, data []byte) { if len(data) != 1+ed25519.SignatureSize { return diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index e3c992d3..0054db82 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -90,23 +90,6 @@ func (c *Core) _addPeerLoop() { }) } -// UpdateConfig updates the configuration in Core with the provided -// config.NodeConfig and then signals the various module goroutines to -// reconfigure themselves if needed. -func (c *Core) UpdateConfig(config *config.NodeConfig) { - c.Act(nil, func() { - c.log.Debugln("Reloading node configuration...") - - // Replace the active configuration with the supplied one - c.config.Replace(*config) - - // Notify the router and switch about the new configuration - panic("TODO") - //c.router.Act(c, c.router.reconfigure) - //c.switchTable.Act(c, c.switchTable.reconfigure) - }) -} - // Start starts up Yggdrasil using the provided config.NodeConfig, and outputs // debug logging through the provided log.Logger. The started stack will include // TCP and UDP sockets, a multicast discovery socket, an admin socket, router, From 815f2a28220801f11c4ee9af9fa566051dec09dc Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 10 May 2021 23:09:59 +0100 Subject: [PATCH 0661/1109] Respond with ICMPv6 Packet Too Big over network --- cmd/yggdrasil/main.go | 3 ++- src/tuntap/icmpv6.go | 7 ------- src/tuntap/iface.go | 14 ++++++++++++++ src/tuntap/tun.go | 15 --------------- 4 files changed, 16 insertions(+), 23 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index e409f4ab..f3158250 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -295,7 +295,7 @@ func main() { } n.multicast.SetupAdminHandlers(n.admin.(*admin.AdminSocket)) // Start the TUN/TAP interface - n.tuntap.Init(&n.core, n.state, logger, tuntap.TunOptions{}) + n.tuntap.Init(&n.core, n.state, logger, nil) if err := n.tuntap.Start(); err != nil { logger.Errorln("An error occurred starting TUN/TAP:", err) } @@ -325,6 +325,7 @@ func main() { c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) // Capture the service being stopped on Windows. + <-c minwinsvc.SetOnExit(n.shutdown) n.shutdown() } diff --git a/src/tuntap/icmpv6.go b/src/tuntap/icmpv6.go index 67c10e54..59d07af3 100644 --- a/src/tuntap/icmpv6.go +++ b/src/tuntap/icmpv6.go @@ -40,13 +40,6 @@ func ipv6Header_Marshal(h *ipv6.Header) ([]byte, error) { return b, nil } -// Initialises the ICMPv6 module by assigning our link-local IPv6 address and -// our MAC address. ICMPv6 messages will always appear to originate from these -// addresses. -func (i *ICMPv6) Init(t *TunAdapter) { - i.tun = t -} - // Creates an ICMPv6 packet based on the given icmp.MessageBody and other // parameters, complete with IP headers only, which can be written directly to // a TUN adapter, or called directly by the CreateICMPv6L2 function when diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index c7222266..30eaf32a 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -4,6 +4,9 @@ import ( "crypto/ed25519" "github.com/yggdrasil-network/yggdrasil-go/src/address" + "golang.org/x/net/icmp" + "golang.org/x/net/ipv6" + //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" //"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" @@ -70,6 +73,17 @@ func (tun *TunAdapter) write() { if len(bs) < 40 { continue } + tun.log.Println(len(bs), tun.MTU()) + if len(bs) > int(tun.MTU()) { + ptb := &icmp.PacketTooBig{ + MTU: int(tun.mtu), + Data: bs[:40], + } + if packet, err := CreateICMPv6(bs[8:24], bs[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb); err == nil { + _, _ = tun.core.WriteTo(packet, from) + } + continue + } var srcAddr, dstAddr address.Address var srcSubnet, dstSubnet address.Subnet copy(srcAddr[:], bs[8:]) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 1618506a..8c8cbf19 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -29,8 +29,6 @@ import ( type MTU = types.MTU -const tun_IPv6_HEADER_LENGTH = 40 - // TunAdapter represents a running TUN interface and extends the // yggdrasil.Adapter type. In order to use the TUN adapter with Yggdrasil, you // should pass this object to the yggdrasil.SetRouterAdapter() function before @@ -43,7 +41,6 @@ type TunAdapter struct { addr address.Address subnet address.Subnet ckr cryptokey - icmpv6 ICMPv6 mtu MTU iface tun.Device phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below @@ -51,11 +48,6 @@ type TunAdapter struct { isOpen bool } -type TunOptions struct { - //Listener *yggdrasil.Listener - //Dialer *yggdrasil.Dialer -} - // Gets the maximum supported MTU for the platform based on the defaults in // defaults.GetDefaults(). func getSupportedMTU(mtu MTU) MTU { @@ -105,12 +97,6 @@ func MaximumMTU() MTU { // Init initialises the TUN module. You must have acquired a Listener from // the Yggdrasil core before this point and it must not be in use elsewhere. func (tun *TunAdapter) Init(core *yggdrasil.Core, config *config.NodeState, log *log.Logger, options interface{}) error { - /* TODO - tunoptions, ok := options.(TunOptions) - if !ok { - return fmt.Errorf("invalid options supplied to TunAdapter module") - } - */ tun.core = core tun.store.init(tun) tun.config = config @@ -118,7 +104,6 @@ func (tun *TunAdapter) Init(core *yggdrasil.Core, config *config.NodeState, log if err := tun.core.SetOutOfBandHandler(tun.oobHandler); err != nil { return fmt.Errorf("tun.core.SetOutOfBandHander: %w", err) } - return nil } From 577b7118ad336f9364b73698457f52b4028514c7 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 10 May 2021 23:16:22 +0100 Subject: [PATCH 0662/1109] remove debug logging --- src/tuntap/iface.go | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 30eaf32a..af76dac4 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -73,7 +73,6 @@ func (tun *TunAdapter) write() { if len(bs) < 40 { continue } - tun.log.Println(len(bs), tun.MTU()) if len(bs) > int(tun.MTU()) { ptb := &icmp.PacketTooBig{ MTU: int(tun.mtu), From 7d49b86456b265cccc3d061e5789159a1e905bc9 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 15 May 2021 13:44:55 -0500 Subject: [PATCH 0663/1109] set version to an obviously unstable value, fix peer address formatting in the connect/disconnect messages --- src/yggdrasil/link.go | 4 ++-- src/yggdrasil/version.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index aec1533d..29b73400 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -14,7 +14,7 @@ import ( //"sync/atomic" "time" - //"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/util" "golang.org/x/net/proxy" @@ -243,7 +243,7 @@ func (intf *link) handler() (chan struct{}, error) { intf.links.core.log.Debugln("DEBUG: registered interface for", intf.name) } intf.links.mutex.Unlock() - themAddr := make([]byte, 16) // TODO address.AddrForNodeID(crypto.GetNodeID(&intf.info.box)) + themAddr := address.AddrForKey(ed25519.PublicKey(intf.info.key[:])) themAddrString := net.IP(themAddr[:]).String() themString := fmt.Sprintf("%s@%s", themAddrString, intf.info.remote) intf.links.core.log.Infof("Connected %s: %s, source %s", diff --git a/src/yggdrasil/version.go b/src/yggdrasil/version.go index ad334c8b..a3c9bce5 100644 --- a/src/yggdrasil/version.go +++ b/src/yggdrasil/version.go @@ -22,7 +22,7 @@ func version_getBaseMetadata() version_metadata { return version_metadata{ meta: [4]byte{'m', 'e', 't', 'a'}, ver: 0, - minorVer: 3, + minorVer: 0, } } From cd4144f22bb35362bfd3bfd44915b85489026f73 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 15 May 2021 14:50:56 -0500 Subject: [PATCH 0664/1109] add minimal src/yggdrasil/api.go functions inspect internal state --- go.mod | 2 +- go.sum | 10 +- src/yggdrasil/api.go | 508 ++++++------------------------------------- 3 files changed, 67 insertions(+), 453 deletions(-) diff --git a/go.mod b/go.mod index 0f3bce55..f830cd2d 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.16 require ( - github.com/Arceliar/ironwood v0.0.0-20210509162436-68a5d50e0d8f + github.com/Arceliar/ironwood v0.0.0-20210515192645-e94fc534a8e0 github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 github.com/cheggaaa/pb/v3 v3.0.6 github.com/fatih/color v1.10.0 // indirect diff --git a/go.sum b/go.sum index 93e4b3a8..eda3ca65 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,5 @@ -github.com/Arceliar/ironwood v0.0.0-20210508094446-74a68e4f5970 h1:sKiz18LynwInybi9BIhM8tdvZlSurnT6rM/ZUEqMgzU= -github.com/Arceliar/ironwood v0.0.0-20210508094446-74a68e4f5970/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= -github.com/Arceliar/ironwood v0.0.0-20210508165109-414e0b35fc84 h1:hhgFLxGnMXtr1TwnFQcUudSphe7g8i0qB/xgfNlLmHE= -github.com/Arceliar/ironwood v0.0.0-20210508165109-414e0b35fc84/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= -github.com/Arceliar/ironwood v0.0.0-20210509152840-bc3ec5afa1ed h1:LXo+ZsQIx2xI6pbojnhK7PenYNMi3edKI9xSdoX/iSw= -github.com/Arceliar/ironwood v0.0.0-20210509152840-bc3ec5afa1ed/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= -github.com/Arceliar/ironwood v0.0.0-20210509162436-68a5d50e0d8f h1:kjCfDzlGtM3BD9X5kF8SxvFhghCncYyJb/rxA/NZwK4= -github.com/Arceliar/ironwood v0.0.0-20210509162436-68a5d50e0d8f/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= +github.com/Arceliar/ironwood v0.0.0-20210515192645-e94fc534a8e0 h1:ljxlQafrAuWzPhUDBVlWkshOlJDyKn9NXAPLJY5tAU0= +github.com/Arceliar/ironwood v0.0.0-20210515192645-e94fc534a8e0/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index c8f7900f..a2c0d541 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -1,268 +1,106 @@ package yggdrasil import ( + "crypto/ed25519" //"encoding/hex" "errors" //"fmt" "net" //"sort" - "time" + //"time" "github.com/gologme/log" "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/Arceliar/phony" ) -// Peer represents a single peer object. This contains information from the -// preferred switch port for this peer, although there may be more than one -// active switch port connection to the peer in reality. -// -// This struct is informational only - you cannot manipulate peer connections -// using instances of this struct. You should use the AddPeer or RemovePeer -// functions instead. +type Self struct { + Key ed25519.PublicKey + Root ed25519.PublicKey + Coords []uint64 +} + type Peer struct { - PublicKey crypto.BoxPubKey // The public key of the remote node - Endpoint string // The connection string used to connect to the peer - BytesSent uint64 // Number of bytes sent to this peer - BytesRecvd uint64 // Number of bytes received from this peer - Protocol string // The transport protocol that this peer is connected with, typically "tcp" - Port uint64 // Switch port number for this peer connection - Uptime time.Duration // How long this peering has been active for + Key ed25519.PublicKey + Root ed25519.PublicKey + Coords []uint64 + Port uint64 } -// SwitchPeer represents a switch connection to a peer. Note that there may be -// multiple switch peers per actual peer, e.g. if there are multiple connections -// to a given node. -// -// This struct is informational only - you cannot manipulate switch peer -// connections using instances of this struct. You should use the AddPeer or -// RemovePeer functions instead. -type SwitchPeer struct { - PublicKey crypto.BoxPubKey // The public key of the remote node - Coords []uint64 // The coordinates of the remote node - BytesSent uint64 // Number of bytes sent via this switch port - BytesRecvd uint64 // Number of bytes received via this switch port - Port uint64 // Switch port number for this switch peer - Protocol string // The transport protocol that this switch port is connected with, typically "tcp" - Endpoint string // The connection string used to connect to the switch peer -} - -// DHTEntry represents a single DHT entry that has been learned or cached from -// DHT searches. type DHTEntry struct { - PublicKey crypto.BoxPubKey - Coords []uint64 - LastSeen time.Duration + Key ed25519.PublicKey + Port uint64 + Next uint64 } -// DHTRes represents a DHT response, as returned by DHTPing. -type DHTRes struct { - PublicKey crypto.BoxPubKey // key of the sender - Coords []uint64 // coords of the sender - Dest crypto.NodeID // the destination node ID - Infos []DHTEntry // response +type PathEntry struct { + Key ed25519.PublicKey + Path []uint64 } -// NodeInfoPayload represents a RequestNodeInfo response, in bytes. -type NodeInfoPayload []byte - -// SwitchQueues represents information from the switch related to link -// congestion and a list of switch queues created in response to congestion on a -// given link. -type SwitchQueues struct { - Queues []SwitchQueue // An array of SwitchQueue objects containing information about individual queues - Count uint64 // The current number of active switch queues - Size uint64 // The current total size of active switch queues - HighestCount uint64 // The highest recorded number of switch queues so far - HighestSize uint64 // The highest recorded total size of switch queues so far - MaximumSize uint64 // The maximum allowed total size of switch queues, as specified by config -} - -// SwitchQueue represents a single switch queue. Switch queues are only created -// in response to congestion on a given link and represent how much data has -// been temporarily cached for sending once the congestion has cleared. -type SwitchQueue struct { - ID string // The ID of the switch queue - Size uint64 // The total size, in bytes, of the queue - Packets uint64 // The number of packets in the queue - Port uint64 // The switch port to which the queue applies -} - -// Session represents an open session with another node. Sessions are opened in -// response to traffic being exchanged between two nodes using Conn objects. -// Note that sessions will automatically be closed by Yggdrasil if no traffic is -// exchanged for around two minutes. type Session struct { - PublicKey crypto.BoxPubKey // The public key of the remote node - Coords []uint64 // The coordinates of the remote node - BytesSent uint64 // Bytes sent to the session - BytesRecvd uint64 // Bytes received from the session - //MTU MTU // The maximum supported message size of the session - Uptime time.Duration // How long this session has been active for - WasMTUFixed bool // This field is no longer used + Key ed25519.PublicKey +} + +func (c *Core) GetSelf() Self { + var self Self + s := c.PacketConn.PacketConn.Debug.GetSelf() + self.Key = s.Key + self.Root = s.Root + self.Coords = s.Coords + return self } -// GetPeers returns one or more Peer objects containing information about active -// peerings with other Yggdrasil nodes, where one of the responses always -// includes information about the current node (with a port number of 0). If -// there is exactly one entry then this node is not connected to any other nodes -// and is therefore isolated. -/* TODO func (c *Core) GetPeers() []Peer { - var ports map[switchPort]*peer - phony.Block(&c.peers, func() { ports = c.peers.ports }) var peers []Peer - var ps []switchPort - for port := range ports { - ps = append(ps, port) - } - sort.Slice(ps, func(i, j int) bool { return ps[i] < ps[j] }) - for _, port := range ps { - p := ports[port] + ps := c.PacketConn.PacketConn.Debug.GetPeers() + for _, p := range ps { var info Peer - phony.Block(p, func() { - info = Peer{ - Endpoint: p.intf.name(), - BytesSent: p.bytesSent, - BytesRecvd: p.bytesRecvd, - Protocol: p.intf.interfaceType(), - Port: uint64(port), - Uptime: time.Since(p.firstSeen), - } - copy(info.PublicKey[:], p.box[:]) - }) + info.Key = p.Key + info.Root = p.Root + info.Coords = p.Coords + info.Port = p.Port peers = append(peers, info) } return peers } -*/ -// GetSwitchPeers returns zero or more SwitchPeer objects containing information -// about switch port connections with other Yggdrasil nodes. Note that, unlike -// GetPeers, GetSwitchPeers does not include information about the current node, -// therefore it is possible for this to return zero elements if the node is -// isolated or not connected to any peers. -/* TODO -func (c *Core) GetSwitchPeers() []SwitchPeer { - var switchpeers []SwitchPeer - var table *lookupTable - var ports map[switchPort]*peer - phony.Block(&c.peers, func() { - table = c.peers.table - ports = c.peers.ports - }) - for _, elem := range table.elems { - peer, isIn := ports[elem.port] - if !isIn { - continue - } - coords := elem.locator.getCoords() - var info SwitchPeer - phony.Block(peer, func() { - info = SwitchPeer{ - Coords: append([]uint64{}, wire_coordsBytestoUint64s(coords)...), - BytesSent: peer.bytesSent, - BytesRecvd: peer.bytesRecvd, - Port: uint64(elem.port), - Protocol: peer.intf.interfaceType(), - Endpoint: peer.intf.remote(), - } - copy(info.PublicKey[:], peer.box[:]) - }) - switchpeers = append(switchpeers, info) - } - return switchpeers -} -*/ - -// GetDHT returns zero or more entries as stored in the DHT, cached primarily -// from searches that have already taken place. -/* TODO func (c *Core) GetDHT() []DHTEntry { - var dhtentries []DHTEntry - getDHT := func() { - now := time.Now() - var dhtentry []*dhtInfo - for _, v := range c.router.dht.table { - dhtentry = append(dhtentry, v) - } - sort.SliceStable(dhtentry, func(i, j int) bool { - return dht_ordered(&c.router.dht.nodeID, dhtentry[i].getNodeID(), dhtentry[j].getNodeID()) - }) - for _, v := range dhtentry { - info := DHTEntry{ - Coords: append([]uint64{}, wire_coordsBytestoUint64s(v.coords)...), - LastSeen: now.Sub(v.recv), - } - copy(info.PublicKey[:], v.key[:]) - dhtentries = append(dhtentries, info) - } + var dhts []DHTEntry + ds := c.PacketConn.PacketConn.Debug.GetDHT() + for _, d := range ds { + var info DHTEntry + info.Key = d.Key + info.Port = d.Port + info.Next = d.Next + dhts = append(dhts, info) } - phony.Block(&c.router, getDHT) - return dhtentries + return dhts +} + +func (c *Core) GetPaths() []PathEntry { + var paths []PathEntry + ps := c.PacketConn.PacketConn.Debug.GetPaths() + for _, p := range ps { + var info PathEntry + info.Key = p.Key + info.Path = p.Path + paths = append(paths, info) + } + return paths } -*/ -// GetSessions returns a list of open sessions from this node to other nodes. -/* TODO func (c *Core) GetSessions() []Session { var sessions []Session - getSessions := func() { - for _, sinfo := range c.router.sessions.sinfos { - var session Session - workerFunc := func() { - session = Session{ - Coords: append([]uint64{}, wire_coordsBytestoUint64s(sinfo.coords)...), - MTU: sinfo._getMTU(), - BytesSent: sinfo.bytesSent, - BytesRecvd: sinfo.bytesRecvd, - Uptime: time.Now().Sub(sinfo.timeOpened), - WasMTUFixed: sinfo.wasMTUFixed, - } - copy(session.PublicKey[:], sinfo.theirPermPub[:]) - } - phony.Block(sinfo, workerFunc) - // TODO? skipped known but timed out sessions? - sessions = append(sessions, session) - } + ss := c.PacketConn.Debug.GetSessions() + for _, s := range ss { + var info Session + info.Key = s.Key + sessions = append(sessions, info) } - phony.Block(&c.router, getSessions) return sessions } -*/ - -// ConnListen returns a listener for Yggdrasil session connections. You can only -// call this function once as each Yggdrasil node can only have a single -// ConnListener. Make sure to keep the reference to this for as long as it is -// needed. -/* TODO? -func (c *Core) ConnListen() (*Listener, error) { - c.router.sessions.listenerMutex.Lock() - defer c.router.sessions.listenerMutex.Unlock() - if c.router.sessions.listener != nil { - return nil, errors.New("a listener already exists") - } - c.router.sessions.listener = &Listener{ - core: c, - conn: make(chan *Conn), - close: make(chan interface{}), - } - return c.router.sessions.listener, nil -} -*/ - -// ConnDialer returns a dialer for Yggdrasil session connections. Since -// ConnDialers are stateless, you can request as many dialers as you like, -// although ideally you should request only one and keep the reference to it for -// as long as it is needed. -/* TODO? -func (c *Core) ConnDialer() (*Dialer, error) { - return &Dialer{ - core: c, - }, nil -} -*/ // ListenTCP starts a new TCP listener. The input URI should match that of the // "Listen" configuration item, e.g. @@ -278,57 +116,6 @@ func (c *Core) ListenTLS(uri string) (*TcpListener, error) { return c.links.tcp.listen(uri, c.links.tcp.tls.forListener) } -// NodeID gets the node ID. This is derived from your router encryption keys. -// Remote nodes wanting to open connections to your node will need to know your -// node ID. -/* TODO? -func (c *Core) NodeID() *crypto.NodeID { - return crypto.GetNodeID(&c.boxPub) -} -*/ - -// TreeID gets the tree ID. This is derived from your switch signing keys. There -// is typically no need to share this key. -/* TODO? -func (c *Core) TreeID() *crypto.TreeID { - return crypto.GetTreeID(&c.sigPub) -} -*/ - -// SigningPublicKey gets the node's signing public key, as used by the switch. -/* TODO? -func (c *Core) SigningPublicKey() string { - return hex.EncodeToString(c.sigPub[:]) -} -*/ - -// EncryptionPublicKey gets the node's encryption public key, as used by the -// router. -/* TODO? -func (c *Core) EncryptionPublicKey() string { - return hex.EncodeToString(c.boxPub[:]) -} -*/ - -// Coords returns the current coordinates of the node. Note that these can -// change at any time for a number of reasons, not limited to but including -// changes to peerings (either yours or a parent nodes) or changes to the network -// root. -// -// This function may return an empty array - this is normal behaviour if either -// you are the root of the network that you are connected to, or you are not -// connected to any other nodes (effectively making you the root of a -// single-node network). -/* TODO? -func (c *Core) Coords() []uint64 { - var coords []byte - phony.Block(&c.router, func() { - coords = c.router.table.self.getCoords() - }) - return wire_coordsBytestoUint64s(coords) -} -*/ - // Address gets the IPv6 address of the Yggdrasil node. This is always a /128 // address. The IPv6 address is only relevant when the node is operating as an // IP router and often is meaningless when embedded into an application, unless @@ -350,89 +137,6 @@ func (c *Core) Subnet() net.IPNet { return net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)} } -// MyNodeInfo gets the currently configured nodeinfo. NodeInfo is typically -// specified through the "NodeInfo" option in the node configuration or using -// the SetNodeInfo function, although it may also contain other built-in values -// such as "buildname", "buildversion" etc. -/* TODO? -func (c *Core) MyNodeInfo() NodeInfoPayload { - return c.router.nodeinfo.getNodeInfo() -} -*/ - -// SetNodeInfo sets the local nodeinfo. Note that nodeinfo can be any value or -// struct, it will be serialised into JSON automatically. -/* TODO? -func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) { - c.router.nodeinfo.setNodeInfo(nodeinfo, nodeinfoprivacy) -} -*/ - -// GetMaximumSessionMTU returns the maximum allowed session MTU size. -/* TODO? -func (c *Core) GetMaximumSessionMTU() MTU { - var mtu MTU - phony.Block(&c.router, func() { - mtu = c.router.sessions.myMaximumMTU - }) - return mtu -} -*/ - -// SetMaximumSessionMTU sets the maximum allowed session MTU size. The default -// value is 65535 bytes. Session pings will be sent to update all open sessions -// if the MTU has changed. -/* TODO? -func (c *Core) SetMaximumSessionMTU(mtu MTU) { - phony.Block(&c.router, func() { - if c.router.sessions.myMaximumMTU != mtu { - c.router.sessions.myMaximumMTU = mtu - c.router.sessions.reconfigure() - } - }) -} -*/ - -// GetNodeInfo requests nodeinfo from a remote node, as specified by the public -// key and coordinates specified. The third parameter specifies whether a cached -// result is acceptable - this results in less traffic being generated than is -// necessary when, e.g. crawling the network. -/* TODO? -func (c *Core) GetNodeInfo(key crypto.BoxPubKey, coords []uint64, nocache bool) (NodeInfoPayload, error) { - response := make(chan *NodeInfoPayload, 1) - c.router.nodeinfo.addCallback(key, func(nodeinfo *NodeInfoPayload) { - defer func() { recover() }() - select { - case response <- nodeinfo: - default: - } - }) - c.router.nodeinfo.sendNodeInfo(key, wire_coordsUint64stoBytes(coords), false) - phony.Block(&c.router.nodeinfo, func() {}) // Wait for sendNodeInfo before starting timer - timer := time.AfterFunc(6*time.Second, func() { close(response) }) - defer timer.Stop() - for res := range response { - return *res, nil - } - return NodeInfoPayload{}, fmt.Errorf("getNodeInfo timeout: %s", hex.EncodeToString(key[:])) -} -*/ - -// SetSessionGatekeeper allows you to configure a handler function for deciding -// whether a session should be allowed or not. The default session firewall is -// implemented in this way. The function receives the public key of the remote -// side and a boolean which is true if we initiated the session or false if we -// received an incoming session request. The function should return true to -// allow the session or false to reject it. -/* TODO? -func (c *Core) SetSessionGatekeeper(f func(pubkey *crypto.BoxPubKey, initiator bool) bool) { - c.router.sessions.isAllowedMutex.Lock() - defer c.router.sessions.isAllowedMutex.Unlock() - - c.router.sessions.isAllowedHandler = f -} -*/ - // SetLogger sets the output logger of the Yggdrasil node after startup. This // may be useful if you want to redirect the output later. Note that this // expects a Logger from the github.com/gologme/log package and not from Go's @@ -481,6 +185,7 @@ func (c *Core) AddPeer(addr string, sintf string) error { return nil } +/* func (c *Core) RemovePeer(addr string, sintf string) error { if sintf == "" { for i, peer := range c.config.Current.Peers { @@ -498,8 +203,7 @@ func (c *Core) RemovePeer(addr string, sintf string) error { } } - panic("TODO") - /* TODO? + panic("TODO") // Get the net.Conn to this peer (if any) and close it c.peers.Act(nil, func() { ports := c.peers.ports for _, peer := range ports { @@ -508,10 +212,10 @@ func (c *Core) RemovePeer(addr string, sintf string) error { } } }) - */ return nil } +*/ // CallPeer calls a peer once. This should be specified in the peer URI format, // e.g.: @@ -522,87 +226,3 @@ func (c *Core) RemovePeer(addr string, sintf string) error { func (c *Core) CallPeer(addr string, sintf string) error { return c.links.call(addr, sintf) } - -// DisconnectPeer disconnects a peer once. This should be specified as a port -// number. -/* TODO? -func (c *Core) DisconnectPeer(port uint64) error { - c.peers.Act(nil, func() { - if p, isIn := c.peers.ports[switchPort(port)]; isIn { - p.Act(&c.peers, p._removeSelf) - } - }) - return nil -} -*/ - -// GetAllowedEncryptionPublicKeys returns the public keys permitted for incoming -// peer connections. If this list is empty then all incoming peer connections -// are accepted by default. -/* TODO? -func (c *Core) GetAllowedEncryptionPublicKeys() []string { - return c.peers.getAllowedEncryptionPublicKeys() -} -*/ - -// AddAllowedEncryptionPublicKey whitelists a key for incoming peer connections. -// By default all incoming peer connections are accepted, but adding public keys -// to the whitelist using this function enables strict checking from that point -// forward. Once the whitelist is enabled, only peer connections from -// whitelisted public keys will be accepted. -/* TODO? -func (c *Core) AddAllowedEncryptionPublicKey(bstr string) (err error) { - c.peers.addAllowedEncryptionPublicKey(bstr) - return nil -} -*/ - -// RemoveAllowedEncryptionPublicKey removes a key from the whitelist for -// incoming peer connections. If none are set, an empty list permits all -// incoming connections. -/* TODO? -func (c *Core) RemoveAllowedEncryptionPublicKey(bstr string) (err error) { - c.peers.removeAllowedEncryptionPublicKey(bstr) - return nil -} -*/ - -// DHTPing sends a DHT ping to the node with the provided key and coords, -// optionally looking up the specified target NodeID. -/* NOT TODO!! -func (c *Core) DHTPing(key crypto.BoxPubKey, coords []uint64, target *crypto.NodeID) (DHTRes, error) { - resCh := make(chan *dhtRes, 1) - info := dhtInfo{ - key: key, - coords: wire_coordsUint64stoBytes(coords), - } - if target == nil { - target = info.getNodeID() - } - rq := dhtReqKey{info.key, *target} - sendPing := func() { - c.router.dht.addCallback(&rq, func(res *dhtRes) { - resCh <- res - }) - c.router.dht.ping(&info, &rq.dest) - } - phony.Block(&c.router, sendPing) - // TODO: do something better than the below... - res := <-resCh - if res != nil { - r := DHTRes{ - Coords: append([]uint64{}, wire_coordsBytestoUint64s(res.Coords)...), - } - copy(r.PublicKey[:], res.Key[:]) - for _, i := range res.Infos { - e := DHTEntry{ - Coords: append([]uint64{}, wire_coordsBytestoUint64s(i.coords)...), - } - copy(e.PublicKey[:], i.key[:]) - r.Infos = append(r.Infos, e) - } - return r, nil - } - return DHTRes{}, fmt.Errorf("DHT ping timeout: %s", hex.EncodeToString(key[:])) -} -*/ From e83b5d08a8b49eb9aa8b3fb47343f201fade0a3c Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 15 May 2021 14:54:25 -0500 Subject: [PATCH 0665/1109] remove ckr --- src/tuntap/admin.go | 65 +------ src/tuntap/ckr.go | 430 -------------------------------------------- src/tuntap/tun.go | 2 - 3 files changed, 4 insertions(+), 493 deletions(-) delete mode 100644 src/tuntap/ckr.go diff --git a/src/tuntap/admin.go b/src/tuntap/admin.go index eb1eb69b..67f80e04 100644 --- a/src/tuntap/admin.go +++ b/src/tuntap/admin.go @@ -1,10 +1,10 @@ package tuntap import ( - "encoding/hex" - "errors" - "fmt" - "net" + //"encoding/hex" + //"errors" + //"fmt" + //"net" "github.com/yggdrasil-network/yggdrasil-go/src/admin" ) @@ -54,61 +54,4 @@ func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { } }) */ - a.AddHandler("getTunnelRouting", []string{}, func(in admin.Info) (admin.Info, error) { - return admin.Info{"enabled": t.ckr.isEnabled()}, nil - }) - a.AddHandler("setTunnelRouting", []string{"enabled"}, func(in admin.Info) (admin.Info, error) { - enabled := false - if e, ok := in["enabled"].(bool); ok { - enabled = e - } - t.ckr.setEnabled(enabled) - return admin.Info{"enabled": enabled}, nil - }) - a.AddHandler("addLocalSubnet", []string{"subnet"}, func(in admin.Info) (admin.Info, error) { - if err := t.ckr.addLocalSubnet(in["subnet"].(string)); err == nil { - return admin.Info{"added": []string{in["subnet"].(string)}}, nil - } - return admin.Info{"not_added": []string{in["subnet"].(string)}}, errors.New("Failed to add source subnet") - }) - a.AddHandler("addRemoteSubnet", []string{"subnet", "box_pub_key"}, func(in admin.Info) (admin.Info, error) { - if err := t.ckr.addRemoteSubnet(in["subnet"].(string), in["box_pub_key"].(string)); err == nil { - return admin.Info{"added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil - } - return admin.Info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to add route") - }) - a.AddHandler("getSourceSubnets", []string{}, func(in admin.Info) (admin.Info, error) { - var subnets []string - getSourceSubnets := func(snets []net.IPNet) { - for _, subnet := range snets { - subnets = append(subnets, subnet.String()) - } - } - getSourceSubnets(t.ckr.ipv4locals) - getSourceSubnets(t.ckr.ipv6locals) - return admin.Info{"source_subnets": subnets}, nil - }) - a.AddHandler("getRoutes", []string{}, func(in admin.Info) (admin.Info, error) { - routes := make(admin.Info) - getRoutes := func(ckrs []cryptokey_route) { - for _, ckr := range ckrs { - routes[ckr.subnet.String()] = hex.EncodeToString(ckr.destination[:]) - } - } - getRoutes(t.ckr.ipv4remotes) - getRoutes(t.ckr.ipv6remotes) - return admin.Info{"routes": routes}, nil - }) - a.AddHandler("removeLocalSubnet", []string{"subnet"}, func(in admin.Info) (admin.Info, error) { - if err := t.ckr.removeLocalSubnet(in["subnet"].(string)); err == nil { - return admin.Info{"removed": []string{in["subnet"].(string)}}, nil - } - return admin.Info{"not_removed": []string{in["subnet"].(string)}}, errors.New("Failed to remove source subnet") - }) - a.AddHandler("removeRemoteSubnet", []string{"subnet", "box_pub_key"}, func(in admin.Info) (admin.Info, error) { - if err := t.ckr.removeRemoteSubnet(in["subnet"].(string), in["box_pub_key"].(string)); err == nil { - return admin.Info{"removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil - } - return admin.Info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route") - }) } diff --git a/src/tuntap/ckr.go b/src/tuntap/ckr.go deleted file mode 100644 index fdb2bbdc..00000000 --- a/src/tuntap/ckr.go +++ /dev/null @@ -1,430 +0,0 @@ -package tuntap - -import ( - "bytes" - "encoding/hex" - "errors" - "fmt" - "net" - "sort" - "sync" - "sync/atomic" - - "github.com/yggdrasil-network/yggdrasil-go/src/address" - "github.com/yggdrasil-network/yggdrasil-go/src/crypto" -) - -// This module implements crypto-key routing, similar to Wireguard, where we -// allow traffic for non-Yggdrasil ranges to be routed over Yggdrasil. - -type cryptokey struct { - tun *TunAdapter - enabled atomic.Value // bool - ipv4remotes []cryptokey_route - ipv6remotes []cryptokey_route - ipv4cache map[address.Address]cryptokey_route - ipv6cache map[address.Address]cryptokey_route - ipv4locals []net.IPNet - ipv6locals []net.IPNet - mutexremotes sync.RWMutex - mutexcaches sync.RWMutex - mutexlocals sync.RWMutex -} - -type cryptokey_route struct { - subnet net.IPNet - destination crypto.BoxPubKey -} - -// Initialise crypto-key routing. This must be done before any other CKR calls. -func (c *cryptokey) init(tun *TunAdapter) { - c.tun = tun - c.configure() -} - -// Configure the CKR routes. This should only ever be ran by the TUN/TAP actor. -func (c *cryptokey) configure() { - current := c.tun.config.GetCurrent() - - // Set enabled/disabled state - c.setEnabled(current.TunnelRouting.Enable) - - // Clear out existing routes - c.mutexremotes.Lock() - c.ipv6remotes = make([]cryptokey_route, 0) - c.ipv4remotes = make([]cryptokey_route, 0) - c.mutexremotes.Unlock() - - // Add IPv6 routes - for ipv6, pubkey := range current.TunnelRouting.IPv6RemoteSubnets { - if err := c.addRemoteSubnet(ipv6, pubkey); err != nil { - c.tun.log.Errorln("Error adding CKR IPv6 remote subnet:", err) - } - } - - // Add IPv4 routes - for ipv4, pubkey := range current.TunnelRouting.IPv4RemoteSubnets { - if err := c.addRemoteSubnet(ipv4, pubkey); err != nil { - c.tun.log.Errorln("Error adding CKR IPv4 remote subnet:", err) - } - } - - // Clear out existing sources - c.mutexlocals.Lock() - c.ipv6locals = make([]net.IPNet, 0) - c.ipv4locals = make([]net.IPNet, 0) - c.mutexlocals.Unlock() - - // Add IPv6 sources - c.ipv6locals = make([]net.IPNet, 0) - for _, source := range current.TunnelRouting.IPv6LocalSubnets { - if err := c.addLocalSubnet(source); err != nil { - c.tun.log.Errorln("Error adding CKR IPv6 local subnet:", err) - } - } - - // Add IPv4 sources - c.ipv4locals = make([]net.IPNet, 0) - for _, source := range current.TunnelRouting.IPv4LocalSubnets { - if err := c.addLocalSubnet(source); err != nil { - c.tun.log.Errorln("Error adding CKR IPv4 local subnet:", err) - } - } - - // Wipe the caches - c.mutexcaches.Lock() - c.ipv4cache = make(map[address.Address]cryptokey_route, 0) - c.ipv6cache = make(map[address.Address]cryptokey_route, 0) - c.mutexcaches.Unlock() -} - -// Enable or disable crypto-key routing. -func (c *cryptokey) setEnabled(enabled bool) { - c.enabled.Store(enabled) -} - -// Check if crypto-key routing is enabled. -func (c *cryptokey) isEnabled() bool { - enabled, ok := c.enabled.Load().(bool) - return ok && enabled -} - -// Check whether the given address (with the address length specified in bytes) -// matches either the current node's address, the node's routed subnet or the -// list of subnets specified in ipv4locals/ipv6locals. -func (c *cryptokey) isValidLocalAddress(addr address.Address, addrlen int) bool { - c.mutexlocals.RLock() - defer c.mutexlocals.RUnlock() - // Does it match a configured CKR source? - if c.isEnabled() { - ip := net.IP(addr[:addrlen]) - // Build our references to the routing sources - var routingsources *[]net.IPNet - - // Check if the prefix is IPv4 or IPv6 - if addrlen == net.IPv6len { - routingsources = &c.ipv6locals - } else if addrlen == net.IPv4len { - routingsources = &c.ipv4locals - } else { - return false - } - - for _, subnet := range *routingsources { - if subnet.Contains(ip) { - return true - } - } - } - - // Doesn't match any of the above - return false -} - -// Adds a source subnet, which allows traffic with these source addresses to -// be tunnelled using crypto-key routing. -func (c *cryptokey) addLocalSubnet(cidr string) error { - c.mutexlocals.Lock() - defer c.mutexlocals.Unlock() - - // Is the CIDR we've been given valid? - _, ipnet, err := net.ParseCIDR(cidr) - if err != nil { - return err - } - - // Get the prefix length and size - _, prefixsize := ipnet.Mask.Size() - - // Build our references to the routing sources - var routingsources *[]net.IPNet - - // Check if the prefix is IPv4 or IPv6 - if prefixsize == net.IPv6len*8 { - routingsources = &c.ipv6locals - } else if prefixsize == net.IPv4len*8 { - routingsources = &c.ipv4locals - } else { - return errors.New("unexpected prefix size") - } - - // Check if we already have this CIDR - for _, subnet := range *routingsources { - if subnet.String() == ipnet.String() { - return errors.New("local subnet already configured") - } - } - - // Add the source subnet - *routingsources = append(*routingsources, *ipnet) - c.tun.log.Infoln("Added CKR local subnet", cidr) - return nil -} - -// Adds a destination route for the given CIDR to be tunnelled to the node -// with the given BoxPubKey. -func (c *cryptokey) addRemoteSubnet(cidr string, dest string) error { - c.mutexremotes.Lock() - c.mutexcaches.Lock() - defer c.mutexremotes.Unlock() - defer c.mutexcaches.Unlock() - - // Is the CIDR we've been given valid? - ipaddr, ipnet, err := net.ParseCIDR(cidr) - if err != nil { - return err - } - - // Get the prefix length and size - _, prefixsize := ipnet.Mask.Size() - - // Build our references to the routing table and cache - var routingtable *[]cryptokey_route - var routingcache *map[address.Address]cryptokey_route - - // Check if the prefix is IPv4 or IPv6 - if prefixsize == net.IPv6len*8 { - routingtable = &c.ipv6remotes - routingcache = &c.ipv6cache - } else if prefixsize == net.IPv4len*8 { - routingtable = &c.ipv4remotes - routingcache = &c.ipv4cache - } else { - return errors.New("unexpected prefix size") - } - - // Is the route an Yggdrasil destination? - var addr address.Address - var snet address.Subnet - copy(addr[:], ipaddr) - copy(snet[:], ipnet.IP) - if addr.IsValid() || snet.IsValid() { - return errors.New("can't specify Yggdrasil destination as crypto-key route") - } - // Do we already have a route for this subnet? - for _, route := range *routingtable { - if route.subnet.String() == ipnet.String() { - return fmt.Errorf("remote subnet already exists for %s", cidr) - } - } - // Decode the public key - if bpk, err := hex.DecodeString(dest); err != nil { - return err - } else if len(bpk) != crypto.BoxPubKeyLen { - return fmt.Errorf("incorrect key length for %s", dest) - } else { - // Add the new crypto-key route - var key crypto.BoxPubKey - copy(key[:], bpk) - *routingtable = append(*routingtable, cryptokey_route{ - subnet: *ipnet, - destination: key, - }) - - // Sort so most specific routes are first - sort.Slice(*routingtable, func(i, j int) bool { - im, _ := (*routingtable)[i].subnet.Mask.Size() - jm, _ := (*routingtable)[j].subnet.Mask.Size() - return im > jm - }) - - // Clear the cache as this route might change future routing - // Setting an empty slice keeps the memory whereas nil invokes GC - for k := range *routingcache { - delete(*routingcache, k) - } - - c.tun.log.Infoln("Added CKR remote subnet", cidr) - return nil - } -} - -// Looks up the most specific route for the given address (with the address -// length specified in bytes) from the crypto-key routing table. An error is -// returned if the address is not suitable or no route was found. -func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (crypto.BoxPubKey, error) { - - // Check if the address is a valid Yggdrasil address - if so it - // is exempt from all CKR checking - if addr.IsValid() { - return crypto.BoxPubKey{}, errors.New("cannot look up CKR for Yggdrasil addresses") - } - - // Build our references to the routing table and cache - var routingtable *[]cryptokey_route - var routingcache *map[address.Address]cryptokey_route - - // Check if the prefix is IPv4 or IPv6 - if addrlen == net.IPv6len { - routingcache = &c.ipv6cache - } else if addrlen == net.IPv4len { - routingcache = &c.ipv4cache - } else { - return crypto.BoxPubKey{}, errors.New("unexpected prefix size") - } - - // Check if there's a cache entry for this addr - c.mutexcaches.RLock() - if route, ok := (*routingcache)[addr]; ok { - c.mutexcaches.RUnlock() - return route.destination, nil - } - c.mutexcaches.RUnlock() - - c.mutexremotes.RLock() - defer c.mutexremotes.RUnlock() - - // Check if the prefix is IPv4 or IPv6 - if addrlen == net.IPv6len { - routingtable = &c.ipv6remotes - } else if addrlen == net.IPv4len { - routingtable = &c.ipv4remotes - } else { - return crypto.BoxPubKey{}, errors.New("unexpected prefix size") - } - - // No cache was found - start by converting the address into a net.IP - ip := make(net.IP, addrlen) - copy(ip[:addrlen], addr[:]) - - // Check if we have a route. At this point c.ipv6remotes should be - // pre-sorted so that the most specific routes are first - for _, route := range *routingtable { - // Does this subnet match the given IP? - if route.subnet.Contains(ip) { - c.mutexcaches.Lock() - defer c.mutexcaches.Unlock() - - // Check if the routing cache is above a certain size, if it is evict - // a random entry so we can make room for this one. We take advantage - // of the fact that the iteration order is random here - for k := range *routingcache { - if len(*routingcache) < 1024 { - break - } - delete(*routingcache, k) - } - - // Cache the entry for future packets to get a faster lookup - (*routingcache)[addr] = route - - // Return the boxPubKey - return route.destination, nil - } - } - - // No route was found if we got to this point - return crypto.BoxPubKey{}, fmt.Errorf("no route to %s", ip.String()) -} - -// Removes a source subnet, which allows traffic with these source addresses to -// be tunnelled using crypto-key routing. -func (c *cryptokey) removeLocalSubnet(cidr string) error { - c.mutexlocals.Lock() - defer c.mutexlocals.Unlock() - - // Is the CIDR we've been given valid? - _, ipnet, err := net.ParseCIDR(cidr) - if err != nil { - return err - } - - // Get the prefix length and size - _, prefixsize := ipnet.Mask.Size() - - // Build our references to the routing sources - var routingsources *[]net.IPNet - - // Check if the prefix is IPv4 or IPv6 - if prefixsize == net.IPv6len*8 { - routingsources = &c.ipv6locals - } else if prefixsize == net.IPv4len*8 { - routingsources = &c.ipv4locals - } else { - return errors.New("unexpected prefix size") - } - - // Check if we already have this CIDR - for idx, subnet := range *routingsources { - if subnet.String() == ipnet.String() { - *routingsources = append((*routingsources)[:idx], (*routingsources)[idx+1:]...) - c.tun.log.Infoln("Removed CKR local subnet", cidr) - return nil - } - } - return errors.New("local subnet not found") -} - -// Removes a destination route for the given CIDR to be tunnelled to the node -// with the given BoxPubKey. -func (c *cryptokey) removeRemoteSubnet(cidr string, dest string) error { - c.mutexremotes.Lock() - c.mutexcaches.Lock() - defer c.mutexremotes.Unlock() - defer c.mutexcaches.Unlock() - - // Is the CIDR we've been given valid? - _, ipnet, err := net.ParseCIDR(cidr) - if err != nil { - return err - } - - // Get the prefix length and size - _, prefixsize := ipnet.Mask.Size() - - // Build our references to the routing table and cache - var routingtable *[]cryptokey_route - var routingcache *map[address.Address]cryptokey_route - - // Check if the prefix is IPv4 or IPv6 - if prefixsize == net.IPv6len*8 { - routingtable = &c.ipv6remotes - routingcache = &c.ipv6cache - } else if prefixsize == net.IPv4len*8 { - routingtable = &c.ipv4remotes - routingcache = &c.ipv4cache - } else { - return errors.New("unexpected prefix size") - } - - // Decode the public key - bpk, err := hex.DecodeString(dest) - if err != nil { - return err - } else if len(bpk) != crypto.BoxPubKeyLen { - return fmt.Errorf("incorrect key length for %s", dest) - } - netStr := ipnet.String() - - for idx, route := range *routingtable { - if bytes.Equal(route.destination[:], bpk) && route.subnet.String() == netStr { - *routingtable = append((*routingtable)[:idx], (*routingtable)[idx+1:]...) - for k := range *routingcache { - delete(*routingcache, k) - } - c.tun.log.Infof("Removed CKR remote subnet %s via %s\n", cidr, dest) - return nil - } - } - return fmt.Errorf("route does not exists for %s", cidr) -} diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 8c8cbf19..1cc1fde5 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -40,7 +40,6 @@ type TunAdapter struct { log *log.Logger addr address.Address subnet address.Subnet - ckr cryptokey mtu MTU iface tun.Device phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below @@ -146,7 +145,6 @@ func (tun *TunAdapter) _start() error { } // TODO tun.core.SetMaximumSessionMTU(tun.MTU()) tun.isOpen = true - tun.ckr.init(tun) go tun.read() go tun.write() return nil From 85fae23919b61ea138b65e035349a98e1be46763 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 15 May 2021 15:00:12 -0500 Subject: [PATCH 0666/1109] remove TunnelRouting from config, remove Signing from key names --- src/config/config.go | 25 +++++++------------------ src/yggdrasil/core.go | 5 +++-- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/config/config.go b/src/config/config.go index b97ad60a..91507baa 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -68,13 +68,12 @@ type NodeConfig struct { 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. To disable\nthe admin socket, use the value \"none\" instead."` MulticastInterfaces []string `comment:"Regular expressions for which interfaces multicast peer discovery\nshould be enabled on. If none specified, multicast peer discovery is\ndisabled. The default value is .* which uses all interfaces."` AllowedPublicKeys []string `comment:"List of peer encryption public keys to allow incoming TCP peering\nconnections from. If left empty/undefined then all connections will\nbe allowed by default. This does not affect outgoing peerings, nor\ndoes it affect link-local peers discovered via multicast."` - SigningPublicKey string `comment:"Your public signing key. You should not ordinarily need to share\nthis with anyone."` - SigningPrivateKey string `comment:"Your private signing key. DO NOT share this with anyone!"` + PublicKey string `comment:"Your public signing key. Your peers may ask you for this to put\ninto their AllowedPublicKeys configuration."` + PrivateKey string `comment:"Your private signing key. DO NOT share this with anyone!"` LinkLocalTCPPort uint16 `comment:"The port number to be used for the link-local TCP listeners for the\nconfigured MulticastInterfaces. This option does not affect listeners\nspecified in the Listen option. Unless you plan to firewall link-local\ntraffic, it is best to leave this as the default value of 0. This\noption cannot currently be changed by reloading config during runtime."` IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."` IfMTU MTU `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` SessionFirewall SessionFirewall `comment:"The session firewall controls who can send/receive network traffic\nto/from. This is useful if you want to protect this node without\nresorting to using a real firewall. This does not affect traffic\nbeing routed via this node to somewhere else. Rules are prioritised as\nfollows: blacklist, whitelist, always allow outgoing, direct, remote."` - TunnelRouting TunnelRouting `comment:"Allow tunneling non-Yggdrasil traffic over Yggdrasil. This effectively\nallows you to use Yggdrasil to route to, or to bridge other networks,\nsimilar to a VPN tunnel. Tunnelling works between any two nodes and\ndoes not require them to be directly peered."` NodeInfoPrivacy bool `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."` NodeInfo map[string]interface{} `comment:"Optional node info. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."` } @@ -89,16 +88,6 @@ type SessionFirewall struct { BlacklistPublicKeys []string `comment:"List of public keys from which network traffic is always rejected,\nregardless of the whitelist, AllowFromDirect or AllowFromRemote."` } -// TunnelRouting contains the crypto-key routing tables for tunneling regular -// IPv4 or IPv6 subnets across the Yggdrasil network. -type TunnelRouting struct { - Enable bool `comment:"Enable or disable tunnel routing."` - IPv6RemoteSubnets map[string]string `comment:"IPv6 subnets belonging to remote nodes, mapped to the node's public\nkey, e.g. { \"aaaa:bbbb:cccc::/e\": \"boxpubkey\", ... }"` - IPv6LocalSubnets []string `comment:"IPv6 subnets belonging to this node's end of the tunnels. Only traffic\nfrom these ranges (or the Yggdrasil node's IPv6 address/subnet)\nwill be tunnelled."` - IPv4RemoteSubnets map[string]string `comment:"IPv4 subnets belonging to remote nodes, mapped to the node's public\nkey, e.g. { \"a.b.c.d/e\": \"boxpubkey\", ... }"` - IPv4LocalSubnets []string `comment:"IPv4 subnets belonging to this node's end of the tunnels. Only traffic\nfrom these ranges will be tunnelled."` -} - // Generates default configuration and returns a pointer to the resulting // NodeConfig. This is used when outputting the -genconf parameter and also when // using -autoconf. @@ -109,8 +98,8 @@ func GenerateConfig() *NodeConfig { cfg := NodeConfig{} cfg.Listen = []string{} cfg.AdminListen = defaults.GetDefaults().DefaultAdminListen - cfg.SigningPublicKey = hex.EncodeToString(spub[:]) - cfg.SigningPrivateKey = hex.EncodeToString(spriv[:]) + cfg.PublicKey = hex.EncodeToString(spub[:]) + cfg.PrivateKey = hex.EncodeToString(spriv[:]) cfg.Peers = []string{} cfg.InterfacePeers = map[string][]string{} cfg.AllowedPublicKeys = []string{} @@ -129,8 +118,8 @@ func GenerateConfig() *NodeConfig { // NewSigningKeys replaces the signing keypair in the NodeConfig with a new // signing keypair. The signing keys are used by the switch to derive the // structure of the spanning tree. -func (cfg *NodeConfig) NewSigningKeys() { +func (cfg *NodeConfig) NewKeys() { spub, spriv := crypto.NewSigKeys() - cfg.SigningPublicKey = hex.EncodeToString(spub[:]) - cfg.SigningPrivateKey = hex.EncodeToString(spriv[:]) + cfg.PublicKey = hex.EncodeToString(spub[:]) + cfg.PrivateKey = hex.EncodeToString(spriv[:]) } diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 0054db82..0d41f711 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -43,16 +43,17 @@ func (c *Core) _init() error { current := c.config.GetCurrent() - sigPriv, err := hex.DecodeString(current.SigningPrivateKey) + sigPriv, err := hex.DecodeString(current.PrivateKey) if err != nil { return err } if len(sigPriv) < ed25519.PrivateKeySize { - return errors.New("SigningPrivateKey is incorrect length") + return errors.New("PrivateKey is incorrect length") } c.secret = ed25519.PrivateKey(sigPriv) c.public = c.secret.Public().(ed25519.PublicKey) + // TODO check public against current.PublicKey, error if they don't match c.PacketConn, err = iw.NewPacketConn(c.secret) return err From 7e10025ef078b9d12b9f56d25f05e27d6ef04f3c Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 15 May 2021 15:16:35 -0500 Subject: [PATCH 0667/1109] get minimal admin socket working (introspection only, no ability to add peers etc) --- src/admin/admin.go | 264 ++++++--------------------------------------- 1 file changed, 30 insertions(+), 234 deletions(-) diff --git a/src/admin/admin.go b/src/admin/admin.go index 83f330ed..a702adfd 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -1,7 +1,7 @@ package admin import ( - //"encoding/hex" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -15,11 +15,11 @@ import ( "github.com/gologme/log" - //"github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/config" //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" //"github.com/yggdrasil-network/yggdrasil-go/src/util" - //"github.com/yggdrasil-network/yggdrasil-go/src/version" + "github.com/yggdrasil-network/yggdrasil-go/src/version" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) @@ -72,17 +72,18 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. } func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { - /* TODO a.AddHandler("getSelf", []string{}, func(in Info) (Info, error) { ip := a.core.Address().String() subnet := a.core.Subnet() + self := a.core.GetSelf() return Info{ "self": Info{ ip: Info{ // TODO"box_pub_key": a.core.EncryptionPublicKey(), "build_name": version.BuildName(), "build_version": version.BuildVersion(), - "coords": fmt.Sprintf("%v", a.core.Coords()), + "key": hex.EncodeToString(self.Key[:]), + "coords": fmt.Sprintf("%v", self.Coords), "subnet": subnet.String(), }, }, @@ -91,245 +92,40 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { a.AddHandler("getPeers", []string{}, func(in Info) (Info, error) { peers := make(Info) for _, p := range a.core.GetPeers() { - panic("TODO") - addr := new(address.Address) // TODO *address.AddrForNodeID(crypto.GetNodeID(&p.PublicKey)) + addr := address.AddrForKey(p.Key) so := net.IP(addr[:]).String() peers[so] = Info{ - "port": p.Port, - "uptime": p.Uptime.Seconds(), - "bytes_sent": p.BytesSent, - "bytes_recvd": p.BytesRecvd, - "proto": p.Protocol, - "endpoint": p.Endpoint, - "box_pub_key": hex.EncodeToString(p.PublicKey[:]), + "key": hex.EncodeToString(p.Key[:]), + "port": p.Port, + "coords": fmt.Sprintf("%v", p.Coords), } } return Info{"peers": peers}, nil }) - a.AddHandler("getSwitchPeers", []string{}, func(in Info) (Info, error) { - switchpeers := make(Info) - for _, s := range a.core.GetSwitchPeers() { - panic("TODO") - addr := new(address.Address) // TODO *address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey)) - so := fmt.Sprint(s.Port) - switchpeers[so] = Info{ - "ip": net.IP(addr[:]).String(), - "coords": fmt.Sprintf("%v", s.Coords), - "port": s.Port, - "bytes_sent": s.BytesSent, - "bytes_recvd": s.BytesRecvd, - "proto": s.Protocol, - "endpoint": s.Endpoint, - "box_pub_key": hex.EncodeToString(s.PublicKey[:]), + a.AddHandler("getDHT", []string{}, func(in Info) (Info, error) { + dht := make(Info) + for _, d := range a.core.GetDHT() { + addr := address.AddrForKey(d.Key) + so := net.IP(addr[:]).String() + dht[so] = Info{ + "key": hex.EncodeToString(d.Key[:]), + "port": fmt.Sprintf("%v", d.Port), + "next": fmt.Sprintf("%v", d.Next), } } - return Info{"switchpeers": switchpeers}, nil + return Info{"dht": dht}, nil }) - /* - a.AddHandler("getSwitchQueues", []string{}, func(in Info) (Info, error) { - queues := a.core.GetSwitchQueues() - return Info{"switchqueues": queues.asMap()}, nil - }) - */ - /* - a.AddHandler("getDHT", []string{}, func(in Info) (Info, error) { - dht := make(Info) - for _, d := range a.core.GetDHT() { - panic("TODO") - addr := new(address.Address) // TODO *address.AddrForNodeID(crypto.GetNodeID(&d.PublicKey)) - so := net.IP(addr[:]).String() - dht[so] = Info{ - "coords": fmt.Sprintf("%v", d.Coords), - "last_seen": d.LastSeen.Seconds(), - "box_pub_key": hex.EncodeToString(d.PublicKey[:]), - } + a.AddHandler("getSessions", []string{}, func(in Info) (Info, error) { + sessions := make(Info) + for _, s := range a.core.GetSessions() { + addr := address.AddrForKey(s.Key) + so := net.IP(addr[:]).String() + sessions[so] = Info{ + "key": hex.EncodeToString(s.Key[:]), } - return Info{"dht": dht}, nil - }) - a.AddHandler("getSessions", []string{}, func(in Info) (Info, error) { - sessions := make(Info) - for _, s := range a.core.GetSessions() { - panic("TODO") - addr := new(address.Address) //*address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey)) - so := net.IP(addr[:]).String() - sessions[so] = Info{ - "coords": fmt.Sprintf("%v", s.Coords), - "bytes_sent": s.BytesSent, - "bytes_recvd": s.BytesRecvd, - "mtu": s.MTU, - "uptime": s.Uptime.Seconds(), - "was_mtu_fixed": s.WasMTUFixed, - "box_pub_key": hex.EncodeToString(s.PublicKey[:]), - } - } - return Info{"sessions": sessions}, nil - }) - a.AddHandler("addPeer", []string{"uri", "[interface]"}, func(in Info) (Info, error) { - // Set sane defaults - intf := "" - // Has interface been specified? - if itf, ok := in["interface"]; ok { - intf = itf.(string) - } - if a.core.AddPeer(in["uri"].(string), intf) == nil { - return Info{ - "added": []string{ - in["uri"].(string), - }, - }, nil - } - return Info{ - "not_added": []string{ - in["uri"].(string), - }, - }, errors.New("Failed to add peer") - }) - a.AddHandler("disconnectPeer", []string{"port"}, func(in Info) (Info, error) { - port, err := strconv.ParseInt(fmt.Sprint(in["port"]), 10, 64) - if err != nil { - return Info{}, err - } - if a.core.DisconnectPeer(uint64(port)) == nil { - return Info{ - "disconnected": []string{ - fmt.Sprint(port), - }, - }, nil - } else { - return Info{ - "not_disconnected": []string{ - fmt.Sprint(port), - }, - }, errors.New("Failed to disconnect peer") - } - }) - a.AddHandler("removePeer", []string{"uri", "[interface]"}, func(in Info) (Info, error) { - // Set sane defaults - intf := "" - // Has interface been specified? - if itf, ok := in["interface"]; ok { - intf = itf.(string) - } - if a.core.RemovePeer(in["uri"].(string), intf) == nil { - return Info{ - "removed": []string{ - in["uri"].(string), - }, - }, nil - } else { - return Info{ - "not_removed": []string{ - in["uri"].(string), - }, - }, errors.New("Failed to remove peer") - } - return Info{ - "not_removed": []string{ - in["uri"].(string), - }, - }, errors.New("Failed to remove peer") - }) - a.AddHandler("getAllowedEncryptionPublicKeys", []string{}, func(in Info) (Info, error) { - return Info{"allowed_box_pubs": a.core.GetAllowedEncryptionPublicKeys()}, nil - }) - a.AddHandler("addAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) { - if a.core.AddAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { - return Info{ - "added": []string{ - in["box_pub_key"].(string), - }, - }, nil - } - return Info{ - "not_added": []string{ - in["box_pub_key"].(string), - }, - }, errors.New("Failed to add allowed key") - }) - a.AddHandler("removeAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) { - if a.core.RemoveAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { - return Info{ - "removed": []string{ - in["box_pub_key"].(string), - }, - }, nil - } - return Info{ - "not_removed": []string{ - in["box_pub_key"].(string), - }, - }, errors.New("Failed to remove allowed key") - }) - a.AddHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in Info) (Info, error) { - var reserr error - var result yggdrasil.DHTRes - if in["target"] == nil { - in["target"] = "none" - } - coords := util.DecodeCoordString(in["coords"].(string)) - var boxPubKey crypto.BoxPubKey - if b, err := hex.DecodeString(in["box_pub_key"].(string)); err == nil { - copy(boxPubKey[:], b) - if n, err := hex.DecodeString(in["target"].(string)); err == nil { - var targetNodeID crypto.NodeID - copy(targetNodeID[:], n) - result, reserr = a.core.DHTPing(boxPubKey, coords, &targetNodeID) - } else { - result, reserr = a.core.DHTPing(boxPubKey, coords, nil) - } - } else { - return Info{}, err - } - if reserr != nil { - return Info{}, reserr - } - infos := make(map[string]map[string]string, len(result.Infos)) - for _, dinfo := range result.Infos { - info := map[string]string{ - "box_pub_key": hex.EncodeToString(dinfo.PublicKey[:]), - "coords": fmt.Sprintf("%v", dinfo.Coords), - } - panic("TODO") - addr := "" //net.IP(address.AddrForNodeID(crypto.GetNodeID(&dinfo.PublicKey))[:]).String() - infos[addr] = info - } - return Info{"nodes": infos}, nil - }) - a.AddHandler("getNodeInfo", []string{"[box_pub_key]", "[coords]", "[nocache]"}, func(in Info) (Info, error) { - var nocache bool - if in["nocache"] != nil { - nocache = in["nocache"].(string) == "true" - } - var boxPubKey crypto.BoxPubKey - var coords []uint64 - if in["box_pub_key"] == nil && in["coords"] == nil { - nodeinfo := a.core.MyNodeInfo() - var jsoninfo interface{} - if err := json.Unmarshal(nodeinfo, &jsoninfo); err != nil { - return Info{}, err - } - return Info{"nodeinfo": jsoninfo}, nil - } else if in["box_pub_key"] == nil || in["coords"] == nil { - return Info{}, errors.New("Expecting both box_pub_key and coords") - } else { - if b, err := hex.DecodeString(in["box_pub_key"].(string)); err == nil { - copy(boxPubKey[:], b) - } else { - return Info{}, err - } - coords = util.DecodeCoordString(in["coords"].(string)) - } - result, err := a.core.GetNodeInfo(boxPubKey, coords, nocache) - if err == nil { - var m map[string]interface{} - if err = json.Unmarshal(result, &m); err == nil { - return Info{"nodeinfo": m}, nil - } - return Info{}, err - } - return Info{}, err - }) - */ + } + return Info{"sessions": sessions}, nil + }) } // Start runs the admin API socket to listen for / respond to admin API calls. From 5b00273dfcaed74160cc690b994cfe6d93f42a23 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 15 May 2021 15:55:47 -0500 Subject: [PATCH 0668/1109] move sessionfirewall into the tuntap. this needs testing. the name is also slightly wrong, since a crypto session can still be set up, packets are just accepted/rejected at the tun/tap level instead --- cmd/yggdrasil/main.go | 32 ++++++-------------------------- src/tuntap/iface.go | 3 +++ src/tuntap/keystore.go | 12 ++++++++++++ src/tuntap/tun.go | 9 ++++++++- 4 files changed, 29 insertions(+), 27 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index f3158250..11310f7f 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "crypto/ed25519" "encoding/hex" "encoding/json" "flag" @@ -277,11 +278,11 @@ func main() { panic(err) } // Register the session firewall gatekeeper function - // TODO n.core.SetSessionGatekeeper(n.sessionFirewall) // Allocate our modules n.admin = &admin.AdminSocket{} n.multicast = &multicast.Multicast{} n.tuntap = &tuntap.TunAdapter{} + n.tuntap.(*tuntap.TunAdapter).SetSessionGatekeeper(n.sessionFirewall) // Start the admin socket n.admin.Init(&n.core, n.state, logger, nil) if err := n.admin.Start(); err != nil { @@ -300,21 +301,6 @@ func main() { logger.Errorln("An error occurred starting TUN/TAP:", err) } n.tuntap.SetupAdminHandlers(n.admin.(*admin.AdminSocket)) - /* - if listener, err := n.core.ConnListen(); err == nil { - if dialer, err := n.core.ConnDialer(); err == nil { - n.tuntap.Init(&n.core, n.state, logger, tuntap.TunOptions{Listener: listener, Dialer: dialer}) - if err := n.tuntap.Start(); err != nil { - logger.Errorln("An error occurred starting TUN/TAP:", err) - } - n.tuntap.SetupAdminHandlers(n.admin.(*admin.AdminSocket)) - } else { - logger.Errorln("Unable to get Dialer:", err) - } - } else { - logger.Errorln("Unable to get Listener:", err) - } - */ // Make some nice output that tells us what our IPv6 address and subnet are. // This is just logged to stdout for the user. address := n.core.Address() @@ -337,7 +323,7 @@ func (n *node) shutdown() { n.core.Stop() } -func (n *node) sessionFirewall(pubkey *crypto.BoxPubKey, initiator bool) bool { +func (n *node) sessionFirewall(pubkey ed25519.PublicKey, initiator bool) bool { n.state.Mutex.RLock() defer n.state.Mutex.RUnlock() @@ -346,14 +332,11 @@ func (n *node) sessionFirewall(pubkey *crypto.BoxPubKey, initiator bool) bool { return true } - // Prepare for checking whitelist/blacklist - var box crypto.BoxPubKey // Reject blacklisted nodes for _, b := range n.state.Current.SessionFirewall.BlacklistPublicKeys { key, err := hex.DecodeString(b) if err == nil { - copy(box[:crypto.BoxPubKeyLen], key) - if box == *pubkey { + if bytes.Equal(key, pubkey) { return false } } @@ -363,8 +346,7 @@ func (n *node) sessionFirewall(pubkey *crypto.BoxPubKey, initiator bool) bool { for _, b := range n.state.Current.SessionFirewall.WhitelistPublicKeys { key, err := hex.DecodeString(b) if err == nil { - copy(box[:crypto.BoxPubKeyLen], key) - if box == *pubkey { + if bytes.Equal(key, pubkey) { return true } } @@ -379,14 +361,12 @@ func (n *node) sessionFirewall(pubkey *crypto.BoxPubKey, initiator bool) bool { // Look and see if the pubkey is that of a direct peer var isDirectPeer bool - /* TODO for _, peer := range n.core.GetPeers() { - if peer.PublicKey == *pubkey { + if bytes.Equal(peer.Key[:], pubkey[:]) { isDirectPeer = true break } } - */ // Allow direct peers if appropriate if n.state.Current.SessionFirewall.AllowFromDirect && isDirectPeer { diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index af76dac4..d2680d0d 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -93,6 +93,9 @@ func (tun *TunAdapter) write() { continue // bad local address/subnet } info := tun.store.update(ed25519.PublicKey(from.(iwt.Addr))) + if info == nil { + continue // Blocked by the gatekeeper + } if srcAddr != info.address && srcSubnet != info.subnet { continue // bad remote address/subnet } diff --git a/src/tuntap/keystore.go b/src/tuntap/keystore.go index ddcf50c4..a930630c 100644 --- a/src/tuntap/keystore.go +++ b/src/tuntap/keystore.go @@ -115,6 +115,18 @@ func (k *keyStore) update(key ed25519.PublicKey) *keyInfo { info.address = *address.AddrForKey(ed25519.PublicKey(info.key[:])) info.subnet = *address.SubnetForKey(ed25519.PublicKey(info.key[:])) info.mtu = MTU(^uint16(0)) // TODO + var isOutgoing bool + if k.addrBuffer[info.address] != nil { + isOutgoing = true + } + if k.subnetBuffer[info.subnet] != nil { + isOutgoing = true + } + if !k.tun.gatekeeper(key, isOutgoing) { + // Blocked by the gatekeeper, so don't create an entry for this + k.mutex.Unlock() + return nil + } k.keyToInfo[info.key] = info k.addrToInfo[info.address] = info k.subnetToInfo[info.subnet] = info diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 1cc1fde5..79736a01 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -44,7 +44,14 @@ type TunAdapter struct { iface tun.Device phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below //mutex sync.RWMutex // Protects the below - isOpen bool + isOpen bool + gatekeeper func(pubkey ed25519.PublicKey, initiator bool) bool +} + +func (tun *TunAdapter) SetSessionGatekeeper(gatekeeper func(pubkey ed25519.PublicKey, initiator bool) bool) { + phony.Block(tun, func() { + tun.gatekeeper = gatekeeper + }) } // Gets the maximum supported MTU for the platform based on the defaults in From f61507238e62c62e8e72e0e6bbd3c58dc52889f2 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 15 May 2021 16:23:44 -0500 Subject: [PATCH 0669/1109] cleanup unused MTU code from tun keystore --- src/tuntap/keystore.go | 2 -- src/tuntap/tun.go | 1 - 2 files changed, 3 deletions(-) diff --git a/src/tuntap/keystore.go b/src/tuntap/keystore.go index a930630c..7974874c 100644 --- a/src/tuntap/keystore.go +++ b/src/tuntap/keystore.go @@ -28,7 +28,6 @@ type keyInfo struct { key keyArray address address.Address subnet address.Subnet - mtu MTU // TODO use this timeout *time.Timer // From calling a time.AfterFunc to do cleanup } @@ -114,7 +113,6 @@ func (k *keyStore) update(key ed25519.PublicKey) *keyInfo { info.key = kArray info.address = *address.AddrForKey(ed25519.PublicKey(info.key[:])) info.subnet = *address.SubnetForKey(ed25519.PublicKey(info.key[:])) - info.mtu = MTU(^uint16(0)) // TODO var isOutgoing bool if k.addrBuffer[info.address] != nil { isOutgoing = true diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 79736a01..e8f7fcbb 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -150,7 +150,6 @@ func (tun *TunAdapter) _start() error { if tun.MTU() != mtu { tun.log.Warnf("Warning: Interface MTU %d automatically adjusted to %d (supported range is 1280-%d)", current.IfMTU, tun.MTU(), MaximumMTU()) } - // TODO tun.core.SetMaximumSessionMTU(tun.MTU()) tun.isOpen = true go tun.read() go tun.write() From dfca87ba80e8e8fa6855dd60ca6d498955dc825c Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 15 May 2021 16:44:56 -0500 Subject: [PATCH 0670/1109] start a reader to disard traffic if the tun is disabled --- src/tuntap/tun.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index e8f7fcbb..6d41f59b 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -138,6 +138,15 @@ func (tun *TunAdapter) _start() error { addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1) if current.IfName == "none" || current.IfName == "dummy" { tun.log.Debugln("Not starting TUN as ifname is none or dummy") + go func() { + bs := make([]byte, tun.core.PacketConn.MTU()) + for { + // Dump traffic to nowhere + if _, _, err := tun.core.PacketConn.ReadFrom(bs); err != nil { + return + } + } + }() return nil } mtu := current.IfMTU From 2c7b22db920239ca4845158a82e1688192602c95 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 16 May 2021 13:01:54 -0500 Subject: [PATCH 0671/1109] allow for multiple traffic types inside the session at the tuntap level, only implement typeSessionTraffic for now --- src/tuntap/iface.go | 20 +++++++++++++++++--- src/tuntap/tun.go | 14 +++++++------- src/tuntap/types.go | 14 ++++++++++++++ 3 files changed, 38 insertions(+), 10 deletions(-) create mode 100644 src/tuntap/types.go diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index d2680d0d..ff91cf17 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -50,6 +50,8 @@ func (tun *TunAdapter) read() { if srcAddr != tun.addr && srcSubnet != tun.subnet { continue // Wrong soruce address } + bs = buf[begin-1 : end] + bs[0] = typeSessionTraffic if dstAddr.IsValid() { tun.store.sendToAddress(dstAddr, bs) } else if dstSubnet.IsValid() { @@ -61,12 +63,24 @@ func (tun *TunAdapter) read() { func (tun *TunAdapter) write() { var buf [TUN_OFFSET_BYTES + 65535]byte for { - bs := buf[TUN_OFFSET_BYTES:] + bs := buf[TUN_OFFSET_BYTES-1:] n, from, err := tun.core.ReadFrom(bs) if err != nil { return } - bs = bs[:n] + if n == 0 { + continue + } + switch bs[0] { + case typeSessionTraffic: + // This is what we want to handle here + default: + continue + } + bs = bs[1:n] + if len(bs) == 0 { + continue + } if bs[0]&0xf0 != 0x60 { continue // not IPv6 } @@ -99,7 +113,7 @@ func (tun *TunAdapter) write() { if srcAddr != info.address && srcSubnet != info.subnet { continue // bad remote address/subnet } - bs = buf[:TUN_OFFSET_BYTES+n] + bs = buf[:TUN_OFFSET_BYTES+len(bs)] n, err = tun.iface.Write(bs, TUN_OFFSET_BYTES) if err != nil { tun.Act(nil, func() { diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 6d41f59b..4492784d 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -150,8 +150,8 @@ func (tun *TunAdapter) _start() error { return nil } mtu := current.IfMTU - if tun.core.MTU() < uint64(mtu) { - mtu = MTU(tun.core.MTU()) + if tun.maxSessionMTU() < mtu { + mtu = tun.maxSessionMTU() } if err := tun.setup(current.IfName, addr, mtu); err != nil { return err @@ -216,11 +216,6 @@ func (tun *TunAdapter) oobHandler(fromKey, toKey ed25519.PublicKey, data []byte) } } -const ( - typeKeyLookup = 1 - typeKeyResponse = 2 -) - func (tun *TunAdapter) sendKeyLookup(partial ed25519.PublicKey) { sig := ed25519.Sign(tun.core.PrivateKey(), partial[:]) bs := append([]byte{typeKeyLookup}, sig...) @@ -232,3 +227,8 @@ func (tun *TunAdapter) sendKeyResponse(dest ed25519.PublicKey) { bs := append([]byte{typeKeyResponse}, sig...) tun.core.SendOutOfBand(dest, bs) } + +func (tun *TunAdapter) maxSessionMTU() MTU { + const sessionTypeOverhead = 1 + return MTU(tun.core.MTU() - sessionTypeOverhead) +} diff --git a/src/tuntap/types.go b/src/tuntap/types.go new file mode 100644 index 00000000..b503e647 --- /dev/null +++ b/src/tuntap/types.go @@ -0,0 +1,14 @@ +package tuntap + +// Out-of-band packet types +const ( + typeKeyDummy = iota + typeKeyLookup + typeKeyResponse +) + +// In-band packet types +const ( + typeSessionDummy = iota + typeSessionTraffic +) From 2d01386d6e821350ee396e703e389b429573bd6a Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 16 May 2021 19:51:09 +0100 Subject: [PATCH 0672/1109] Refactor admin socket, export request/response structs, remove types package --- src/admin/admin.go | 235 ++++++++++++++++----------------------- src/admin/error.go | 5 + src/admin/getdht.go | 34 ++++++ src/admin/getpeers.go | 35 ++++++ src/admin/getself.go | 31 ++++++ src/admin/getsessions.go | 30 +++++ src/config/config.go | 5 +- src/defaults/defaults.go | 6 +- src/multicast/admin.go | 33 +++++- src/tuntap/admin.go | 70 ++++-------- src/tuntap/tun.go | 15 ++- src/tuntap/tun_darwin.go | 6 +- src/types/types.go | 3 - 13 files changed, 290 insertions(+), 218 deletions(-) create mode 100644 src/admin/error.go create mode 100644 src/admin/getdht.go create mode 100644 src/admin/getpeers.go create mode 100644 src/admin/getself.go create mode 100644 src/admin/getsessions.go delete mode 100644 src/types/types.go diff --git a/src/admin/admin.go b/src/admin/admin.go index a702adfd..855b0545 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -1,7 +1,6 @@ package admin import ( - "encoding/hex" "encoding/json" "errors" "fmt" @@ -9,17 +8,12 @@ import ( "net/url" "os" - //"strconv" "strings" "time" "github.com/gologme/log" - "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/config" - //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" - //"github.com/yggdrasil-network/yggdrasil-go/src/util" - "github.com/yggdrasil-network/yggdrasil-go/src/version" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) @@ -34,16 +28,26 @@ type AdminSocket struct { started bool } -// Info refers to information that is returned to the admin socket handler. -type Info map[string]interface{} +type AdminSocketResponse struct { + Status string `json:"status"` + Request struct { + Name string `json:"request"` + KeepAlive bool `json:"keepalive"` + } `json:"request"` + Response interface{} `json:"response"` +} type handler struct { - args []string // List of human-readable argument names - handler func(Info) (Info, error) // First is input map, second is output + args []string // List of human-readable argument names + handler func(json.RawMessage) (interface{}, error) // First is input map, second is output +} + +type ListResponse struct { + List map[string][]string `json:"list"` } // AddHandler is called for each admin function to add the handler and help documentation to the API. -func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc func(Info) (Info, error)) error { +func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc func(json.RawMessage) (interface{}, error)) error { if _, ok := a.handlers[strings.ToLower(name)]; ok { return errors.New("handler already exists") } @@ -61,70 +65,60 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. a.handlers = make(map[string]handler) current := state.GetCurrent() a.listenaddr = current.AdminListen - a.AddHandler("list", []string{}, func(in Info) (Info, error) { - handlers := make(map[string]interface{}) - for handlername, handler := range a.handlers { - handlers[handlername] = Info{"fields": handler.args} + _ = a.AddHandler("list", []string{}, func(_ json.RawMessage) (interface{}, error) { + res := &ListResponse{} + for name, handler := range a.handlers { + res.List[name] = handler.args } - return Info{"list": handlers}, nil + return res, nil }) return nil } func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { - a.AddHandler("getSelf", []string{}, func(in Info) (Info, error) { - ip := a.core.Address().String() - subnet := a.core.Subnet() - self := a.core.GetSelf() - return Info{ - "self": Info{ - ip: Info{ - // TODO"box_pub_key": a.core.EncryptionPublicKey(), - "build_name": version.BuildName(), - "build_version": version.BuildVersion(), - "key": hex.EncodeToString(self.Key[:]), - "coords": fmt.Sprintf("%v", self.Coords), - "subnet": subnet.String(), - }, - }, - }, nil - }) - a.AddHandler("getPeers", []string{}, func(in Info) (Info, error) { - peers := make(Info) - for _, p := range a.core.GetPeers() { - addr := address.AddrForKey(p.Key) - so := net.IP(addr[:]).String() - peers[so] = Info{ - "key": hex.EncodeToString(p.Key[:]), - "port": p.Port, - "coords": fmt.Sprintf("%v", p.Coords), - } + _ = a.AddHandler("getSelf", []string{}, func(in json.RawMessage) (interface{}, error) { + req := &GetSelfRequest{} + res := &GetSelfResponse{} + if err := json.Unmarshal(in, &req); err != nil { + return nil, err } - return Info{"peers": peers}, nil - }) - a.AddHandler("getDHT", []string{}, func(in Info) (Info, error) { - dht := make(Info) - for _, d := range a.core.GetDHT() { - addr := address.AddrForKey(d.Key) - so := net.IP(addr[:]).String() - dht[so] = Info{ - "key": hex.EncodeToString(d.Key[:]), - "port": fmt.Sprintf("%v", d.Port), - "next": fmt.Sprintf("%v", d.Next), - } + if err := a.getSelfHandler(req, res); err != nil { + return nil, err } - return Info{"dht": dht}, nil + return res, nil }) - a.AddHandler("getSessions", []string{}, func(in Info) (Info, error) { - sessions := make(Info) - for _, s := range a.core.GetSessions() { - addr := address.AddrForKey(s.Key) - so := net.IP(addr[:]).String() - sessions[so] = Info{ - "key": hex.EncodeToString(s.Key[:]), - } + _ = a.AddHandler("getPeers", []string{}, func(in json.RawMessage) (interface{}, error) { + req := &GetPeersRequest{} + res := &GetPeersResponse{} + if err := json.Unmarshal(in, &req); err != nil { + return nil, err } - return Info{"sessions": sessions}, nil + if err := a.getPeersHandler(req, res); err != nil { + return nil, err + } + return res, nil + }) + _ = a.AddHandler("getDHT", []string{}, func(in json.RawMessage) (interface{}, error) { + req := &GetDHTRequest{} + res := &GetDHTResponse{} + if err := json.Unmarshal(in, &req); err != nil { + return nil, err + } + if err := a.getDHTHandler(req, res); err != nil { + return nil, err + } + return res, nil + }) + _ = a.AddHandler("getSessions", []string{}, func(in json.RawMessage) (interface{}, error) { + req := &GetSessionsRequest{} + res := &GetSessionsResponse{} + if err := json.Unmarshal(in, &req); err != nil { + return nil, err + } + if err := a.getSessionsHandler(req, res); err != nil { + return nil, err + } + return res, nil }) } @@ -209,20 +203,20 @@ func (a *AdminSocket) listen() { // handleRequest calls the request handler for each request sent to the admin API. func (a *AdminSocket) handleRequest(conn net.Conn) { decoder := json.NewDecoder(conn) + decoder.DisallowUnknownFields() + encoder := json.NewEncoder(conn) encoder.SetIndent("", " ") - recv := make(Info) - send := make(Info) + + defer conn.Close() defer func() { r := recover() if r != nil { - send = Info{ - "status": "error", - "error": "Check your syntax and input types", - } a.log.Debugln("Admin socket error:", r) - if err := encoder.Encode(&send); err != nil { + if err := encoder.Encode(&ErrorResponse{ + Error: "Check your syntax and input types", + }); err != nil { a.log.Debugln("Admin socket JSON encode error:", err) } conn.Close() @@ -230,83 +224,40 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { }() for { - // Start with a clean slate on each request - recv = Info{} - send = Info{} - - // Decode the input - if err := decoder.Decode(&recv); err != nil { - a.log.Debugln("Admin socket JSON decode error:", err) - return - } - - // Send the request back with the response, and default to "error" - // unless the status is changed below by one of the handlers - send["request"] = recv - send["status"] = "error" - - n := strings.ToLower(recv["request"].(string)) - - if _, ok := recv["request"]; !ok { - send["error"] = "No request sent" - goto respond - } - - if h, ok := a.handlers[n]; ok { - // Check that we have all the required arguments - for _, arg := range h.args { - // An argument in [square brackets] is optional and not required, - // so we can safely ignore those - if strings.HasPrefix(arg, "[") && strings.HasSuffix(arg, "]") { - continue + var err error + var buf json.RawMessage + _ = decoder.Decode(&buf) + var resp AdminSocketResponse + resp.Status = "success" + if err = json.Unmarshal(buf, &resp.Request); err == nil { + if resp.Request.Name == "" { + resp.Status = "error" + resp.Response = &ErrorResponse{ + Error: "No request specified", } - // Check if the field is missing - if _, ok := recv[arg]; !ok { - send = Info{ - "status": "error", - "error": "Expected field missing: " + arg, - "expecting": arg, + } else if h, ok := a.handlers[strings.ToLower(resp.Request.Name)]; ok { + resp.Response, err = h.handler(buf) + if err != nil { + resp.Status = "error" + resp.Response = &ErrorResponse{ + Error: err.Error(), } - goto respond - } - } - - // By this point we should have all the fields we need, so call - // the handler - response, err := h.handler(recv) - if err != nil { - send["error"] = err.Error() - if response != nil { - send["response"] = response - goto respond } } else { - send["status"] = "success" - if response != nil { - send["response"] = response - goto respond + resp.Status = "error" + resp.Response = &ErrorResponse{ + Error: fmt.Sprintf("Unknown action '%s', try 'list' for help", resp.Request.Name), } } + } + j, _ := json.Marshal(resp) + if err = encoder.Encode(resp); err != nil { + a.log.Debugln("Encode error:", err) + } + if !resp.Request.KeepAlive { + break } else { - // Start with a clean response on each request, which defaults to an error - // state. If a handler is found below then this will be overwritten - send = Info{ - "request": recv, - "status": "error", - "error": fmt.Sprintf("Unknown action '%s', try 'list' for help", recv["request"].(string)), - } - goto respond - } - - // Send the response back - respond: - if err := encoder.Encode(&send); err != nil { - return - } - - // If "keepalive" isn't true then close the connection - if keepalive, ok := recv["keepalive"]; !ok || !keepalive.(bool) { - conn.Close() + continue } } } diff --git a/src/admin/error.go b/src/admin/error.go new file mode 100644 index 00000000..2c8016eb --- /dev/null +++ b/src/admin/error.go @@ -0,0 +1,5 @@ +package admin + +type ErrorResponse struct { + Error string `json:"error"` +} diff --git a/src/admin/getdht.go b/src/admin/getdht.go new file mode 100644 index 00000000..2796e4ff --- /dev/null +++ b/src/admin/getdht.go @@ -0,0 +1,34 @@ +package admin + +import ( + "encoding/hex" + "net" + + "github.com/yggdrasil-network/yggdrasil-go/src/address" +) + +type GetDHTRequest struct{} + +type GetDHTResponse struct { + DHT map[string]DHTEntry `json:"dht"` +} + +type DHTEntry struct { + PublicKey string `json:"key"` + Port uint64 `json:"port"` + Next uint64 `json:"next"` +} + +func (a *AdminSocket) getDHTHandler(req *GetDHTRequest, res *GetDHTResponse) error { + res.DHT = map[string]DHTEntry{} + for _, d := range a.core.GetDHT() { + addr := address.AddrForKey(d.Key) + so := net.IP(addr[:]).String() + res.DHT[so] = DHTEntry{ + PublicKey: hex.EncodeToString(d.Key[:]), + Port: d.Port, + Next: d.Next, + } + } + return nil +} diff --git a/src/admin/getpeers.go b/src/admin/getpeers.go new file mode 100644 index 00000000..9f53cf23 --- /dev/null +++ b/src/admin/getpeers.go @@ -0,0 +1,35 @@ +package admin + +import ( + "encoding/hex" + "net" + + "github.com/yggdrasil-network/yggdrasil-go/src/address" +) + +type GetPeersRequest struct { +} + +type GetPeersResponse struct { + Peers map[string]PeerEntry `json:"peers"` +} + +type PeerEntry struct { + PublicKey string `json:"key"` + Port uint64 `json:"port"` + Coords []uint64 `json:"coords"` +} + +func (a *AdminSocket) getPeersHandler(req *GetPeersRequest, res *GetPeersResponse) error { + res.Peers = map[string]PeerEntry{} + for _, p := range a.core.GetPeers() { + addr := address.AddrForKey(p.Key) + so := net.IP(addr[:]).String() + res.Peers[so] = PeerEntry{ + PublicKey: hex.EncodeToString(p.Key), + Port: p.Port, + Coords: p.Coords, + } + } + return nil +} diff --git a/src/admin/getself.go b/src/admin/getself.go new file mode 100644 index 00000000..895411bf --- /dev/null +++ b/src/admin/getself.go @@ -0,0 +1,31 @@ +package admin + +import ( + "crypto/ed25519" + "encoding/hex" + + "github.com/yggdrasil-network/yggdrasil-go/src/version" +) + +type GetSelfRequest struct{} + +type GetSelfResponse struct { + BuildName string `json:"build_name"` + BuildVersion string `json:"build_version"` + PublicKey string `json:"key"` + Coords []uint64 `json:"coords"` + IPAddress string `json:"address"` + Subnet string `json:"subnet"` +} + +func (a *AdminSocket) getSelfHandler(req *GetSelfRequest, res *GetSelfResponse) error { + res.BuildName = version.BuildName() + res.BuildVersion = version.BuildVersion() + public := a.core.PrivateKey().Public().(ed25519.PublicKey) + res.PublicKey = hex.EncodeToString(public[:]) + res.IPAddress = a.core.Address().String() + snet := a.core.Subnet() + res.Subnet = snet.String() + // TODO: res.coords + return nil +} diff --git a/src/admin/getsessions.go b/src/admin/getsessions.go new file mode 100644 index 00000000..3a0c19b6 --- /dev/null +++ b/src/admin/getsessions.go @@ -0,0 +1,30 @@ +package admin + +import ( + "encoding/hex" + "net" + + "github.com/yggdrasil-network/yggdrasil-go/src/address" +) + +type GetSessionsRequest struct{} + +type GetSessionsResponse struct { + Sessions map[string]SessionEntry `json:"sessions"` +} + +type SessionEntry struct { + PublicKey string `json:"key"` +} + +func (a *AdminSocket) getSessionsHandler(req *GetSessionsRequest, res *GetSessionsResponse) error { + res.Sessions = map[string]SessionEntry{} + for _, s := range a.core.GetSessions() { + addr := address.AddrForKey(s.Key) + so := net.IP(addr[:]).String() + res.Sessions[so] = SessionEntry{ + PublicKey: hex.EncodeToString(s.Key[:]), + } + } + return nil +} diff --git a/src/config/config.go b/src/config/config.go index 91507baa..4aa9a213 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -22,11 +22,8 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" - "github.com/yggdrasil-network/yggdrasil-go/src/types" ) -type MTU = types.MTU - // NodeState represents the active and previous configuration of an Yggdrasil // node. A NodeState object is returned when starting an Yggdrasil node. Note // that this structure and related functions are likely to disappear soon. @@ -72,7 +69,7 @@ type NodeConfig struct { PrivateKey string `comment:"Your private signing key. DO NOT share this with anyone!"` LinkLocalTCPPort uint16 `comment:"The port number to be used for the link-local TCP listeners for the\nconfigured MulticastInterfaces. This option does not affect listeners\nspecified in the Listen option. Unless you plan to firewall link-local\ntraffic, it is best to leave this as the default value of 0. This\noption cannot currently be changed by reloading config during runtime."` IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."` - IfMTU MTU `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` + IfMTU uint16 `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` SessionFirewall SessionFirewall `comment:"The session firewall controls who can send/receive network traffic\nto/from. This is useful if you want to protect this node without\nresorting to using a real firewall. This does not affect traffic\nbeing routed via this node to somewhere else. Rules are prioritised as\nfollows: blacklist, whitelist, always allow outgoing, direct, remote."` NodeInfoPrivacy bool `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."` NodeInfo map[string]interface{} `comment:"Optional node info. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."` diff --git a/src/defaults/defaults.go b/src/defaults/defaults.go index a0d372e4..57248088 100644 --- a/src/defaults/defaults.go +++ b/src/defaults/defaults.go @@ -1,7 +1,5 @@ package defaults -import "github.com/yggdrasil-network/yggdrasil-go/src/types" - // 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. @@ -16,7 +14,7 @@ type platformDefaultParameters struct { DefaultMulticastInterfaces []string // TUN/TAP - MaximumIfMTU types.MTU - DefaultIfMTU types.MTU + MaximumIfMTU uint16 + DefaultIfMTU uint16 DefaultIfName string } diff --git a/src/multicast/admin.go b/src/multicast/admin.go index cafee07f..2ae6ec08 100644 --- a/src/multicast/admin.go +++ b/src/multicast/admin.go @@ -1,13 +1,34 @@ package multicast -import "github.com/yggdrasil-network/yggdrasil-go/src/admin" +import ( + "encoding/json" + + "github.com/yggdrasil-network/yggdrasil-go/src/admin" +) + +type GetMulticastInterfacesRequest struct{} +type GetMulticastInterfacesResponse struct { + Interfaces []string `json:"multicast_interfaces"` +} + +func (m *Multicast) getMulticastInterfacesHandler(req *GetMulticastInterfacesRequest, res *GetMulticastInterfacesResponse) error { + res.Interfaces = []string{} + for _, v := range m.Interfaces() { + res.Interfaces = append(res.Interfaces, v.Name) + } + return nil +} func (m *Multicast) SetupAdminHandlers(a *admin.AdminSocket) { - a.AddHandler("getMulticastInterfaces", []string{}, func(in admin.Info) (admin.Info, error) { - var intfs []string - for _, v := range m.Interfaces() { - intfs = append(intfs, v.Name) + _ = a.AddHandler("getMulticastInterfaces", []string{}, func(in json.RawMessage) (interface{}, error) { + req := &GetMulticastInterfacesRequest{} + res := &GetMulticastInterfacesResponse{} + if err := json.Unmarshal(in, &req); err != nil { + return nil, err } - return admin.Info{"multicast_interfaces": intfs}, nil + if err := m.getMulticastInterfacesHandler(req, res); err != nil { + return nil, err + } + return res, nil }) } diff --git a/src/tuntap/admin.go b/src/tuntap/admin.go index 67f80e04..5b50ff24 100644 --- a/src/tuntap/admin.go +++ b/src/tuntap/admin.go @@ -1,57 +1,31 @@ package tuntap import ( - //"encoding/hex" - //"errors" - //"fmt" - //"net" + "encoding/json" "github.com/yggdrasil-network/yggdrasil-go/src/admin" ) -func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { - a.AddHandler("getTunTap", []string{}, func(in admin.Info) (r admin.Info, e error) { - defer func() { - if err := recover(); err != nil { - r = admin.Info{"none": admin.Info{}} - e = nil - } - }() +type GetTUNRequest struct{} +type GetTUNResponse map[string]uint16 - return admin.Info{ - t.Name(): admin.Info{ - "mtu": t.mtu, - }, - }, nil - }) - /* - // TODO: rewrite this as I'm fairly sure it doesn't work right on many - // platforms anyway, but it may require changes to Water - a.AddHandler("setTunTap", []string{"name", "[tap_mode]", "[mtu]"}, func(in Info) (Info, error) { - // Set sane defaults - iftapmode := defaults.GetDefaults().DefaultIfTAPMode - ifmtu := defaults.GetDefaults().DefaultIfMTU - // Has TAP mode been specified? - if tap, ok := in["tap_mode"]; ok { - iftapmode = tap.(bool) - } - // Check we have enough params for MTU - if mtu, ok := in["mtu"]; ok { - if mtu.(float64) >= 1280 && ifmtu <= defaults.GetDefaults().MaximumIfMTU { - ifmtu = int(in["mtu"].(float64)) - } - } - // Start the TUN adapter - if err := a.startTunWithMTU(in["name"].(string), iftapmode, ifmtu); err != nil { - return Info{}, errors.New("Failed to configure adapter") - } else { - return Info{ - a.core.router.tun.iface.Name(): Info{ - "tap_mode": a.core.router.tun.iface.IsTAP(), - "mtu": ifmtu, - }, - }, nil - } - }) - */ +func (t *TunAdapter) getTUNHandler(req *GetTUNRequest, res *GetTUNResponse) error { + res = &GetTUNResponse{ + t.Name(): t.MTU(), + } + return nil +} + +func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { + _ = a.AddHandler("getTunTap", []string{}, func(in json.RawMessage) (interface{}, error) { + req := &GetTUNRequest{} + res := &GetTUNResponse{} + if err := json.Unmarshal(in, &req); err != nil { + return nil, err + } + if err := t.getTUNHandler(req, res); err != nil { + return nil, err + } + return res, nil + }) } diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 6d41f59b..a2f93e31 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -23,11 +23,10 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" - "github.com/yggdrasil-network/yggdrasil-go/src/types" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) -type MTU = types.MTU +type MTU uint16 // TunAdapter represents a running TUN interface and extends the // yggdrasil.Adapter type. In order to use the TUN adapter with Yggdrasil, you @@ -40,7 +39,7 @@ type TunAdapter struct { log *log.Logger addr address.Address subnet address.Subnet - mtu MTU + mtu uint16 iface tun.Device phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below //mutex sync.RWMutex // Protects the below @@ -56,7 +55,7 @@ func (tun *TunAdapter) SetSessionGatekeeper(gatekeeper func(pubkey ed25519.Publi // Gets the maximum supported MTU for the platform based on the defaults in // defaults.GetDefaults(). -func getSupportedMTU(mtu MTU) MTU { +func getSupportedMTU(mtu uint16) uint16 { if mtu < 1280 { return 1280 } @@ -78,7 +77,7 @@ func (tun *TunAdapter) Name() string { // MTU gets the adapter's MTU. This can range between 1280 and 65535, although // the maximum value is determined by your platform. The returned value will // never exceed that of MaximumMTU(). -func (tun *TunAdapter) MTU() MTU { +func (tun *TunAdapter) MTU() uint16 { return getSupportedMTU(tun.mtu) } @@ -89,14 +88,14 @@ func DefaultName() string { // DefaultMTU gets the default TUN interface MTU for your platform. This can // be as high as MaximumMTU(), depending on platform, but is never lower than 1280. -func DefaultMTU() MTU { +func DefaultMTU() uint16 { return defaults.GetDefaults().DefaultIfMTU } // MaximumMTU returns the maximum supported TUN interface MTU for your // platform. This can be as high as 65535, depending on platform, but is never // lower than 1280. -func MaximumMTU() MTU { +func MaximumMTU() uint16 { return defaults.GetDefaults().MaximumIfMTU } @@ -151,7 +150,7 @@ func (tun *TunAdapter) _start() error { } mtu := current.IfMTU if tun.core.MTU() < uint64(mtu) { - mtu = MTU(tun.core.MTU()) + mtu = uint16(tun.core.MTU()) } if err := tun.setup(current.IfName, addr, mtu); err != nil { return err diff --git a/src/tuntap/tun_darwin.go b/src/tuntap/tun_darwin.go index b984e164..b398eb19 100644 --- a/src/tuntap/tun_darwin.go +++ b/src/tuntap/tun_darwin.go @@ -16,7 +16,7 @@ import ( ) // Configures the "utun" adapter with the correct IPv6 address and MTU. -func (tun *TunAdapter) setup(ifname string, addr string, mtu MTU) error { +func (tun *TunAdapter) setup(ifname string, addr string, mtu uint16) error { if ifname == "auto" { ifname = "utun" } @@ -25,8 +25,8 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu MTU) error { panic(err) } tun.iface = iface - if mtu, err := iface.MTU(); err == nil { - tun.mtu = getSupportedMTU(MTU(mtu)) + if m, err := iface.MTU(); err == nil { + tun.mtu = getSupportedMTU(uint16(m)) } else { tun.mtu = 0 } diff --git a/src/types/types.go b/src/types/types.go deleted file mode 100644 index 5d1c024f..00000000 --- a/src/types/types.go +++ /dev/null @@ -1,3 +0,0 @@ -package types - -type MTU uint16 From 2e45e970c64cfbca6536964e2eddafb2a48c0d74 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 16 May 2021 13:52:52 -0500 Subject: [PATCH 0673/1109] work-in-progress adding nodeinfo --- src/tuntap/iface.go | 3 + src/tuntap/nodeinfo.go | 200 +++++++++++++++++++++++++++++++++++++++++ src/tuntap/tun.go | 15 ++-- src/tuntap/types.go | 2 + 4 files changed, 211 insertions(+), 9 deletions(-) create mode 100644 src/tuntap/nodeinfo.go diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index ff91cf17..87129f2b 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -74,6 +74,9 @@ func (tun *TunAdapter) write() { switch bs[0] { case typeSessionTraffic: // This is what we want to handle here + if !tun.isEnabled { + continue // Drop traffic if the tun is disabled + } default: continue } diff --git a/src/tuntap/nodeinfo.go b/src/tuntap/nodeinfo.go new file mode 100644 index 00000000..a88916ca --- /dev/null +++ b/src/tuntap/nodeinfo.go @@ -0,0 +1,200 @@ +package tuntap + +import ( + "encoding/json" + "errors" + "runtime" + "strings" + "time" + + "github.com/Arceliar/phony" + //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/version" + + iwt "github.com/Arceliar/ironwood/types" +) + +// NodeInfoPayload represents a RequestNodeInfo response, in bytes. +type NodeInfoPayload []byte + +type nodeinfo struct { + phony.Inbox + tun *TunAdapter + myNodeInfo NodeInfoPayload + callbacks map[keyArray]nodeinfoCallback + cache map[keyArray]nodeinfoCached + //table *lookupTable +} + +type nodeinfoCached struct { + payload NodeInfoPayload + created time.Time +} + +type nodeinfoCallback struct { + call func(nodeinfo *NodeInfoPayload) + created time.Time +} + +// Represents a session nodeinfo packet. +type nodeinfoReqRes struct { + Key keyArray // Sender's permanent key + IsResponse bool + NodeInfo NodeInfoPayload +} + +// Initialises the nodeinfo cache/callback maps, and starts a goroutine to keep +// the cache/callback maps clean of stale entries +func (m *nodeinfo) init(tun *TunAdapter) { + m.Act(nil, func() { + m._init(tun) + }) +} + +func (m *nodeinfo) _init(tun *TunAdapter) { + m.tun = tun + m.callbacks = make(map[keyArray]nodeinfoCallback) + m.cache = make(map[keyArray]nodeinfoCached) + + m._cleanup() +} + +func (m *nodeinfo) _cleanup() { + for boxPubKey, callback := range m.callbacks { + if time.Since(callback.created) > time.Minute { + delete(m.callbacks, boxPubKey) + } + } + for boxPubKey, cache := range m.cache { + if time.Since(cache.created) > time.Hour { + delete(m.cache, boxPubKey) + } + } + time.AfterFunc(time.Second*30, func() { + m.Act(nil, m._cleanup) + }) +} + +// Add a callback for a nodeinfo lookup +func (m *nodeinfo) addCallback(sender keyArray, call func(nodeinfo *NodeInfoPayload)) { + m.Act(nil, func() { + m._addCallback(sender, call) + }) +} + +func (m *nodeinfo) _addCallback(sender keyArray, call func(nodeinfo *NodeInfoPayload)) { + m.callbacks[sender] = nodeinfoCallback{ + created: time.Now(), + call: call, + } +} + +// Handles the callback, if there is one +func (m *nodeinfo) _callback(sender keyArray, nodeinfo NodeInfoPayload) { + if callback, ok := m.callbacks[sender]; ok { + callback.call(&nodeinfo) + delete(m.callbacks, sender) + } +} + +// Get the current node's nodeinfo +func (m *nodeinfo) getNodeInfo() (p NodeInfoPayload) { + phony.Block(m, func() { + p = m._getNodeInfo() + }) + return +} + +func (m *nodeinfo) _getNodeInfo() NodeInfoPayload { + return m.myNodeInfo +} + +// Set the current node's nodeinfo +func (m *nodeinfo) setNodeInfo(given interface{}, privacy bool) (err error) { + phony.Block(m, func() { + err = m._setNodeInfo(given, privacy) + }) + return +} + +func (m *nodeinfo) _setNodeInfo(given interface{}, privacy bool) error { + defaults := map[string]interface{}{ + "buildname": version.BuildName(), + "buildversion": version.BuildVersion(), + "buildplatform": runtime.GOOS, + "buildarch": runtime.GOARCH, + } + newnodeinfo := make(map[string]interface{}) + if !privacy { + for k, v := range defaults { + newnodeinfo[k] = v + } + } + if nodeinfomap, ok := given.(map[string]interface{}); ok { + for key, value := range nodeinfomap { + if _, ok := defaults[key]; ok { + if strvalue, strok := value.(string); strok && strings.EqualFold(strvalue, "null") || value == nil { + delete(newnodeinfo, key) + } + continue + } + newnodeinfo[key] = value + } + } + newjson, err := json.Marshal(newnodeinfo) + if err == nil { + if len(newjson) > 16384 { + return errors.New("NodeInfo exceeds max length of 16384 bytes") + } + m.myNodeInfo = newjson + return nil + } + return err +} + +// Add nodeinfo into the cache for a node +func (m *nodeinfo) _addCachedNodeInfo(key keyArray, payload NodeInfoPayload) { + m.cache[key] = nodeinfoCached{ + created: time.Now(), + payload: payload, + } +} + +// Get a nodeinfo entry from the cache +func (m *nodeinfo) _getCachedNodeInfo(key keyArray) (NodeInfoPayload, error) { + if nodeinfo, ok := m.cache[key]; ok { + return nodeinfo.payload, nil + } + return NodeInfoPayload{}, errors.New("No cache entry found") +} + +func (m *nodeinfo) sendReq(from phony.Actor, key keyArray, callback func(nodeinfo *NodeInfoPayload)) { + m.Act(from, func() { + m._sendReq(key, callback) + }) +} + +func (m *nodeinfo) _sendReq(key keyArray, callback func(nodeinfo *NodeInfoPayload)) { + if callback != nil { + m._addCallback(key, callback) + } + m.tun.core.WriteTo([]byte{typeSessionNodeInfoRequest}, iwt.Addr(key[:])) +} + +func (m *nodeinfo) handleReq(from phony.Actor, key keyArray) { + m.Act(from, func() { + m._sendRes(key) + }) +} + +func (m *nodeinfo) handleRes(from phony.Actor, key keyArray, info NodeInfoPayload) { + m.Act(from, func() { + m._callback(key, info) + m._addCachedNodeInfo(key, info) + }) +} + +func (m *nodeinfo) _sendRes(key keyArray) { + bs := append([]byte{typeSessionNodeInfoResponse}, m._getNodeInfo()...) + m.tun.core.WriteTo(bs, iwt.Addr(key[:])) +} diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 4492784d..f5d19433 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -45,7 +45,9 @@ type TunAdapter struct { phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below //mutex sync.RWMutex // Protects the below isOpen bool + isEnabled bool // Used by the writer to drop sessionTraffic if not enabled gatekeeper func(pubkey ed25519.PublicKey, initiator bool) bool + nodeinfo nodeinfo } func (tun *TunAdapter) SetSessionGatekeeper(gatekeeper func(pubkey ed25519.PublicKey, initiator bool) bool) { @@ -107,6 +109,7 @@ func (tun *TunAdapter) Init(core *yggdrasil.Core, config *config.NodeState, log tun.store.init(tun) tun.config = config tun.log = log + tun.nodeinfo.init(tun) if err := tun.core.SetOutOfBandHandler(tun.oobHandler); err != nil { return fmt.Errorf("tun.core.SetOutOfBandHander: %w", err) } @@ -138,15 +141,8 @@ func (tun *TunAdapter) _start() error { addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1) if current.IfName == "none" || current.IfName == "dummy" { tun.log.Debugln("Not starting TUN as ifname is none or dummy") - go func() { - bs := make([]byte, tun.core.PacketConn.MTU()) - for { - // Dump traffic to nowhere - if _, _, err := tun.core.PacketConn.ReadFrom(bs); err != nil { - return - } - } - }() + tun.isEnabled = false + go tun.write() return nil } mtu := current.IfMTU @@ -160,6 +156,7 @@ func (tun *TunAdapter) _start() error { tun.log.Warnf("Warning: Interface MTU %d automatically adjusted to %d (supported range is 1280-%d)", current.IfMTU, tun.MTU(), MaximumMTU()) } tun.isOpen = true + tun.isEnabled = true go tun.read() go tun.write() return nil diff --git a/src/tuntap/types.go b/src/tuntap/types.go index b503e647..f1267a92 100644 --- a/src/tuntap/types.go +++ b/src/tuntap/types.go @@ -11,4 +11,6 @@ const ( const ( typeSessionDummy = iota typeSessionTraffic + typeSessionNodeInfoRequest + typeSessionNodeInfoResponse ) From a6c254c87a5a59ed7cc1f15b730a40f671f63a35 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 16 May 2021 14:00:37 -0500 Subject: [PATCH 0674/1109] more nodeinfo WIP, still needs admin socket support --- src/tuntap/iface.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 87129f2b..a3275436 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -77,6 +77,17 @@ func (tun *TunAdapter) write() { if !tun.isEnabled { continue // Drop traffic if the tun is disabled } + case typeSessionNodeInfoRequest: + var key keyArray + copy(key[:], from.(iwt.Addr)) + tun.nodeinfo.handleReq(nil, key) + continue + case typeSessionNodeInfoResponse: + var key keyArray + copy(key[:], from.(iwt.Addr)) + res := append([]byte(nil), bs[1:n]...) + tun.nodeinfo.handleRes(nil, key, res) + continue default: continue } From 416eadbcff01821c9e41313b018cbe930e06a078 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 16 May 2021 20:00:45 +0100 Subject: [PATCH 0675/1109] Use uint64 for MTU for forward-compatibility --- src/admin/admin.go | 1 - src/config/config.go | 2 +- src/defaults/defaults.go | 4 ++-- src/tuntap/admin.go | 2 +- src/tuntap/tun.go | 12 ++++++------ src/tuntap/tun_bsd.go | 4 ++-- src/tuntap/tun_darwin.go | 4 ++-- src/tuntap/tun_linux.go | 4 ++-- src/tuntap/tun_other.go | 4 ++-- src/tuntap/tun_windows.go | 6 +++--- 10 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/admin/admin.go b/src/admin/admin.go index 855b0545..fc9d09c3 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -250,7 +250,6 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { } } } - j, _ := json.Marshal(resp) if err = encoder.Encode(resp); err != nil { a.log.Debugln("Encode error:", err) } diff --git a/src/config/config.go b/src/config/config.go index 4aa9a213..49cc8a2a 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -69,7 +69,7 @@ type NodeConfig struct { PrivateKey string `comment:"Your private signing key. DO NOT share this with anyone!"` LinkLocalTCPPort uint16 `comment:"The port number to be used for the link-local TCP listeners for the\nconfigured MulticastInterfaces. This option does not affect listeners\nspecified in the Listen option. Unless you plan to firewall link-local\ntraffic, it is best to leave this as the default value of 0. This\noption cannot currently be changed by reloading config during runtime."` IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."` - IfMTU uint16 `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` + IfMTU uint64 `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` SessionFirewall SessionFirewall `comment:"The session firewall controls who can send/receive network traffic\nto/from. This is useful if you want to protect this node without\nresorting to using a real firewall. This does not affect traffic\nbeing routed via this node to somewhere else. Rules are prioritised as\nfollows: blacklist, whitelist, always allow outgoing, direct, remote."` NodeInfoPrivacy bool `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."` NodeInfo map[string]interface{} `comment:"Optional node info. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."` diff --git a/src/defaults/defaults.go b/src/defaults/defaults.go index 57248088..a885c5dd 100644 --- a/src/defaults/defaults.go +++ b/src/defaults/defaults.go @@ -14,7 +14,7 @@ type platformDefaultParameters struct { DefaultMulticastInterfaces []string // TUN/TAP - MaximumIfMTU uint16 - DefaultIfMTU uint16 + MaximumIfMTU uint64 + DefaultIfMTU uint64 DefaultIfName string } diff --git a/src/tuntap/admin.go b/src/tuntap/admin.go index 5b50ff24..15a7463a 100644 --- a/src/tuntap/admin.go +++ b/src/tuntap/admin.go @@ -7,7 +7,7 @@ import ( ) type GetTUNRequest struct{} -type GetTUNResponse map[string]uint16 +type GetTUNResponse map[string]uint64 func (t *TunAdapter) getTUNHandler(req *GetTUNRequest, res *GetTUNResponse) error { res = &GetTUNResponse{ diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index a2f93e31..a5e8f9e1 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -39,7 +39,7 @@ type TunAdapter struct { log *log.Logger addr address.Address subnet address.Subnet - mtu uint16 + mtu uint64 iface tun.Device phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below //mutex sync.RWMutex // Protects the below @@ -55,7 +55,7 @@ func (tun *TunAdapter) SetSessionGatekeeper(gatekeeper func(pubkey ed25519.Publi // Gets the maximum supported MTU for the platform based on the defaults in // defaults.GetDefaults(). -func getSupportedMTU(mtu uint16) uint16 { +func getSupportedMTU(mtu uint64) uint64 { if mtu < 1280 { return 1280 } @@ -77,7 +77,7 @@ func (tun *TunAdapter) Name() string { // MTU gets the adapter's MTU. This can range between 1280 and 65535, although // the maximum value is determined by your platform. The returned value will // never exceed that of MaximumMTU(). -func (tun *TunAdapter) MTU() uint16 { +func (tun *TunAdapter) MTU() uint64 { return getSupportedMTU(tun.mtu) } @@ -88,14 +88,14 @@ func DefaultName() string { // DefaultMTU gets the default TUN interface MTU for your platform. This can // be as high as MaximumMTU(), depending on platform, but is never lower than 1280. -func DefaultMTU() uint16 { +func DefaultMTU() uint64 { return defaults.GetDefaults().DefaultIfMTU } // MaximumMTU returns the maximum supported TUN interface MTU for your // platform. This can be as high as 65535, depending on platform, but is never // lower than 1280. -func MaximumMTU() uint16 { +func MaximumMTU() uint64 { return defaults.GetDefaults().MaximumIfMTU } @@ -150,7 +150,7 @@ func (tun *TunAdapter) _start() error { } mtu := current.IfMTU if tun.core.MTU() < uint64(mtu) { - mtu = uint16(tun.core.MTU()) + mtu = tun.core.MTU() } if err := tun.setup(current.IfName, addr, mtu); err != nil { return err diff --git a/src/tuntap/tun_bsd.go b/src/tuntap/tun_bsd.go index 4710e8cd..75158857 100644 --- a/src/tuntap/tun_bsd.go +++ b/src/tuntap/tun_bsd.go @@ -73,14 +73,14 @@ type in6_ifreq_lifetime struct { } // Configures the TUN adapter with the correct IPv6 address and MTU. -func (tun *TunAdapter) setup(ifname string, addr string, mtu MTU) error { +func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error { iface, err := wgtun.CreateTUN(ifname, int(mtu)) if err != nil { panic(err) } tun.iface = iface if mtu, err := iface.MTU(); err == nil { - tun.mtu = getSupportedMTU(MTU(mtu)) + tun.mtu = getSupportedMTU(uint64(mtu)) } else { tun.mtu = 0 } diff --git a/src/tuntap/tun_darwin.go b/src/tuntap/tun_darwin.go index b398eb19..609b42e3 100644 --- a/src/tuntap/tun_darwin.go +++ b/src/tuntap/tun_darwin.go @@ -16,7 +16,7 @@ import ( ) // Configures the "utun" adapter with the correct IPv6 address and MTU. -func (tun *TunAdapter) setup(ifname string, addr string, mtu uint16) error { +func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error { if ifname == "auto" { ifname = "utun" } @@ -26,7 +26,7 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint16) error { } tun.iface = iface if m, err := iface.MTU(); err == nil { - tun.mtu = getSupportedMTU(uint16(m)) + tun.mtu = getSupportedMTU(uint64(m)) } else { tun.mtu = 0 } diff --git a/src/tuntap/tun_linux.go b/src/tuntap/tun_linux.go index ad7b64ef..0a845368 100644 --- a/src/tuntap/tun_linux.go +++ b/src/tuntap/tun_linux.go @@ -10,7 +10,7 @@ import ( ) // Configures the TUN adapter with the correct IPv6 address and MTU. -func (tun *TunAdapter) setup(ifname string, addr string, mtu MTU) error { +func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error { if ifname == "auto" { ifname = "\000" } @@ -20,7 +20,7 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu MTU) error { } tun.iface = iface if mtu, err := iface.MTU(); err == nil { - tun.mtu = getSupportedMTU(MTU(mtu)) + tun.mtu = getSupportedMTU(uint64(mtu)) } else { tun.mtu = 0 } diff --git a/src/tuntap/tun_other.go b/src/tuntap/tun_other.go index 8a27f57b..c0321267 100644 --- a/src/tuntap/tun_other.go +++ b/src/tuntap/tun_other.go @@ -10,14 +10,14 @@ import ( ) // Configures the TUN adapter with the correct IPv6 address and MTU. -func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error { +func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error { iface, err := wgtun.CreateTUN(ifname, mtu) if err != nil { panic(err) } tun.iface = iface if mtu, err := iface.MTU(); err == nil { - tun.mtu = getSupportedMTU(mtu) + tun.mtu = getSupportedMTU(uint64(mtu)) } else { tun.mtu = 0 } diff --git a/src/tuntap/tun_windows.go b/src/tuntap/tun_windows.go index 8eb21658..7b7ee710 100644 --- a/src/tuntap/tun_windows.go +++ b/src/tuntap/tun_windows.go @@ -19,7 +19,7 @@ import ( // This is to catch Windows platforms // Configures the TUN adapter with the correct IPv6 address and MTU. -func (tun *TunAdapter) setup(ifname string, addr string, mtu MTU) error { +func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error { if ifname == "auto" { ifname = defaults.GetDefaults().DefaultIfName } @@ -43,14 +43,14 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu MTU) error { return err } if mtu, err := iface.MTU(); err == nil { - tun.mtu = MTU(mtu) + tun.mtu = uint64(mtu) } return nil }) } // Sets the MTU of the TAP adapter. -func (tun *TunAdapter) setupMTU(mtu MTU) error { +func (tun *TunAdapter) setupMTU(mtu uint64) error { if tun.iface == nil || tun.Name() == "" { return errors.New("Can't configure MTU as TUN adapter is not present") } From 6413e95c48a880225e953ef278911c9fb3cd06fa Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 16 May 2021 20:05:22 +0100 Subject: [PATCH 0676/1109] Fix bug --- src/tuntap/tun.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 26aa552e..97d0ba8b 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -224,7 +224,7 @@ func (tun *TunAdapter) sendKeyResponse(dest ed25519.PublicKey) { tun.core.SendOutOfBand(dest, bs) } -func (tun *TunAdapter) maxSessionMTU() MTU { +func (tun *TunAdapter) maxSessionMTU() uint64 { const sessionTypeOverhead = 1 - return MTU(tun.core.MTU() - sessionTypeOverhead) + return tun.core.MTU() - sessionTypeOverhead } From 31c1c9b586c55131f2879a04c674771eea69c21d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 16 May 2021 20:53:40 +0100 Subject: [PATCH 0677/1109] Fix admin socket list --- src/admin/admin.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/admin/admin.go b/src/admin/admin.go index fc9d09c3..8ad8d907 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -43,7 +43,11 @@ type handler struct { } type ListResponse struct { - List map[string][]string `json:"list"` + List map[string]ListEntry `json:"list"` +} + +type ListEntry struct { + Fields []string `json:"fields"` } // AddHandler is called for each admin function to add the handler and help documentation to the API. @@ -66,9 +70,13 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. current := state.GetCurrent() a.listenaddr = current.AdminListen _ = a.AddHandler("list", []string{}, func(_ json.RawMessage) (interface{}, error) { - res := &ListResponse{} + res := &ListResponse{ + List: map[string]ListEntry{}, + } for name, handler := range a.handlers { - res.List[name] = handler.args + res.List[name] = ListEntry{ + Fields: handler.args, + } } return res, nil }) From 058dec0cca25cae5d81369babfa1c64d28e36d89 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 16 May 2021 21:01:59 +0100 Subject: [PATCH 0678/1109] Fix getself, gettuntap etc --- src/admin/getself.go | 20 +++++++++++++------- src/tuntap/admin.go | 12 +++++++++--- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/admin/getself.go b/src/admin/getself.go index 895411bf..bec084c5 100644 --- a/src/admin/getself.go +++ b/src/admin/getself.go @@ -10,22 +10,28 @@ import ( type GetSelfRequest struct{} type GetSelfResponse struct { + Self map[string]SelfEntry `json:"self"` +} + +type SelfEntry struct { BuildName string `json:"build_name"` BuildVersion string `json:"build_version"` PublicKey string `json:"key"` Coords []uint64 `json:"coords"` - IPAddress string `json:"address"` Subnet string `json:"subnet"` } func (a *AdminSocket) getSelfHandler(req *GetSelfRequest, res *GetSelfResponse) error { - res.BuildName = version.BuildName() - res.BuildVersion = version.BuildVersion() + res.Self = make(map[string]SelfEntry) public := a.core.PrivateKey().Public().(ed25519.PublicKey) - res.PublicKey = hex.EncodeToString(public[:]) - res.IPAddress = a.core.Address().String() + addr := a.core.Address().String() snet := a.core.Subnet() - res.Subnet = snet.String() - // TODO: res.coords + res.Self[addr] = SelfEntry{ + BuildName: version.BuildName(), + BuildVersion: version.BuildVersion(), + PublicKey: hex.EncodeToString(public[:]), + Subnet: snet.String(), + // TODO: coords + } return nil } diff --git a/src/tuntap/admin.go b/src/tuntap/admin.go index 15a7463a..862a3c66 100644 --- a/src/tuntap/admin.go +++ b/src/tuntap/admin.go @@ -7,11 +7,17 @@ import ( ) type GetTUNRequest struct{} -type GetTUNResponse map[string]uint64 +type GetTUNResponse map[string]TUNEntry + +type TUNEntry struct { + MTU uint64 `json:"mtu"` +} func (t *TunAdapter) getTUNHandler(req *GetTUNRequest, res *GetTUNResponse) error { - res = &GetTUNResponse{ - t.Name(): t.MTU(), + *res = GetTUNResponse{ + t.Name(): TUNEntry{ + MTU: t.MTU(), + }, } return nil } From fad071ffe98f3bd8f70d7688775e6700bf286410 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 16 May 2021 15:27:51 -0500 Subject: [PATCH 0679/1109] WIP on nodeinfo admin handler --- go.mod | 2 +- go.sum | 4 ++-- src/admin/getself.go | 7 +++--- src/tuntap/admin.go | 1 + src/tuntap/nodeinfo.go | 48 ++++++++++++++++++++++++++++++++++++------ 5 files changed, 48 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index f830cd2d..9c35842f 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.16 require ( - github.com/Arceliar/ironwood v0.0.0-20210515192645-e94fc534a8e0 + github.com/Arceliar/ironwood v0.0.0-20210516202558-11160a0940e1 github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 github.com/cheggaaa/pb/v3 v3.0.6 github.com/fatih/color v1.10.0 // indirect diff --git a/go.sum b/go.sum index eda3ca65..e9fe5248 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/ironwood v0.0.0-20210515192645-e94fc534a8e0 h1:ljxlQafrAuWzPhUDBVlWkshOlJDyKn9NXAPLJY5tAU0= -github.com/Arceliar/ironwood v0.0.0-20210515192645-e94fc534a8e0/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= +github.com/Arceliar/ironwood v0.0.0-20210516202558-11160a0940e1 h1:EVa/kL+8lgZmnAit/a0DIacKNZ/lj7NwjmO4B++t4PU= +github.com/Arceliar/ironwood v0.0.0-20210516202558-11160a0940e1/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= diff --git a/src/admin/getself.go b/src/admin/getself.go index bec084c5..7effcc46 100644 --- a/src/admin/getself.go +++ b/src/admin/getself.go @@ -1,7 +1,6 @@ package admin import ( - "crypto/ed25519" "encoding/hex" "github.com/yggdrasil-network/yggdrasil-go/src/version" @@ -23,15 +22,15 @@ type SelfEntry struct { func (a *AdminSocket) getSelfHandler(req *GetSelfRequest, res *GetSelfResponse) error { res.Self = make(map[string]SelfEntry) - public := a.core.PrivateKey().Public().(ed25519.PublicKey) + self := a.core.GetSelf() addr := a.core.Address().String() snet := a.core.Subnet() res.Self[addr] = SelfEntry{ BuildName: version.BuildName(), BuildVersion: version.BuildVersion(), - PublicKey: hex.EncodeToString(public[:]), + PublicKey: hex.EncodeToString(self.Key[:]), Subnet: snet.String(), - // TODO: coords + Coords: self.Coords, } return nil } diff --git a/src/tuntap/admin.go b/src/tuntap/admin.go index 862a3c66..01cb56fa 100644 --- a/src/tuntap/admin.go +++ b/src/tuntap/admin.go @@ -34,4 +34,5 @@ func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { } return res, nil }) + _ = a.AddHandler("getNodeInfo", []string{"key"}, t.nodeinfo.nodeInfoAdminHandler) } diff --git a/src/tuntap/nodeinfo.go b/src/tuntap/nodeinfo.go index a88916ca..6e6a2076 100644 --- a/src/tuntap/nodeinfo.go +++ b/src/tuntap/nodeinfo.go @@ -1,6 +1,7 @@ package tuntap import ( + "encoding/hex" "encoding/json" "errors" "runtime" @@ -23,7 +24,6 @@ type nodeinfo struct { myNodeInfo NodeInfoPayload callbacks map[keyArray]nodeinfoCallback cache map[keyArray]nodeinfoCached - //table *lookupTable } type nodeinfoCached struct { @@ -32,7 +32,7 @@ type nodeinfoCached struct { } type nodeinfoCallback struct { - call func(nodeinfo *NodeInfoPayload) + call func(nodeinfo NodeInfoPayload) created time.Time } @@ -76,13 +76,13 @@ func (m *nodeinfo) _cleanup() { } // Add a callback for a nodeinfo lookup -func (m *nodeinfo) addCallback(sender keyArray, call func(nodeinfo *NodeInfoPayload)) { +func (m *nodeinfo) addCallback(sender keyArray, call func(nodeinfo NodeInfoPayload)) { m.Act(nil, func() { m._addCallback(sender, call) }) } -func (m *nodeinfo) _addCallback(sender keyArray, call func(nodeinfo *NodeInfoPayload)) { +func (m *nodeinfo) _addCallback(sender keyArray, call func(nodeinfo NodeInfoPayload)) { m.callbacks[sender] = nodeinfoCallback{ created: time.Now(), call: call, @@ -92,7 +92,7 @@ func (m *nodeinfo) _addCallback(sender keyArray, call func(nodeinfo *NodeInfoPay // Handles the callback, if there is one func (m *nodeinfo) _callback(sender keyArray, nodeinfo NodeInfoPayload) { if callback, ok := m.callbacks[sender]; ok { - callback.call(&nodeinfo) + callback.call(nodeinfo) delete(m.callbacks, sender) } } @@ -168,13 +168,13 @@ func (m *nodeinfo) _getCachedNodeInfo(key keyArray) (NodeInfoPayload, error) { return NodeInfoPayload{}, errors.New("No cache entry found") } -func (m *nodeinfo) sendReq(from phony.Actor, key keyArray, callback func(nodeinfo *NodeInfoPayload)) { +func (m *nodeinfo) sendReq(from phony.Actor, key keyArray, callback func(nodeinfo NodeInfoPayload)) { m.Act(from, func() { m._sendReq(key, callback) }) } -func (m *nodeinfo) _sendReq(key keyArray, callback func(nodeinfo *NodeInfoPayload)) { +func (m *nodeinfo) _sendReq(key keyArray, callback func(nodeinfo NodeInfoPayload)) { if callback != nil { m._addCallback(key, callback) } @@ -198,3 +198,37 @@ func (m *nodeinfo) _sendRes(key keyArray) { bs := append([]byte{typeSessionNodeInfoResponse}, m._getNodeInfo()...) m.tun.core.WriteTo(bs, iwt.Addr(key[:])) } + +// Admin socket stuff + +type GetNodeInfoRequest struct { + Key string `json:"key"` +} +type GetNodeInfoResponse map[string]NodeInfoPayload + +func (m *nodeinfo) nodeInfoAdminHandler(in json.RawMessage) (interface{}, error) { + var req GetNodeInfoRequest + if err := json.Unmarshal(in, &req); err != nil { + return nil, err + } + var key keyArray + var kbs []byte + var err error + if kbs, err = hex.DecodeString(req.Key); err != nil { + return nil, err + } + copy(key[:], kbs) + ch := make(chan []byte, 1) + m.sendReq(nil, key, func(info NodeInfoPayload) { + ch <- info + }) + timer := time.NewTimer(6 * time.Second) + defer timer.Stop() + select { + case <-timer.C: + return nil, errors.New("timeout") + case info := <-ch: + res := GetNodeInfoResponse{req.Key: info} + return res, nil + } +} From eb4a22724fc512d2300596fbafd9cbed1e583de0 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 16 May 2021 15:55:30 -0500 Subject: [PATCH 0680/1109] possibly fix admin socket getnodeinfo --- src/tuntap/nodeinfo.go | 33 ++++++--------------------------- src/tuntap/tun.go | 1 + 2 files changed, 7 insertions(+), 27 deletions(-) diff --git a/src/tuntap/nodeinfo.go b/src/tuntap/nodeinfo.go index 6e6a2076..1c747681 100644 --- a/src/tuntap/nodeinfo.go +++ b/src/tuntap/nodeinfo.go @@ -23,7 +23,6 @@ type nodeinfo struct { tun *TunAdapter myNodeInfo NodeInfoPayload callbacks map[keyArray]nodeinfoCallback - cache map[keyArray]nodeinfoCached } type nodeinfoCached struct { @@ -54,8 +53,6 @@ func (m *nodeinfo) init(tun *TunAdapter) { func (m *nodeinfo) _init(tun *TunAdapter) { m.tun = tun m.callbacks = make(map[keyArray]nodeinfoCallback) - m.cache = make(map[keyArray]nodeinfoCached) - m._cleanup() } @@ -65,11 +62,6 @@ func (m *nodeinfo) _cleanup() { delete(m.callbacks, boxPubKey) } } - for boxPubKey, cache := range m.cache { - if time.Since(cache.created) > time.Hour { - delete(m.cache, boxPubKey) - } - } time.AfterFunc(time.Second*30, func() { m.Act(nil, m._cleanup) }) @@ -152,22 +144,6 @@ func (m *nodeinfo) _setNodeInfo(given interface{}, privacy bool) error { return err } -// Add nodeinfo into the cache for a node -func (m *nodeinfo) _addCachedNodeInfo(key keyArray, payload NodeInfoPayload) { - m.cache[key] = nodeinfoCached{ - created: time.Now(), - payload: payload, - } -} - -// Get a nodeinfo entry from the cache -func (m *nodeinfo) _getCachedNodeInfo(key keyArray) (NodeInfoPayload, error) { - if nodeinfo, ok := m.cache[key]; ok { - return nodeinfo.payload, nil - } - return NodeInfoPayload{}, errors.New("No cache entry found") -} - func (m *nodeinfo) sendReq(from phony.Actor, key keyArray, callback func(nodeinfo NodeInfoPayload)) { m.Act(from, func() { m._sendReq(key, callback) @@ -190,7 +166,6 @@ func (m *nodeinfo) handleReq(from phony.Actor, key keyArray) { func (m *nodeinfo) handleRes(from phony.Actor, key keyArray, info NodeInfoPayload) { m.Act(from, func() { m._callback(key, info) - m._addCachedNodeInfo(key, info) }) } @@ -204,7 +179,7 @@ func (m *nodeinfo) _sendRes(key keyArray) { type GetNodeInfoRequest struct { Key string `json:"key"` } -type GetNodeInfoResponse map[string]NodeInfoPayload +type GetNodeInfoResponse map[string]interface{} func (m *nodeinfo) nodeInfoAdminHandler(in json.RawMessage) (interface{}, error) { var req GetNodeInfoRequest @@ -228,7 +203,11 @@ func (m *nodeinfo) nodeInfoAdminHandler(in json.RawMessage) (interface{}, error) case <-timer.C: return nil, errors.New("timeout") case info := <-ch: - res := GetNodeInfoResponse{req.Key: info} + var msg json.RawMessage + if err := msg.UnmarshalJSON(info); err != nil { + return nil, err + } + res := GetNodeInfoResponse{req.Key: msg} return res, nil } } diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 97d0ba8b..53546c32 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -109,6 +109,7 @@ func (tun *TunAdapter) Init(core *yggdrasil.Core, config *config.NodeState, log tun.config = config tun.log = log tun.nodeinfo.init(tun) + tun.nodeinfo.setNodeInfo(config.Current.NodeInfo, config.Current.NodeInfoPrivacy) if err := tun.core.SetOutOfBandHandler(tun.oobHandler); err != nil { return fmt.Errorf("tun.core.SetOutOfBandHander: %w", err) } From 8d09e68e806f235f841d7730ec43f9b711c038a9 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 16 May 2021 16:16:58 -0500 Subject: [PATCH 0681/1109] admin socket getpaths --- src/admin/admin.go | 11 +++++++++++ src/admin/getpaths.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 src/admin/getpaths.go diff --git a/src/admin/admin.go b/src/admin/admin.go index 8ad8d907..c82d705d 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -117,6 +117,17 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { } return res, nil }) + _ = a.AddHandler("getPaths", []string{}, func(in json.RawMessage) (interface{}, error) { + req := &GetPathsRequest{} + res := &GetPathsResponse{} + if err := json.Unmarshal(in, &req); err != nil { + return nil, err + } + if err := a.getPathsHandler(req, res); err != nil { + return nil, err + } + return res, nil + }) _ = a.AddHandler("getSessions", []string{}, func(in json.RawMessage) (interface{}, error) { req := &GetSessionsRequest{} res := &GetSessionsResponse{} diff --git a/src/admin/getpaths.go b/src/admin/getpaths.go new file mode 100644 index 00000000..c8e97d01 --- /dev/null +++ b/src/admin/getpaths.go @@ -0,0 +1,33 @@ +package admin + +import ( + "encoding/hex" + "net" + + "github.com/yggdrasil-network/yggdrasil-go/src/address" +) + +type GetPathsRequest struct { +} + +type GetPathsResponse struct { + Paths map[string]PathEntry `json:"paths"` +} + +type PathEntry struct { + PublicKey string `json:"key"` + Path []uint64 `json:"path"` +} + +func (a *AdminSocket) getPathsHandler(req *GetPathsRequest, res *GetPathsResponse) error { + res.Paths = map[string]PathEntry{} + for _, p := range a.core.GetPaths() { + addr := address.AddrForKey(p.Key) + so := net.IP(addr[:]).String() + res.Paths[so] = PathEntry{ + PublicKey: hex.EncodeToString(p.Key), + Path: p.Path, + } + } + return nil +} From b11cf7a2f29dc75aad8c98dc2a1a4e44f92a6922 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Tue, 18 May 2021 20:43:38 -0500 Subject: [PATCH 0682/1109] update ironwood dependency, fix api --- go.mod | 2 +- go.sum | 4 ++-- src/admin/getdht.go | 4 ++-- src/yggdrasil/api.go | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 9c35842f..600026ba 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.16 require ( - github.com/Arceliar/ironwood v0.0.0-20210516202558-11160a0940e1 + github.com/Arceliar/ironwood v0.0.0-20210519013150-a5401869b037 github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 github.com/cheggaaa/pb/v3 v3.0.6 github.com/fatih/color v1.10.0 // indirect diff --git a/go.sum b/go.sum index e9fe5248..5e39b487 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/ironwood v0.0.0-20210516202558-11160a0940e1 h1:EVa/kL+8lgZmnAit/a0DIacKNZ/lj7NwjmO4B++t4PU= -github.com/Arceliar/ironwood v0.0.0-20210516202558-11160a0940e1/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= +github.com/Arceliar/ironwood v0.0.0-20210519013150-a5401869b037 h1:SQ7opLc8dCNAgyYIeVZUGwvZ5YrfqRLHMwOGWfH/S/k= +github.com/Arceliar/ironwood v0.0.0-20210519013150-a5401869b037/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= diff --git a/src/admin/getdht.go b/src/admin/getdht.go index 2796e4ff..5dc95547 100644 --- a/src/admin/getdht.go +++ b/src/admin/getdht.go @@ -16,7 +16,7 @@ type GetDHTResponse struct { type DHTEntry struct { PublicKey string `json:"key"` Port uint64 `json:"port"` - Next uint64 `json:"next"` + Rest uint64 `json:"rest"` } func (a *AdminSocket) getDHTHandler(req *GetDHTRequest, res *GetDHTResponse) error { @@ -27,7 +27,7 @@ func (a *AdminSocket) getDHTHandler(req *GetDHTRequest, res *GetDHTResponse) err res.DHT[so] = DHTEntry{ PublicKey: hex.EncodeToString(d.Key[:]), Port: d.Port, - Next: d.Next, + Rest: d.Rest, } } return nil diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index a2c0d541..1740e47b 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -31,7 +31,7 @@ type Peer struct { type DHTEntry struct { Key ed25519.PublicKey Port uint64 - Next uint64 + Rest uint64 } type PathEntry struct { @@ -73,7 +73,7 @@ func (c *Core) GetDHT() []DHTEntry { var info DHTEntry info.Key = d.Key info.Port = d.Port - info.Next = d.Next + info.Rest = d.Rest dhts = append(dhts, info) } return dhts From 8668abf48117d4e2fb729313bf13b26e6cfb593b Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 22 May 2021 19:54:52 -0500 Subject: [PATCH 0683/1109] WIP adding crawling debug packets --- src/tuntap/debug.go | 122 ++++++++++++++++++++++++++++++++++++++++++++ src/tuntap/iface.go | 5 ++ src/tuntap/tun.go | 2 + src/tuntap/types.go | 1 + 4 files changed, 130 insertions(+) create mode 100644 src/tuntap/debug.go diff --git a/src/tuntap/debug.go b/src/tuntap/debug.go new file mode 100644 index 00000000..ee19d3f0 --- /dev/null +++ b/src/tuntap/debug.go @@ -0,0 +1,122 @@ +package tuntap + +import ( + "time" + + iwt "github.com/Arceliar/ironwood/types" + "github.com/Arceliar/phony" +) + +const ( + typeDebugDummy = iota + typeDebugGetSelfRequest + typeDebugGetSelfResponse + typeDebugGetPeersRequest + typeDebugGetPeersResponse + typeDebugGetDHTRequest + typeDebugGetDHTResponse +) + +type reqInfo struct { + ch chan []byte + timer time.Timer // time.AfterFunc cleanup +} + +type debugHandler struct { + phony.Inbox + tun *TunAdapter + sreqs struct{} // TODO + preqs map[keyArray]*reqInfo + dreqs map[keyArray]*reqInfo +} + +func (d *debugHandler) init(tun *TunAdapter) { + d.tun = tun + d.preqs = make(map[keyArray]*reqInfo) + d.dreqs = make(map[keyArray]*reqInfo) +} + +func (d *debugHandler) handleDebug(from phony.Actor, key keyArray, bs []byte) { + d.Act(from, func() { + d._handleDebug(key, bs) + }) +} + +func (d *debugHandler) _handleDebug(key keyArray, bs []byte) { + if len(bs) == 0 { + return + } + switch bs[0] { + case typeDebugDummy: + case typeDebugGetSelfRequest: + d._handleGetSelfRequest(key) + case typeDebugGetSelfResponse: + d._handleGetSelfResponse(key, bs[1:]) + case typeDebugGetPeersRequest: + d._handleGetPeersRequest(key) + case typeDebugGetPeersResponse: + d._handleGetPeersResponse(key, bs[1:]) + case typeDebugGetDHTRequest: + d._handleGetDHTRequest(key) + case typeDebugGetDHTResponse: + d._handleGetDHTResponse(key, bs[1:]) + default: + } +} + +func (d *debugHandler) _handleGetSelfRequest(key keyArray) { + // TODO +} + +func (d *debugHandler) _handleGetSelfResponse(key keyArray, bs []byte) { + // TODO +} + +func (d *debugHandler) _handleGetPeersRequest(key keyArray) { + peers := d.tun.core.GetPeers() + var bs []byte + for _, p := range peers { + tmp := append(bs, p.Key[:]...) + const responseOverhead = 1 + if uint64(len(tmp))+1 > d.tun.maxSessionMTU() { + break + } + bs = tmp + } + d._sendDebug(key, typeDebugGetPeersResponse, bs) +} + +func (d *debugHandler) _handleGetPeersResponse(key keyArray, bs []byte) { + if info := d.preqs[key]; info != nil { + info.timer.Stop() + info.ch <- bs + delete(d.preqs, key) + } +} + +func (d *debugHandler) _handleGetDHTRequest(key keyArray) { + dinfos := d.tun.core.GetDHT() + var bs []byte + for _, dinfo := range dinfos { + tmp := append(bs, dinfo.Key[:]...) + const responseOverhead = 1 + if uint64(len(tmp))+1 > d.tun.maxSessionMTU() { + break + } + bs = tmp + } + d._sendDebug(key, typeDebugGetDHTResponse, bs) +} + +func (d *debugHandler) _handleGetDHTResponse(key keyArray, bs []byte) { + if info := d.dreqs[key]; info != nil { + info.timer.Stop() + info.ch <- bs + delete(d.dreqs, key) + } +} + +func (d *debugHandler) _sendDebug(key keyArray, dType uint8, data []byte) { + bs := append([]byte{typeSessionDebug, dType}, data...) + d.tun.core.WriteTo(bs, iwt.Addr(key[:])) +} diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index a3275436..20720db4 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -88,6 +88,11 @@ func (tun *TunAdapter) write() { res := append([]byte(nil), bs[1:n]...) tun.nodeinfo.handleRes(nil, key, res) continue + case typeSessionDebug: + var key keyArray + copy(key[:], from.(iwt.Addr)) + data := append([]byte(nil), bs[1:n]...) + tun.debug.handleDebug(nil, key, data) default: continue } diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 53546c32..36736719 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -47,6 +47,7 @@ type TunAdapter struct { isEnabled bool // Used by the writer to drop sessionTraffic if not enabled gatekeeper func(pubkey ed25519.PublicKey, initiator bool) bool nodeinfo nodeinfo + debug debugHandler } func (tun *TunAdapter) SetSessionGatekeeper(gatekeeper func(pubkey ed25519.PublicKey, initiator bool) bool) { @@ -113,6 +114,7 @@ func (tun *TunAdapter) Init(core *yggdrasil.Core, config *config.NodeState, log if err := tun.core.SetOutOfBandHandler(tun.oobHandler); err != nil { return fmt.Errorf("tun.core.SetOutOfBandHander: %w", err) } + tun.debug.init(tun) return nil } diff --git a/src/tuntap/types.go b/src/tuntap/types.go index f1267a92..102e03f0 100644 --- a/src/tuntap/types.go +++ b/src/tuntap/types.go @@ -13,4 +13,5 @@ const ( typeSessionTraffic typeSessionNodeInfoRequest typeSessionNodeInfoResponse + typeSessionDebug // Debug messages, intended to be removed at some point ) From c7b004d36fa38e6447ddbf7051702cb916c9f072 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 22 May 2021 20:25:14 -0500 Subject: [PATCH 0684/1109] get debugGetPeers and debugGetDHT working in the admin socket --- src/tuntap/admin.go | 2 + src/tuntap/debug.go | 147 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 145 insertions(+), 4 deletions(-) diff --git a/src/tuntap/admin.go b/src/tuntap/admin.go index 01cb56fa..800703c4 100644 --- a/src/tuntap/admin.go +++ b/src/tuntap/admin.go @@ -35,4 +35,6 @@ func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { return res, nil }) _ = a.AddHandler("getNodeInfo", []string{"key"}, t.nodeinfo.nodeInfoAdminHandler) + _ = a.AddHandler("debugGetPeers", []string{"key"}, t.debug.getPeersHandler) + _ = a.AddHandler("debugGetDHT", []string{"key"}, t.debug.getDHTHandler) } diff --git a/src/tuntap/debug.go b/src/tuntap/debug.go index ee19d3f0..9b1bd512 100644 --- a/src/tuntap/debug.go +++ b/src/tuntap/debug.go @@ -1,6 +1,9 @@ package tuntap import ( + "encoding/hex" + "encoding/json" + "errors" "time" iwt "github.com/Arceliar/ironwood/types" @@ -18,8 +21,8 @@ const ( ) type reqInfo struct { - ch chan []byte - timer time.Timer // time.AfterFunc cleanup + callback func([]byte) + timer *time.Timer // time.AfterFunc cleanup } type debugHandler struct { @@ -72,6 +75,26 @@ func (d *debugHandler) _handleGetSelfResponse(key keyArray, bs []byte) { // TODO } +func (d *debugHandler) sendGetPeersRequest(key keyArray, callback func([]byte)) { + d.Act(nil, func() { + if info := d.preqs[key]; info != nil { + info.timer.Stop() + delete(d.preqs, key) + } + info := new(reqInfo) + info.callback = callback + info.timer = time.AfterFunc(time.Minute, func() { + d.Act(nil, func() { + if d.preqs[key] == info { + delete(d.preqs, key) + } + }) + }) + d.preqs[key] = info + d._sendDebug(key, typeDebugGetPeersRequest, nil) + }) +} + func (d *debugHandler) _handleGetPeersRequest(key keyArray) { peers := d.tun.core.GetPeers() var bs []byte @@ -89,11 +112,31 @@ func (d *debugHandler) _handleGetPeersRequest(key keyArray) { func (d *debugHandler) _handleGetPeersResponse(key keyArray, bs []byte) { if info := d.preqs[key]; info != nil { info.timer.Stop() - info.ch <- bs + info.callback(bs) delete(d.preqs, key) } } +func (d *debugHandler) sendGetDHTRequest(key keyArray, callback func([]byte)) { + d.Act(nil, func() { + if info := d.dreqs[key]; info != nil { + info.timer.Stop() + delete(d.dreqs, key) + } + info := new(reqInfo) + info.callback = callback + info.timer = time.AfterFunc(time.Minute, func() { + d.Act(nil, func() { + if d.dreqs[key] == info { + delete(d.dreqs, key) + } + }) + }) + d.dreqs[key] = info + d._sendDebug(key, typeDebugGetDHTRequest, nil) + }) +} + func (d *debugHandler) _handleGetDHTRequest(key keyArray) { dinfos := d.tun.core.GetDHT() var bs []byte @@ -111,7 +154,7 @@ func (d *debugHandler) _handleGetDHTRequest(key keyArray) { func (d *debugHandler) _handleGetDHTResponse(key keyArray, bs []byte) { if info := d.dreqs[key]; info != nil { info.timer.Stop() - info.ch <- bs + info.callback(bs) delete(d.dreqs, key) } } @@ -120,3 +163,99 @@ func (d *debugHandler) _sendDebug(key keyArray, dType uint8, data []byte) { bs := append([]byte{typeSessionDebug, dType}, data...) d.tun.core.WriteTo(bs, iwt.Addr(key[:])) } + +// Admin socket stuff + +type DebugGetPeersRequest struct { + Key string `json:"key"` +} + +type DebugGetPeersResponse map[string]interface{} + +func (d *debugHandler) getPeersHandler(in json.RawMessage) (interface{}, error) { + var req DebugGetPeersRequest + if err := json.Unmarshal(in, &req); err != nil { + return nil, err + } + var key keyArray + var kbs []byte + var err error + if kbs, err = hex.DecodeString(req.Key); err != nil { + return nil, err + } + copy(key[:], kbs) + ch := make(chan []byte, 1) + d.sendGetPeersRequest(key, func(info []byte) { + ch <- info + }) + timer := time.NewTimer(6 * time.Second) + defer timer.Stop() + select { + case <-timer.C: + return nil, errors.New("timeout") + case info := <-ch: + ks := make(map[string][]string) + bs := info + for len(bs) >= len(key) { + ks["keys"] = append(ks["keys"], hex.EncodeToString(bs[:len(key)])) + bs = bs[len(key):] + } + js, err := json.Marshal(ks) + if err != nil { + return nil, err + } + var msg json.RawMessage + if err := msg.UnmarshalJSON(js); err != nil { + return nil, err + } + res := GetNodeInfoResponse{req.Key: msg} + return res, nil + } +} + +type DebugGetDHTRequest struct { + Key string `json:"key"` +} + +type DebugGetDHTResponse map[string]interface{} + +func (d *debugHandler) getDHTHandler(in json.RawMessage) (interface{}, error) { + var req DebugGetDHTRequest + if err := json.Unmarshal(in, &req); err != nil { + return nil, err + } + var key keyArray + var kbs []byte + var err error + if kbs, err = hex.DecodeString(req.Key); err != nil { + return nil, err + } + copy(key[:], kbs) + ch := make(chan []byte, 1) + d.sendGetDHTRequest(key, func(info []byte) { + ch <- info + }) + timer := time.NewTimer(6 * time.Second) + defer timer.Stop() + select { + case <-timer.C: + return nil, errors.New("timeout") + case info := <-ch: + ks := make(map[string][]string) + bs := info + for len(bs) >= len(key) { + ks["keys"] = append(ks["keys"], hex.EncodeToString(bs[:len(key)])) + bs = bs[len(key):] + } + js, err := json.Marshal(ks) + if err != nil { + return nil, err + } + var msg json.RawMessage + if err := msg.UnmarshalJSON(js); err != nil { + return nil, err + } + res := GetNodeInfoResponse{req.Key: msg} + return res, nil + } +} From 233cf0c96246384830adc3f1a1184b07f0671201 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 22 May 2021 21:27:11 -0500 Subject: [PATCH 0685/1109] add remote debugGetSelf and fix some return type things in the other debug functions --- src/tuntap/admin.go | 1 + src/tuntap/debug.go | 77 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 73 insertions(+), 5 deletions(-) diff --git a/src/tuntap/admin.go b/src/tuntap/admin.go index 800703c4..39d7cf5b 100644 --- a/src/tuntap/admin.go +++ b/src/tuntap/admin.go @@ -35,6 +35,7 @@ func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { return res, nil }) _ = a.AddHandler("getNodeInfo", []string{"key"}, t.nodeinfo.nodeInfoAdminHandler) + _ = a.AddHandler("debugGetSelf", []string{"key"}, t.debug.getSelfHandler) _ = a.AddHandler("debugGetPeers", []string{"key"}, t.debug.getPeersHandler) _ = a.AddHandler("debugGetDHT", []string{"key"}, t.debug.getDHTHandler) } diff --git a/src/tuntap/debug.go b/src/tuntap/debug.go index 9b1bd512..974715a5 100644 --- a/src/tuntap/debug.go +++ b/src/tuntap/debug.go @@ -28,13 +28,14 @@ type reqInfo struct { type debugHandler struct { phony.Inbox tun *TunAdapter - sreqs struct{} // TODO + sreqs map[keyArray]*reqInfo preqs map[keyArray]*reqInfo dreqs map[keyArray]*reqInfo } func (d *debugHandler) init(tun *TunAdapter) { d.tun = tun + d.sreqs = make(map[keyArray]*reqInfo) d.preqs = make(map[keyArray]*reqInfo) d.dreqs = make(map[keyArray]*reqInfo) } @@ -67,12 +68,41 @@ func (d *debugHandler) _handleDebug(key keyArray, bs []byte) { } } +func (d *debugHandler) sendGetSelfRequest(key keyArray, callback func([]byte)) { + d.Act(nil, func() { + if info := d.sreqs[key]; info != nil { + info.timer.Stop() + delete(d.sreqs, key) + } + info := new(reqInfo) + info.callback = callback + info.timer = time.AfterFunc(time.Minute, func() { + d.Act(nil, func() { + if d.sreqs[key] == info { + delete(d.sreqs, key) + } + }) + }) + d.sreqs[key] = info + d._sendDebug(key, typeDebugGetSelfRequest, nil) + }) +} + func (d *debugHandler) _handleGetSelfRequest(key keyArray) { - // TODO + self := d.tun.core.GetSelf() + bs, err := json.Marshal(self) + if err != nil { + return + } + d._sendDebug(key, typeDebugGetSelfResponse, bs) } func (d *debugHandler) _handleGetSelfResponse(key keyArray, bs []byte) { - // TODO + if info := d.sreqs[key]; info != nil { + info.timer.Stop() + info.callback(bs) + delete(d.sreqs, key) + } } func (d *debugHandler) sendGetPeersRequest(key keyArray, callback func([]byte)) { @@ -166,6 +196,43 @@ func (d *debugHandler) _sendDebug(key keyArray, dType uint8, data []byte) { // Admin socket stuff +type DebugGetSelfRequest struct { + Key string `json:"key"` +} + +type DebugGetSelfResponse map[string]interface{} + +func (d *debugHandler) getSelfHandler(in json.RawMessage) (interface{}, error) { + var req DebugGetSelfRequest + if err := json.Unmarshal(in, &req); err != nil { + return nil, err + } + var key keyArray + var kbs []byte + var err error + if kbs, err = hex.DecodeString(req.Key); err != nil { + return nil, err + } + copy(key[:], kbs) + ch := make(chan []byte, 1) + d.sendGetSelfRequest(key, func(info []byte) { + ch <- info + }) + timer := time.NewTimer(6 * time.Second) + defer timer.Stop() + select { + case <-timer.C: + return nil, errors.New("timeout") + case info := <-ch: + var msg json.RawMessage + if err := msg.UnmarshalJSON(info); err != nil { + return nil, err + } + res := DebugGetSelfResponse{req.Key: msg} + return res, nil + } +} + type DebugGetPeersRequest struct { Key string `json:"key"` } @@ -208,7 +275,7 @@ func (d *debugHandler) getPeersHandler(in json.RawMessage) (interface{}, error) if err := msg.UnmarshalJSON(js); err != nil { return nil, err } - res := GetNodeInfoResponse{req.Key: msg} + res := DebugGetPeersResponse{req.Key: msg} return res, nil } } @@ -255,7 +322,7 @@ func (d *debugHandler) getDHTHandler(in json.RawMessage) (interface{}, error) { if err := msg.UnmarshalJSON(js); err != nil { return nil, err } - res := GetNodeInfoResponse{req.Key: msg} + res := DebugGetDHTResponse{req.Key: msg} return res, nil } } From 29dda650b516bc2207cac367eee0635645a9deb0 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 23 May 2021 11:58:52 -0500 Subject: [PATCH 0686/1109] tun session protocol traffic cleanup --- src/tuntap/admin.go | 8 +- src/tuntap/iface.go | 16 +-- src/tuntap/nodeinfo.go | 16 +-- src/tuntap/{debug.go => proto.go} | 180 ++++++++++++++++-------------- src/tuntap/tun.go | 8 +- src/tuntap/types.go | 12 +- 6 files changed, 125 insertions(+), 115 deletions(-) rename src/tuntap/{debug.go => proto.go} (54%) diff --git a/src/tuntap/admin.go b/src/tuntap/admin.go index 39d7cf5b..565fa690 100644 --- a/src/tuntap/admin.go +++ b/src/tuntap/admin.go @@ -34,8 +34,8 @@ func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { } return res, nil }) - _ = a.AddHandler("getNodeInfo", []string{"key"}, t.nodeinfo.nodeInfoAdminHandler) - _ = a.AddHandler("debugGetSelf", []string{"key"}, t.debug.getSelfHandler) - _ = a.AddHandler("debugGetPeers", []string{"key"}, t.debug.getPeersHandler) - _ = a.AddHandler("debugGetDHT", []string{"key"}, t.debug.getDHTHandler) + _ = a.AddHandler("getNodeInfo", []string{"key"}, t.proto.nodeinfo.nodeInfoAdminHandler) + _ = a.AddHandler("debugGetSelf", []string{"key"}, t.proto.getSelfHandler) + _ = a.AddHandler("debugGetPeers", []string{"key"}, t.proto.getPeersHandler) + _ = a.AddHandler("debugGetDHT", []string{"key"}, t.proto.getDHTHandler) } diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 20720db4..81fbe4d4 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -77,22 +77,12 @@ func (tun *TunAdapter) write() { if !tun.isEnabled { continue // Drop traffic if the tun is disabled } - case typeSessionNodeInfoRequest: - var key keyArray - copy(key[:], from.(iwt.Addr)) - tun.nodeinfo.handleReq(nil, key) - continue - case typeSessionNodeInfoResponse: - var key keyArray - copy(key[:], from.(iwt.Addr)) - res := append([]byte(nil), bs[1:n]...) - tun.nodeinfo.handleRes(nil, key, res) - continue - case typeSessionDebug: + case typeSessionProto: var key keyArray copy(key[:], from.(iwt.Addr)) data := append([]byte(nil), bs[1:n]...) - tun.debug.handleDebug(nil, key, data) + tun.proto.handleProto(nil, key, data) + continue default: continue } diff --git a/src/tuntap/nodeinfo.go b/src/tuntap/nodeinfo.go index 1c747681..9cc8ade6 100644 --- a/src/tuntap/nodeinfo.go +++ b/src/tuntap/nodeinfo.go @@ -20,7 +20,7 @@ type NodeInfoPayload []byte type nodeinfo struct { phony.Inbox - tun *TunAdapter + proto *protoHandler myNodeInfo NodeInfoPayload callbacks map[keyArray]nodeinfoCallback } @@ -44,14 +44,14 @@ type nodeinfoReqRes struct { // Initialises the nodeinfo cache/callback maps, and starts a goroutine to keep // the cache/callback maps clean of stale entries -func (m *nodeinfo) init(tun *TunAdapter) { +func (m *nodeinfo) init(proto *protoHandler) { m.Act(nil, func() { - m._init(tun) + m._init(proto) }) } -func (m *nodeinfo) _init(tun *TunAdapter) { - m.tun = tun +func (m *nodeinfo) _init(proto *protoHandler) { + m.proto = proto m.callbacks = make(map[keyArray]nodeinfoCallback) m._cleanup() } @@ -154,7 +154,7 @@ func (m *nodeinfo) _sendReq(key keyArray, callback func(nodeinfo NodeInfoPayload if callback != nil { m._addCallback(key, callback) } - m.tun.core.WriteTo([]byte{typeSessionNodeInfoRequest}, iwt.Addr(key[:])) + m.proto.tun.core.WriteTo([]byte{typeSessionProto, typeProtoNodeInfoRequest}, iwt.Addr(key[:])) } func (m *nodeinfo) handleReq(from phony.Actor, key keyArray) { @@ -170,8 +170,8 @@ func (m *nodeinfo) handleRes(from phony.Actor, key keyArray, info NodeInfoPayloa } func (m *nodeinfo) _sendRes(key keyArray) { - bs := append([]byte{typeSessionNodeInfoResponse}, m._getNodeInfo()...) - m.tun.core.WriteTo(bs, iwt.Addr(key[:])) + bs := append([]byte{typeSessionProto, typeProtoNodeInfoResponse}, m._getNodeInfo()...) + m.proto.tun.core.WriteTo(bs, iwt.Addr(key[:])) } // Admin socket stuff diff --git a/src/tuntap/debug.go b/src/tuntap/proto.go similarity index 54% rename from src/tuntap/debug.go rename to src/tuntap/proto.go index 974715a5..d4fe72a4 100644 --- a/src/tuntap/debug.go +++ b/src/tuntap/proto.go @@ -25,173 +25,189 @@ type reqInfo struct { timer *time.Timer // time.AfterFunc cleanup } -type debugHandler struct { +type protoHandler struct { phony.Inbox - tun *TunAdapter - sreqs map[keyArray]*reqInfo - preqs map[keyArray]*reqInfo - dreqs map[keyArray]*reqInfo + tun *TunAdapter + nodeinfo nodeinfo + sreqs map[keyArray]*reqInfo + preqs map[keyArray]*reqInfo + dreqs map[keyArray]*reqInfo } -func (d *debugHandler) init(tun *TunAdapter) { - d.tun = tun - d.sreqs = make(map[keyArray]*reqInfo) - d.preqs = make(map[keyArray]*reqInfo) - d.dreqs = make(map[keyArray]*reqInfo) +func (p *protoHandler) init(tun *TunAdapter) { + p.tun = tun + p.nodeinfo.init(p) + p.sreqs = make(map[keyArray]*reqInfo) + p.preqs = make(map[keyArray]*reqInfo) + p.dreqs = make(map[keyArray]*reqInfo) } -func (d *debugHandler) handleDebug(from phony.Actor, key keyArray, bs []byte) { - d.Act(from, func() { - d._handleDebug(key, bs) +func (p *protoHandler) handleProto(from phony.Actor, key keyArray, bs []byte) { + if len(bs) == 0 { + return + } + switch bs[0] { + case typeProtoDummy: + case typeProtoNodeInfoRequest: + p.nodeinfo.handleReq(p, key) + case typeProtoNodeInfoResponse: + p.nodeinfo.handleRes(p, key, bs[1:]) + case typeProtoDebug: + p._handleDebug(key, bs[1:]) + } +} + +func (p *protoHandler) handleDebug(from phony.Actor, key keyArray, bs []byte) { + p.Act(from, func() { + p._handleDebug(key, bs) }) } -func (d *debugHandler) _handleDebug(key keyArray, bs []byte) { +func (p *protoHandler) _handleDebug(key keyArray, bs []byte) { if len(bs) == 0 { return } switch bs[0] { case typeDebugDummy: case typeDebugGetSelfRequest: - d._handleGetSelfRequest(key) + p._handleGetSelfRequest(key) case typeDebugGetSelfResponse: - d._handleGetSelfResponse(key, bs[1:]) + p._handleGetSelfResponse(key, bs[1:]) case typeDebugGetPeersRequest: - d._handleGetPeersRequest(key) + p._handleGetPeersRequest(key) case typeDebugGetPeersResponse: - d._handleGetPeersResponse(key, bs[1:]) + p._handleGetPeersResponse(key, bs[1:]) case typeDebugGetDHTRequest: - d._handleGetDHTRequest(key) + p._handleGetDHTRequest(key) case typeDebugGetDHTResponse: - d._handleGetDHTResponse(key, bs[1:]) - default: + p._handleGetDHTResponse(key, bs[1:]) } } -func (d *debugHandler) sendGetSelfRequest(key keyArray, callback func([]byte)) { - d.Act(nil, func() { - if info := d.sreqs[key]; info != nil { +func (p *protoHandler) sendGetSelfRequest(key keyArray, callback func([]byte)) { + p.Act(nil, func() { + if info := p.sreqs[key]; info != nil { info.timer.Stop() - delete(d.sreqs, key) + delete(p.sreqs, key) } info := new(reqInfo) info.callback = callback info.timer = time.AfterFunc(time.Minute, func() { - d.Act(nil, func() { - if d.sreqs[key] == info { - delete(d.sreqs, key) + p.Act(nil, func() { + if p.sreqs[key] == info { + delete(p.sreqs, key) } }) }) - d.sreqs[key] = info - d._sendDebug(key, typeDebugGetSelfRequest, nil) + p.sreqs[key] = info + p._sendDebug(key, typeDebugGetSelfRequest, nil) }) } -func (d *debugHandler) _handleGetSelfRequest(key keyArray) { - self := d.tun.core.GetSelf() - bs, err := json.Marshal(self) +func (p *protoHandler) _handleGetSelfRequest(key keyArray) { + self := p.tun.core.GetSelf() + bs, err := json.Marshal(self) // FIXME this puts keys in base64, not hex if err != nil { return } - d._sendDebug(key, typeDebugGetSelfResponse, bs) + p._sendDebug(key, typeDebugGetSelfResponse, bs) } -func (d *debugHandler) _handleGetSelfResponse(key keyArray, bs []byte) { - if info := d.sreqs[key]; info != nil { +func (p *protoHandler) _handleGetSelfResponse(key keyArray, bs []byte) { + if info := p.sreqs[key]; info != nil { info.timer.Stop() info.callback(bs) - delete(d.sreqs, key) + delete(p.sreqs, key) } } -func (d *debugHandler) sendGetPeersRequest(key keyArray, callback func([]byte)) { - d.Act(nil, func() { - if info := d.preqs[key]; info != nil { +func (p *protoHandler) sendGetPeersRequest(key keyArray, callback func([]byte)) { + p.Act(nil, func() { + if info := p.preqs[key]; info != nil { info.timer.Stop() - delete(d.preqs, key) + delete(p.preqs, key) } info := new(reqInfo) info.callback = callback info.timer = time.AfterFunc(time.Minute, func() { - d.Act(nil, func() { - if d.preqs[key] == info { - delete(d.preqs, key) + p.Act(nil, func() { + if p.preqs[key] == info { + delete(p.preqs, key) } }) }) - d.preqs[key] = info - d._sendDebug(key, typeDebugGetPeersRequest, nil) + p.preqs[key] = info + p._sendDebug(key, typeDebugGetPeersRequest, nil) }) } -func (d *debugHandler) _handleGetPeersRequest(key keyArray) { - peers := d.tun.core.GetPeers() +func (p *protoHandler) _handleGetPeersRequest(key keyArray) { + peers := p.tun.core.GetPeers() var bs []byte - for _, p := range peers { - tmp := append(bs, p.Key[:]...) - const responseOverhead = 1 - if uint64(len(tmp))+1 > d.tun.maxSessionMTU() { + for _, pinfo := range peers { + tmp := append(bs, pinfo.Key[:]...) + const responseOverhead = 2 // 1 debug type, 1 getpeers type + if uint64(len(tmp))+responseOverhead > p.tun.maxSessionMTU() { break } bs = tmp } - d._sendDebug(key, typeDebugGetPeersResponse, bs) + p._sendDebug(key, typeDebugGetPeersResponse, bs) } -func (d *debugHandler) _handleGetPeersResponse(key keyArray, bs []byte) { - if info := d.preqs[key]; info != nil { +func (p *protoHandler) _handleGetPeersResponse(key keyArray, bs []byte) { + if info := p.preqs[key]; info != nil { info.timer.Stop() info.callback(bs) - delete(d.preqs, key) + delete(p.preqs, key) } } -func (d *debugHandler) sendGetDHTRequest(key keyArray, callback func([]byte)) { - d.Act(nil, func() { - if info := d.dreqs[key]; info != nil { +func (p *protoHandler) sendGetDHTRequest(key keyArray, callback func([]byte)) { + p.Act(nil, func() { + if info := p.dreqs[key]; info != nil { info.timer.Stop() - delete(d.dreqs, key) + delete(p.dreqs, key) } info := new(reqInfo) info.callback = callback info.timer = time.AfterFunc(time.Minute, func() { - d.Act(nil, func() { - if d.dreqs[key] == info { - delete(d.dreqs, key) + p.Act(nil, func() { + if p.dreqs[key] == info { + delete(p.dreqs, key) } }) }) - d.dreqs[key] = info - d._sendDebug(key, typeDebugGetDHTRequest, nil) + p.dreqs[key] = info + p._sendDebug(key, typeDebugGetDHTRequest, nil) }) } -func (d *debugHandler) _handleGetDHTRequest(key keyArray) { - dinfos := d.tun.core.GetDHT() +func (p *protoHandler) _handleGetDHTRequest(key keyArray) { + dinfos := p.tun.core.GetDHT() var bs []byte for _, dinfo := range dinfos { tmp := append(bs, dinfo.Key[:]...) - const responseOverhead = 1 - if uint64(len(tmp))+1 > d.tun.maxSessionMTU() { + const responseOverhead = 2 // 1 debug type, 1 getdht type + if uint64(len(tmp))+responseOverhead > p.tun.maxSessionMTU() { break } bs = tmp } - d._sendDebug(key, typeDebugGetDHTResponse, bs) + p._sendDebug(key, typeDebugGetDHTResponse, bs) } -func (d *debugHandler) _handleGetDHTResponse(key keyArray, bs []byte) { - if info := d.dreqs[key]; info != nil { +func (p *protoHandler) _handleGetDHTResponse(key keyArray, bs []byte) { + if info := p.dreqs[key]; info != nil { info.timer.Stop() info.callback(bs) - delete(d.dreqs, key) + delete(p.dreqs, key) } } -func (d *debugHandler) _sendDebug(key keyArray, dType uint8, data []byte) { - bs := append([]byte{typeSessionDebug, dType}, data...) - d.tun.core.WriteTo(bs, iwt.Addr(key[:])) +func (p *protoHandler) _sendDebug(key keyArray, dType uint8, data []byte) { + bs := append([]byte{typeSessionProto, typeProtoDebug, dType}, data...) + p.tun.core.WriteTo(bs, iwt.Addr(key[:])) } // Admin socket stuff @@ -202,7 +218,7 @@ type DebugGetSelfRequest struct { type DebugGetSelfResponse map[string]interface{} -func (d *debugHandler) getSelfHandler(in json.RawMessage) (interface{}, error) { +func (p *protoHandler) getSelfHandler(in json.RawMessage) (interface{}, error) { var req DebugGetSelfRequest if err := json.Unmarshal(in, &req); err != nil { return nil, err @@ -215,7 +231,7 @@ func (d *debugHandler) getSelfHandler(in json.RawMessage) (interface{}, error) { } copy(key[:], kbs) ch := make(chan []byte, 1) - d.sendGetSelfRequest(key, func(info []byte) { + p.sendGetSelfRequest(key, func(info []byte) { ch <- info }) timer := time.NewTimer(6 * time.Second) @@ -239,7 +255,7 @@ type DebugGetPeersRequest struct { type DebugGetPeersResponse map[string]interface{} -func (d *debugHandler) getPeersHandler(in json.RawMessage) (interface{}, error) { +func (p *protoHandler) getPeersHandler(in json.RawMessage) (interface{}, error) { var req DebugGetPeersRequest if err := json.Unmarshal(in, &req); err != nil { return nil, err @@ -252,7 +268,7 @@ func (d *debugHandler) getPeersHandler(in json.RawMessage) (interface{}, error) } copy(key[:], kbs) ch := make(chan []byte, 1) - d.sendGetPeersRequest(key, func(info []byte) { + p.sendGetPeersRequest(key, func(info []byte) { ch <- info }) timer := time.NewTimer(6 * time.Second) @@ -286,7 +302,7 @@ type DebugGetDHTRequest struct { type DebugGetDHTResponse map[string]interface{} -func (d *debugHandler) getDHTHandler(in json.RawMessage) (interface{}, error) { +func (p *protoHandler) getDHTHandler(in json.RawMessage) (interface{}, error) { var req DebugGetDHTRequest if err := json.Unmarshal(in, &req); err != nil { return nil, err @@ -299,7 +315,7 @@ func (d *debugHandler) getDHTHandler(in json.RawMessage) (interface{}, error) { } copy(key[:], kbs) ch := make(chan []byte, 1) - d.sendGetDHTRequest(key, func(info []byte) { + p.sendGetDHTRequest(key, func(info []byte) { ch <- info }) timer := time.NewTimer(6 * time.Second) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 36736719..3df42892 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -46,8 +46,7 @@ type TunAdapter struct { isOpen bool isEnabled bool // Used by the writer to drop sessionTraffic if not enabled gatekeeper func(pubkey ed25519.PublicKey, initiator bool) bool - nodeinfo nodeinfo - debug debugHandler + proto protoHandler } func (tun *TunAdapter) SetSessionGatekeeper(gatekeeper func(pubkey ed25519.PublicKey, initiator bool) bool) { @@ -109,12 +108,11 @@ func (tun *TunAdapter) Init(core *yggdrasil.Core, config *config.NodeState, log tun.store.init(tun) tun.config = config tun.log = log - tun.nodeinfo.init(tun) - tun.nodeinfo.setNodeInfo(config.Current.NodeInfo, config.Current.NodeInfoPrivacy) + tun.proto.init(tun) + tun.proto.nodeinfo.setNodeInfo(config.Current.NodeInfo, config.Current.NodeInfoPrivacy) if err := tun.core.SetOutOfBandHandler(tun.oobHandler); err != nil { return fmt.Errorf("tun.core.SetOutOfBandHander: %w", err) } - tun.debug.init(tun) return nil } diff --git a/src/tuntap/types.go b/src/tuntap/types.go index 102e03f0..4d8bba1f 100644 --- a/src/tuntap/types.go +++ b/src/tuntap/types.go @@ -11,7 +11,13 @@ const ( const ( typeSessionDummy = iota typeSessionTraffic - typeSessionNodeInfoRequest - typeSessionNodeInfoResponse - typeSessionDebug // Debug messages, intended to be removed at some point + typeSessionProto +) + +// Protocol packet types +const ( + typeProtoDummy = iota + typeProtoNodeInfoRequest + typeProtoNodeInfoResponse + typeProtoDebug = 255 ) From e6f86a9bd7c59f77d78caaf0d4e06cc36183f106 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 23 May 2021 12:19:27 -0500 Subject: [PATCH 0687/1109] cleanup proto admin socket response formats --- src/tuntap/nodeinfo.go | 13 ++++++++----- src/tuntap/proto.go | 19 +++++++++++++++---- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/tuntap/nodeinfo.go b/src/tuntap/nodeinfo.go index 9cc8ade6..1597f35a 100644 --- a/src/tuntap/nodeinfo.go +++ b/src/tuntap/nodeinfo.go @@ -4,15 +4,17 @@ import ( "encoding/hex" "encoding/json" "errors" + "net" "runtime" "strings" "time" - "github.com/Arceliar/phony" - //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" - "github.com/yggdrasil-network/yggdrasil-go/src/version" - iwt "github.com/Arceliar/ironwood/types" + "github.com/Arceliar/phony" + + //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/version" ) // NodeInfoPayload represents a RequestNodeInfo response, in bytes. @@ -207,7 +209,8 @@ func (m *nodeinfo) nodeInfoAdminHandler(in json.RawMessage) (interface{}, error) if err := msg.UnmarshalJSON(info); err != nil { return nil, err } - res := GetNodeInfoResponse{req.Key: msg} + ip := net.IP(address.AddrForKey(kbs)[:]) + res := GetNodeInfoResponse{ip.String(): msg} return res, nil } } diff --git a/src/tuntap/proto.go b/src/tuntap/proto.go index d4fe72a4..c101ff71 100644 --- a/src/tuntap/proto.go +++ b/src/tuntap/proto.go @@ -4,10 +4,14 @@ import ( "encoding/hex" "encoding/json" "errors" + "fmt" + "net" "time" iwt "github.com/Arceliar/ironwood/types" "github.com/Arceliar/phony" + + "github.com/yggdrasil-network/yggdrasil-go/src/address" ) const ( @@ -106,7 +110,11 @@ func (p *protoHandler) sendGetSelfRequest(key keyArray, callback func([]byte)) { func (p *protoHandler) _handleGetSelfRequest(key keyArray) { self := p.tun.core.GetSelf() - bs, err := json.Marshal(self) // FIXME this puts keys in base64, not hex + res := map[string]string{ + "key": hex.EncodeToString(self.Key[:]), + "coords": fmt.Sprintf("%v", self.Coords), + } + bs, err := json.Marshal(res) // FIXME this puts keys in base64, not hex if err != nil { return } @@ -244,7 +252,8 @@ func (p *protoHandler) getSelfHandler(in json.RawMessage) (interface{}, error) { if err := msg.UnmarshalJSON(info); err != nil { return nil, err } - res := DebugGetSelfResponse{req.Key: msg} + ip := net.IP(address.AddrForKey(kbs)[:]) + res := DebugGetSelfResponse{ip.String(): msg} return res, nil } } @@ -291,7 +300,8 @@ func (p *protoHandler) getPeersHandler(in json.RawMessage) (interface{}, error) if err := msg.UnmarshalJSON(js); err != nil { return nil, err } - res := DebugGetPeersResponse{req.Key: msg} + ip := net.IP(address.AddrForKey(kbs)[:]) + res := DebugGetPeersResponse{ip.String(): msg} return res, nil } } @@ -338,7 +348,8 @@ func (p *protoHandler) getDHTHandler(in json.RawMessage) (interface{}, error) { if err := msg.UnmarshalJSON(js); err != nil { return nil, err } - res := DebugGetDHTResponse{req.Key: msg} + ip := net.IP(address.AddrForKey(kbs)[:]) + res := DebugGetDHTResponse{ip.String(): msg} return res, nil } } From f69f02386dae5d18d63f43bf86b0308cdcc31018 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 23 May 2021 13:37:46 -0500 Subject: [PATCH 0688/1109] rename debug admin socket functions --- src/tuntap/admin.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tuntap/admin.go b/src/tuntap/admin.go index 565fa690..9c9ceb67 100644 --- a/src/tuntap/admin.go +++ b/src/tuntap/admin.go @@ -35,7 +35,7 @@ func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { return res, nil }) _ = a.AddHandler("getNodeInfo", []string{"key"}, t.proto.nodeinfo.nodeInfoAdminHandler) - _ = a.AddHandler("debugGetSelf", []string{"key"}, t.proto.getSelfHandler) - _ = a.AddHandler("debugGetPeers", []string{"key"}, t.proto.getPeersHandler) - _ = a.AddHandler("debugGetDHT", []string{"key"}, t.proto.getDHTHandler) + _ = a.AddHandler("debug_remoteGetSelf", []string{"key"}, t.proto.getSelfHandler) + _ = a.AddHandler("debug_remoteGetPeers", []string{"key"}, t.proto.getPeersHandler) + _ = a.AddHandler("debug_remoteGetDHT", []string{"key"}, t.proto.getDHTHandler) } From 0343dad934d4e7faa7076379292b8ca7254b37f5 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 23 May 2021 14:33:28 -0500 Subject: [PATCH 0689/1109] remove obsolete crypto package --- cmd/yggdrasil/main.go | 18 +-- src/config/config.go | 12 +- src/crypto/crypto.go | 311 ------------------------------------------ src/yggdrasil/link.go | 13 +- 4 files changed, 25 insertions(+), 329 deletions(-) delete mode 100644 src/crypto/crypto.go diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 11310f7f..0ffad89d 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -25,7 +25,7 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/admin" "github.com/yggdrasil-network/yggdrasil-go/src/config" - "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/module" "github.com/yggdrasil-network/yggdrasil-go/src/multicast" "github.com/yggdrasil-network/yggdrasil-go/src/tuntap" @@ -220,23 +220,23 @@ func main() { return } // Have we been asked for the node address yet? If so, print it and then stop. - getNodeID := func() *crypto.NodeID { - // TODO: curve + getNodeKey := func() ed25519.PublicKey { + if pubkey, err := hex.DecodeString(cfg.PublicKey); err == nil { + return ed25519.PublicKey(pubkey) + } return nil } switch { case *getaddr: - if nodeid := getNodeID(); nodeid != nil { - panic("TODO") - addr := new(address.Address) //*address.AddrForNodeID(nodeid) + if key := getNodeKey(); key != nil { + addr := address.AddrForKey(key) ip := net.IP(addr[:]) fmt.Println(ip.String()) } return case *getsnet: - if nodeid := getNodeID(); nodeid != nil { - panic("TODO") - snet := new(address.Address) //*address.SubnetForNodeID(nodeid) + if key := getNodeKey(); key != nil { + snet := address.SubnetForKey(key) ipnet := net.IPNet{ IP: append(snet[:], 0, 0, 0, 0, 0, 0, 0, 0), Mask: net.CIDRMask(len(snet)*8, 128), diff --git a/src/config/config.go b/src/config/config.go index 49cc8a2a..073e1597 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -17,10 +17,10 @@ configuration option that is not provided. package config import ( + "crypto/ed25519" "encoding/hex" "sync" - "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" ) @@ -90,7 +90,10 @@ type SessionFirewall struct { // using -autoconf. func GenerateConfig() *NodeConfig { // Generate encryption keys. - spub, spriv := crypto.NewSigKeys() + spub, spriv, err := ed25519.GenerateKey(nil) + if err != nil { + panic(err) + } // Create a node configuration and populate it. cfg := NodeConfig{} cfg.Listen = []string{} @@ -116,7 +119,10 @@ func GenerateConfig() *NodeConfig { // signing keypair. The signing keys are used by the switch to derive the // structure of the spanning tree. func (cfg *NodeConfig) NewKeys() { - spub, spriv := crypto.NewSigKeys() + spub, spriv, err := ed25519.GenerateKey(nil) + if err != nil { + panic(err) + } cfg.PublicKey = hex.EncodeToString(spub[:]) cfg.PrivateKey = hex.EncodeToString(spriv[:]) } diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go deleted file mode 100644 index dcedb2c7..00000000 --- a/src/crypto/crypto.go +++ /dev/null @@ -1,311 +0,0 @@ -// Package crypto is a wrapper around packages under golang.org/x/crypto/, particulaly curve25519, ed25519, and nacl/box. -// This is used to avoid explicitly importing and using these packages throughout yggdrasil. -// It also includes the all-important NodeID and TreeID types, which are used to identify nodes in the DHT and in the spanning tree's root selection algorithm, respectively. -package crypto - -/* - -This part of the package wraps crypto operations needed elsewhere - -In particular, it exposes key generation for ed25519 and nacl box - -It also defines NodeID and TreeID as hashes of keys, and wraps hash functions - -*/ - -import ( - "crypto/rand" - "crypto/sha512" - "encoding/hex" - "sync" - - "golang.org/x/crypto/curve25519" - "golang.org/x/crypto/ed25519" - "golang.org/x/crypto/nacl/box" -) - -//////////////////////////////////////////////////////////////////////////////// - -// NodeID and TreeID - -// NodeIDLen is the length (in bytes) of a NodeID. -const NodeIDLen = sha512.Size - -// TreeIDLen is the length (in bytes) of a TreeID. -const TreeIDLen = sha512.Size - -// handleLen is the length (in bytes) of a Handle. -const handleLen = 8 - -// NodeID is how a yggdrasil node is identified in the DHT, and is used to derive IPv6 addresses and subnets in the main executable. It is a sha512sum hash of the node's BoxPubKey -type NodeID [NodeIDLen]byte - -// TreeID is how a yggdrasil node is identified in the root selection algorithm used to construct the spanning tree. -type TreeID [TreeIDLen]byte - -type Handle [handleLen]byte - -func (n *NodeID) String() string { - return hex.EncodeToString(n[:]) -} - -// Network returns "nodeid" nearly always right now. -func (n *NodeID) Network() string { - return "nodeid" -} - -// PrefixLength returns the number of bits set in a masked NodeID. -func (n *NodeID) PrefixLength() int { - var len int - for i, v := range *n { - _, _ = i, v - if v == 0xff { - len += 8 - continue - } - for v&0x80 != 0 { - len++ - v <<= 1 - } - if v != 0 { - return -1 - } - for i++; i < NodeIDLen; i++ { - if n[i] != 0 { - return -1 - } - } - break - } - return len -} - -// GetNodeID returns the NodeID associated with a BoxPubKey. -func GetNodeID(pub *BoxPubKey) *NodeID { - h := sha512.Sum512(pub[:]) - return (*NodeID)(&h) -} - -// GetTreeID returns the TreeID associated with a BoxPubKey -func GetTreeID(pub *SigPubKey) *TreeID { - h := sha512.Sum512(pub[:]) - return (*TreeID)(&h) -} - -// NewHandle returns a new (cryptographically random) Handle, used by the session code to identify which session an incoming packet is associated with. -func NewHandle() *Handle { - var h Handle - _, err := rand.Read(h[:]) - if err != nil { - panic(err) - } - return &h -} - -//////////////////////////////////////////////////////////////////////////////// - -// Signatures - -// SigPubKeyLen is the length of a SigPubKey in bytes. -const SigPubKeyLen = ed25519.PublicKeySize - -// SigPrivKeyLen is the length of a SigPrivKey in bytes. -const SigPrivKeyLen = ed25519.PrivateKeySize - -// SigLen is the length of SigBytes. -const SigLen = ed25519.SignatureSize - -// SigPubKey is a public ed25519 signing key. -type SigPubKey [SigPubKeyLen]byte - -// SigPrivKey is a private ed25519 signing key. -type SigPrivKey [SigPrivKeyLen]byte - -// SigBytes is an ed25519 signature. -type SigBytes [SigLen]byte - -// NewSigKeys generates a public/private ed25519 key pair. -func NewSigKeys() (*SigPubKey, *SigPrivKey) { - var pub SigPubKey - var priv SigPrivKey - pubSlice, privSlice, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - panic(err) - } - copy(pub[:], pubSlice) - copy(priv[:], privSlice) - return &pub, &priv -} - -// Sign returns the SigBytes signing a message. -func Sign(priv *SigPrivKey, msg []byte) *SigBytes { - var sig SigBytes - sigSlice := ed25519.Sign(priv[:], msg) - copy(sig[:], sigSlice) - return &sig -} - -// Verify returns true if the provided signature matches the key and message. -func Verify(pub *SigPubKey, msg []byte, sig *SigBytes) bool { - // Should sig be an array instead of a slice?... - // It's fixed size, but - return ed25519.Verify(pub[:], msg, sig[:]) -} - -// Public returns the SigPubKey associated with this SigPrivKey. -func (p SigPrivKey) Public() SigPubKey { - priv := make(ed25519.PrivateKey, ed25519.PrivateKeySize) - copy(priv[:], p[:]) - pub := priv.Public().(ed25519.PublicKey) - var sigPub SigPubKey - copy(sigPub[:], pub[:]) - return sigPub -} - -//////////////////////////////////////////////////////////////////////////////// - -// NaCl-like crypto "box" (curve25519+xsalsa20+poly1305) - -// BoxPubKeyLen is the length of a BoxPubKey in bytes. -const BoxPubKeyLen = 32 - -// BoxPrivKeyLen is the length of a BoxPrivKey in bytes. -const BoxPrivKeyLen = 32 - -// BoxSharedKeyLen is the length of a BoxSharedKey in bytes. -const BoxSharedKeyLen = 32 - -// BoxNonceLen is the length of a BoxNonce in bytes. -const BoxNonceLen = 24 - -// BoxOverhead is the length of the overhead from boxing something. -const BoxOverhead = box.Overhead - -// BoxPubKey is a NaCl-like "box" public key (curve25519+xsalsa20+poly1305). -type BoxPubKey [BoxPubKeyLen]byte - -// BoxPrivKey is a NaCl-like "box" private key (curve25519+xsalsa20+poly1305). -type BoxPrivKey [BoxPrivKeyLen]byte - -// BoxSharedKey is a NaCl-like "box" shared key (curve25519+xsalsa20+poly1305). -type BoxSharedKey [BoxSharedKeyLen]byte - -// BoxNonce is the nonce used in NaCl-like crypto "box" operations (curve25519+xsalsa20+poly1305), and must not be reused for different messages encrypted using the same BoxSharedKey. -type BoxNonce [BoxNonceLen]byte - -// String returns a string representation of the "box" key. -func (k BoxPubKey) String() string { - return hex.EncodeToString(k[:]) -} - -// Network returns "curve25519" for "box" keys. -func (n BoxPubKey) Network() string { - return "curve25519" -} - -// NewBoxKeys generates a new pair of public/private crypto box keys. -func NewBoxKeys() (*BoxPubKey, *BoxPrivKey) { - pubBytes, privBytes, err := box.GenerateKey(rand.Reader) - if err != nil { - panic(err) - } - pub := (*BoxPubKey)(pubBytes) - priv := (*BoxPrivKey)(privBytes) - return pub, priv -} - -// GetSharedKey returns the shared key derived from your private key and the destination's public key. -func GetSharedKey(myPrivKey *BoxPrivKey, - othersPubKey *BoxPubKey) *BoxSharedKey { - var shared [BoxSharedKeyLen]byte - priv := (*[BoxPrivKeyLen]byte)(myPrivKey) - pub := (*[BoxPubKeyLen]byte)(othersPubKey) - box.Precompute(&shared, pub, priv) - return (*BoxSharedKey)(&shared) -} - -// pool is used internally by BoxOpen and BoxSeal to avoid allocating temporary space -var pool = sync.Pool{New: func() interface{} { return []byte(nil) }} - -// BoxOpen returns a message and true if it successfully opens a crypto box using the provided shared key and nonce. -// The boxed input slice's backing array is reused for the unboxed output when possible. -func BoxOpen(shared *BoxSharedKey, - boxed []byte, - nonce *BoxNonce) ([]byte, bool) { - s := (*[BoxSharedKeyLen]byte)(shared) - n := (*[BoxNonceLen]byte)(nonce) - temp := append(pool.Get().([]byte), boxed...) - unboxed, success := box.OpenAfterPrecomputation(boxed[:0], temp, n, s) - pool.Put(temp[:0]) - return unboxed, success -} - -// BoxSeal seals a crypto box using the provided shared key, returning the box and the nonce needed to decrypt it. -// If nonce is nil, a random BoxNonce will be used and returned. -// If nonce is non-nil, then nonce.Increment() will be called before using it, and the incremented BoxNonce is what is returned. -// The unboxed input slice's backing array is reused for the boxed output when possible. -func BoxSeal(shared *BoxSharedKey, unboxed []byte, nonce *BoxNonce) ([]byte, *BoxNonce) { - if nonce == nil { - nonce = NewBoxNonce() - } - nonce.Increment() - s := (*[BoxSharedKeyLen]byte)(shared) - n := (*[BoxNonceLen]byte)(nonce) - temp := append(pool.Get().([]byte), unboxed...) - boxed := box.SealAfterPrecomputation(unboxed[:0], temp, n, s) - pool.Put(temp[:0]) - return boxed, nonce -} - -// NewBoxNonce generates a (cryptographically) random BoxNonce. -func NewBoxNonce() *BoxNonce { - var nonce BoxNonce - _, err := rand.Read(nonce[:]) - for ; err == nil && nonce[0] == 0xff; _, err = rand.Read(nonce[:]) { - // Make sure nonce isn't too high - // This is just to make rollover unlikely to happen - // Rollover is fine, but it may kill the session and force it to reopen - } - if err != nil { - panic(err) - } - return &nonce -} - -// Increment adds 2 to a BoxNonce, which is useful if one node intends to send only with odd BoxNonce values, and the other only with even BoxNonce values. -func (n *BoxNonce) Increment() { - oldNonce := *n - n[len(n)-1] += 2 - for i := len(n) - 2; i >= 0; i-- { - if n[i+1] < oldNonce[i+1] { - n[i]++ - } - } -} - -// Public returns the BoxPubKey associated with this BoxPrivKey. -func (p BoxPrivKey) Public() BoxPubKey { - var boxPub [BoxPubKeyLen]byte - var boxPriv [BoxPrivKeyLen]byte - copy(boxPriv[:BoxPrivKeyLen], p[:BoxPrivKeyLen]) - curve25519.ScalarBaseMult(&boxPub, &boxPriv) - return boxPub -} - -// Minus is the result of subtracting the provided BoNonce from this BoxNonce, bounded at +- 64. -// It's primarily used to determine if a new BoxNonce is higher than the last known BoxNonce from a crypto session, and by how much. -// This is used in the machinery that makes sure replayed packets can't keep a session open indefinitely or stuck using old/bad information about a node. -func (n *BoxNonce) Minus(m *BoxNonce) int64 { - diff := int64(0) - for idx := range n { - diff *= 256 - diff += int64(n[idx]) - int64(m[idx]) - if diff > 64 { - diff = 64 - } - if diff < -64 { - diff = -64 - } - } - return diff -} diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 29b73400..b22a59c4 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -15,12 +15,13 @@ import ( "time" "github.com/yggdrasil-network/yggdrasil-go/src/address" - "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/util" "golang.org/x/net/proxy" //"github.com/Arceliar/phony" // TODO? use instead of mutexes ) +type keyArray [ed25519.PublicKeySize]byte + type links struct { core *Core mutex sync.RWMutex // protects links below @@ -32,7 +33,7 @@ type links struct { // linkInfo is used as a map key type linkInfo struct { - key crypto.SigPubKey + key keyArray linkType string // Type of link, e.g. TCP, AWDL local string // Local name or address remote string // Remote name or address @@ -50,7 +51,7 @@ type link struct { } type linkOptions struct { - pinnedEd25519Keys map[crypto.SigPubKey]struct{} + pinnedEd25519Keys map[keyArray]struct{} } func (l *links) init(c *Core) error { @@ -80,10 +81,10 @@ func (l *links) call(uri string, sintf string) error { pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/") tcpOpts := tcpOptions{} if pubkeys, ok := u.Query()["ed25519"]; ok && len(pubkeys) > 0 { - tcpOpts.pinnedEd25519Keys = make(map[crypto.SigPubKey]struct{}) + tcpOpts.pinnedEd25519Keys = make(map[keyArray]struct{}) for _, pubkey := range pubkeys { if sigPub, err := hex.DecodeString(pubkey); err == nil { - var sigPubKey crypto.SigPubKey + var sigPubKey keyArray copy(sigPubKey[:], sigPub) tcpOpts.pinnedEd25519Keys[sigPubKey] = struct{}{} } @@ -200,7 +201,7 @@ func (intf *link) handler() (chan struct{}, error) { // Check if the remote side matches the keys we expected. This is a bit of a weak // check - in future versions we really should check a signature or something like that. if pinned := intf.options.pinnedEd25519Keys; pinned != nil { - var key crypto.SigPubKey + var key keyArray copy(key[:], meta.key) if _, allowed := pinned[key]; !allowed { intf.links.core.log.Errorf("Failed to connect to node: %q sent ed25519 key that does not match pinned keys", intf.name) From 018f35d9a2c2971e5dbdfe4312224d2ce15367fd Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 23 May 2021 14:42:26 -0500 Subject: [PATCH 0690/1109] rename src/yggdrasil to src/core --- cmd/yggdrasil/main.go | 4 ++-- src/admin/admin.go | 6 +++--- src/{yggdrasil => core}/api.go | 2 +- src/{yggdrasil => core}/core.go | 2 +- src/{yggdrasil => core}/core_test.go | 2 +- src/{yggdrasil => core}/debug.go | 2 +- src/{yggdrasil => core}/doc.go | 8 ++++---- src/{yggdrasil => core}/link.go | 2 +- src/{yggdrasil => core}/tcp.go | 2 +- src/{yggdrasil => core}/tcp_darwin.go | 2 +- src/{yggdrasil => core}/tcp_linux.go | 2 +- src/{yggdrasil => core}/tcp_other.go | 2 +- src/{yggdrasil => core}/tls.go | 2 +- src/{yggdrasil => core}/version.go | 2 +- src/module/module.go | 4 ++-- src/multicast/multicast.go | 8 ++++---- src/tuntap/tun.go | 6 +++--- 17 files changed, 29 insertions(+), 29 deletions(-) rename src/{yggdrasil => core}/api.go (99%) rename src/{yggdrasil => core}/core.go (99%) rename src/{yggdrasil => core}/core_test.go (99%) rename src/{yggdrasil => core}/debug.go (97%) rename src/{yggdrasil => core}/doc.go (96%) rename src/{yggdrasil => core}/link.go (99%) rename src/{yggdrasil => core}/tcp.go (99%) rename src/{yggdrasil => core}/tcp_darwin.go (97%) rename src/{yggdrasil => core}/tcp_linux.go (98%) rename src/{yggdrasil => core}/tcp_other.go (94%) rename src/{yggdrasil => core}/tls.go (99%) rename src/{yggdrasil => core}/version.go (99%) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 0ffad89d..e0d79fc4 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -26,15 +26,15 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/admin" "github.com/yggdrasil-network/yggdrasil-go/src/config" + "github.com/yggdrasil-network/yggdrasil-go/src/core" "github.com/yggdrasil-network/yggdrasil-go/src/module" "github.com/yggdrasil-network/yggdrasil-go/src/multicast" "github.com/yggdrasil-network/yggdrasil-go/src/tuntap" "github.com/yggdrasil-network/yggdrasil-go/src/version" - "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) type node struct { - core yggdrasil.Core + core core.Core state *config.NodeState tuntap module.Module // tuntap.TunAdapter multicast module.Module // multicast.Multicast diff --git a/src/admin/admin.go b/src/admin/admin.go index c82d705d..d2e066a3 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -14,13 +14,13 @@ import ( "github.com/gologme/log" "github.com/yggdrasil-network/yggdrasil-go/src/config" - "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" + "github.com/yggdrasil-network/yggdrasil-go/src/core" ) // TODO: Add authentication type AdminSocket struct { - core *yggdrasil.Core + core *core.Core log *log.Logger listenaddr string listener net.Listener @@ -63,7 +63,7 @@ func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc func(js } // Init runs the initial admin setup. -func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) error { +func (a *AdminSocket) Init(c *core.Core, state *config.NodeState, log *log.Logger, options interface{}) error { a.core = c a.log = log a.handlers = make(map[string]handler) diff --git a/src/yggdrasil/api.go b/src/core/api.go similarity index 99% rename from src/yggdrasil/api.go rename to src/core/api.go index 1740e47b..ec03df8d 100644 --- a/src/yggdrasil/api.go +++ b/src/core/api.go @@ -1,4 +1,4 @@ -package yggdrasil +package core import ( "crypto/ed25519" diff --git a/src/yggdrasil/core.go b/src/core/core.go similarity index 99% rename from src/yggdrasil/core.go rename to src/core/core.go index 0d41f711..0adc51b7 100644 --- a/src/yggdrasil/core.go +++ b/src/core/core.go @@ -1,4 +1,4 @@ -package yggdrasil +package core import ( "crypto/ed25519" diff --git a/src/yggdrasil/core_test.go b/src/core/core_test.go similarity index 99% rename from src/yggdrasil/core_test.go rename to src/core/core_test.go index 5f302c23..e1585c3a 100644 --- a/src/yggdrasil/core_test.go +++ b/src/core/core_test.go @@ -1,4 +1,4 @@ -package yggdrasil +package core import ( "bytes" diff --git a/src/yggdrasil/debug.go b/src/core/debug.go similarity index 97% rename from src/yggdrasil/debug.go rename to src/core/debug.go index b9bd5cfb..0fc08259 100644 --- a/src/yggdrasil/debug.go +++ b/src/core/debug.go @@ -1,6 +1,6 @@ // +build debug -package yggdrasil +package core import "fmt" diff --git a/src/yggdrasil/doc.go b/src/core/doc.go similarity index 96% rename from src/yggdrasil/doc.go rename to src/core/doc.go index 9b9dd738..595af229 100644 --- a/src/yggdrasil/doc.go +++ b/src/core/doc.go @@ -1,5 +1,5 @@ /* -Package yggdrasil implements the core functionality of the Yggdrasil Network. +Package core implements the core functionality of the Yggdrasil Network. Introduction @@ -34,11 +34,11 @@ This may look something like this: "os" "github.com/gologme/log" "github.com/yggdrasil-network/yggdrasil-go/src/config" - "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" + "github.com/yggdrasil-network/yggdrasil-go/src/core" ) type node struct { - core yggdrasil.Core + core core.Core config *config.NodeConfig log *log.Logger } @@ -173,4 +173,4 @@ then you should manually implement acknowledgement and retransmission of messages. */ -package yggdrasil +package core diff --git a/src/yggdrasil/link.go b/src/core/link.go similarity index 99% rename from src/yggdrasil/link.go rename to src/core/link.go index b22a59c4..3bdb02ae 100644 --- a/src/yggdrasil/link.go +++ b/src/core/link.go @@ -1,4 +1,4 @@ -package yggdrasil +package core import ( "crypto/ed25519" diff --git a/src/yggdrasil/tcp.go b/src/core/tcp.go similarity index 99% rename from src/yggdrasil/tcp.go rename to src/core/tcp.go index 59ca2ace..9031256a 100644 --- a/src/yggdrasil/tcp.go +++ b/src/core/tcp.go @@ -1,4 +1,4 @@ -package yggdrasil +package core // This sends packets to peers using TCP as a transport // It's generally better tested than the UDP implementation diff --git a/src/yggdrasil/tcp_darwin.go b/src/core/tcp_darwin.go similarity index 97% rename from src/yggdrasil/tcp_darwin.go rename to src/core/tcp_darwin.go index 3d0626c0..6b85c621 100644 --- a/src/yggdrasil/tcp_darwin.go +++ b/src/core/tcp_darwin.go @@ -1,6 +1,6 @@ // +build darwin -package yggdrasil +package core import ( "syscall" diff --git a/src/yggdrasil/tcp_linux.go b/src/core/tcp_linux.go similarity index 98% rename from src/yggdrasil/tcp_linux.go rename to src/core/tcp_linux.go index e18f92b1..39e3ba7a 100644 --- a/src/yggdrasil/tcp_linux.go +++ b/src/core/tcp_linux.go @@ -1,6 +1,6 @@ // +build linux -package yggdrasil +package core import ( "syscall" diff --git a/src/yggdrasil/tcp_other.go b/src/core/tcp_other.go similarity index 94% rename from src/yggdrasil/tcp_other.go rename to src/core/tcp_other.go index 7ee41971..97b81ed1 100644 --- a/src/yggdrasil/tcp_other.go +++ b/src/core/tcp_other.go @@ -1,6 +1,6 @@ // +build !darwin,!linux -package yggdrasil +package core import ( "syscall" diff --git a/src/yggdrasil/tls.go b/src/core/tls.go similarity index 99% rename from src/yggdrasil/tls.go rename to src/core/tls.go index fd8a291c..4fdcf997 100644 --- a/src/yggdrasil/tls.go +++ b/src/core/tls.go @@ -1,4 +1,4 @@ -package yggdrasil +package core import ( "bytes" diff --git a/src/yggdrasil/version.go b/src/core/version.go similarity index 99% rename from src/yggdrasil/version.go rename to src/core/version.go index a3c9bce5..6676c7b6 100644 --- a/src/yggdrasil/version.go +++ b/src/core/version.go @@ -1,4 +1,4 @@ -package yggdrasil +package core // This file contains the version metadata struct // Used in the initial connection setup and key exchange diff --git a/src/module/module.go b/src/module/module.go index 24854e71..d13b8cd2 100644 --- a/src/module/module.go +++ b/src/module/module.go @@ -5,13 +5,13 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/admin" "github.com/yggdrasil-network/yggdrasil-go/src/config" - "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" + "github.com/yggdrasil-network/yggdrasil-go/src/core" ) // 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 + Init(core *core.Core, state *config.NodeState, log *log.Logger, options interface{}) error Start() error Stop() error SetupAdminHandlers(a *admin.AdminSocket) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 6825776d..b7334da1 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -11,7 +11,7 @@ import ( "github.com/gologme/log" "github.com/yggdrasil-network/yggdrasil-go/src/config" - "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" + "github.com/yggdrasil-network/yggdrasil-go/src/core" "golang.org/x/net/ipv6" ) @@ -21,7 +21,7 @@ import ( // automatically. type Multicast struct { phony.Inbox - core *yggdrasil.Core + core *core.Core config *config.NodeState log *log.Logger sock *ipv6.PacketConn @@ -38,13 +38,13 @@ type interfaceInfo struct { } type listenerInfo struct { - listener *yggdrasil.TcpListener + listener *core.TcpListener time time.Time interval time.Duration } // Init prepares the multicast interface for use. -func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) error { +func (m *Multicast) Init(core *core.Core, state *config.NodeState, log *log.Logger, options interface{}) error { m.core = core m.config = state m.log = log diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 3df42892..49d8e426 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -22,8 +22,8 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/config" + "github.com/yggdrasil-network/yggdrasil-go/src/core" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" - "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) type MTU uint16 @@ -33,7 +33,7 @@ type MTU uint16 // should pass this object to the yggdrasil.SetRouterAdapter() function before // calling yggdrasil.Start(). type TunAdapter struct { - core *yggdrasil.Core + core *core.Core store keyStore config *config.NodeState log *log.Logger @@ -103,7 +103,7 @@ func MaximumMTU() uint64 { // Init initialises the TUN module. You must have acquired a Listener from // the Yggdrasil core before this point and it must not be in use elsewhere. -func (tun *TunAdapter) Init(core *yggdrasil.Core, config *config.NodeState, log *log.Logger, options interface{}) error { +func (tun *TunAdapter) Init(core *core.Core, config *config.NodeState, log *log.Logger, options interface{}) error { tun.core = core tun.store.init(tun) tun.config = config From 6bc2044ced8674cc2d07c8dfc4759a0ec5b19c26 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 23 May 2021 17:52:10 -0500 Subject: [PATCH 0691/1109] update ironwood dependency, fix ansible code, go mod tidy --- cmd/yggdrasilsim/dial.go | 61 -- cmd/yggdrasilsim/main.go | 6 - cmd/yggdrasilsim/node.go | 28 - cmd/yggdrasilsim/store.go | 41 - contrib/ansible/genkeys.go | 60 +- go.mod | 3 +- go.sum | 4 +- misc/sim/fc00-2017-08-12.txt | 1593 ---------------------------------- misc/sim/merge.py | 62 -- misc/sim/run-sim | 2 - misc/sim/treesim-forward.py | 901 ------------------- misc/sim/treesim.go | 459 ---------- src/core/link.go | 3 +- 13 files changed, 25 insertions(+), 3198 deletions(-) delete mode 100644 cmd/yggdrasilsim/dial.go delete mode 100644 cmd/yggdrasilsim/main.go delete mode 100644 cmd/yggdrasilsim/node.go delete mode 100644 cmd/yggdrasilsim/store.go delete mode 100644 misc/sim/fc00-2017-08-12.txt delete mode 100644 misc/sim/merge.py delete mode 100755 misc/sim/run-sim delete mode 100644 misc/sim/treesim-forward.py delete mode 100644 misc/sim/treesim.go diff --git a/cmd/yggdrasilsim/dial.go b/cmd/yggdrasilsim/dial.go deleted file mode 100644 index c7892d40..00000000 --- a/cmd/yggdrasilsim/dial.go +++ /dev/null @@ -1,61 +0,0 @@ -package main - -import ( - "fmt" - "sort" - "time" - - "github.com/yggdrasil-network/yggdrasil-go/src/crypto" -) - -func doListen(recvNode *simNode) { - // TODO be able to stop the listeners somehow so they don't leak across different tests - for { - c, err := recvNode.listener.Accept() - if err != nil { - panic(err) - } - c.Close() - } -} - -func dialTest(sendNode, recvNode *simNode) { - if sendNode.id == recvNode.id { - fmt.Println("Skipping dial to self") - return - } - var mask crypto.NodeID - for idx := range mask { - mask[idx] = 0xff - } - for { - c, err := sendNode.dialer.DialByNodeIDandMask(nil, &recvNode.nodeID, &mask) - if c != nil { - c.Close() - return - } - if err != nil { - fmt.Println("Dial failed:", err) - } - time.Sleep(time.Second) - } -} - -func dialStore(store nodeStore) { - var nodeIdxs []int - for idx, n := range store { - nodeIdxs = append(nodeIdxs, idx) - go doListen(n) - } - sort.Slice(nodeIdxs, func(i, j int) bool { - return nodeIdxs[i] < nodeIdxs[j] - }) - for _, idx := range nodeIdxs { - sendNode := store[idx] - for _, jdx := range nodeIdxs { - recvNode := store[jdx] - fmt.Printf("Dialing from node %d to node %d / %d...\n", idx, jdx, len(store)) - dialTest(sendNode, recvNode) - } - } -} diff --git a/cmd/yggdrasilsim/main.go b/cmd/yggdrasilsim/main.go deleted file mode 100644 index 25504c92..00000000 --- a/cmd/yggdrasilsim/main.go +++ /dev/null @@ -1,6 +0,0 @@ -package main - -func main() { - store := makeStoreSquareGrid(4) - dialStore(store) -} diff --git a/cmd/yggdrasilsim/node.go b/cmd/yggdrasilsim/node.go deleted file mode 100644 index 65e6a805..00000000 --- a/cmd/yggdrasilsim/node.go +++ /dev/null @@ -1,28 +0,0 @@ -package main - -import ( - "io/ioutil" - - "github.com/gologme/log" - - "github.com/yggdrasil-network/yggdrasil-go/src/config" - "github.com/yggdrasil-network/yggdrasil-go/src/crypto" - "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" -) - -type simNode struct { - core yggdrasil.Core - id int - nodeID crypto.NodeID - dialer *yggdrasil.Dialer - listener *yggdrasil.Listener -} - -func newNode(id int) *simNode { - n := simNode{id: id} - n.core.Start(config.GenerateConfig(), log.New(ioutil.Discard, "", 0)) - n.nodeID = *n.core.NodeID() - n.dialer, _ = n.core.ConnDialer() - n.listener, _ = n.core.ConnListen() - return &n -} diff --git a/cmd/yggdrasilsim/store.go b/cmd/yggdrasilsim/store.go deleted file mode 100644 index 6fce81a4..00000000 --- a/cmd/yggdrasilsim/store.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -type nodeStore map[int]*simNode - -func makeStoreSingle() nodeStore { - s := make(nodeStore) - s[0] = newNode(0) - return s -} - -func linkNodes(a *simNode, b *simNode) { - la := a.core.NewSimlink() - lb := b.core.NewSimlink() - la.SetDestination(lb) - lb.SetDestination(la) - la.Start() - lb.Start() -} - -func makeStoreSquareGrid(sideLength int) nodeStore { - store := make(nodeStore) - nNodes := sideLength * sideLength - idxs := make([]int, 0, nNodes) - // TODO shuffle nodeIDs - for idx := 1; idx <= nNodes; idx++ { - idxs = append(idxs, idx) - } - for _, idx := range idxs { - n := newNode(idx) - store[idx] = n - } - for idx := 0; idx < nNodes; idx++ { - if (idx % sideLength) != 0 { - linkNodes(store[idxs[idx]], store[idxs[idx-1]]) - } - if idx >= sideLength { - linkNodes(store[idxs[idx]], store[idxs[idx-sideLength]]) - } - } - return store -} diff --git a/contrib/ansible/genkeys.go b/contrib/ansible/genkeys.go index 681431b5..4a02b9bd 100644 --- a/contrib/ansible/genkeys.go +++ b/contrib/ansible/genkeys.go @@ -6,6 +6,7 @@ This file generates crypto keys for [ansible-yggdrasil](https://github.com/jcgru package main import ( + "crypto/ed25519" "encoding/hex" "flag" "fmt" @@ -14,7 +15,6 @@ import ( "github.com/cheggaaa/pb/v3" "github.com/yggdrasil-network/yggdrasil-go/src/address" - "github.com/yggdrasil-network/yggdrasil-go/src/crypto" ) var numHosts = flag.Int("hosts", 1, "number of host vars to generate") @@ -23,7 +23,6 @@ var keyTries = flag.Int("tries", 1000, "number of tries before taking the best k type keySet struct { priv []byte pub []byte - id []byte ip string } @@ -37,27 +36,15 @@ func main() { return } - var encryptionKeys []keySet + var keys []keySet for i := 0; i < *numHosts+1; i++ { - encryptionKeys = append(encryptionKeys, newBoxKey()) + keys = append(keys, newKey()) bar.Increment() } - encryptionKeys = sortKeySetArray(encryptionKeys) + keys = sortKeySetArray(keys) for i := 0; i < *keyTries-*numHosts-1; i++ { - encryptionKeys[0] = newBoxKey() - encryptionKeys = bubbleUpTo(encryptionKeys, 0) - bar.Increment() - } - - var signatureKeys []keySet - for i := 0; i < *numHosts+1; i++ { - signatureKeys = append(signatureKeys, newSigKey()) - bar.Increment() - } - signatureKeys = sortKeySetArray(signatureKeys) - for i := 0; i < *keyTries-*numHosts-1; i++ { - signatureKeys[0] = newSigKey() - signatureKeys = bubbleUpTo(signatureKeys, 0) + keys[0] = newKey() + keys = bubbleUpTo(keys, 0) bar.Increment() } @@ -70,43 +57,36 @@ func main() { return } defer file.Close() - file.WriteString(fmt.Sprintf("yggdrasil_encryption_public_key: %v\n", hex.EncodeToString(encryptionKeys[i].pub))) - file.WriteString("yggdrasil_encryption_private_key: \"{{ vault_yggdrasil_encryption_private_key }}\"\n") - file.WriteString(fmt.Sprintf("yggdrasil_signing_public_key: %v\n", hex.EncodeToString(signatureKeys[i].pub))) - file.WriteString("yggdrasil_signing_private_key: \"{{ vault_yggdrasil_signing_private_key }}\"\n") - file.WriteString(fmt.Sprintf("ansible_host: %v\n", encryptionKeys[i].ip)) + file.WriteString(fmt.Sprintf("yggdrasil_public_key: %v\n", hex.EncodeToString(keys[i].pub))) + file.WriteString("yggdrasil_private_key: \"{{ vault_yggdrasil_private_key }}\"\n") + file.WriteString(fmt.Sprintf("ansible_host: %v\n", keys[i].ip)) file, err = os.Create(fmt.Sprintf("host_vars/%x/vault", i)) if err != nil { return } defer file.Close() - file.WriteString(fmt.Sprintf("vault_yggdrasil_encryption_private_key: %v\n", hex.EncodeToString(encryptionKeys[i].priv))) - file.WriteString(fmt.Sprintf("vault_yggdrasil_signing_private_key: %v\n", hex.EncodeToString(signatureKeys[i].priv))) + file.WriteString(fmt.Sprintf("vault_yggdrasil_private_key: %v\n", hex.EncodeToString(keys[i].priv))) bar.Increment() } bar.Finish() } -func newBoxKey() keySet { - pub, priv := crypto.NewBoxKeys() - id := crypto.GetNodeID(pub) - ip := net.IP(address.AddrForNodeID(id)[:]).String() - return keySet{priv[:], pub[:], id[:], ip} -} - -func newSigKey() keySet { - pub, priv := crypto.NewSigKeys() - id := crypto.GetTreeID(pub) - return keySet{priv[:], pub[:], id[:], ""} +func newKey() keySet { + pub, priv, err := ed25519.GenerateKey(nil) + if err != nil { + panic(err) + } + ip := net.IP(address.AddrForKey(pub)[:]).String() + return keySet{priv[:], pub[:], ip} } func isBetter(oldID, newID []byte) bool { for idx := range oldID { - if newID[idx] > oldID[idx] { + if newID[idx] < oldID[idx] { return true } - if newID[idx] < oldID[idx] { + if newID[idx] > oldID[idx] { return false } } @@ -122,7 +102,7 @@ func sortKeySetArray(sets []keySet) []keySet { func bubbleUpTo(sets []keySet, num int) []keySet { for i := 0; i < len(sets)-num-1; i++ { - if isBetter(sets[i+1].id, sets[i].id) { + if isBetter(sets[i+1].pub, sets[i].pub) { var tmp = sets[i] sets[i] = sets[i+1] sets[i+1] = tmp diff --git a/go.mod b/go.mod index 600026ba..23346640 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.16 require ( - github.com/Arceliar/ironwood v0.0.0-20210519013150-a5401869b037 + github.com/Arceliar/ironwood v0.0.0-20210523223424-d320cf0ed78e github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 github.com/cheggaaa/pb/v3 v3.0.6 github.com/fatih/color v1.10.0 // indirect @@ -16,7 +16,6 @@ require ( github.com/rivo/uniseg v0.2.0 // indirect github.com/vishvananda/netlink v1.1.0 github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect - golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b golang.org/x/text v0.3.6-0.20210220033129-8f690f22cf1c diff --git a/go.sum b/go.sum index 5e39b487..9c66c19d 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/ironwood v0.0.0-20210519013150-a5401869b037 h1:SQ7opLc8dCNAgyYIeVZUGwvZ5YrfqRLHMwOGWfH/S/k= -github.com/Arceliar/ironwood v0.0.0-20210519013150-a5401869b037/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= +github.com/Arceliar/ironwood v0.0.0-20210523223424-d320cf0ed78e h1:EoZ4Dfm3xBDFjXRUzZUH+44NVvQ8tLf/VyESuC0BijI= +github.com/Arceliar/ironwood v0.0.0-20210523223424-d320cf0ed78e/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= diff --git a/misc/sim/fc00-2017-08-12.txt b/misc/sim/fc00-2017-08-12.txt deleted file mode 100644 index 13fa724d..00000000 --- a/misc/sim/fc00-2017-08-12.txt +++ /dev/null @@ -1,1593 +0,0 @@ -0 1 -2 3 -2 4 -2 5 -3 6 -3 7 -3 8 -3 9 -3 10 -3 11 -4 11 -4 12 -4 13 -4 14 -4 15 -4 16 -4 17 -4 18 -4 19 -4 20 -4 21 -4 22 -4 23 -4 24 -4 25 -4 26 -4 27 -4 28 -4 29 -4 30 -4 31 -4 32 -4 33 -4 34 -4 35 -4 36 -4 37 -4 38 -4 39 -4 40 -4 41 -4 42 -4 43 -4 44 -4 45 -4 46 -4 47 -4 48 -5 10 -5 11 -5 14 -5 20 -5 22 -5 24 -5 25 -5 29 -5 31 -5 36 -5 39 -5 47 -5 49 -5 50 -5 51 -5 52 -5 53 -5 54 -5 55 -5 56 -5 57 -5 58 -5 59 -5 60 -5 61 -5 62 -5 63 -5 64 -5 65 -5 66 -5 67 -5 68 -5 69 -5 70 -6 71 -6 72 -7 0 -7 6 -7 19 -7 50 -7 73 -7 74 -7 75 -7 76 -7 77 -7 78 -7 79 -7 80 -7 81 -7 82 -7 83 -7 84 -7 85 -7 86 -7 87 -7 88 -7 89 -7 90 -7 91 -7 92 -7 93 -8 50 -9 8 -9 50 -9 94 -9 95 -10 96 -10 97 -10 98 -10 99 -12 5 -12 7 -12 100 -12 101 -12 102 -14 15 -14 26 -14 31 -14 32 -14 103 -14 104 -14 105 -16 5 -16 6 -16 71 -16 101 -16 102 -16 106 -16 107 -16 108 -16 109 -20 7 -20 8 -20 9 -20 110 -20 111 -20 101 -20 102 -21 7 -21 20 -24 8 -24 110 -24 101 -24 112 -24 113 -24 114 -24 115 -24 116 -28 37 -28 38 -28 117 -29 118 -30 119 -31 26 -31 32 -31 104 -32 26 -32 46 -32 105 -33 5 -33 101 -33 102 -34 5 -34 101 -35 7 -38 37 -40 101 -41 120 -41 121 -42 122 -42 123 -45 7 -45 124 -47 8 -52 54 -52 55 -52 56 -52 57 -52 58 -52 59 -52 125 -53 52 -53 54 -53 57 -53 60 -53 61 -53 126 -54 55 -56 55 -56 57 -56 58 -56 59 -56 125 -57 54 -57 55 -57 58 -57 59 -58 54 -58 55 -58 59 -59 54 -59 55 -61 60 -62 52 -62 54 -62 55 -62 56 -62 57 -62 58 -62 59 -62 125 -63 64 -63 127 -65 99 -66 100 -67 49 -70 128 -129 3 -129 6 -129 130 -129 131 -130 7 -130 8 -130 9 -130 10 -130 22 -73 74 -74 0 -74 1 -74 19 -74 75 -74 132 -74 133 -74 76 -135 7 -135 136 -135 137 -136 3 -136 20 -136 50 -136 110 -136 111 -136 138 -136 139 -136 140 -136 141 -136 142 -136 77 -136 78 -136 79 -136 143 -136 144 -136 80 -136 145 -136 146 -136 147 -136 148 -136 149 -136 150 -136 151 -136 152 -136 153 -136 154 -136 155 -136 156 -136 157 -136 158 -137 7 -137 19 -137 20 -137 22 -137 50 -137 73 -137 75 -137 110 -137 111 -137 78 -137 79 -137 143 -137 80 -137 147 -137 149 -137 150 -137 81 -137 86 -137 87 -137 88 -137 89 -137 91 -137 93 -137 159 -137 160 -137 161 -137 162 -137 163 -137 164 -110 7 -110 8 -110 9 -110 65 -110 130 -110 139 -110 79 -110 83 -110 120 -110 108 -110 165 -110 166 -110 114 -110 167 -110 168 -110 169 -110 170 -138 8 -138 9 -138 67 -138 137 -139 7 -140 7 -140 9 -140 110 -140 171 -140 172 -140 173 -140 174 -141 7 -141 137 -141 110 -141 175 -142 7 -142 176 -144 7 -144 137 -80 78 -145 7 -145 110 -146 137 -147 7 -148 7 -148 110 -151 7 -151 137 -153 111 -154 3 -154 6 -156 7 -156 137 -157 77 -158 137 -158 119 -159 7 -160 101 -160 96 -160 97 -161 7 -161 110 -162 7 -162 74 -163 7 -163 74 -164 7 -100 0 -100 3 -100 8 -100 9 -100 20 -100 22 -100 50 -100 69 -100 72 -100 74 -100 84 -100 161 -100 177 -100 178 -100 179 -100 180 -100 181 -100 182 -100 183 -100 94 -100 184 -100 185 -100 186 -100 187 -100 188 -100 189 -100 190 -101 0 -101 18 -101 19 -101 25 -101 49 -101 74 -101 106 -101 107 -102 0 -102 6 -102 19 -102 74 -102 191 -102 192 -102 193 -102 194 -178 7 -180 7 -181 6 -181 72 -182 23 -183 10 -184 7 -184 9 -185 7 -186 195 -187 177 -187 196 -187 197 -187 198 -189 7 -190 199 -106 200 -202 203 -203 8 -203 9 -203 28 -203 204 -204 3 -204 8 -204 9 -204 20 -204 50 -204 130 -204 110 -204 100 -204 94 -204 205 -204 176 -206 4 -206 13 -206 14 -206 15 -206 103 -206 207 -206 208 -206 209 -206 210 -103 13 -103 211 -103 120 -207 15 -207 210 -207 212 -208 15 -209 15 -210 13 -210 14 -210 15 -210 103 -210 213 -211 15 -211 120 -120 13 -212 120 -214 215 -214 216 -214 217 -214 218 -214 219 -217 4 -217 7 -217 136 -217 137 -217 110 -217 220 -220 3 -220 5 -220 12 -220 20 -220 21 -220 22 -220 23 -220 24 -220 49 -220 51 -220 52 -220 53 -220 54 -220 55 -220 56 -220 57 -220 58 -220 59 -220 60 -220 61 -220 62 -220 129 -220 130 -220 137 -220 138 -220 139 -220 82 -220 160 -220 178 -220 180 -220 221 -220 222 -220 223 -220 224 -220 225 -220 226 -220 227 -220 96 -220 228 -220 229 -220 230 -220 231 -220 97 -220 232 -220 233 -220 234 -235 236 -236 237 -236 238 -237 239 -237 240 -237 241 -243 103 -243 210 -243 211 -243 220 -243 126 -243 244 -126 52 -126 56 -126 57 -126 58 -126 59 -126 60 -126 61 -126 62 -244 5 -244 10 -244 179 -244 245 -244 246 -244 112 -244 247 -221 7 -221 201 -222 7 -222 71 -222 100 -222 178 -222 228 -223 23 -223 182 -224 5 -224 52 -224 54 -224 55 -224 56 -224 57 -224 58 -224 59 -224 62 -224 126 -224 232 -224 121 -224 125 -224 248 -226 16 -226 249 -226 250 -228 7 -228 71 -228 100 -228 178 -229 7 -229 233 -230 4 -230 18 -230 24 -230 67 -230 159 -230 121 -230 251 -231 7 -231 252 -231 253 -97 96 -232 5 -232 54 -232 55 -232 56 -232 57 -232 58 -232 59 -232 62 -232 121 -232 125 -232 248 -246 96 -246 97 -246 112 -246 254 -112 255 -112 170 -196 177 -257 4 -257 5 -257 16 -257 101 -257 102 -257 178 -257 258 -258 6 -258 16 -258 71 -258 222 -258 226 -258 113 -113 50 -113 65 -113 71 -113 259 -108 260 -109 101 -262 7 -263 137 -265 17 -128 7 -128 101 -266 100 -266 177 -266 267 -267 177 -268 18 -114 65 -114 165 -168 7 -272 7 -272 136 -272 273 -272 274 -273 274 -273 275 -276 277 -277 92 -278 7 -121 52 -121 53 -121 54 -121 55 -121 56 -121 57 -121 58 -121 59 -121 60 -121 61 -121 62 -121 126 -121 114 -125 55 -125 57 -125 58 -125 59 -248 52 -248 53 -248 55 -248 56 -248 57 -248 59 -248 62 -248 98 -251 116 -116 110 -116 160 -197 177 -279 136 -279 280 -280 136 -281 140 -281 171 -173 9 -283 7 -283 136 -283 137 -283 110 -283 141 -283 175 -284 182 -285 7 -285 101 -285 220 -205 5 -205 7 -205 8 -205 9 -205 136 -205 137 -205 110 -205 142 -205 100 -205 102 -205 113 -205 176 -176 7 -176 8 -176 9 -176 286 -288 5 -288 220 -289 290 -290 10 -290 137 -290 291 -290 292 -290 293 -290 294 -290 295 -291 237 -291 239 -291 296 -291 297 -291 298 -291 299 -293 292 -293 294 -295 110 -300 7 -300 301 -300 302 -300 303 -302 301 -303 301 -304 305 -305 306 -306 118 -307 102 -309 10 -309 310 -310 10 -311 312 -312 237 -313 111 -314 7 -314 137 -316 239 -317 318 -317 319 -318 237 -318 291 -318 239 -318 320 -318 321 -318 322 -320 237 -320 242 -320 291 -320 239 -320 323 -320 296 -320 324 -326 7 -326 136 -326 77 -329 29 -118 330 -331 180 -119 18 -332 333 -334 141 -335 4 -335 5 -335 101 -335 102 -335 336 -335 337 -336 2 -336 3 -336 6 -336 11 -336 12 -336 16 -336 18 -336 20 -336 29 -336 30 -336 33 -336 34 -336 36 -336 37 -336 38 -336 39 -336 63 -336 107 -336 223 -336 257 -336 268 -336 119 -336 338 -336 339 -336 340 -336 341 -336 342 -337 4 -337 5 -337 7 -337 9 -337 12 -337 13 -337 16 -337 20 -337 23 -337 28 -337 33 -337 34 -337 47 -337 50 -337 63 -337 65 -337 70 -337 129 -337 130 -337 136 -337 137 -337 110 -337 84 -337 90 -337 100 -337 101 -337 102 -337 177 -337 179 -337 106 -337 206 -337 220 -337 257 -337 113 -337 261 -337 259 -337 336 -337 343 -337 344 -337 345 -337 346 -337 347 -338 3 -338 4 -338 5 -338 11 -338 339 -339 3 -339 10 -339 11 -341 7 -341 18 -341 22 -341 130 -341 268 -341 348 -343 113 -345 101 -345 102 -347 261 -349 23 -349 100 -349 182 -349 220 -349 223 -349 284 -349 350 -351 5 -352 100 -354 7 -354 137 -355 102 -355 356 -355 357 -356 41 -357 3 -357 4 -357 6 -357 12 -357 14 -357 16 -357 20 -357 26 -357 29 -357 31 -357 32 -357 33 -357 46 -357 66 -357 79 -357 257 -357 171 -357 173 -357 335 -357 337 -357 358 -357 359 -359 4 -359 14 -359 32 -362 7 -362 100 -260 237 -260 363 -364 0 -364 1 -364 7 -364 19 -364 73 -364 74 -364 75 -364 132 -364 133 -364 162 -364 163 -364 100 -364 101 -364 102 -364 365 -364 366 -364 367 -364 368 -364 369 -364 370 -364 371 -364 372 -364 373 -364 374 -364 375 -364 376 -364 377 -365 74 -365 373 -366 74 -367 74 -368 74 -369 7 -369 74 -369 137 -370 74 -371 74 -372 7 -372 74 -372 137 -373 74 -373 132 -374 74 -375 74 -376 74 -377 74 -377 376 -378 323 -379 240 -380 140 -380 171 -381 382 -381 383 -381 384 -381 385 -382 383 -382 384 -383 102 -383 384 -384 386 -384 387 -385 102 -385 384 -386 193 -387 193 -389 390 -391 3 -391 71 -391 101 -391 258 -391 113 -391 339 -393 3 -393 110 -394 291 -395 282 -395 396 -396 390 -397 237 -397 291 -397 318 -397 320 -397 296 -398 160 -348 10 -399 82 -400 4 -400 5 -400 101 -400 102 -400 336 -400 337 -400 357 -400 401 -401 0 -401 6 -401 12 -401 16 -401 20 -401 33 -401 74 -401 102 -401 192 -401 193 -401 257 -401 205 -401 307 -401 335 -401 337 -401 345 -401 355 -401 364 -401 383 -401 385 -401 402 -401 403 -401 404 -401 405 -401 406 -402 102 -403 102 -403 121 -404 4 -404 5 -404 7 -404 16 -404 129 -404 137 -404 100 -404 101 -404 102 -404 220 -404 258 -404 336 -404 337 -405 4 -405 5 -405 102 -406 0 -406 1 -406 7 -406 19 -406 73 -406 74 -406 75 -406 132 -406 133 -406 76 -406 162 -406 163 -406 100 -406 101 -406 102 -406 364 -406 365 -406 366 -406 367 -406 368 -406 369 -406 371 -406 372 -406 373 -406 374 -406 375 -406 376 -406 377 -407 10 -409 237 -409 410 -409 411 -412 102 -413 12 -414 7 -415 16 -415 24 -415 113 -415 337 -416 241 -420 102 -420 402 -422 242 -422 325 -422 423 -424 7 -424 100 -426 136 -427 7 -427 137 -428 7 -428 110 -429 422 -430 131 -430 126 -430 431 -431 129 -432 177 -435 296 -437 7 -437 438 -441 226 -442 5 -443 7 -443 110 -443 168 -444 260 -445 394 -448 5 -449 5 -451 130 -451 328 -452 7 -452 137 -453 101 -453 308 -453 339 -453 341 -454 82 -455 7 -455 137 -456 301 -459 4 -459 5 -459 16 -459 101 -459 102 -459 178 -459 228 -459 257 -459 336 -459 337 -459 357 -459 401 -460 461 -461 237 -461 462 -463 318 -463 464 -465 454 -466 5 -466 10 -466 110 -466 121 -467 267 -468 7 -469 226 -469 250 -471 100 -472 119 -473 312 -474 137 -474 101 -478 110 -479 180 -480 100 -480 186 -480 195 -482 5 -482 53 -482 59 -482 60 -482 61 -482 220 -482 126 -482 483 -483 5 -483 52 -483 53 -483 55 -483 56 -483 59 -483 60 -483 61 -483 62 -483 220 -483 126 -483 224 -483 232 -483 121 -483 125 -484 7 -484 110 -484 408 -486 237 -487 68 -488 300 -488 301 -489 113 -492 209 -493 494 -494 32 -494 389 -494 395 -494 396 -495 15 -496 244 -496 327 -496 497 -497 485 -498 7 -498 137 -500 7 -500 136 -500 137 -501 110 -502 7 -502 137 -503 20 -503 110 -503 92 -504 23 -504 100 -504 182 -504 223 -504 349 -505 92 -505 277 -505 506 -507 7 -507 136 -507 137 -508 280 -510 3 -511 129 -511 357 -512 121 -514 100 -516 242 -517 30 -517 65 -517 114 -518 242 -519 489 -521 237 -522 320 -522 513 -522 523 -524 110 -525 7 -525 137 -526 100 -527 7 -527 77 -527 157 -527 528 -528 77 -528 157 -529 5 -529 110 -530 7 -531 242 -531 450 -532 429 -533 7 -533 136 -533 142 -533 205 -533 286 -534 126 -535 237 -535 536 -537 74 -537 364 -537 406 -538 7 -538 136 -540 148 -541 237 -542 0 -542 3 -542 6 -542 7 -542 8 -542 9 -542 12 -542 20 -542 21 -542 22 -542 42 -542 49 -542 130 -542 74 -542 137 -542 139 -542 140 -542 141 -542 77 -542 79 -542 143 -542 150 -542 84 -542 100 -542 178 -542 180 -542 183 -542 190 -542 204 -542 217 -542 222 -542 226 -542 227 -542 228 -542 229 -542 233 -542 271 -542 283 -542 285 -542 290 -542 291 -542 292 -542 293 -542 294 -542 300 -542 309 -542 326 -542 118 -542 337 -542 349 -542 352 -542 364 -542 404 -542 406 -542 407 -542 417 -542 474 -542 477 -542 504 -542 514 -542 519 -542 527 -542 530 -542 543 -542 544 -542 545 -542 546 -542 547 -542 548 -542 549 -542 550 -542 551 -542 552 -542 553 -542 554 -544 36 -545 190 -546 130 -546 110 -546 184 -546 544 -547 7 -547 8 -547 9 -547 136 -547 100 -547 204 -547 220 -547 244 -547 550 -547 551 -548 7 -548 153 -548 100 -548 220 -548 550 -549 7 -549 136 -549 137 -549 110 -549 141 -549 175 -550 3 -550 20 -550 50 -550 110 -550 111 -550 138 -550 141 -550 79 -550 143 -550 145 -550 146 -550 149 -550 150 -550 158 -550 184 -550 217 -550 229 -550 233 -550 283 -550 205 -550 295 -550 99 -550 337 -550 443 -550 458 -550 484 -550 503 -550 546 -550 549 -550 555 -550 556 -551 3 -551 8 -551 9 -551 20 -551 50 -551 130 -551 110 -551 138 -551 146 -551 100 -551 184 -551 203 -551 204 -551 205 -551 176 -551 337 -552 10 -553 7 -553 100 -553 440 -554 4 -554 7 -554 136 -554 137 -554 101 -554 214 -554 217 -554 218 -554 220 -554 332 -554 433 -554 491 -554 509 -554 550 -557 177 -557 196 -557 432 -558 318 -558 559 -560 4 -560 270 -560 360 -561 110 -562 14 -562 15 -562 563 -564 276 -564 505 -564 565 -565 92 -566 5 -566 220 -569 221 -570 141 -570 175 -573 160 -574 14 -574 31 -574 32 -575 47 -577 7 -577 136 -578 4 -578 5 -578 7 -579 461 -580 110 -581 291 -583 25 -584 237 -585 342 -586 7 -586 137 -587 4 -587 16 -587 101 -587 336 -587 337 -589 14 -589 137 -589 244 -589 96 -589 97 -589 246 -589 112 -589 248 -589 99 -589 254 -589 590 -589 591 -593 312 -595 291 -595 434 -596 169 -596 254 -597 5 -597 52 -597 55 -597 220 -597 126 -597 121 -598 312 -599 161 -600 472 -601 94 -602 7 -602 603 -603 7 -604 381 -604 382 -604 383 -604 384 -604 385 -605 96 -605 97 -606 7 -606 475 -608 99 -609 15 -610 237 -611 239 -612 5 -612 68 -612 487 -613 291 -614 7 -614 142 -614 205 -614 286 -615 7 -615 137 -616 291 -616 457 -617 170 -619 8 -619 9 -619 204 -619 220 -619 243 -619 476 -619 515 -619 551 -619 620 -621 137 -621 110 -621 220 -622 67 -622 121 -623 4 -623 7 -623 74 -623 137 -623 101 -623 102 -623 364 -623 406 -624 74 -624 364 -624 406 -625 63 -625 127 -626 5 -627 110 -627 100 -628 7 -629 461 -630 387 -632 336 -632 340 -632 342 -632 585 -633 244 -633 112 -633 247 -633 255 -633 606 \ No newline at end of file diff --git a/misc/sim/merge.py b/misc/sim/merge.py deleted file mode 100644 index 52e4f877..00000000 --- a/misc/sim/merge.py +++ /dev/null @@ -1,62 +0,0 @@ -import glob -import sys -inputDirPath = sys.argv[1] - -inputFilePaths = glob.glob(inputDirPath+"/*") -inputFilePaths.sort() - -merged = dict() - -stretches = [] - -total = 0 -for inputFilePath in inputFilePaths: - print "Processing file {}".format(inputFilePath) - with open(inputFilePath, 'r') as f: - inData = f.readlines() - pathsChecked = 0. - avgStretch = 0. - for line in inData: - dat = line.rstrip('\n').split(' ') - eHops = int(dat[0]) - nHops = int(dat[1]) - count = int(dat[2]) - if eHops not in merged: merged[eHops] = dict() - if nHops not in merged[eHops]: merged[eHops][nHops] = 0 - merged[eHops][nHops] += count - total += count - pathsChecked += count - stretch = float(nHops)/eHops - avgStretch += stretch*count - finStretch = avgStretch / max(1, pathsChecked) - stretches.append(str(finStretch)) - -hopsUsed = 0. -hopsNeeded = 0. -avgStretch = 0. -results = [] -for eHops in sorted(merged.keys()): - for nHops in sorted(merged[eHops].keys()): - count = merged[eHops][nHops] - result = "{} {} {}".format(eHops, nHops, count) - results.append(result) - hopsUsed += nHops*count - hopsNeeded += eHops*count - stretch = float(nHops)/eHops - avgStretch += stretch*count - print result -bandwidthUsage = hopsUsed/max(1, hopsNeeded) -avgStretch /= max(1, total) - -with open("results.txt", "w") as f: - f.write('\n'.join(results)) - -with open("stretches.txt", "w") as f: - f.write('\n'.join(stretches)) - -print "Total files processed: {}".format(len(inputFilePaths)) -print "Total paths found: {}".format(total) -print "Bandwidth usage: {}".format(bandwidthUsage) -print "Average stretch: {}".format(avgStretch) - - diff --git a/misc/sim/run-sim b/misc/sim/run-sim deleted file mode 100755 index 14057e81..00000000 --- a/misc/sim/run-sim +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -go run -tags debug misc/sim/treesim.go "$@" diff --git a/misc/sim/treesim-forward.py b/misc/sim/treesim-forward.py deleted file mode 100644 index f7ca509d..00000000 --- a/misc/sim/treesim-forward.py +++ /dev/null @@ -1,901 +0,0 @@ -# Tree routing scheme (named Yggdrasil, after the world tree from Norse mythology) -# Steps: -# 1: Pick any node, here I'm using highest nodeID -# 2: Build spanning tree, each node stores path back to root -# Optionally with weights for each hop -# Ties broken by preferring a parent with higher degree -# 3: Distance metric: self->peer + (via tree) peer->dest -# 4: Perform (modified) greedy lookup via this metric for each direction (A->B and B->A) -# 5: Source-route traffic using the better of those two paths - -# Note: This makes no attempt to simulate a dynamic network -# E.g. A node's peers cannot be disconnected - -# TODO: -# Make better use of drop? -# In particular, we should be ignoring *all* recently dropped *paths* to the root -# To minimize route flapping -# Not really an issue in the sim, but probably needed for a real network - -import array -import gc -import glob -import gzip -import heapq -import os -import random -import time - -############# -# Constants # -############# - -# Reminder of where link cost comes in -LINK_COST = 1 - -# Timeout before dropping something, in simulated seconds -TIMEOUT = 60 - -########### -# Classes # -########### - -class PathInfo: - def __init__(self, nodeID): - self.nodeID = nodeID # e.g. IP - self.coords = [] # Position in tree - self.tstamp = 0 # Timestamp from sender, to keep track of old vs new info - self.degree = 0 # Number of peers the sender has, used to break ties - # The above should be signed - self.path = [nodeID] # Path to node (in path-vector route) - self.time = 0 # Time info was updated, to keep track of e.g. timeouts - self.treeID = nodeID # Hack, let tree use different ID than IP, used so we can dijkstra once and test many roots - def clone(self): - # Return a deep-enough copy of the path - clone = PathInfo(None) - clone.nodeID = self.nodeID - clone.coords = self.coords[:] - clone.tstamp = self.tstamp - clone.degree = self.degree - clone.path = self.path[:] - clone.time = self.time - clone.treeID = self.treeID - return clone -# End class PathInfo - -class Node: - def __init__(self, nodeID): - self.info = PathInfo(nodeID) # Self NodeInfo - self.root = None # PathInfo to node at root of tree - self.drop = dict() # PathInfo to nodes from clus that have timed out - self.peers = dict() # PathInfo to peers - self.links = dict() # Links to peers (to pass messages) - self.msgs = [] # Said messages - self.table = dict() # Pre-computed lookup table of peer info - - def tick(self): - # Do periodic maintenance stuff, including push updates - self.info.time += 1 - if self.info.time > self.info.tstamp + TIMEOUT/4: - # Update timestamp at least once every 1/4 timeout period - # This should probably be randomized in a real implementation - self.info.tstamp = self.info.time - self.info.degree = 0# TODO decide if degree should be used, len(self.peers) - changed = False # Used to track when the network has converged - changed |= self.cleanRoot() - self.cleanDropped() - # Should probably send messages infrequently if there's nothing new to report - if self.info.tstamp == self.info.time: - msg = self.createMessage() - self.sendMessage(msg) - return changed - - def cleanRoot(self): - changed = False - if self.root and self.info.time - self.root.time > TIMEOUT: - print "DEBUG: clean root,", self.root.path - self.drop[self.root.treeID] = self.root - self.root = None - changed = True - if not self.root or self.root.treeID < self.info.treeID: - # No need to drop someone who'se worse than us - self.info.coords = [self.info.nodeID] - self.root = self.info.clone() - changed = True - elif self.root.treeID == self.info.treeID: - self.root = self.info.clone() - return changed - - def cleanDropped(self): - # May actually be a treeID... better to iterate over keys explicitly - nodeIDs = sorted(self.drop.keys()) - for nodeID in nodeIDs: - node = self.drop[nodeID] - if self.info.time - node.time > 4*TIMEOUT: - del self.drop[nodeID] - return None - - def createMessage(self): - # Message is just a tuple - # First element is the sender - # Second element is the root - # We will .clone() everything during the send operation - msg = (self.info, self.root) - return msg - - def sendMessage(self, msg): - for link in self.links.values(): - newMsg = (msg[0].clone(), msg[1].clone()) - link.msgs.append(newMsg) - return None - - def handleMessages(self): - changed = False - while self.msgs: - changed |= self.handleMessage(self.msgs.pop()) - return changed - - def handleMessage(self, msg): - changed = False - for node in msg: - # Update the path and timestamp for the sender and root info - node.path.append(self.info.nodeID) - node.time = self.info.time - # Update the sender's info in our list of peers - sender = msg[0] - self.peers[sender.nodeID] = sender - # Decide if we want to update the root - root = msg[1] - updateRoot = False - isSameParent = False - isBetterParent = False - if len(self.root.path) > 1 and len(root.path) > 1: - parent = self.peers[self.root.path[-2]] - if parent.nodeID == sender.nodeID: isSameParent = True - if sender.degree > parent.degree: - # This would also be where you check path uptime/reliability/whatever - # All else being equal, we prefer parents with high degree - # We are trusting peers to report degree correctly in this case - # So expect some performance reduction if your peers aren't trustworthy - # (Lies can increase average stretch by a few %) - isBetterParent = True - if self.info.nodeID in root.path[:-1]: pass # No loopy routes allowed - elif root.treeID in self.drop and self.drop[root.treeID].tstamp >= root.tstamp: pass - elif not self.root: updateRoot = True - elif self.root.treeID < root.treeID: updateRoot = True - elif self.root.treeID != root.treeID: pass - elif self.root.tstamp > root.tstamp: pass - elif len(root.path) < len(self.root.path): updateRoot = True - elif isBetterParent and len(root.path) == len(self.root.path): updateRoot = True - elif isSameParent and self.root.tstamp < root.tstamp: updateRoot = True - if updateRoot: - if not self.root or self.root.path != root.path: changed = True - self.root = root - self.info.coords = self.root.path - return changed - - def lookup(self, dest): - # Note: Can loop in an unconverged network - # The person looking up the route is responsible for checking for loops - best = None - bestDist = 0 - for node in self.peers.itervalues(): - # dist = distance to node + dist (on tree) from node to dest - dist = len(node.path)-1 + treeDist(node.coords, dest.coords) - if not best or dist < bestDist: - best = node - bestDist = dist - if best: - next = best.path[-2] - assert next in self.peers - return next - else: - # We failed to look something up - # TODO some way to signal this which doesn't crash - assert False - - def initTable(self): - # Pre-computes a lookup table for destination coords - # Insert parent first so you prefer them as a next-hop - self.table.clear() - parent = self.info.nodeID - if len(self.info.coords) >= 2: parent = self.info.coords[-2] - for peer in self.peers.itervalues(): - current = self.table - for coord in peer.coords: - if coord not in current: current[coord] = (peer.nodeID, dict()) - old = current[coord] - next = old[1] - oldPeer = self.peers[old[0]] - oldDist = len(oldPeer.coords) - oldDeg = oldPeer.degree - newDist = len(peer.coords) - newDeg = peer.degree - # Prefer parent - # Else prefer short distance from root - # If equal distance, prefer high degree - if peer.nodeID == parent: current[coord] = (peer.nodeID, next) - elif newDist < oldDist: current[coord] = (peer.nodeID, next) - elif newDist == oldDist and newDeg > oldDeg: current[coord] = (peer.nodeID, next) - current = next - return None - - def lookup_new(self, dest): - # Use pre-computed lookup table to look up next hop for dest coords - assert self.table - if len(self.info.coords) >= 2: parent = self.info.coords[-2] - else: parent = None - current = (parent, self.table) - c = None - for coord in dest.coords: - c = coord - if coord not in current[1]: break - current = current[1][coord] - next = current[0] - if c in self.peers: next = c - if next not in self.peers: - assert next == None - # You're the root of a different connected component - # You'd drop the packet in this case - # To make the path cache not die, need to return a valid next hop... - # Returning self for that reason - next = self.info.nodeID - return next -# End class Node - -#################### -# Helper Functions # -#################### - -def getIndexOfLCA(source, dest): - # Return index of last common ancestor in source/dest coords - # -1 if no common ancestor (e.g. different roots) - lcaIdx = -1 - minLen = min(len(source), len(dest)) - for idx in xrange(minLen): - if source[idx] == dest[idx]: lcaIdx = idx - else: break - return lcaIdx - -def treePath(source, dest): - # Return path with source at head and dest at tail - lastMatch = getIndexOfLCA(source, dest) - path = dest[-1:lastMatch:-1] + source[lastMatch:] - assert path[0] == dest[-1] - assert path[-1] == source[-1] - return path - -def treeDist(source, dest): - dist = len(source) + len(dest) - lcaIdx = getIndexOfLCA(source, dest) - dist -= 2*(lcaIdx+1) - return dist - -def dijkstra(nodestore, startingNodeID): - # Idea to use heapq and basic implementation taken from stackexchange post - # http://codereview.stackexchange.com/questions/79025/dijkstras-algorithm-in-python - nodeIDs = sorted(nodestore.keys()) - nNodes = len(nodeIDs) - idxs = dict() - for nodeIdx in xrange(nNodes): - nodeID = nodeIDs[nodeIdx] - idxs[nodeID] = nodeIdx - dists = array.array("H", [0]*nNodes) - queue = [(0, startingNodeID)] - while queue: - dist, nodeID = heapq.heappop(queue) - idx = idxs[nodeID] - if not dists[idx]: # Unvisited, otherwise we skip it - dists[idx] = dist - for peer in nodestore[nodeID].links: - if not dists[idxs[peer]]: - # Peer is also unvisited, so add to queue - heapq.heappush(queue, (dist+LINK_COST, peer)) - return dists - -def dijkstrall(nodestore): - # Idea to use heapq and basic implementation taken from stackexchange post - # http://codereview.stackexchange.com/questions/79025/dijkstras-algorithm-in-python - nodeIDs = sorted(nodestore.keys()) - nNodes = len(nodeIDs) - idxs = dict() - for nodeIdx in xrange(nNodes): - nodeID = nodeIDs[nodeIdx] - idxs[nodeID] = nodeIdx - dists = array.array("H", [0]*nNodes*nNodes) # use GetCacheIndex(nNodes, start, end) - for sourceIdx in xrange(nNodes): - print "Finding shortest paths for node {} / {} ({})".format(sourceIdx+1, nNodes, nodeIDs[sourceIdx]) - queue = [(0, sourceIdx)] - while queue: - dist, nodeIdx = heapq.heappop(queue) - distIdx = getCacheIndex(nNodes, sourceIdx, nodeIdx) - if not dists[distIdx]: # Unvisited, otherwise we skip it - dists[distIdx] = dist - for peer in nodestore[nodeIDs[nodeIdx]].links: - pIdx = idxs[peer] - pdIdx = getCacheIndex(nNodes, sourceIdx, pIdx) - if not dists[pdIdx]: - # Peer is also unvisited, so add to queue - heapq.heappush(queue, (dist+LINK_COST, pIdx)) - return dists - -def linkNodes(node1, node2): - node1.links[node2.info.nodeID] = node2 - node2.links[node1.info.nodeID] = node1 - -############################ -# Store topology functions # -############################ - -def makeStoreSquareGrid(sideLength, randomize=True): - # Simple grid in a sideLength*sideLength square - # Just used to validate that the code runs - store = dict() - nodeIDs = list(range(sideLength*sideLength)) - if randomize: random.shuffle(nodeIDs) - for nodeID in nodeIDs: - store[nodeID] = Node(nodeID) - for index in xrange(len(nodeIDs)): - if (index % sideLength != 0): linkNodes(store[nodeIDs[index]], store[nodeIDs[index-1]]) - if (index >= sideLength): linkNodes(store[nodeIDs[index]], store[nodeIDs[index-sideLength]]) - print "Grid store created, size {}".format(len(store)) - return store - -def makeStoreASRelGraph(pathToGraph): - #Existing network graphs, in caida.org's asrel format (ASx|ASy|z per line, z denotes relationship type) - with open(pathToGraph, "r") as f: - inData = f.readlines() - store = dict() - for line in inData: - if line.strip()[0] == "#": continue # Skip comment lines - line = line.replace('|'," ") - nodes = map(int, line.split()[0:2]) - if nodes[0] not in store: store[nodes[0]] = Node(nodes[0]) - if nodes[1] not in store: store[nodes[1]] = Node(nodes[1]) - linkNodes(store[nodes[0]], store[nodes[1]]) - print "CAIDA AS-relation graph successfully imported, size {}".format(len(store)) - return store - -def makeStoreASRelGraphMaxDeg(pathToGraph, degIdx=0): - with open(pathToGraph, "r") as f: - inData = f.readlines() - store = dict() - nodeDeg = dict() - for line in inData: - if line.strip()[0] == "#": continue # Skip comment lines - line = line.replace('|'," ") - nodes = map(int, line.split()[0:2]) - if nodes[0] not in nodeDeg: nodeDeg[nodes[0]] = 0 - if nodes[1] not in nodeDeg: nodeDeg[nodes[1]] = 0 - nodeDeg[nodes[0]] += 1 - nodeDeg[nodes[1]] += 1 - sortedNodes = sorted(nodeDeg.keys(), \ - key=lambda x: (nodeDeg[x], x), \ - reverse=True) - maxDegNodeID = sortedNodes[degIdx] - return makeStoreASRelGraphFixedRoot(pathToGraph, maxDegNodeID) - -def makeStoreASRelGraphFixedRoot(pathToGraph, rootNodeID): - with open(pathToGraph, "r") as f: - inData = f.readlines() - store = dict() - for line in inData: - if line.strip()[0] == "#": continue # Skip comment lines - line = line.replace('|'," ") - nodes = map(int, line.split()[0:2]) - if nodes[0] not in store: - store[nodes[0]] = Node(nodes[0]) - if nodes[0] == rootNodeID: store[nodes[0]].info.treeID += 1000000000 - if nodes[1] not in store: - store[nodes[1]] = Node(nodes[1]) - if nodes[1] == rootNodeID: store[nodes[1]].info.treeID += 1000000000 - linkNodes(store[nodes[0]], store[nodes[1]]) - print "CAIDA AS-relation graph successfully imported, size {}".format(len(store)) - return store - -def makeStoreDimesEdges(pathToGraph, rootNodeID=None): - # Read from a DIMES csv-formatted graph from a gzip file - store = dict() - with gzip.open(pathToGraph, "r") as f: - inData = f.readlines() - size = len(inData) - index = 0 - for edge in inData: - if not index % 1000: - pct = 100.0*index/size - print "Processing edge {}, {:.2f}%".format(index, pct) - index += 1 - dat = edge.rstrip().split(',') - node1 = "N" + str(dat[0].strip()) - node2 = "N" + str(dat[1].strip()) - if '?' in node1 or '?' in node2: continue #Unknown node - if node1 == rootNodeID: node1 = "R" + str(dat[0].strip()) - if node2 == rootNodeID: node2 = "R" + str(dat[1].strip()) - if node1 not in store: store[node1] = Node(node1) - if node2 not in store: store[node2] = Node(node2) - if node1 != node2: linkNodes(store[node1], store[node2]) - print "DIMES graph successfully imported, size {}".format(len(store)) - return store - -def makeStoreGeneratedGraph(pathToGraph, root=None): - with open(pathToGraph, "r") as f: - inData = f.readlines() - store = dict() - for line in inData: - if line.strip()[0] == "#": continue # Skip comment lines - nodes = map(int, line.strip().split(' ')[0:2]) - node1 = nodes[0] - node2 = nodes[1] - if node1 == root: node1 += 1000000 - if node2 == root: node2 += 1000000 - if node1 not in store: store[node1] = Node(node1) - if node2 not in store: store[node2] = Node(node2) - linkNodes(store[node1], store[node2]) - print "Generated graph successfully imported, size {}".format(len(store)) - return store - - -############################################ -# Functions used as parts of network tests # -############################################ - -def idleUntilConverged(store): - nodeIDs = sorted(store.keys()) - timeOfLastChange = 0 - step = 0 - # Idle until the network has converged - while step - timeOfLastChange < 4*TIMEOUT: - step += 1 - print "Step: {}, last change: {}".format(step, timeOfLastChange) - changed = False - for nodeID in nodeIDs: - # Update node status, send messages - changed |= store[nodeID].tick() - for nodeID in nodeIDs: - # Process messages - changed |= store[nodeID].handleMessages() - if changed: timeOfLastChange = step - initTables(store) - return store - -def getCacheIndex(nodes, sourceIndex, destIndex): - return sourceIndex*nodes + destIndex - -def initTables(store): - nodeIDs = sorted(store.keys()) - nNodes = len(nodeIDs) - print "Initializing routing tables for {} nodes".format(nNodes) - for idx in xrange(nNodes): - nodeID = nodeIDs[idx] - store[nodeID].initTable() - print "Routing tables initialized" - return None - -def getCache(store): - nodeIDs = sorted(store.keys()) - nNodes = len(nodeIDs) - nodeIdxs = dict() - for nodeIdx in xrange(nNodes): - nodeIdxs[nodeIDs[nodeIdx]] = nodeIdx - cache = array.array("H", [0]*nNodes*nNodes) - for sourceIdx in xrange(nNodes): - sourceID = nodeIDs[sourceIdx] - print "Building fast lookup table for node {} / {} ({})".format(sourceIdx+1, nNodes, sourceID) - for destIdx in xrange(nNodes): - destID = nodeIDs[destIdx] - if sourceID == destID: nextHop = destID # lookup would fail - else: nextHop = store[sourceID].lookup(store[destID].info) - nextHopIdx = nodeIdxs[nextHop] - cache[getCacheIndex(nNodes, sourceIdx, destIdx)] = nextHopIdx - return cache - -def testPaths(store, dists): - cache = getCache(store) - nodeIDs = sorted(store.keys()) - nNodes = len(nodeIDs) - idxs = dict() - for nodeIdx in xrange(nNodes): - nodeID = nodeIDs[nodeIdx] - idxs[nodeID] = nodeIdx - results = dict() - for sourceIdx in xrange(nNodes): - sourceID = nodeIDs[sourceIdx] - print "Testing paths from node {} / {} ({})".format(sourceIdx+1, len(nodeIDs), sourceID) - #dists = dijkstra(store, sourceID) - for destIdx in xrange(nNodes): - destID = nodeIDs[destIdx] - if destID == sourceID: continue # Skip self - distIdx = getCacheIndex(nNodes, sourceIdx, destIdx) - eHops = dists[distIdx] - if not eHops: continue # The network is split, no path exists - hops = 0 - for pair in ((sourceIdx, destIdx),): - nHops = 0 - locIdx = pair[0] - dIdx = pair[1] - while locIdx != dIdx: - locIdx = cache[getCacheIndex(nNodes, locIdx, dIdx)] - nHops += 1 - if not hops or nHops < hops: hops = nHops - if eHops not in results: results[eHops] = dict() - if hops not in results[eHops]: results[eHops][hops] = 0 - results[eHops][hops] += 1 - return results - -def getAvgStretch(pathMatrix): - avgStretch = 0. - checked = 0. - for eHops in sorted(pathMatrix.keys()): - for nHops in sorted(pathMatrix[eHops].keys()): - count = pathMatrix[eHops][nHops] - stretch = float(nHops)/float(max(1, eHops)) - avgStretch += stretch*count - checked += count - avgStretch /= max(1, checked) - return avgStretch - -def getMaxStretch(pathMatrix): - maxStretch = 0. - for eHops in sorted(pathMatrix.keys()): - for nHops in sorted(pathMatrix[eHops].keys()): - stretch = float(nHops)/float(max(1, eHops)) - maxStretch = max(maxStretch, stretch) - return maxStretch - -def getCertSizes(store): - # Returns nCerts frequency distribution - # De-duplicates common certs (for shared prefixes in the path) - sizes = dict() - for node in store.values(): - certs = set() - for peer in node.peers.values(): - pCerts = set() - assert len(peer.path) == 2 - assert peer.coords[-1] == peer.path[0] - hops = peer.coords + peer.path[1:] - for hopIdx in xrange(len(hops)-1): - send = hops[hopIdx] - if send == node.info.nodeID: continue # We created it, already have it - path = hops[0:hopIdx+2] - # Each cert is signed by the sender - # Includes information about the path from the sender to the next hop - # Next hop is at hopIdx+1, so the path to next hop is hops[0:hopIdx+2] - cert = "{}:{}".format(send, path) - certs.add(cert) - size = len(certs) - if size not in sizes: sizes[size] = 0 - sizes[size] += 1 - return sizes - -def getMinLinkCertSizes(store): - # Returns nCerts frequency distribution - # De-duplicates common certs (for shared prefixes in the path) - # Based on the minimum number of certs that must be traded through a particular link - # Handled per link - sizes = dict() - for node in store.values(): - peerCerts = dict() - for peer in node.peers.values(): - pCerts = set() - assert len(peer.path) == 2 - assert peer.coords[-1] == peer.path[0] - hops = peer.coords + peer.path[1:] - for hopIdx in xrange(len(hops)-1): - send = hops[hopIdx] - if send == node.info.nodeID: continue # We created it, already have it - path = hops[0:hopIdx+2] - # Each cert is signed by the sender - # Includes information about the path from the sender to the next hop - # Next hop is at hopIdx+1, so the path to next hop is hops[0:hopIdx+2] - cert = "{}:{}".format(send, path) - pCerts.add(cert) - peerCerts[peer.nodeID] = pCerts - for peer in peerCerts: - size = 0 - pCerts = peerCerts[peer] - for cert in pCerts: - required = True - for p2 in peerCerts: - if p2 == peer: continue - p2Certs = peerCerts[p2] - if cert in p2Certs: required = False - if required: size += 1 - if size not in sizes: sizes[size] = 0 - sizes[size] += 1 - return sizes - -def getPathSizes(store): - # Returns frequency distribution of the total number of hops the routing table - # I.e. a node with 3 peers, each with 5 hop coord+path, would count as 3x5=15 - sizes = dict() - for node in store.values(): - size = 0 - for peer in node.peers.values(): - assert len(peer.path) == 2 - assert peer.coords[-1] == peer.path[0] - peerSize = len(peer.coords) + len(peer.path) - 1 # double-counts peer, -1 - size += peerSize - if size not in sizes: sizes[size] = 0 - sizes[size] += 1 - return sizes - -def getPeerSizes(store): - # Returns frequency distribution of the number of peers each node has - sizes = dict() - for node in store.values(): - nPeers = len(node.peers) - if nPeers not in sizes: sizes[nPeers] = 0 - sizes[nPeers] += 1 - return sizes - -def getAvgSize(sizes): - sumSizes = 0 - nNodes = 0 - for size in sizes: - count = sizes[size] - sumSizes += size*count - nNodes += count - avgSize = float(sumSizes)/max(1, nNodes) - return avgSize - -def getMaxSize(sizes): - return max(sizes.keys()) - -def getMinSize(sizes): - return min(sizes.keys()) - -def getResults(pathMatrix): - results = [] - for eHops in sorted(pathMatrix.keys()): - for nHops in sorted(pathMatrix[eHops].keys()): - count = pathMatrix[eHops][nHops] - results.append("{} {} {}".format(eHops, nHops, count)) - return '\n'.join(results) - -#################################### -# Functions to run different tests # -#################################### - -def runTest(store): - # Runs the usual set of tests on the store - # Does not save results, so only meant for quick tests - # To e.g. check the code works, maybe warm up the pypy jit - for node in store.values(): - node.info.time = random.randint(0, TIMEOUT) - node.info.tstamp = TIMEOUT - print "Begin testing network" - dists = None - if not dists: dists = dijkstrall(store) - idleUntilConverged(store) - pathMatrix = testPaths(store, dists) - avgStretch = getAvgStretch(pathMatrix) - maxStretch = getMaxStretch(pathMatrix) - peers = getPeerSizes(store) - certs = getCertSizes(store) - paths = getPathSizes(store) - linkCerts = getMinLinkCertSizes(store) - avgPeerSize = getAvgSize(peers) - maxPeerSize = getMaxSize(peers) - avgCertSize = getAvgSize(certs) - maxCertSize = getMaxSize(certs) - avgPathSize = getAvgSize(paths) - maxPathSize = getMaxSize(paths) - avgLinkCert = getAvgSize(linkCerts) - maxLinkCert = getMaxSize(linkCerts) - totalCerts = sum(map(lambda x: x*certs[x], certs.keys())) - totalLinks = sum(map(lambda x: x*peers[x], peers.keys())) # one-way links - avgCertsPerLink = float(totalCerts)/max(1, totalLinks) - print "Finished testing network" - print "Avg / Max stretch: {} / {}".format(avgStretch, maxStretch) - print "Avg / Max nPeers size: {} / {}".format(avgPeerSize, maxPeerSize) - print "Avg / Max nCerts size: {} / {}".format(avgCertSize, maxCertSize) - print "Avg / Max total hops in any node's routing table: {} / {}".format(avgPathSize, maxPathSize) - print "Avg / Max lower bound cert requests per link (one-way): {} / {}".format(avgLinkCert, maxLinkCert) - print "Avg certs per link (one-way): {}".format(avgCertsPerLink) - return # End of function - -def rootNodeASTest(path, outDir="output-treesim-AS", dists=None, proc = 1): - # Checks performance for every possible choice of root node - # Saves output for each root node to a separate file on disk - # path = input path to some caida.org formatted AS-relationship graph - if not os.path.exists(outDir): os.makedirs(outDir) - assert os.path.exists(outDir) - store = makeStoreASRelGraph(path) - nodes = sorted(store.keys()) - for nodeIdx in xrange(len(nodes)): - if nodeIdx % proc != 0: continue # Work belongs to someone else - rootNodeID = nodes[nodeIdx] - outpath = outDir+"/{}".format(rootNodeID) - if os.path.exists(outpath): - print "Skipping {}, already processed".format(rootNodeID) - continue - store = makeStoreASRelGraphFixedRoot(path, rootNodeID) - for node in store.values(): - node.info.time = random.randint(0, TIMEOUT) - node.info.tstamp = TIMEOUT - print "Beginning {}, size {}".format(nodeIdx, len(store)) - if not dists: dists = dijkstrall(store) - idleUntilConverged(store) - pathMatrix = testPaths(store, dists) - avgStretch = getAvgStretch(pathMatrix) - maxStretch = getMaxStretch(pathMatrix) - results = getResults(pathMatrix) - with open(outpath, "w") as f: - f.write(results) - print "Finished test for root AS {} ({} / {})".format(rootNodeID, nodeIdx+1, len(store)) - print "Avg / Max stretch: {} / {}".format(avgStretch, maxStretch) - #break # Stop after 1, because they can take forever - return # End of function - -def timelineASTest(): - # Meant to study the performance of the network as a function of network size - # Loops over a set of AS-relationship graphs - # Runs a test on each graph, selecting highest-degree node as the root - # Saves results for each graph to a separate file on disk - outDir = "output-treesim-timeline-AS" - if not os.path.exists(outDir): os.makedirs(outDir) - assert os.path.exists(outDir) - paths = sorted(glob.glob("asrel/datasets/*")) - for path in paths: - date = os.path.basename(path).split(".")[0] - outpath = outDir+"/{}".format(date) - if os.path.exists(outpath): - print "Skipping {}, already processed".format(date) - continue - store = makeStoreASRelGraphMaxDeg(path) - dists = None - for node in store.values(): - node.info.time = random.randint(0, TIMEOUT) - node.info.tstamp = TIMEOUT - print "Beginning {}, size {}".format(date, len(store)) - if not dists: dists = dijkstrall(store) - idleUntilConverged(store) - pathMatrix = testPaths(store, dists) - avgStretch = getAvgStretch(pathMatrix) - maxStretch = getMaxStretch(pathMatrix) - results = getResults(pathMatrix) - with open(outpath, "w") as f: - f.write(results) - print "Finished {} with {} nodes".format(date, len(store)) - print "Avg / Max stretch: {} / {}".format(avgStretch, maxStretch) - #break # Stop after 1, because they can take forever - return # End of function - -def timelineDimesTest(): - # Meant to study the performance of the network as a function of network size - # Loops over a set of AS-relationship graphs - # Runs a test on each graph, selecting highest-degree node as the root - # Saves results for each graph to a separate file on disk - outDir = "output-treesim-timeline-dimes" - if not os.path.exists(outDir): os.makedirs(outDir) - assert os.path.exists(outDir) - # Input files are named ASEdgesX_Y where X = month (no leading 0), Y = year - paths = sorted(glob.glob("DIMES/ASEdges/*.gz")) - exists = set(glob.glob(outDir+"/*")) - for path in paths: - date = os.path.basename(path).split(".")[0] - outpath = outDir+"/{}".format(date) - if outpath in exists: - print "Skipping {}, already processed".format(date) - continue - store = makeStoreDimesEdges(path) - # Get the highest degree node and make it root - # Sorted by nodeID just to make it stable in the event of a tie - nodeIDs = sorted(store.keys()) - bestRoot = "" - bestDeg = 0 - for nodeID in nodeIDs: - node = store[nodeID] - if len(node.links) > bestDeg: - bestRoot = nodeID - bestDeg = len(node.links) - assert bestRoot - store = makeStoreDimesEdges(path, bestRoot) - rootID = "R" + bestRoot[1:] - assert rootID in store - # Don't forget to set random seed before setting times - # To make results reproducible - nodeIDs = sorted(store.keys()) - random.seed(12345) - for nodeID in nodeIDs: - node = store[nodeID] - node.info.time = random.randint(0, TIMEOUT) - node.info.tstamp = TIMEOUT - print "Beginning {}, size {}".format(date, len(store)) - if not dists: dists = dijkstrall(store) - idleUntilConverged(store) - pathMatrix = testPaths(store, dists) - avgStretch = getAvgStretch(pathMatrix) - maxStretch = getMaxStretch(pathMatrix) - results = getResults(pathMatrix) - with open(outpath, "w") as f: - f.write(results) - print "Finished {} with {} nodes".format(date, len(store)) - print "Avg / Max stretch: {} / {}".format(avgStretch, maxStretch) - break # Stop after 1, because they can take forever - return # End of function - -def scalingTest(maxTests=None, inputDir="graphs"): - # Meant to study the performance of the network as a function of network size - # Loops over a set of nodes in a previously generated graph - # Runs a test on each graph, testing each node as the root - # if maxTests is set, tests only that number of roots (highest degree first) - # Saves results for each graph to a separate file on disk - outDir = "output-treesim-{}".format(inputDir) - if not os.path.exists(outDir): os.makedirs(outDir) - assert os.path.exists(outDir) - paths = sorted(glob.glob("{}/*".format(inputDir))) - exists = set(glob.glob(outDir+"/*")) - for path in paths: - gc.collect() # pypy waits for gc to close files - graph = os.path.basename(path).split(".")[0] - store = makeStoreGeneratedGraph(path) - # Get the highest degree node and make it root - # Sorted by nodeID just to make it stable in the event of a tie - nodeIDs = sorted(store.keys(), key=lambda x: len(store[x].links), reverse=True) - dists = None - if maxTests: nodeIDs = nodeIDs[:maxTests] - for nodeID in nodeIDs: - nodeIDStr = str(nodeID).zfill(len(str(len(store)-1))) - outpath = outDir+"/{}-{}".format(graph, nodeIDStr) - if outpath in exists: - print "Skipping {}-{}, already processed".format(graph, nodeIDStr) - continue - store = makeStoreGeneratedGraph(path, nodeID) - # Don't forget to set random seed before setting times - random.seed(12345) # To make results reproducible - nIDs = sorted(store.keys()) - for nID in nIDs: - node = store[nID] - node.info.time = random.randint(0, TIMEOUT) - node.info.tstamp = TIMEOUT - print "Beginning {}, size {}".format(graph, len(store)) - if not dists: dists = dijkstrall(store) - idleUntilConverged(store) - pathMatrix = testPaths(store, dists) - avgStretch = getAvgStretch(pathMatrix) - maxStretch = getMaxStretch(pathMatrix) - results = getResults(pathMatrix) - with open(outpath, "w") as f: - f.write(results) - print "Finished {} with {} nodes for root {}".format(graph, len(store), nodeID) - print "Avg / Max stretch: {} / {}".format(avgStretch, maxStretch) - return # End of function - -################## -# Main Execution # -################## - -if __name__ == "__main__": - if True: # Run a quick test - random.seed(12345) # DEBUG - store = makeStoreSquareGrid(4) - runTest(store) # Quick test - store = None - # Do some real work - #runTest(makeStoreDimesEdges("DIMES/ASEdges/ASEdges1_2007.csv.gz")) - #timelineDimesTest() - #rootNodeASTest("asrel/datasets/19980101.as-rel.txt") - #timelineASTest() - #rootNodeASTest("hype-2016-09-19.list", "output-treesim-hype") - #scalingTest(None, "graphs-20") # First argument 1 to only test 1 root per graph - #store = makeStoreGeneratedGraph("bgp_tables") - #store = makeStoreGeneratedGraph("skitter") - #store = makeStoreASRelGraphMaxDeg("hype-2016-09-19.list") #http://hia.cjdns.ca/watchlist/c/walk.peers.20160919 - #store = makeStoreGeneratedGraph("fc00-2017-08-12.txt") - if store: runTest(store) - #rootNodeASTest("skitter", "output-treesim-skitter", None, 0, 1) - #scalingTest(1, "graphs-20") # First argument 1 to only test 1 root per graph - #scalingTest(1, "graphs-21") # First argument 1 to only test 1 root per graph - #scalingTest(1, "graphs-22") # First argument 1 to only test 1 root per graph - #scalingTest(1, "graphs-23") # First argument 1 to only test 1 root per graph - if not store: - import sys - args = sys.argv - if len(args) == 2: - job_number = int(sys.argv[1]) - rootNodeASTest("fc00-2017-08-12.txt", "fc00", None, job_number) - else: - print "Usage: {} job_number".format(args[0]) - print "job_number = which job set to run on this node (1-indexed)" - diff --git a/misc/sim/treesim.go b/misc/sim/treesim.go deleted file mode 100644 index 22cf881f..00000000 --- a/misc/sim/treesim.go +++ /dev/null @@ -1,459 +0,0 @@ -// +build !lint - -package main - -import ( - "bufio" - "flag" - "fmt" - "os" - "runtime" - "runtime/pprof" - "strconv" - "strings" - "time" - - "github.com/gologme/log" - - . "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" - - . "github.com/yggdrasil-network/yggdrasil-go/src/crypto" -) - -//////////////////////////////////////////////////////////////////////////////// - -type Node struct { - index int - core Core - send chan<- []byte - recv <-chan []byte -} - -func (n *Node) init(index int) { - n.index = index - n.core.Init() - n.send = n.core.DEBUG_getSend() - n.recv = n.core.DEBUG_getRecv() - n.core.DEBUG_simFixMTU() -} - -func (n *Node) printTraffic() { - for { - packet := <-n.recv - fmt.Println(n.index, packet) - //panic("Got a packet") - } -} - -func (n *Node) startPeers() { - //for _, p := range n.core.Peers.Ports { - // go p.MainLoop() - //} - //go n.printTraffic() - //n.core.Peers.DEBUG_startPeers() -} - -func linkNodes(m, n *Node) { - // Don't allow duplicates - if m.core.DEBUG_getPeers().DEBUG_hasPeer(n.core.DEBUG_getSigningPublicKey()) { - return - } - // Create peers - // Buffering reduces packet loss in the sim - // This slightly speeds up testing (fewer delays before retrying a ping) - pLinkPub, pLinkPriv := m.core.DEBUG_newBoxKeys() - qLinkPub, qLinkPriv := m.core.DEBUG_newBoxKeys() - p := m.core.DEBUG_getPeers().DEBUG_newPeer(n.core.DEBUG_getEncryptionPublicKey(), - n.core.DEBUG_getSigningPublicKey(), *m.core.DEBUG_getSharedKey(pLinkPriv, qLinkPub)) - q := n.core.DEBUG_getPeers().DEBUG_newPeer(m.core.DEBUG_getEncryptionPublicKey(), - m.core.DEBUG_getSigningPublicKey(), *n.core.DEBUG_getSharedKey(qLinkPriv, pLinkPub)) - DEBUG_simLinkPeers(p, q) - return -} - -func makeStoreSquareGrid(sideLength int) map[int]*Node { - store := make(map[int]*Node) - nNodes := sideLength * sideLength - idxs := make([]int, 0, nNodes) - // TODO shuffle nodeIDs - for idx := 1; idx <= nNodes; idx++ { - idxs = append(idxs, idx) - } - for _, idx := range idxs { - node := &Node{} - node.init(idx) - store[idx] = node - } - for idx := 0; idx < nNodes; idx++ { - if (idx % sideLength) != 0 { - linkNodes(store[idxs[idx]], store[idxs[idx-1]]) - } - if idx >= sideLength { - linkNodes(store[idxs[idx]], store[idxs[idx-sideLength]]) - } - } - //for _, node := range store { node.initPorts() } - return store -} - -func makeStoreStar(nNodes int) map[int]*Node { - store := make(map[int]*Node) - center := &Node{} - center.init(0) - store[0] = center - for idx := 1; idx < nNodes; idx++ { - node := &Node{} - node.init(idx) - store[idx] = node - linkNodes(center, node) - } - return store -} - -func loadGraph(path string) map[int]*Node { - f, err := os.Open(path) - if err != nil { - panic(err) - } - defer f.Close() - store := make(map[int]*Node) - s := bufio.NewScanner(f) - for s.Scan() { - line := s.Text() - nodeIdxstrs := strings.Split(line, " ") - nodeIdx0, _ := strconv.Atoi(nodeIdxstrs[0]) - nodeIdx1, _ := strconv.Atoi(nodeIdxstrs[1]) - if store[nodeIdx0] == nil { - node := &Node{} - node.init(nodeIdx0) - store[nodeIdx0] = node - } - if store[nodeIdx1] == nil { - node := &Node{} - node.init(nodeIdx1) - store[nodeIdx1] = node - } - linkNodes(store[nodeIdx0], store[nodeIdx1]) - } - //for _, node := range store { node.initPorts() } - return store -} - -//////////////////////////////////////////////////////////////////////////////// - -func startNetwork(store map[[32]byte]*Node) { - for _, node := range store { - node.startPeers() - } -} - -func getKeyedStore(store map[int]*Node) map[[32]byte]*Node { - newStore := make(map[[32]byte]*Node) - for _, node := range store { - newStore[node.core.DEBUG_getSigningPublicKey()] = node - } - return newStore -} - -func testPaths(store map[[32]byte]*Node) bool { - nNodes := len(store) - count := 0 - for _, source := range store { - count++ - fmt.Printf("Testing paths from node %d / %d (%d)\n", count, nNodes, source.index) - for _, dest := range store { - //if source == dest { continue } - destLoc := dest.core.DEBUG_getLocator() - coords := destLoc.DEBUG_getCoords() - temp := 0 - ttl := ^uint64(0) - oldTTL := ttl - for here := source; here != dest; { - temp++ - if temp > 4096 { - fmt.Println("Loop?") - time.Sleep(time.Second) - return false - } - nextPort := here.core.DEBUG_switchLookup(coords) - // First check if "here" is accepting packets from the previous node - // TODO explain how this works - ports := here.core.DEBUG_getPeers().DEBUG_getPorts() - nextPeer := ports[nextPort] - if nextPeer == nil { - fmt.Println("Peer associated with next port is nil") - return false - } - next := store[nextPeer.DEBUG_getSigKey()] - /* - if next == here { - //for idx, link := range here.links { - // fmt.Println("DUMP:", idx, link.nodeID) - //} - if nextPort != 0 { panic("This should not be") } - fmt.Println("Failed to route:", source.index, here.index, dest.index, oldTTL, ttl) - //here.table.DEBUG_dumpTable() - //fmt.Println("Ports:", here.nodeID, here.ports) - return false - panic(fmt.Sprintln("Routing Loop:", - source.index, - here.index, - dest.index)) - } - */ - if temp > 4090 { - fmt.Println("DEBUG:", - source.index, source.core.DEBUG_getLocator(), - here.index, here.core.DEBUG_getLocator(), - dest.index, dest.core.DEBUG_getLocator()) - //here.core.DEBUG_getSwitchTable().DEBUG_dumpTable() - } - if here != source { - // This is sufficient to check for routing loops or blackholes - //break - } - if here == next { - fmt.Println("Drop:", source.index, here.index, dest.index, oldTTL) - return false - } - here = next - } - } - } - return true -} - -func stressTest(store map[[32]byte]*Node) { - fmt.Println("Stress testing network...") - nNodes := len(store) - dests := make([][]byte, 0, nNodes) - for _, dest := range store { - loc := dest.core.DEBUG_getLocator() - coords := loc.DEBUG_getCoords() - dests = append(dests, coords) - } - lookups := 0 - start := time.Now() - for _, source := range store { - for _, coords := range dests { - source.core.DEBUG_switchLookup(coords) - lookups++ - } - } - timed := time.Since(start) - fmt.Printf("%d lookups in %s (%f lookups per second)\n", - lookups, - timed, - float64(lookups)/timed.Seconds()) -} - -func pingNodes(store map[[32]byte]*Node) { - fmt.Println("Sending pings...") - nNodes := len(store) - count := 0 - equiv := func(a []byte, b []byte) bool { - if len(a) != len(b) { - return false - } - for idx := 0; idx < len(a); idx++ { - if a[idx] != b[idx] { - return false - } - } - return true - } - for _, source := range store { - count++ - //if count > 16 { break } - fmt.Printf("Sending packets from node %d/%d (%d)\n", count, nNodes, source.index) - sourceKey := source.core.DEBUG_getEncryptionPublicKey() - payload := sourceKey[:] - sourceAddr := source.core.DEBUG_getAddr()[:] - sendTo := func(bs []byte, destAddr []byte) { - packet := make([]byte, 40+len(bs)) - copy(packet[8:24], sourceAddr) - copy(packet[24:40], destAddr) - copy(packet[40:], bs) - packet[0] = 6 << 4 - source.send <- packet - } - destCount := 0 - for _, dest := range store { - destCount += 1 - fmt.Printf("%d Nodes, %d Send, %d Recv\n", nNodes, count, destCount) - if dest == source { - fmt.Println("Skipping self") - continue - } - destAddr := dest.core.DEBUG_getAddr()[:] - ticker := time.NewTicker(150 * time.Millisecond) - sendTo(payload, destAddr) - for loop := true; loop; { - select { - case packet := <-dest.recv: - { - if equiv(payload, packet[len(packet)-len(payload):]) { - loop = false - } - } - case <-ticker.C: - sendTo(payload, destAddr) - //dumpDHTSize(store) // note that this uses racey functions to read things... - } - } - ticker.Stop() - } - //break // Only try sending pings from 1 node - // This is because, for some reason, stopTun() doesn't always close it - // And if two tuns are up, bad things happen (sends via wrong interface) - } - fmt.Println("Finished pinging nodes") -} - -func pingBench(store map[[32]byte]*Node) { - fmt.Println("Benchmarking pings...") - nPings := 0 - payload := make([]byte, 1280+40) // MTU + ipv6 header - var timed time.Duration - //nNodes := len(store) - count := 0 - for _, source := range store { - count++ - //fmt.Printf("Sending packets from node %d/%d (%d)\n", count, nNodes, source.index) - getPing := func(key [32]byte, decodedCoords []byte) []byte { - // TODO write some function to do this the right way, put... somewhere... - coords := DEBUG_wire_encode_coords(decodedCoords) - packet := make([]byte, 0, len(key)+len(coords)+len(payload)) - packet = append(packet, key[:]...) - packet = append(packet, coords...) - packet = append(packet, payload[:]...) - return packet - } - for _, dest := range store { - key := dest.core.DEBUG_getEncryptionPublicKey() - loc := dest.core.DEBUG_getLocator() - coords := loc.DEBUG_getCoords() - ping := getPing(key, coords) - // TODO make sure the session is open first - start := time.Now() - for i := 0; i < 1000000; i++ { - source.send <- ping - nPings++ - } - timed += time.Since(start) - break - } - break - } - fmt.Printf("Sent %d pings in %s (%f per second)\n", - nPings, - timed, - float64(nPings)/timed.Seconds()) -} - -func dumpStore(store map[NodeID]*Node) { - for _, node := range store { - fmt.Println("DUMPSTORE:", node.index, node.core.DEBUG_getLocator()) - node.core.DEBUG_getSwitchTable().DEBUG_dumpTable() - } -} - -func dumpDHTSize(store map[[32]byte]*Node) { - var min, max, sum int - for _, node := range store { - num := node.core.DEBUG_getDHTSize() - min = num - max = num - break - } - for _, node := range store { - num := node.core.DEBUG_getDHTSize() - if num < min { - min = num - } - if num > max { - max = num - } - sum += num - } - avg := float64(sum) / float64(len(store)) - fmt.Printf("DHT min %d / avg %f / max %d\n", min, avg, max) -} - -func (n *Node) startTCP(listen string) { - n.core.DEBUG_setupAndStartGlobalTCPInterface(listen) -} - -func (n *Node) connectTCP(remoteAddr string) { - n.core.AddPeer(remoteAddr, remoteAddr) -} - -//////////////////////////////////////////////////////////////////////////////// - -var cpuprofile = flag.String("cpuprofile", "", "write cpu profile `file`") -var memprofile = flag.String("memprofile", "", "write memory profile to this file") - -func main() { - flag.Parse() - if *cpuprofile != "" { - f, err := os.Create(*cpuprofile) - if err != nil { - panic(fmt.Sprintf("could not create CPU profile: ", err)) - } - if err := pprof.StartCPUProfile(f); err != nil { - panic(fmt.Sprintf("could not start CPU profile: ", err)) - } - defer pprof.StopCPUProfile() - } - if *memprofile != "" { - f, err := os.Create(*memprofile) - if err != nil { - panic(fmt.Sprintf("could not create memory profile: ", err)) - } - defer func() { pprof.WriteHeapProfile(f); f.Close() }() - } - fmt.Println("Test") - Util_testAddrIDMask() - idxstore := makeStoreSquareGrid(4) - //idxstore := makeStoreStar(256) - //idxstore := loadGraph("misc/sim/hype-2016-09-19.list") - //idxstore := loadGraph("misc/sim/fc00-2017-08-12.txt") - //idxstore := loadGraph("skitter") - kstore := getKeyedStore(idxstore) - //* - logger := log.New(os.Stderr, "", log.Flags()) - for _, n := range kstore { - n.core.DEBUG_setLogger(logger) - } - //*/ - startNetwork(kstore) - //time.Sleep(10*time.Second) - // Note that testPaths only works if pressure is turned off - // Otherwise congestion can lead to routing loops? - for finished := false; !finished; { - finished = testPaths(kstore) - } - pingNodes(kstore) - //pingBench(kstore) // Only after disabling debug output - //stressTest(kstore) - //time.Sleep(120 * time.Second) - dumpDHTSize(kstore) // note that this uses racey functions to read things... - if false { - // This connects the sim to the local network - for _, node := range kstore { - node.startTCP("localhost:0") - node.connectTCP("localhost:12345") - break // just 1 - } - for _, node := range kstore { - go func() { - // Just dump any packets sent to this node - for range node.recv { - } - }() - } - var block chan struct{} - <-block - } - runtime.GC() -} diff --git a/src/core/link.go b/src/core/link.go index 3bdb02ae..9b8e089f 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -250,7 +250,8 @@ func (intf *link) handler() (chan struct{}, error) { intf.links.core.log.Infof("Connected %s: %s, source %s", strings.ToUpper(intf.info.linkType), themString, intf.info.local) // Run the handler - err = intf.links.core.PacketConn.HandleConn(ed25519.PublicKey(intf.info.key[:]), intf.conn) + var metric uint64 // TODO exchange metric in matadata, use max value + err = intf.links.core.PacketConn.HandleConn(ed25519.PublicKey(intf.info.key[:]), intf.conn, metric) // TODO don't report an error if it's just a 'use of closed network connection' if err != nil { intf.links.core.log.Infof("Disconnected %s: %s, source %s; error: %s", From 58af92812e032554acd488721ccf4f6285871232 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 23 May 2021 18:40:36 -0500 Subject: [PATCH 0692/1109] add metric to metadata exchange, but currently left at default 0 value --- src/core/link.go | 6 +++++- src/core/version.go | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/core/link.go b/src/core/link.go index 9b8e089f..d7de9435 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -158,6 +158,8 @@ func (intf *link) handler() (chan struct{}, error) { defer intf.conn.Close() meta := version_getBaseMetadata() meta.key = intf.links.core.public + // TODO set meta.metric + metric := uint64(meta.metric) metaBytes := meta.encode() // TODO timeouts on send/recv (goroutine for send/recv, channel select w/ timer) var err error @@ -190,6 +192,9 @@ func (intf *link) handler() (chan struct{}, error) { if !meta.decode(metaBytes) { return nil, errors.New("failed to decode metadata") } + if metric < uint64(meta.metric) { + metric = uint64(meta.metric) + } if !meta.check() { intf.links.core.log.Errorf("Failed to connect to node: %s is incompatible version (local %s, remote %s)", intf.lname, @@ -250,7 +255,6 @@ func (intf *link) handler() (chan struct{}, error) { intf.links.core.log.Infof("Connected %s: %s, source %s", strings.ToUpper(intf.info.linkType), themString, intf.info.local) // Run the handler - var metric uint64 // TODO exchange metric in matadata, use max value err = intf.links.core.PacketConn.HandleConn(ed25519.PublicKey(intf.info.key[:]), intf.conn, metric) // TODO don't report an error if it's just a 'use of closed network connection' if err != nil { diff --git a/src/core/version.go b/src/core/version.go index 6676c7b6..2c449ec5 100644 --- a/src/core/version.go +++ b/src/core/version.go @@ -14,6 +14,7 @@ type version_metadata struct { ver uint8 // 1 byte in this version // Everything after this point potentially depends on the version number, and is subject to change in future versions minorVer uint8 // 1 byte in this version + metric uint8 // 1 byte in this version key ed25519.PublicKey } @@ -31,6 +32,7 @@ func version_getMetaLength() (mlen int) { mlen += 4 // meta mlen++ // ver, as long as it's < 127, which it is in this version mlen++ // minorVer, as long as it's < 127, which it is in this version + mlen++ // metric mlen += ed25519.PublicKeySize // key return } @@ -41,6 +43,7 @@ func (m *version_metadata) encode() []byte { bs = append(bs, m.meta[:]...) bs = append(bs, m.ver) bs = append(bs, m.minorVer) + bs = append(bs, m.metric) bs = append(bs, m.key[:]...) if len(bs) != version_getMetaLength() { panic("Inconsistent metadata length") @@ -57,6 +60,7 @@ func (m *version_metadata) decode(bs []byte) bool { offset += copy(m.meta[:], bs[offset:]) m.ver, offset = bs[offset], offset+1 m.minorVer, offset = bs[offset], offset+1 + m.metric, offset = bs[offset], offset+1 m.key = append([]byte(nil), bs[offset:]...) return true } From 70c5b06286ecc23653b2c2f720b1fee1bf1b1c32 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 23 May 2021 20:34:13 -0500 Subject: [PATCH 0693/1109] use url.URL in place of string for most internal listen/peer address handling --- src/core/api.go | 11 +++++-- src/core/core.go | 4 +-- src/core/link.go | 10 +++--- src/core/tcp.go | 82 ++++++++++++++++++++++++++---------------------- 4 files changed, 61 insertions(+), 46 deletions(-) diff --git a/src/core/api.go b/src/core/api.go index ec03df8d..adfec61a 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -3,9 +3,10 @@ package core import ( "crypto/ed25519" //"encoding/hex" - "errors" + //"errors" //"fmt" "net" + "net/url" //"sort" //"time" @@ -150,6 +151,7 @@ func (c *Core) SetLogger(log *log.Logger) { // socks://a.b.c.d:e/f.g.h.i:j // This adds the peer to the peer list, so that they will be called again if the // connection drops. +/* func (c *Core) AddPeer(addr string, sintf string) error { if err := c.CallPeer(addr, sintf); err != nil { // TODO: We maybe want this to write the peer to the persistent @@ -184,6 +186,7 @@ func (c *Core) AddPeer(addr string, sintf string) error { } return nil } +*/ /* func (c *Core) RemovePeer(addr string, sintf string) error { @@ -224,5 +227,9 @@ func (c *Core) RemovePeer(addr string, sintf string) error { // This does not add the peer to the peer list, so if the connection drops, the // peer will not be called again automatically. func (c *Core) CallPeer(addr string, sintf string) error { - return c.links.call(addr, sintf) + u, err := url.Parse(addr) + if err != nil { + return err + } + return c.links.call(u, sintf) } diff --git a/src/core/core.go b/src/core/core.go index 0adc51b7..a30b2556 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -68,7 +68,7 @@ func (c *Core) _addPeerLoop() { // Add peers from the Peers section for _, peer := range current.Peers { - go func(peer, intf string) { + go func(peer string, intf string) { if err := c.CallPeer(peer, intf); err != nil { c.log.Errorln("Failed to add peer:", err) } @@ -78,7 +78,7 @@ func (c *Core) _addPeerLoop() { // Add peers from the InterfacePeers section for intf, intfpeers := range current.InterfacePeers { for _, peer := range intfpeers { - go func(peer, intf string) { + go func(peer string, intf string) { if err := c.CallPeer(peer, intf); err != nil { c.log.Errorln("Failed to add peer:", err) } diff --git a/src/core/link.go b/src/core/link.go index d7de9435..aac5d1a6 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -73,11 +73,11 @@ func (l *links) reconfigure() { l.tcp.reconfigure() } -func (l *links) call(uri string, sintf string) error { - u, err := url.Parse(uri) - if err != nil { - return fmt.Errorf("peer %s is not correctly formatted (%s)", uri, err) - } +func (l *links) call(u *url.URL, sintf string) error { + //u, err := url.Parse(uri) + //if err != nil { + // return fmt.Errorf("peer %s is not correctly formatted (%s)", uri, err) + //} pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/") tcpOpts := tcpOptions{} if pubkeys, ok := u.Query()["ed25519"]; ok && len(pubkeys) > 0 { diff --git a/src/core/tcp.go b/src/core/tcp.go index 9031256a..3500364f 100644 --- a/src/core/tcp.go +++ b/src/core/tcp.go @@ -19,6 +19,7 @@ import ( "fmt" "math/rand" "net" + "net/url" "strings" "sync" "time" @@ -26,7 +27,7 @@ import ( "golang.org/x/net/proxy" "github.com/yggdrasil-network/yggdrasil-go/src/address" - "github.com/yggdrasil-network/yggdrasil-go/src/util" + //"github.com/yggdrasil-network/yggdrasil-go/src/util" ) const default_timeout = 6 * time.Second @@ -107,17 +108,21 @@ func (t *tcp) init(l *links) error { t.links.core.config.Mutex.RLock() defer t.links.core.config.Mutex.RUnlock() for _, listenaddr := range t.links.core.config.Current.Listen { - switch listenaddr[:6] { - case "tcp://": - if _, err := t.listen(listenaddr[6:], nil); err != nil { + u, err := url.Parse(listenaddr) + if err != nil { + t.links.core.log.Errorln("Failed to parse listener: listener", listenaddr, "is not correctly formatted, ignoring") + } + switch u.Scheme { + case "tcp": + if _, err := t.listen(u.Host, nil); err != nil { return err } - case "tls://": - if _, err := t.listen(listenaddr[6:], t.tls.forListener); err != nil { + case "tls": + if _, err := t.listen(u.Host, t.tls.forListener); err != nil { return err } default: - t.links.core.log.Errorln("Failed to add listener: listener", listenaddr, "is not correctly formatted, ignoring") + t.links.core.log.Errorln("Failed to add listener: listener", u.String(), "is not correctly formatted, ignoring") } } @@ -135,40 +140,43 @@ func (t *tcp) stop() error { } func (t *tcp) reconfigure() { - t.links.core.config.Mutex.RLock() - added := util.Difference(t.links.core.config.Current.Listen, t.links.core.config.Previous.Listen) - deleted := util.Difference(t.links.core.config.Previous.Listen, t.links.core.config.Current.Listen) - t.links.core.config.Mutex.RUnlock() - if len(added) > 0 || len(deleted) > 0 { - for _, a := range added { - switch a[:6] { - case "tcp://": - if _, err := t.listen(a[6:], nil); err != nil { - t.links.core.log.Errorln("Error adding TCP", a[6:], "listener:", err) + panic("TODO cleanup obsolete reconfigure() stuff") + /* + t.links.core.config.Mutex.RLock() + added := util.Difference(t.links.core.config.Current.Listen, t.links.core.config.Previous.Listen) + deleted := util.Difference(t.links.core.config.Previous.Listen, t.links.core.config.Current.Listen) + t.links.core.config.Mutex.RUnlock() + if len(added) > 0 || len(deleted) > 0 { + for _, a := range added { + switch a[:6] { + case "tcp://": + if _, err := t.listen(a[6:], nil); err != nil { + t.links.core.log.Errorln("Error adding TCP", a[6:], "listener:", err) + } + case "tls://": + if _, err := t.listen(a[6:], t.tls.forListener); err != nil { + t.links.core.log.Errorln("Error adding TLS", a[6:], "listener:", err) + } + default: + t.links.core.log.Errorln("Failed to add listener: listener", a, "is not correctly formatted, ignoring") } - case "tls://": - if _, err := t.listen(a[6:], t.tls.forListener); err != nil { - t.links.core.log.Errorln("Error adding TLS", a[6:], "listener:", err) + } + for _, d := range deleted { + if d[:6] != "tcp://" && d[:6] != "tls://" { + t.links.core.log.Errorln("Failed to delete listener: listener", d, "is not correctly formatted, ignoring") + continue + } + t.mutex.Lock() + if listener, ok := t.listeners[d[6:]]; ok { + t.mutex.Unlock() + listener.Stop() + t.links.core.log.Infoln("Stopped TCP listener:", d[6:]) + } else { + t.mutex.Unlock() } - default: - t.links.core.log.Errorln("Failed to add listener: listener", a, "is not correctly formatted, ignoring") } } - for _, d := range deleted { - if d[:6] != "tcp://" && d[:6] != "tls://" { - t.links.core.log.Errorln("Failed to delete listener: listener", d, "is not correctly formatted, ignoring") - continue - } - t.mutex.Lock() - if listener, ok := t.listeners[d[6:]]; ok { - t.mutex.Unlock() - listener.Stop() - t.links.core.log.Infoln("Stopped TCP listener:", d[6:]) - } else { - t.mutex.Unlock() - } - } - } + */ } func (t *tcp) listen(listenaddr string, upgrade *TcpUpgrade) (*TcpListener, error) { From fd5cda6329b594d4fb58cbe84eb5fdf42205e6d1 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 23 May 2021 20:58:34 -0500 Subject: [PATCH 0694/1109] read metric from urls for listen and peers --- src/core/api.go | 8 ++++---- src/core/link.go | 25 +++++++------------------ src/core/tcp.go | 21 +++++++++++++-------- src/multicast/multicast.go | 3 ++- 4 files changed, 26 insertions(+), 31 deletions(-) diff --git a/src/core/api.go b/src/core/api.go index adfec61a..999acd4c 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -106,15 +106,15 @@ func (c *Core) GetSessions() []Session { // ListenTCP starts a new TCP listener. The input URI should match that of the // "Listen" configuration item, e.g. // tcp://a.b.c.d:e -func (c *Core) ListenTCP(uri string) (*TcpListener, error) { - return c.links.tcp.listen(uri, nil) +func (c *Core) ListenTCP(uri string, metric uint8) (*TcpListener, error) { + return c.links.tcp.listen(uri, nil, metric) } // 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.links.tcp.listen(uri, c.links.tcp.tls.forListener) +func (c *Core) ListenTLS(uri string, metric uint8) (*TcpListener, error) { + return c.links.tcp.listen(uri, c.links.tcp.tls.forListener, metric) } // Address gets the IPv6 address of the Yggdrasil node. This is always a /128 diff --git a/src/core/link.go b/src/core/link.go index aac5d1a6..5993d767 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -8,6 +8,7 @@ import ( "io" "net" "net/url" + "strconv" "strings" "sync" @@ -52,6 +53,7 @@ type link struct { type linkOptions struct { pinnedEd25519Keys map[keyArray]struct{} + metric uint8 } func (l *links) init(c *Core) error { @@ -90,6 +92,10 @@ func (l *links) call(u *url.URL, sintf string) error { } } } + if ms := u.Query()["metric"]; len(ms) == 1 { + m64, _ := strconv.ParseUint(ms[0], 10, 8) + tcpOpts.metric = uint8(m64) + } switch u.Scheme { case "tcp": l.tcp.call(u.Host, tcpOpts, sintf) @@ -110,23 +116,6 @@ func (l *links) call(u *url.URL, sintf string) error { return nil } -func (l *links) listen(uri string) error { - u, err := url.Parse(uri) - if err != nil { - return fmt.Errorf("listener %s is not correctly formatted (%s)", uri, err) - } - switch u.Scheme { - case "tcp": - _, err := l.tcp.listen(u.Host, nil) - return err - case "tls": - _, err := l.tcp.listen(u.Host, l.tcp.tls.forListener) - return err - default: - return errors.New("unknown listen scheme: " + u.Scheme) - } -} - func (l *links) create(conn net.Conn, name, linkType, local, remote string, incoming, force bool, options linkOptions) (*link, error) { // Technically anything unique would work for names, but let's pick something human readable, just for debugging intf := link{ @@ -158,7 +147,7 @@ func (intf *link) handler() (chan struct{}, error) { defer intf.conn.Close() meta := version_getBaseMetadata() meta.key = intf.links.core.public - // TODO set meta.metric + meta.metric = intf.options.metric metric := uint64(meta.metric) metaBytes := meta.encode() // TODO timeouts on send/recv (goroutine for send/recv, channel select w/ timer) diff --git a/src/core/tcp.go b/src/core/tcp.go index 3500364f..588da698 100644 --- a/src/core/tcp.go +++ b/src/core/tcp.go @@ -20,6 +20,7 @@ import ( "math/rand" "net" "net/url" + "strconv" "strings" "sync" "time" @@ -50,7 +51,7 @@ type tcp struct { // multicast interfaces. type TcpListener struct { Listener net.Listener - upgrade *TcpUpgrade + opts tcpOptions stop chan struct{} } @@ -112,13 +113,18 @@ func (t *tcp) init(l *links) error { if err != nil { t.links.core.log.Errorln("Failed to parse listener: listener", listenaddr, "is not correctly formatted, ignoring") } + var metric uint8 // TODO parse from url + if ms := u.Query()["metric"]; len(ms) == 1 { + m64, _ := strconv.ParseUint(ms[0], 10, 8) + metric = uint8(m64) + } switch u.Scheme { case "tcp": - if _, err := t.listen(u.Host, nil); err != nil { + if _, err := t.listen(u.Host, nil, metric); err != nil { return err } case "tls": - if _, err := t.listen(u.Host, t.tls.forListener); err != nil { + if _, err := t.listen(u.Host, t.tls.forListener, metric); err != nil { return err } default: @@ -179,7 +185,7 @@ func (t *tcp) reconfigure() { */ } -func (t *tcp) listen(listenaddr string, upgrade *TcpUpgrade) (*TcpListener, error) { +func (t *tcp) listen(listenaddr string, upgrade *TcpUpgrade, metric uint8) (*TcpListener, error) { var err error ctx := context.Background() @@ -190,9 +196,10 @@ func (t *tcp) listen(listenaddr string, upgrade *TcpUpgrade) (*TcpListener, erro if err == nil { l := TcpListener{ Listener: listener, - upgrade: upgrade, + opts: tcpOptions{upgrade: upgrade}, stop: make(chan struct{}), } + l.opts.metric = metric t.waitgroup.Add(1) go t.listener(&l, listenaddr) return &l, nil @@ -243,9 +250,7 @@ func (t *tcp) listener(l *TcpListener, listenaddr string) { continue } t.waitgroup.Add(1) - options := tcpOptions{ - upgrade: l.upgrade, - } + options := l.opts go t.handler(sock, true, options) } } diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index b7334da1..56f46133 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -274,7 +274,8 @@ func (m *Multicast) _announce() { if nfo, ok := m.listeners[iface.Name]; !ok || nfo.listener.Listener == nil { // No listener was found - let's create one listenaddr := fmt.Sprintf("[%s%%%s]:%d", addrIP, iface.Name, m.listenPort) - if li, err := m.core.ListenTCP(listenaddr); err == nil { + var metric uint8 // TODO parse this from config + if li, err := m.core.ListenTCP(listenaddr, metric); err == nil { m.log.Debugln("Started multicasting on", iface.Name) // Store the listener so that we can stop it later if needed info = &listenerInfo{listener: li, time: time.Now()} From 5f2bcaa71f33158ed72a21769bbb9fc9c3911a0b Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 23 May 2021 21:47:12 -0500 Subject: [PATCH 0695/1109] add Listen to api and listenURL to tcp --- src/core/api.go | 14 +++++++------- src/core/core.go | 13 +++++++++++-- src/core/link.go | 2 +- src/core/tcp.go | 26 ++++++++++++++++++++++++++ src/multicast/multicast.go | 17 +++++++++++++---- 5 files changed, 58 insertions(+), 14 deletions(-) diff --git a/src/core/api.go b/src/core/api.go index 999acd4c..df39046c 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -103,17 +103,21 @@ func (c *Core) GetSessions() []Session { return sessions } +func (c *Core) Listen(u *url.URL, sintf string) (*TcpListener, error) { + return c.links.tcp.listenURL(u, sintf) +} + // ListenTCP starts a new TCP listener. The input URI should match that of the // "Listen" configuration item, e.g. // tcp://a.b.c.d:e -func (c *Core) ListenTCP(uri string, metric uint8) (*TcpListener, error) { +func (c *Core) xListenTCP(uri string, metric uint8) (*TcpListener, error) { return c.links.tcp.listen(uri, nil, metric) } // 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, metric uint8) (*TcpListener, error) { +func (c *Core) xListenTLS(uri string, metric uint8) (*TcpListener, error) { return c.links.tcp.listen(uri, c.links.tcp.tls.forListener, metric) } @@ -226,10 +230,6 @@ func (c *Core) RemovePeer(addr string, sintf string) error { // socks://a.b.c.d:e/f.g.h.i:j // This does not add the peer to the peer list, so if the connection drops, the // peer will not be called again automatically. -func (c *Core) CallPeer(addr string, sintf string) error { - u, err := url.Parse(addr) - if err != nil { - return err - } +func (c *Core) CallPeer(u *url.URL, sintf string) error { return c.links.call(u, sintf) } diff --git a/src/core/core.go b/src/core/core.go index a30b2556..ba2b89b0 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "errors" "io/ioutil" + "net/url" "time" iw "github.com/Arceliar/ironwood/encrypted" @@ -69,7 +70,11 @@ func (c *Core) _addPeerLoop() { // Add peers from the Peers section for _, peer := range current.Peers { go func(peer string, intf string) { - if err := c.CallPeer(peer, intf); err != nil { + u, err := url.Parse(peer) + if err != nil { + c.log.Errorln("Failed to parse peer url:", peer, err) + } + if err := c.CallPeer(u, intf); err != nil { c.log.Errorln("Failed to add peer:", err) } }(peer, "") // TODO: this should be acted and not in a goroutine? @@ -79,7 +84,11 @@ func (c *Core) _addPeerLoop() { for intf, intfpeers := range current.InterfacePeers { for _, peer := range intfpeers { go func(peer string, intf string) { - if err := c.CallPeer(peer, intf); err != nil { + u, err := url.Parse(peer) + if err != nil { + c.log.Errorln("Failed to parse peer url:", peer, err) + } + if err := c.CallPeer(u, intf); err != nil { c.log.Errorln("Failed to add peer:", err) } }(peer, intf) // TODO: this should be acted and not in a goroutine? diff --git a/src/core/link.go b/src/core/link.go index 5993d767..f547c7ff 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -80,7 +80,6 @@ func (l *links) call(u *url.URL, sintf string) error { //if err != nil { // return fmt.Errorf("peer %s is not correctly formatted (%s)", uri, err) //} - pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/") tcpOpts := tcpOptions{} if pubkeys, ok := u.Query()["ed25519"]; ok && len(pubkeys) > 0 { tcpOpts.pinnedEd25519Keys = make(map[keyArray]struct{}) @@ -106,6 +105,7 @@ func (l *links) call(u *url.URL, sintf string) error { tcpOpts.socksProxyAuth.User = u.User.Username() tcpOpts.socksProxyAuth.Password, _ = u.User.Password() } + pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/") l.tcp.call(pathtokens[0], tcpOpts, sintf) case "tls": tcpOpts.upgrade = l.tcp.tls.forDialer diff --git a/src/core/tcp.go b/src/core/tcp.go index 588da698..66ac7af9 100644 --- a/src/core/tcp.go +++ b/src/core/tcp.go @@ -185,6 +185,32 @@ func (t *tcp) reconfigure() { */ } +func (t *tcp) listenURL(u *url.URL, sintf string) (*TcpListener, error) { + var metric uint8 + if ms := u.Query()["metric"]; len(ms) == 1 { + m64, _ := strconv.ParseUint(ms[0], 10, 8) + metric = uint8(m64) + } + var listener *TcpListener + var err error + hostport := u.Host // Used for tcp and tls + if len(sintf) != 0 { + host, port, err := net.SplitHostPort(hostport) + if err == nil { + hostport = fmt.Sprintf("[%s%%%s]:%s", host, sintf, port) + } + } + switch u.Scheme { + case "tcp": + listener, err = t.listen(hostport, nil, metric) + case "tls": + listener, err = t.listen(hostport, t.tls.forListener, metric) + default: + t.links.core.log.Errorln("Failed to add listener: listener", u.String(), "is not correctly formatted, ignoring") + } + return listener, err +} + func (t *tcp) listen(listenaddr string, upgrade *TcpUpgrade, metric uint8) (*TcpListener, error) { var err error diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 56f46133..66edaa8e 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net" + "net/url" "regexp" "time" @@ -273,9 +274,13 @@ func (m *Multicast) _announce() { var info *listenerInfo if nfo, ok := m.listeners[iface.Name]; !ok || nfo.listener.Listener == nil { // No listener was found - let's create one - listenaddr := fmt.Sprintf("[%s%%%s]:%d", addrIP, iface.Name, m.listenPort) - var metric uint8 // TODO parse this from config - if li, err := m.core.ListenTCP(listenaddr, metric); err == nil { + var metric uint8 // TODO parse this from the config + urlString := fmt.Sprintf("tcp://[%s]:%d/?metric=%d", addrIP, m.listenPort, metric) + u, err := url.Parse(urlString) + if err != nil { + panic(err) + } + if li, err := m.core.Listen(u, iface.Name); err == nil { m.log.Debugln("Started multicasting on", iface.Name) // Store the listener so that we can stop it later if needed info = &listenerInfo{listener: li, time: time.Now()} @@ -353,7 +358,11 @@ func (m *Multicast) listen() { }) if _, ok := interfaces[from.Zone]; ok { addr.Zone = "" - if err := m.core.CallPeer("tcp://"+addr.String(), from.Zone); err != nil { + u, err := url.Parse("tcp://" + addr.String()) + if err != nil { + m.log.Debugln("Call from multicast failed, parse error:", addr.String(), err) + } + if err := m.core.CallPeer(u, from.Zone); err != nil { m.log.Debugln("Call from multicast failed:", err) } } From c60dd42baa6b832923128c1fd637e480c883841e Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 23 May 2021 21:51:09 -0500 Subject: [PATCH 0696/1109] cleanup --- src/core/api.go | 17 +++-------------- src/core/tcp.go | 18 ++---------------- 2 files changed, 5 insertions(+), 30 deletions(-) diff --git a/src/core/api.go b/src/core/api.go index df39046c..f28b592a 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -103,24 +103,13 @@ func (c *Core) GetSessions() []Session { return sessions } +// Listen starts a new listener (either TCP or TLS). The input should be a url.URL +// parsed from a string of the form e.g. "tcp://a.b.c.d:e". In the case of a +// link-local address, the interface should be provided as the second argument. func (c *Core) Listen(u *url.URL, sintf string) (*TcpListener, error) { return c.links.tcp.listenURL(u, sintf) } -// ListenTCP starts a new TCP listener. The input URI should match that of the -// "Listen" configuration item, e.g. -// tcp://a.b.c.d:e -func (c *Core) xListenTCP(uri string, metric uint8) (*TcpListener, error) { - return c.links.tcp.listen(uri, nil, metric) -} - -// 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) xListenTLS(uri string, metric uint8) (*TcpListener, error) { - return c.links.tcp.listen(uri, c.links.tcp.tls.forListener, metric) -} - // Address gets the IPv6 address of the Yggdrasil node. This is always a /128 // address. The IPv6 address is only relevant when the node is operating as an // IP router and often is meaningless when embedded into an application, unless diff --git a/src/core/tcp.go b/src/core/tcp.go index 66ac7af9..2cba8182 100644 --- a/src/core/tcp.go +++ b/src/core/tcp.go @@ -113,22 +113,8 @@ func (t *tcp) init(l *links) error { if err != nil { t.links.core.log.Errorln("Failed to parse listener: listener", listenaddr, "is not correctly formatted, ignoring") } - var metric uint8 // TODO parse from url - if ms := u.Query()["metric"]; len(ms) == 1 { - m64, _ := strconv.ParseUint(ms[0], 10, 8) - metric = uint8(m64) - } - switch u.Scheme { - case "tcp": - if _, err := t.listen(u.Host, nil, metric); err != nil { - return err - } - case "tls": - if _, err := t.listen(u.Host, t.tls.forListener, metric); err != nil { - return err - } - default: - t.links.core.log.Errorln("Failed to add listener: listener", u.String(), "is not correctly formatted, ignoring") + if _, err := t.listenURL(u, ""); err != nil { + return err } } From 8a60c605f6d3f762c7a7686f3a7a38d14ab1f633 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 24 May 2021 18:53:54 -0500 Subject: [PATCH 0697/1109] remove metric stuff, there's already enough new stuff to test, maybe revisit this in a future release --- go.mod | 2 +- go.sum | 4 ++-- src/core/link.go | 13 +------------ src/core/tcp.go | 13 +++---------- src/core/version.go | 4 ---- src/multicast/multicast.go | 3 +-- 6 files changed, 8 insertions(+), 31 deletions(-) diff --git a/go.mod b/go.mod index 23346640..5e5b23d4 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.16 require ( - github.com/Arceliar/ironwood v0.0.0-20210523223424-d320cf0ed78e + github.com/Arceliar/ironwood v0.0.0-20210522034542-c2e274961490 github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 github.com/cheggaaa/pb/v3 v3.0.6 github.com/fatih/color v1.10.0 // indirect diff --git a/go.sum b/go.sum index 9c66c19d..454c7168 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/ironwood v0.0.0-20210523223424-d320cf0ed78e h1:EoZ4Dfm3xBDFjXRUzZUH+44NVvQ8tLf/VyESuC0BijI= -github.com/Arceliar/ironwood v0.0.0-20210523223424-d320cf0ed78e/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= +github.com/Arceliar/ironwood v0.0.0-20210522034542-c2e274961490 h1:z1Rd8RMNVrpNYIXHn7f62wz8nbuPkJFs9Z24sHIhNrc= +github.com/Arceliar/ironwood v0.0.0-20210522034542-c2e274961490/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= diff --git a/src/core/link.go b/src/core/link.go index f547c7ff..563fa3b5 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -8,7 +8,6 @@ import ( "io" "net" "net/url" - "strconv" "strings" "sync" @@ -53,7 +52,6 @@ type link struct { type linkOptions struct { pinnedEd25519Keys map[keyArray]struct{} - metric uint8 } func (l *links) init(c *Core) error { @@ -91,10 +89,6 @@ func (l *links) call(u *url.URL, sintf string) error { } } } - if ms := u.Query()["metric"]; len(ms) == 1 { - m64, _ := strconv.ParseUint(ms[0], 10, 8) - tcpOpts.metric = uint8(m64) - } switch u.Scheme { case "tcp": l.tcp.call(u.Host, tcpOpts, sintf) @@ -147,8 +141,6 @@ func (intf *link) handler() (chan struct{}, error) { defer intf.conn.Close() meta := version_getBaseMetadata() meta.key = intf.links.core.public - meta.metric = intf.options.metric - metric := uint64(meta.metric) metaBytes := meta.encode() // TODO timeouts on send/recv (goroutine for send/recv, channel select w/ timer) var err error @@ -181,9 +173,6 @@ func (intf *link) handler() (chan struct{}, error) { if !meta.decode(metaBytes) { return nil, errors.New("failed to decode metadata") } - if metric < uint64(meta.metric) { - metric = uint64(meta.metric) - } if !meta.check() { intf.links.core.log.Errorf("Failed to connect to node: %s is incompatible version (local %s, remote %s)", intf.lname, @@ -244,7 +233,7 @@ func (intf *link) handler() (chan struct{}, error) { intf.links.core.log.Infof("Connected %s: %s, source %s", strings.ToUpper(intf.info.linkType), themString, intf.info.local) // Run the handler - err = intf.links.core.PacketConn.HandleConn(ed25519.PublicKey(intf.info.key[:]), intf.conn, metric) + err = intf.links.core.PacketConn.HandleConn(ed25519.PublicKey(intf.info.key[:]), intf.conn) // TODO don't report an error if it's just a 'use of closed network connection' if err != nil { intf.links.core.log.Infof("Disconnected %s: %s, source %s; error: %s", diff --git a/src/core/tcp.go b/src/core/tcp.go index 2cba8182..5f79d2e7 100644 --- a/src/core/tcp.go +++ b/src/core/tcp.go @@ -20,7 +20,6 @@ import ( "math/rand" "net" "net/url" - "strconv" "strings" "sync" "time" @@ -172,11 +171,6 @@ func (t *tcp) reconfigure() { } func (t *tcp) listenURL(u *url.URL, sintf string) (*TcpListener, error) { - var metric uint8 - if ms := u.Query()["metric"]; len(ms) == 1 { - m64, _ := strconv.ParseUint(ms[0], 10, 8) - metric = uint8(m64) - } var listener *TcpListener var err error hostport := u.Host // Used for tcp and tls @@ -188,16 +182,16 @@ func (t *tcp) listenURL(u *url.URL, sintf string) (*TcpListener, error) { } switch u.Scheme { case "tcp": - listener, err = t.listen(hostport, nil, metric) + listener, err = t.listen(hostport, nil) case "tls": - listener, err = t.listen(hostport, t.tls.forListener, metric) + listener, err = t.listen(hostport, t.tls.forListener) default: t.links.core.log.Errorln("Failed to add listener: listener", u.String(), "is not correctly formatted, ignoring") } return listener, err } -func (t *tcp) listen(listenaddr string, upgrade *TcpUpgrade, metric uint8) (*TcpListener, error) { +func (t *tcp) listen(listenaddr string, upgrade *TcpUpgrade) (*TcpListener, error) { var err error ctx := context.Background() @@ -211,7 +205,6 @@ func (t *tcp) listen(listenaddr string, upgrade *TcpUpgrade, metric uint8) (*Tcp opts: tcpOptions{upgrade: upgrade}, stop: make(chan struct{}), } - l.opts.metric = metric t.waitgroup.Add(1) go t.listener(&l, listenaddr) return &l, nil diff --git a/src/core/version.go b/src/core/version.go index 2c449ec5..6676c7b6 100644 --- a/src/core/version.go +++ b/src/core/version.go @@ -14,7 +14,6 @@ type version_metadata struct { ver uint8 // 1 byte in this version // Everything after this point potentially depends on the version number, and is subject to change in future versions minorVer uint8 // 1 byte in this version - metric uint8 // 1 byte in this version key ed25519.PublicKey } @@ -32,7 +31,6 @@ func version_getMetaLength() (mlen int) { mlen += 4 // meta mlen++ // ver, as long as it's < 127, which it is in this version mlen++ // minorVer, as long as it's < 127, which it is in this version - mlen++ // metric mlen += ed25519.PublicKeySize // key return } @@ -43,7 +41,6 @@ func (m *version_metadata) encode() []byte { bs = append(bs, m.meta[:]...) bs = append(bs, m.ver) bs = append(bs, m.minorVer) - bs = append(bs, m.metric) bs = append(bs, m.key[:]...) if len(bs) != version_getMetaLength() { panic("Inconsistent metadata length") @@ -60,7 +57,6 @@ func (m *version_metadata) decode(bs []byte) bool { offset += copy(m.meta[:], bs[offset:]) m.ver, offset = bs[offset], offset+1 m.minorVer, offset = bs[offset], offset+1 - m.metric, offset = bs[offset], offset+1 m.key = append([]byte(nil), bs[offset:]...) return true } diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 66edaa8e..c1b87890 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -274,8 +274,7 @@ func (m *Multicast) _announce() { var info *listenerInfo if nfo, ok := m.listeners[iface.Name]; !ok || nfo.listener.Listener == nil { // No listener was found - let's create one - var metric uint8 // TODO parse this from the config - urlString := fmt.Sprintf("tcp://[%s]:%d/?metric=%d", addrIP, m.listenPort, metric) + urlString := fmt.Sprintf("tcp://[%s]:%d", addrIP, m.listenPort) u, err := url.Parse(urlString) if err != nil { panic(err) From 180654c495f597b3673a206672d134370e321051 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 29 May 2021 11:13:59 -0500 Subject: [PATCH 0698/1109] possibly fix src/core/core_test.go --- src/core/core_test.go | 61 ++++++++++++++----------------------------- src/core/link.go | 6 ++--- 2 files changed, 22 insertions(+), 45 deletions(-) diff --git a/src/core/core_test.go b/src/core/core_test.go index e1585c3a..224907b9 100644 --- a/src/core/core_test.go +++ b/src/core/core_test.go @@ -3,6 +3,7 @@ package core import ( "bytes" "math/rand" + "net/url" "os" "testing" "time" @@ -50,10 +51,16 @@ func CreateAndConnectTwo(t testing.TB, verbose bool) (nodeA *Core, nodeB *Core) t.Fatal(err) } - err = nodeB.AddPeer("tcp://"+nodeA.link.tcp.getAddr().String(), "") + u, err := url.Parse("tcp://" + nodeA.links.tcp.getAddr().String()) if err != nil { t.Fatal(err) } + err = nodeB.CallPeer(u, "") + if err != nil { + t.Fatal(err) + } + + time.Sleep(100 * time.Millisecond) if l := len(nodeA.GetPeers()); l != 1 { t.Fatal("unexpected number of peers", l) @@ -70,7 +77,7 @@ 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 { + if len(nodeA.GetPeers()) > 0 && len(nodeB.GetPeers()) > 0 { return true } } @@ -80,26 +87,12 @@ func WaitConnected(nodeA, nodeB *Core) bool { // 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) + n, from, err := nodeA.ReadFrom(buf) if err != nil { t.Error(err) return @@ -108,7 +101,7 @@ func CreateEchoListener(t testing.TB, nodeA *Core, bufLen int, repeats int) chan t.Error("missing data") return } - _, err = conn.Write(buf) + _, err = nodeA.WriteTo(buf, from) if err != nil { t.Error(err) } @@ -127,6 +120,8 @@ func TestCore_Start_Connect(t *testing.T) { // 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) + defer nodeA.Stop() + defer nodeB.Stop() msgLen := 1500 done := CreateEchoListener(t, nodeA, msgLen, 1) @@ -135,24 +130,15 @@ func TestCore_Start_Transfer(t *testing.T) { 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() + // Send msg := make([]byte, msgLen) rand.Read(msg) - conn.Write(msg) + _, err := nodeB.WriteTo(msg, nodeA.LocalAddr()) if err != nil { t.Fatal(err) } buf := make([]byte, msgLen) - _, err = conn.Read(buf) + _, _, err = nodeB.ReadFrom(buf) if err != nil { t.Fatal(err) } @@ -173,16 +159,7 @@ func BenchmarkCore_Start_Transfer(b *testing.B) { 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() + // Send msg := make([]byte, msgLen) rand.Read(msg) buf := make([]byte, msgLen) @@ -191,11 +168,11 @@ func BenchmarkCore_Start_Transfer(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - conn.Write(msg) + _, err := nodeB.WriteTo(msg, nodeA.LocalAddr()) if err != nil { b.Fatal(err) } - _, err = conn.Read(buf) + _, _, err = nodeB.ReadFrom(buf) if err != nil { b.Fatal(err) } diff --git a/src/core/link.go b/src/core/link.go index 563fa3b5..1bd8b67c 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -187,7 +187,7 @@ func (intf *link) handler() (chan struct{}, error) { var key keyArray copy(key[:], meta.key) if _, allowed := pinned[key]; !allowed { - intf.links.core.log.Errorf("Failed to connect to node: %q sent ed25519 key that does not match pinned keys", intf.name) + intf.links.core.log.Errorf("Failed to connect to node: %q sent ed25519 key that does not match pinned keys", intf.name()) return nil, fmt.Errorf("failed to connect: host sent ed25519 key that does not match pinned keys") } } @@ -213,7 +213,7 @@ func (intf *link) handler() (chan struct{}, error) { intf.links.mutex.Unlock() // FIXME we should really return an error and let the caller block instead // That lets them do things like close connections on its own, avoid printing a connection message in the first place, etc. - intf.links.core.log.Debugln("DEBUG: found existing interface for", intf.name) + intf.links.core.log.Debugln("DEBUG: found existing interface for", intf.name()) return oldIntf.closed, nil } else { intf.closed = make(chan struct{}) @@ -224,7 +224,7 @@ func (intf *link) handler() (chan struct{}, error) { intf.links.mutex.Unlock() close(intf.closed) }() - intf.links.core.log.Debugln("DEBUG: registered interface for", intf.name) + intf.links.core.log.Debugln("DEBUG: registered interface for", intf.name()) } intf.links.mutex.Unlock() themAddr := address.AddrForKey(ed25519.PublicKey(intf.info.key[:])) From e25ad9ed21ab2a099766523163b1a9932b898e2a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 29 May 2021 20:42:06 -0500 Subject: [PATCH 0699/1109] cleanup unused code --- src/core/link.go | 4 -- src/core/tcp.go | 40 --------------- src/tuntap/nodeinfo.go | 27 ---------- src/tuntap/proto.go | 6 --- src/util/cancellation.go | 103 --------------------------------------- 5 files changed, 180 deletions(-) delete mode 100644 src/util/cancellation.go diff --git a/src/core/link.go b/src/core/link.go index 1bd8b67c..80bd0c30 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -69,10 +69,6 @@ func (l *links) init(c *Core) error { return nil } -func (l *links) reconfigure() { - l.tcp.reconfigure() -} - func (l *links) call(u *url.URL, sintf string) error { //u, err := url.Parse(uri) //if err != nil { diff --git a/src/core/tcp.go b/src/core/tcp.go index 5f79d2e7..48163e8c 100644 --- a/src/core/tcp.go +++ b/src/core/tcp.go @@ -130,46 +130,6 @@ func (t *tcp) stop() error { return nil } -func (t *tcp) reconfigure() { - panic("TODO cleanup obsolete reconfigure() stuff") - /* - t.links.core.config.Mutex.RLock() - added := util.Difference(t.links.core.config.Current.Listen, t.links.core.config.Previous.Listen) - deleted := util.Difference(t.links.core.config.Previous.Listen, t.links.core.config.Current.Listen) - t.links.core.config.Mutex.RUnlock() - if len(added) > 0 || len(deleted) > 0 { - for _, a := range added { - switch a[:6] { - case "tcp://": - if _, err := t.listen(a[6:], nil); err != nil { - t.links.core.log.Errorln("Error adding TCP", a[6:], "listener:", err) - } - case "tls://": - if _, err := t.listen(a[6:], t.tls.forListener); err != nil { - t.links.core.log.Errorln("Error adding TLS", a[6:], "listener:", err) - } - default: - t.links.core.log.Errorln("Failed to add listener: listener", a, "is not correctly formatted, ignoring") - } - } - for _, d := range deleted { - if d[:6] != "tcp://" && d[:6] != "tls://" { - t.links.core.log.Errorln("Failed to delete listener: listener", d, "is not correctly formatted, ignoring") - continue - } - t.mutex.Lock() - if listener, ok := t.listeners[d[6:]]; ok { - t.mutex.Unlock() - listener.Stop() - t.links.core.log.Infoln("Stopped TCP listener:", d[6:]) - } else { - t.mutex.Unlock() - } - } - } - */ -} - func (t *tcp) listenURL(u *url.URL, sintf string) (*TcpListener, error) { var listener *TcpListener var err error diff --git a/src/tuntap/nodeinfo.go b/src/tuntap/nodeinfo.go index 1597f35a..3249b20c 100644 --- a/src/tuntap/nodeinfo.go +++ b/src/tuntap/nodeinfo.go @@ -27,23 +27,11 @@ type nodeinfo struct { callbacks map[keyArray]nodeinfoCallback } -type nodeinfoCached struct { - payload NodeInfoPayload - created time.Time -} - type nodeinfoCallback struct { call func(nodeinfo NodeInfoPayload) created time.Time } -// Represents a session nodeinfo packet. -type nodeinfoReqRes struct { - Key keyArray // Sender's permanent key - IsResponse bool - NodeInfo NodeInfoPayload -} - // Initialises the nodeinfo cache/callback maps, and starts a goroutine to keep // the cache/callback maps clean of stale entries func (m *nodeinfo) init(proto *protoHandler) { @@ -69,13 +57,6 @@ func (m *nodeinfo) _cleanup() { }) } -// Add a callback for a nodeinfo lookup -func (m *nodeinfo) addCallback(sender keyArray, call func(nodeinfo NodeInfoPayload)) { - m.Act(nil, func() { - m._addCallback(sender, call) - }) -} - func (m *nodeinfo) _addCallback(sender keyArray, call func(nodeinfo NodeInfoPayload)) { m.callbacks[sender] = nodeinfoCallback{ created: time.Now(), @@ -91,14 +72,6 @@ func (m *nodeinfo) _callback(sender keyArray, nodeinfo NodeInfoPayload) { } } -// Get the current node's nodeinfo -func (m *nodeinfo) getNodeInfo() (p NodeInfoPayload) { - phony.Block(m, func() { - p = m._getNodeInfo() - }) - return -} - func (m *nodeinfo) _getNodeInfo() NodeInfoPayload { return m.myNodeInfo } diff --git a/src/tuntap/proto.go b/src/tuntap/proto.go index c101ff71..79d84b2e 100644 --- a/src/tuntap/proto.go +++ b/src/tuntap/proto.go @@ -61,12 +61,6 @@ func (p *protoHandler) handleProto(from phony.Actor, key keyArray, bs []byte) { } } -func (p *protoHandler) handleDebug(from phony.Actor, key keyArray, bs []byte) { - p.Act(from, func() { - p._handleDebug(key, bs) - }) -} - func (p *protoHandler) _handleDebug(key keyArray, bs []byte) { if len(bs) == 0 { return diff --git a/src/util/cancellation.go b/src/util/cancellation.go deleted file mode 100644 index 7fc83147..00000000 --- a/src/util/cancellation.go +++ /dev/null @@ -1,103 +0,0 @@ -package util - -import ( - "errors" - "runtime" - "sync" - "time" -) - -// Cancellation is used to signal when things should shut down, such as signaling anything associated with a Conn to exit. -// This is and is similar to a context, but with an error to specify the reason for the cancellation. -type Cancellation interface { - Finished() <-chan struct{} // Finished returns a channel which will be closed when Cancellation.Cancel is first called. - Cancel(error) error // Cancel closes the channel returned by Finished and sets the error returned by error, or else returns the existing error if the Cancellation has already run. - Error() error // Error returns the error provided to Cancel, or nil if no error has been provided. -} - -// CancellationFinalized is an error returned if a cancellation object was garbage collected and the finalizer was run. -// If you ever see this, then you're probably doing something wrong with your code. -var CancellationFinalized = errors.New("finalizer called") - -// CancellationTimeoutError is used when a CancellationWithTimeout or CancellationWithDeadline is cancelled due to said timeout. -var CancellationTimeoutError = errors.New("timeout") - -// CancellationFinalizer is set as a finalizer when creating a new cancellation with NewCancellation(), and generally shouldn't be needed by the user, but is included in case other implementations of the same interface want to make use of it. -func CancellationFinalizer(c Cancellation) { - c.Cancel(CancellationFinalized) -} - -type cancellation struct { - cancel chan struct{} - mutex sync.RWMutex - err error - done bool -} - -// NewCancellation returns a pointer to a struct satisfying the Cancellation interface. -func NewCancellation() Cancellation { - c := cancellation{ - cancel: make(chan struct{}), - } - runtime.SetFinalizer(&c, CancellationFinalizer) - return &c -} - -// Finished returns a channel which will be closed when Cancellation.Cancel is first called. -func (c *cancellation) Finished() <-chan struct{} { - return c.cancel -} - -// Cancel closes the channel returned by Finished and sets the error returned by error, or else returns the existing error if the Cancellation has already run. -func (c *cancellation) Cancel(err error) error { - c.mutex.Lock() - defer c.mutex.Unlock() - if c.done { - return c.err - } - c.err = err - c.done = true - close(c.cancel) - return nil -} - -// Error returns the error provided to Cancel, or nil if no error has been provided. -func (c *cancellation) Error() error { - c.mutex.RLock() - err := c.err - c.mutex.RUnlock() - return err -} - -// CancellationChild returns a new Cancellation which can be Cancelled independently of the parent, but which will also be Cancelled if the parent is Cancelled first. -func CancellationChild(parent Cancellation) Cancellation { - child := NewCancellation() - go func() { - select { - case <-child.Finished(): - case <-parent.Finished(): - child.Cancel(parent.Error()) - } - }() - return child -} - -// CancellationWithTimeout returns a ChildCancellation that will automatically be Cancelled with a CancellationTimeoutError after the timeout. -func CancellationWithTimeout(parent Cancellation, timeout time.Duration) Cancellation { - child := CancellationChild(parent) - go func() { - timer := time.NewTimer(timeout) - defer TimerStop(timer) - select { - case <-child.Finished(): - case <-timer.C: - child.Cancel(CancellationTimeoutError) - } - }() - return child -} - -// CancellationWithTimeout returns a ChildCancellation that will automatically be Cancelled with a CancellationTimeoutError after the specified deadline. -func CancellationWithDeadline(parent Cancellation, deadline time.Time) Cancellation { - return CancellationWithTimeout(parent, deadline.Sub(time.Now())) -} From 1db7437b80d228ea8e84504fe3d253d41c573124 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 29 May 2021 21:37:13 -0500 Subject: [PATCH 0700/1109] more cleanup and fix a busyloop when the admin socket is shut down --- src/admin/admin.go | 29 +++++++++++++++++++++++++---- src/core/core.go | 13 +++++++++---- src/core/core_test.go | 2 +- src/core/tcp.go | 1 - src/tuntap/icmpv6.go | 6 +----- 5 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/admin/admin.go b/src/admin/admin.go index d2e066a3..8322b06f 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -25,7 +25,7 @@ type AdminSocket struct { listenaddr string listener net.Listener handlers map[string]handler - started bool + done chan struct{} } type AdminSocketResponse struct { @@ -69,6 +69,8 @@ func (a *AdminSocket) Init(c *core.Core, state *config.NodeState, log *log.Logge a.handlers = make(map[string]handler) current := state.GetCurrent() a.listenaddr = current.AdminListen + a.done = make(chan struct{}) + close(a.done) // Start in a done / not-started state _ = a.AddHandler("list", []string{}, func(_ json.RawMessage) (interface{}, error) { res := &ListResponse{ List: map[string]ListEntry{}, @@ -144,21 +146,32 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { // Start runs the admin API socket to listen for / respond to admin API calls. func (a *AdminSocket) Start() error { if a.listenaddr != "none" && a.listenaddr != "" { + a.done = make(chan struct{}) go a.listen() - a.started = true } return nil } // IsStarted returns true if the module has been started. func (a *AdminSocket) IsStarted() bool { - return a.started + select { + case <-a.done: + // Not blocking, so we're not currently running + return false + default: + // Blocked, so we must have started + return true + } } // Stop will stop the admin API and close the socket. func (a *AdminSocket) Stop() error { if a.listener != nil { - a.started = false + select { + case <-a.done: + default: + close(a.done) + } return a.listener.Close() } return nil @@ -215,6 +228,14 @@ func (a *AdminSocket) listen() { conn, err := a.listener.Accept() if err == nil { go a.handleRequest(conn) + } else { + select { + case <-a.done: + // Not blocked, so we havent started or already stopped + return + default: + // Blocked, so we're supposed to keep running + } } } } diff --git a/src/core/core.go b/src/core/core.go index ba2b89b0..f3942cdb 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -95,9 +95,11 @@ func (c *Core) _addPeerLoop() { } } - c.addPeerTimer = time.AfterFunc(time.Minute, func() { - c.Act(nil, c._addPeerLoop) - }) + if c.addPeerTimer != nil { + c.addPeerTimer = time.AfterFunc(time.Minute, func() { + c.Act(nil, c._addPeerLoop) + }) + } } // Start starts up Yggdrasil using the provided config.NodeConfig, and outputs @@ -149,7 +151,9 @@ func (c *Core) _start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState // return nil, err //} - c.Act(c, c._addPeerLoop) + c.addPeerTimer = time.AfterFunc(0, func() { + c.Act(nil, c._addPeerLoop) + }) c.log.Infoln("Startup complete") return &c.config, nil @@ -166,6 +170,7 @@ func (c *Core) _stop() { c.log.Infoln("Stopping...") if c.addPeerTimer != nil { c.addPeerTimer.Stop() + c.addPeerTimer = nil } c.links.stop() /* FIXME this deadlocks, need a waitgroup or something to coordinate shutdown diff --git a/src/core/core_test.go b/src/core/core_test.go index 224907b9..ce4a4156 100644 --- a/src/core/core_test.go +++ b/src/core/core_test.go @@ -142,7 +142,7 @@ func TestCore_Start_Transfer(t *testing.T) { if err != nil { t.Fatal(err) } - if bytes.Compare(msg, buf) != 0 { + if !bytes.Equal(msg, buf) { t.Fatal("expected echo") } <-done diff --git a/src/core/tcp.go b/src/core/tcp.go index 48163e8c..eced7726 100644 --- a/src/core/tcp.go +++ b/src/core/tcp.go @@ -31,7 +31,6 @@ import ( ) const default_timeout = 6 * time.Second -const tcp_ping_interval = (default_timeout * 2 / 3) // The TCP listener and information about active TCP connections, to avoid duplication. type tcp struct { diff --git a/src/tuntap/icmpv6.go b/src/tuntap/icmpv6.go index 59d07af3..b7461a2d 100644 --- a/src/tuntap/icmpv6.go +++ b/src/tuntap/icmpv6.go @@ -17,11 +17,7 @@ import ( "golang.org/x/net/ipv6" ) -const len_ETHER = 14 - -type ICMPv6 struct { - tun *TunAdapter -} +type ICMPv6 struct{} // Marshal returns the binary encoding of h. func ipv6Header_Marshal(h *ipv6.Header) ([]byte, error) { From 978124dbb1ec6960cca96fa716433748e3e7adbb Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 31 May 2021 06:39:53 -0500 Subject: [PATCH 0701/1109] update dependency (ironwood), fix units in core benchmark --- go.mod | 2 +- go.sum | 4 ++-- src/core/core_test.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 5e5b23d4..07d340e1 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.16 require ( - github.com/Arceliar/ironwood v0.0.0-20210522034542-c2e274961490 + github.com/Arceliar/ironwood v0.0.0-20210531083357-daeea6bc386a github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 github.com/cheggaaa/pb/v3 v3.0.6 github.com/fatih/color v1.10.0 // indirect diff --git a/go.sum b/go.sum index 454c7168..cafa8c84 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/ironwood v0.0.0-20210522034542-c2e274961490 h1:z1Rd8RMNVrpNYIXHn7f62wz8nbuPkJFs9Z24sHIhNrc= -github.com/Arceliar/ironwood v0.0.0-20210522034542-c2e274961490/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= +github.com/Arceliar/ironwood v0.0.0-20210531083357-daeea6bc386a h1:szuwvAlzD3r7PEcv4d3tin0V/4ZS/YQKL8RmLAXbE3E= +github.com/Arceliar/ironwood v0.0.0-20210531083357-daeea6bc386a/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= diff --git a/src/core/core_test.go b/src/core/core_test.go index ce4a4156..74012333 100644 --- a/src/core/core_test.go +++ b/src/core/core_test.go @@ -164,7 +164,7 @@ func BenchmarkCore_Start_Transfer(b *testing.B) { rand.Read(msg) buf := make([]byte, msgLen) - b.SetBytes(int64(b.N * msgLen)) + b.SetBytes(int64(msgLen)) b.ResetTimer() for i := 0; i < b.N; i++ { From 166336a41822b9e4021bde2870636a7e9bc6abbc Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 2 Jun 2021 14:19:32 +0100 Subject: [PATCH 0702/1109] Remove config.NodeState (hot reconfig is no longer supported) --- cmd/yggdrasil/main.go | 27 +++++++++++------------ src/admin/admin.go | 7 +++--- src/config/config.go | 32 +-------------------------- src/core/core.go | 44 +++++++++++++------------------------- src/core/core_test.go | 6 ++---- src/core/link.go | 4 +++- src/core/tcp.go | 6 +++--- src/module/module.go | 2 +- src/multicast/multicast.go | 16 +++++++------- src/tuntap/tun.go | 21 +++++++++++------- 10 files changed, 63 insertions(+), 102 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index e0d79fc4..6484edf9 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -35,7 +35,7 @@ import ( type node struct { core core.Core - state *config.NodeState + config *config.NodeConfig tuntap module.Module // tuntap.TunAdapter multicast module.Module // multicast.Multicast admin module.Module // admin.AdminSocket @@ -272,8 +272,7 @@ func main() { n := node{} // Now start Yggdrasil - this starts the DHT, router, switch and other core // components needed for Yggdrasil to operate - n.state, err = n.core.Start(cfg, logger) - if err != nil { + if err = n.core.Start(cfg, logger); err != nil { logger.Errorln("An error occurred during startup") panic(err) } @@ -284,19 +283,19 @@ func main() { n.tuntap = &tuntap.TunAdapter{} n.tuntap.(*tuntap.TunAdapter).SetSessionGatekeeper(n.sessionFirewall) // Start the admin socket - n.admin.Init(&n.core, n.state, logger, nil) + n.admin.Init(&n.core, cfg, logger, nil) if err := n.admin.Start(); err != nil { logger.Errorln("An error occurred starting admin socket:", err) } n.admin.SetupAdminHandlers(n.admin.(*admin.AdminSocket)) // Start the multicast interface - n.multicast.Init(&n.core, n.state, logger, nil) + n.multicast.Init(&n.core, cfg, logger, nil) if err := n.multicast.Start(); err != nil { logger.Errorln("An error occurred starting multicast:", err) } n.multicast.SetupAdminHandlers(n.admin.(*admin.AdminSocket)) // Start the TUN/TAP interface - n.tuntap.Init(&n.core, n.state, logger, nil) + n.tuntap.Init(&n.core, cfg, logger, nil) if err := n.tuntap.Start(); err != nil { logger.Errorln("An error occurred starting TUN/TAP:", err) } @@ -324,16 +323,16 @@ func (n *node) shutdown() { } func (n *node) sessionFirewall(pubkey ed25519.PublicKey, initiator bool) bool { - n.state.Mutex.RLock() - defer n.state.Mutex.RUnlock() + n.config.RLock() + defer n.config.RUnlock() // Allow by default if the session firewall is disabled - if !n.state.Current.SessionFirewall.Enable { + if !n.config.SessionFirewall.Enable { return true } // Reject blacklisted nodes - for _, b := range n.state.Current.SessionFirewall.BlacklistPublicKeys { + for _, b := range n.config.SessionFirewall.BlacklistPublicKeys { key, err := hex.DecodeString(b) if err == nil { if bytes.Equal(key, pubkey) { @@ -343,7 +342,7 @@ func (n *node) sessionFirewall(pubkey ed25519.PublicKey, initiator bool) bool { } // Allow whitelisted nodes - for _, b := range n.state.Current.SessionFirewall.WhitelistPublicKeys { + for _, b := range n.config.SessionFirewall.WhitelistPublicKeys { key, err := hex.DecodeString(b) if err == nil { if bytes.Equal(key, pubkey) { @@ -353,7 +352,7 @@ func (n *node) sessionFirewall(pubkey ed25519.PublicKey, initiator bool) bool { } // Allow outbound sessions if appropriate - if n.state.Current.SessionFirewall.AlwaysAllowOutbound { + if n.config.SessionFirewall.AlwaysAllowOutbound { if initiator { return true } @@ -369,12 +368,12 @@ func (n *node) sessionFirewall(pubkey ed25519.PublicKey, initiator bool) bool { } // Allow direct peers if appropriate - if n.state.Current.SessionFirewall.AllowFromDirect && isDirectPeer { + if n.config.SessionFirewall.AllowFromDirect && isDirectPeer { return true } // Allow remote nodes if appropriate - if n.state.Current.SessionFirewall.AllowFromRemote && !isDirectPeer { + if n.config.SessionFirewall.AllowFromRemote && !isDirectPeer { return true } diff --git a/src/admin/admin.go b/src/admin/admin.go index 8322b06f..7d5c66d1 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -63,12 +63,13 @@ func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc func(js } // Init runs the initial admin setup. -func (a *AdminSocket) Init(c *core.Core, state *config.NodeState, log *log.Logger, options interface{}) error { +func (a *AdminSocket) Init(c *core.Core, nc *config.NodeConfig, log *log.Logger, options interface{}) error { a.core = c a.log = log a.handlers = make(map[string]handler) - current := state.GetCurrent() - a.listenaddr = current.AdminListen + nc.RLock() + a.listenaddr = nc.AdminListen + nc.RUnlock() a.done = make(chan struct{}) close(a.done) // Start in a done / not-started state _ = a.AddHandler("list", []string{}, func(_ json.RawMessage) (interface{}, error) { diff --git a/src/config/config.go b/src/config/config.go index 073e1597..af47face 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -24,41 +24,11 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/defaults" ) -// NodeState represents the active and previous configuration of an Yggdrasil -// node. A NodeState object is returned when starting an Yggdrasil node. Note -// that this structure and related functions are likely to disappear soon. -type NodeState struct { - Current NodeConfig - Previous NodeConfig - Mutex sync.RWMutex -} - -// Current returns the active node configuration. -func (s *NodeState) GetCurrent() NodeConfig { - s.Mutex.RLock() - defer s.Mutex.RUnlock() - return s.Current -} - -// Previous returns the previous node configuration. -func (s *NodeState) GetPrevious() NodeConfig { - s.Mutex.RLock() - defer s.Mutex.RUnlock() - return s.Previous -} - -// Replace the node configuration with new configuration. -func (s *NodeState) Replace(n NodeConfig) { - s.Mutex.Lock() - defer s.Mutex.Unlock() - s.Previous = s.Current - s.Current = n -} - // NodeConfig is the main configuration structure, containing configuration // options that are necessary for an Yggdrasil node to run. You will need to // supply one of these structs to the Yggdrasil core when starting a node. type NodeConfig struct { + sync.RWMutex Peers []string `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j. These connections\nwill obey the operating system routing table, therefore you should\nuse this section when you may connect via different interfaces."` InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ tcp://a.b.c.d:e ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."` Listen []string `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntcp://0.0.0.0:0 or tcp://[::]:0 to listen on all interfaces."` diff --git a/src/core/core.go b/src/core/core.go index f3942cdb..b98776b9 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -25,7 +25,7 @@ type Core struct { // guarantee that it will be covered by the mutex phony.Inbox *iw.PacketConn - config config.NodeState // Config + config *config.NodeConfig // Config secret ed25519.PrivateKey public ed25519.PublicKey links links @@ -42,9 +42,9 @@ func (c *Core) _init() error { c.log = log.New(ioutil.Discard, "", 0) } - current := c.config.GetCurrent() - - sigPriv, err := hex.DecodeString(current.PrivateKey) + c.config.RLock() + sigPriv, err := hex.DecodeString(c.config.PrivateKey) + c.config.RUnlock() if err != nil { return err } @@ -64,11 +64,11 @@ func (c *Core) _init() error { // configure them. The loop ensures that disconnected peers will eventually // be reconnected with. func (c *Core) _addPeerLoop() { - // Get the peers from the config - these could change! - current := c.config.GetCurrent() + c.config.RLock() + defer c.config.RUnlock() // Add peers from the Peers section - for _, peer := range current.Peers { + for _, peer := range c.config.Peers { go func(peer string, intf string) { u, err := url.Parse(peer) if err != nil { @@ -81,7 +81,7 @@ func (c *Core) _addPeerLoop() { } // Add peers from the InterfacePeers section - for intf, intfpeers := range current.InterfacePeers { + for intf, intfpeers := range c.config.InterfacePeers { for _, peer := range intfpeers { go func(peer string, intf string) { u, err := url.Parse(peer) @@ -107,21 +107,17 @@ func (c *Core) _addPeerLoop() { // TCP and UDP sockets, a multicast discovery socket, an admin socket, router, // switch and DHT node. A config.NodeState is returned which contains both the // current and previous configurations (from reconfigures). -func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (conf *config.NodeState, err error) { +func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (err error) { phony.Block(c, func() { - conf, err = c._start(nc, log) + err = c._start(nc, log) }) return } // This function is unsafe and should only be ran by the core actor. -func (c *Core) _start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, error) { +func (c *Core) _start(nc *config.NodeConfig, log *log.Logger) error { c.log = log - - c.config = config.NodeState{ - Current: *nc, - Previous: *nc, - } + c.config = nc if name := version.BuildName(); name != "unknown" { c.log.Infoln("Build name:", name) @@ -133,30 +129,20 @@ func (c *Core) _start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState c.log.Infoln("Starting up...") if err := c._init(); err != nil { c.log.Errorln("Failed to initialize core") - return nil, err + return err } if err := c.links.init(c); err != nil { c.log.Errorln("Failed to start link interfaces") - return nil, err + return err } - //if err := c.switchTable.start(); err != nil { - // c.log.Errorln("Failed to start switch") - // return nil, err - //} - - //if err := c.router.start(); err != nil { - // c.log.Errorln("Failed to start router") - // return nil, err - //} - c.addPeerTimer = time.AfterFunc(0, func() { c.Act(nil, c._addPeerLoop) }) c.log.Infoln("Startup complete") - return &c.config, nil + return nil } // Stop shuts down the Yggdrasil node. diff --git a/src/core/core_test.go b/src/core/core_test.go index 74012333..7e7f32d1 100644 --- a/src/core/core_test.go +++ b/src/core/core_test.go @@ -40,14 +40,12 @@ func GetLoggerWithPrefix(prefix string, verbose bool) *log.Logger { // 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 { + if err := nodeA.Start(GenerateConfig(), GetLoggerWithPrefix("A: ", verbose)); err != nil { t.Fatal(err) } nodeB = new(Core) - _, err = nodeB.Start(GenerateConfig(), GetLoggerWithPrefix("B: ", verbose)) - if err != nil { + if err := nodeB.Start(GenerateConfig(), GetLoggerWithPrefix("B: ", verbose)); err != nil { t.Fatal(err) } diff --git a/src/core/link.go b/src/core/link.go index 80bd0c30..b4cd15ff 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -188,7 +188,9 @@ func (intf *link) handler() (chan struct{}, error) { } } // Check if we're authorized to connect to this key / IP - allowed := intf.links.core.config.GetCurrent().AllowedPublicKeys + intf.links.core.config.RLock() + allowed := intf.links.core.config.AllowedPublicKeys + intf.links.core.config.RUnlock() isallowed := len(allowed) == 0 for _, k := range allowed { if k == hex.EncodeToString(meta.key) { // TODO: this is yuck diff --git a/src/core/tcp.go b/src/core/tcp.go index eced7726..17e37660 100644 --- a/src/core/tcp.go +++ b/src/core/tcp.go @@ -104,9 +104,9 @@ func (t *tcp) init(l *links) error { t.listeners = make(map[string]*TcpListener) t.mutex.Unlock() - t.links.core.config.Mutex.RLock() - defer t.links.core.config.Mutex.RUnlock() - for _, listenaddr := range t.links.core.config.Current.Listen { + t.links.core.config.RLock() + defer t.links.core.config.RUnlock() + for _, listenaddr := range t.links.core.config.Listen { u, err := url.Parse(listenaddr) if err != nil { t.links.core.log.Errorln("Failed to parse listener: listener", listenaddr, "is not correctly formatted, ignoring") diff --git a/src/module/module.go b/src/module/module.go index d13b8cd2..8f915005 100644 --- a/src/module/module.go +++ b/src/module/module.go @@ -11,7 +11,7 @@ import ( // Module is an interface that defines which functions must be supported by a // given Yggdrasil module. type Module interface { - Init(core *core.Core, state *config.NodeState, log *log.Logger, options interface{}) error + Init(core *core.Core, state *config.NodeConfig, log *log.Logger, options interface{}) error Start() error Stop() error SetupAdminHandlers(a *admin.AdminSocket) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index c1b87890..02ff0c44 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -23,7 +23,7 @@ import ( type Multicast struct { phony.Inbox core *core.Core - config *config.NodeState + config *config.NodeConfig log *log.Logger sock *ipv6.PacketConn groupAddr string @@ -45,14 +45,13 @@ type listenerInfo struct { } // Init prepares the multicast interface for use. -func (m *Multicast) Init(core *core.Core, state *config.NodeState, log *log.Logger, options interface{}) error { +func (m *Multicast) Init(core *core.Core, nc *config.NodeConfig, log *log.Logger, options interface{}) error { m.core = core - m.config = state + m.config = nc m.log = log m.listeners = make(map[string]*listenerInfo) m._interfaces = make(map[string]interfaceInfo) - current := m.config.GetCurrent() - m.listenPort = current.LinkLocalTCPPort + m.listenPort = m.config.LinkLocalTCPPort m.groupAddr = "[ff02::114]:9001" return nil } @@ -73,7 +72,9 @@ func (m *Multicast) _start() error { if m.isOpen { return fmt.Errorf("multicast module is already started") } - if len(m.config.GetCurrent().MulticastInterfaces) == 0 { + m.config.RLock() + defer m.config.RUnlock() + if len(m.config.MulticastInterfaces) == 0 { return nil } m.log.Infoln("Starting multicast module") @@ -161,8 +162,7 @@ func (m *Multicast) Interfaces() map[string]net.Interface { func (m *Multicast) getAllowedInterfaces() map[string]net.Interface { interfaces := make(map[string]net.Interface) // Get interface expressions from config - current := m.config.GetCurrent() - exprs := current.MulticastInterfaces + exprs := m.config.MulticastInterfaces // Ask the system for network interfaces allifaces, err := net.Interfaces() if err != nil { diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 49d8e426..ecc7c06c 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -35,7 +35,7 @@ type MTU uint16 type TunAdapter struct { core *core.Core store keyStore - config *config.NodeState + config *config.NodeConfig log *log.Logger addr address.Address subnet address.Subnet @@ -103,13 +103,17 @@ func MaximumMTU() uint64 { // Init initialises the TUN module. You must have acquired a Listener from // the Yggdrasil core before this point and it must not be in use elsewhere. -func (tun *TunAdapter) Init(core *core.Core, config *config.NodeState, log *log.Logger, options interface{}) error { +func (tun *TunAdapter) Init(core *core.Core, config *config.NodeConfig, log *log.Logger, options interface{}) error { tun.core = core tun.store.init(tun) tun.config = config tun.log = log tun.proto.init(tun) - tun.proto.nodeinfo.setNodeInfo(config.Current.NodeInfo, config.Current.NodeInfoPrivacy) + tun.config.RLock() + if err := tun.proto.nodeinfo.setNodeInfo(tun.config.NodeInfo, tun.config.NodeInfoPrivacy); err != nil { + return fmt.Errorf("tun.proto.nodeinfo.setNodeInfo: %w", err) + } + tun.config.RUnlock() if err := tun.core.SetOutOfBandHandler(tun.oobHandler); err != nil { return fmt.Errorf("tun.core.SetOutOfBandHander: %w", err) } @@ -130,7 +134,8 @@ func (tun *TunAdapter) _start() error { if tun.isOpen { return errors.New("TUN module is already started") } - current := tun.config.GetCurrent() + tun.config.RLock() + defer tun.config.RUnlock() if tun.config == nil { return errors.New("no configuration available to TUN") } @@ -139,21 +144,21 @@ func (tun *TunAdapter) _start() error { tun.addr = *address.AddrForKey(pk) tun.subnet = *address.SubnetForKey(pk) addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1) - if current.IfName == "none" || current.IfName == "dummy" { + if tun.config.IfName == "none" || tun.config.IfName == "dummy" { tun.log.Debugln("Not starting TUN as ifname is none or dummy") tun.isEnabled = false go tun.write() return nil } - mtu := current.IfMTU + mtu := tun.config.IfMTU if tun.maxSessionMTU() < mtu { mtu = tun.maxSessionMTU() } - if err := tun.setup(current.IfName, addr, mtu); err != nil { + if err := tun.setup(tun.config.IfName, addr, mtu); err != nil { return err } if tun.MTU() != mtu { - tun.log.Warnf("Warning: Interface MTU %d automatically adjusted to %d (supported range is 1280-%d)", current.IfMTU, tun.MTU(), MaximumMTU()) + tun.log.Warnf("Warning: Interface MTU %d automatically adjusted to %d (supported range is 1280-%d)", tun.config.IfMTU, tun.MTU(), MaximumMTU()) } tun.isOpen = true tun.isEnabled = true From 8932ab051916416eb71452e7febc4de0ab8b8378 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 2 Jun 2021 14:40:09 +0100 Subject: [PATCH 0703/1109] Fix lint errors --- cmd/yggdrasil/main.go | 25 +++++++++++++++---------- cmd/yggdrasilctl/main.go | 4 ++-- src/core/core.go | 2 +- src/core/link.go | 12 ------------ src/core/tcp.go | 4 ++-- src/multicast/multicast.go | 6 +++--- src/multicast/multicast_darwin.go | 2 -- src/tuntap/iface.go | 6 +----- src/tuntap/keystore.go | 8 ++++---- src/tuntap/nodeinfo.go | 4 ++-- src/tuntap/proto.go | 2 +- src/tuntap/tun.go | 4 ++-- src/tuntap/tun_darwin.go | 15 +++++++++------ src/tuntap/types.go | 4 ++-- 14 files changed, 44 insertions(+), 54 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 6484edf9..0f830bb2 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -107,7 +107,9 @@ func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *config if err != nil { panic(err) } - json.Unmarshal(confJson, &cfg) + if err := json.Unmarshal(confJson, &cfg); err != nil { + panic(err) + } // Overlay our newly mapped configuration onto the autoconf node config that // we generated above. if err = mapstructure.Decode(dat, &cfg); err != nil { @@ -283,20 +285,23 @@ func main() { n.tuntap = &tuntap.TunAdapter{} n.tuntap.(*tuntap.TunAdapter).SetSessionGatekeeper(n.sessionFirewall) // Start the admin socket - n.admin.Init(&n.core, cfg, logger, nil) - if err := n.admin.Start(); err != nil { + if err := n.admin.Init(&n.core, cfg, logger, nil); err != nil { + logger.Errorln("An error occured initialising admin socket:", err) + } else if err := n.admin.Start(); err != nil { logger.Errorln("An error occurred starting admin socket:", err) } n.admin.SetupAdminHandlers(n.admin.(*admin.AdminSocket)) // Start the multicast interface - n.multicast.Init(&n.core, cfg, logger, nil) - if err := n.multicast.Start(); err != nil { + if err := n.multicast.Init(&n.core, cfg, logger, nil); err != nil { + logger.Errorln("An error occured initialising multicast:", err) + } else if err := n.multicast.Start(); err != nil { logger.Errorln("An error occurred starting multicast:", err) } n.multicast.SetupAdminHandlers(n.admin.(*admin.AdminSocket)) // Start the TUN/TAP interface - n.tuntap.Init(&n.core, cfg, logger, nil) - if err := n.tuntap.Start(); err != nil { + if err := n.tuntap.Init(&n.core, cfg, logger, nil); err != nil { + logger.Errorln("An error occurred initialising TUN/TAP:", err) + } else if err := n.tuntap.Start(); err != nil { logger.Errorln("An error occurred starting TUN/TAP:", err) } n.tuntap.SetupAdminHandlers(n.admin.(*admin.AdminSocket)) @@ -316,9 +321,9 @@ func main() { } func (n *node) shutdown() { - n.admin.Stop() - n.multicast.Stop() - n.tuntap.Stop() + _ = n.admin.Stop() + _ = n.multicast.Stop() + _ = n.tuntap.Stop() n.core.Stop() } diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index 38d935a6..884656db 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -47,8 +47,8 @@ func run() int { fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] command [key=value] [key=value] ...\n\n", os.Args[0]) fmt.Println("Options:") flag.PrintDefaults() - fmt.Println("\nPlease note that options must always specified BEFORE the command\non the command line or they will be ignored.\n") - fmt.Println("Commands:\n - Use \"list\" for a list of available commands\n") + fmt.Println("\nPlease note that options must always specified BEFORE the command\non the command line or they will be ignored.\n") // nolint:govet + fmt.Println("Commands:\n - Use \"list\" for a list of available commands\n") // nolint:govet fmt.Println("Examples:") fmt.Println(" - ", os.Args[0], "list") fmt.Println(" - ", os.Args[0], "getPeers") diff --git a/src/core/core.go b/src/core/core.go index b98776b9..de916c1b 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -158,7 +158,7 @@ func (c *Core) _stop() { c.addPeerTimer.Stop() c.addPeerTimer = nil } - c.links.stop() + _ = c.links.stop() /* FIXME this deadlocks, need a waitgroup or something to coordinate shutdown for _, peer := range c.GetPeers() { c.DisconnectPeer(peer.Port) diff --git a/src/core/link.go b/src/core/link.go index b4cd15ff..51b679a6 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -250,15 +250,3 @@ func (intf *link) close() { func (intf *link) name() string { return intf.lname } - -func (intf *link) local() string { - return intf.info.local -} - -func (intf *link) remote() string { - return intf.info.remote -} - -func (intf *link) interfaceType() string { - return intf.info.linkType -} diff --git a/src/core/tcp.go b/src/core/tcp.go index 17e37660..21a3ba79 100644 --- a/src/core/tcp.go +++ b/src/core/tcp.go @@ -67,7 +67,7 @@ type tcpOptions struct { } func (l *TcpListener) Stop() { - defer func() { recover() }() + defer func() { _ = recover() }() close(l.stop) } @@ -75,7 +75,7 @@ func (l *TcpListener) Stop() { func (t *tcp) setExtraOptions(c net.Conn) { switch sock := c.(type) { case *net.TCPConn: - sock.SetNoDelay(true) + _ = sock.SetNoDelay(true) // TODO something for socks5 default: } diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 02ff0c44..8f8054dd 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -91,7 +91,7 @@ func (m *Multicast) _start() error { return err } m.sock = ipv6.NewPacketConn(conn) - if err = m.sock.SetControlMessage(ipv6.FlagDst, true); err != nil { + if err = m.sock.SetControlMessage(ipv6.FlagDst, true); err != nil { // nolint:staticcheck // Windows can't set this flag, so we need to handle it in other ways } @@ -269,7 +269,7 @@ func (m *Multicast) _announce() { continue } // Join the multicast group - m.sock.JoinGroup(&iface, groupAddr) + _ = m.sock.JoinGroup(&iface, groupAddr) // Try and see if we already have a TCP listener for this interface var info *listenerInfo if nfo, ok := m.listeners[iface.Name]; !ok || nfo.listener.Listener == nil { @@ -304,7 +304,7 @@ func (m *Multicast) _announce() { a.Zone = "" destAddr.Zone = iface.Name msg := []byte(a.String()) - m.sock.WriteTo(msg, nil, destAddr) + _, _ = m.sock.WriteTo(msg, nil, destAddr) } if info.interval.Seconds() < 15 { info.interval += time.Second diff --git a/src/multicast/multicast_darwin.go b/src/multicast/multicast_darwin.go index ceff5b44..e7075c0a 100644 --- a/src/multicast/multicast_darwin.go +++ b/src/multicast/multicast_darwin.go @@ -29,8 +29,6 @@ import ( "golang.org/x/sys/unix" ) -var awdlGoroutineStarted bool - func (m *Multicast) _multicastStarted() { if !m.isOpen { return diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 81fbe4d4..da66e9d2 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -123,16 +123,12 @@ func (tun *TunAdapter) write() { continue // bad remote address/subnet } bs = buf[:TUN_OFFSET_BYTES+len(bs)] - n, err = tun.iface.Write(bs, TUN_OFFSET_BYTES) - if err != nil { + if _, err = tun.iface.Write(bs, TUN_OFFSET_BYTES); err != nil { tun.Act(nil, func() { if !tun.isOpen { tun.log.Errorln("TUN iface write error:", err) } }) } - if n != len(bs) { - // TODO some kind of error reporting for a partial write - } } } diff --git a/src/tuntap/keystore.go b/src/tuntap/keystore.go index 7974874c..03f429f7 100644 --- a/src/tuntap/keystore.go +++ b/src/tuntap/keystore.go @@ -50,7 +50,7 @@ func (k *keyStore) sendToAddress(addr address.Address, bs []byte) { if info := k.addrToInfo[addr]; info != nil { k.resetTimeout(info) k.mutex.Unlock() - k.tun.core.WriteTo(bs, iwt.Addr(info.key[:])) + _, _ = k.tun.core.WriteTo(bs, iwt.Addr(info.key[:])) } else { var buf *buffer if buf = k.addrBuffer[addr]; buf == nil { @@ -79,7 +79,7 @@ func (k *keyStore) sendToSubnet(subnet address.Subnet, bs []byte) { if info := k.subnetToInfo[subnet]; info != nil { k.resetTimeout(info) k.mutex.Unlock() - k.tun.core.WriteTo(bs, iwt.Addr(info.key[:])) + _, _ = k.tun.core.WriteTo(bs, iwt.Addr(info.key[:])) } else { var buf *buffer if buf = k.subnetBuffer[subnet]; buf == nil { @@ -132,13 +132,13 @@ func (k *keyStore) update(key ed25519.PublicKey) *keyInfo { k.mutex.Unlock() if buf := k.addrBuffer[info.address]; buf != nil { for _, bs := range buf.packets { - k.tun.core.WriteTo(bs, iwt.Addr(info.key[:])) + _, _ = k.tun.core.WriteTo(bs, iwt.Addr(info.key[:])) } delete(k.addrBuffer, info.address) } if buf := k.subnetBuffer[info.subnet]; buf != nil { for _, bs := range buf.packets { - k.tun.core.WriteTo(bs, iwt.Addr(info.key[:])) + _, _ = k.tun.core.WriteTo(bs, iwt.Addr(info.key[:])) } delete(k.subnetBuffer, info.subnet) } diff --git a/src/tuntap/nodeinfo.go b/src/tuntap/nodeinfo.go index 3249b20c..a61a4429 100644 --- a/src/tuntap/nodeinfo.go +++ b/src/tuntap/nodeinfo.go @@ -129,7 +129,7 @@ func (m *nodeinfo) _sendReq(key keyArray, callback func(nodeinfo NodeInfoPayload if callback != nil { m._addCallback(key, callback) } - m.proto.tun.core.WriteTo([]byte{typeSessionProto, typeProtoNodeInfoRequest}, iwt.Addr(key[:])) + _, _ = m.proto.tun.core.WriteTo([]byte{typeSessionProto, typeProtoNodeInfoRequest}, iwt.Addr(key[:])) } func (m *nodeinfo) handleReq(from phony.Actor, key keyArray) { @@ -146,7 +146,7 @@ func (m *nodeinfo) handleRes(from phony.Actor, key keyArray, info NodeInfoPayloa func (m *nodeinfo) _sendRes(key keyArray) { bs := append([]byte{typeSessionProto, typeProtoNodeInfoResponse}, m._getNodeInfo()...) - m.proto.tun.core.WriteTo(bs, iwt.Addr(key[:])) + _, _ = m.proto.tun.core.WriteTo(bs, iwt.Addr(key[:])) } // Admin socket stuff diff --git a/src/tuntap/proto.go b/src/tuntap/proto.go index 79d84b2e..62b21563 100644 --- a/src/tuntap/proto.go +++ b/src/tuntap/proto.go @@ -209,7 +209,7 @@ func (p *protoHandler) _handleGetDHTResponse(key keyArray, bs []byte) { func (p *protoHandler) _sendDebug(key keyArray, dType uint8, data []byte) { bs := append([]byte{typeSessionProto, typeProtoDebug, dType}, data...) - p.tun.core.WriteTo(bs, iwt.Addr(key[:])) + _, _ = p.tun.core.WriteTo(bs, iwt.Addr(key[:])) } // Admin socket stuff diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index ecc7c06c..c1b90e75 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -221,13 +221,13 @@ func (tun *TunAdapter) oobHandler(fromKey, toKey ed25519.PublicKey, data []byte) func (tun *TunAdapter) sendKeyLookup(partial ed25519.PublicKey) { sig := ed25519.Sign(tun.core.PrivateKey(), partial[:]) bs := append([]byte{typeKeyLookup}, sig...) - tun.core.SendOutOfBand(partial, bs) + _ = tun.core.SendOutOfBand(partial, bs) } func (tun *TunAdapter) sendKeyResponse(dest ed25519.PublicKey) { sig := ed25519.Sign(tun.core.PrivateKey(), dest[:]) bs := append([]byte{typeKeyResponse}, sig...) - tun.core.SendOutOfBand(dest, bs) + _ = tun.core.SendOutOfBand(dest, bs) } func (tun *TunAdapter) maxSessionMTU() uint64 { diff --git a/src/tuntap/tun_darwin.go b/src/tuntap/tun_darwin.go index 609b42e3..060ad144 100644 --- a/src/tuntap/tun_darwin.go +++ b/src/tuntap/tun_darwin.go @@ -40,26 +40,29 @@ const ( darwin_ND6_INFINITE_LIFETIME = 0xFFFFFFFF // netinet6/nd6.h ) +// nolint:structcheck type in6_addrlifetime struct { - ia6t_expire float64 - ia6t_preferred float64 + ia6t_expire float64 // nolint:unused + ia6t_preferred float64 // nolint:unused ia6t_vltime uint32 ia6t_pltime uint32 } +// nolint:structcheck type sockaddr_in6 struct { sin6_len uint8 sin6_family uint8 - sin6_port uint8 - sin6_flowinfo uint32 + sin6_port uint8 // nolint:unused + sin6_flowinfo uint32 // nolint:unused sin6_addr [8]uint16 - sin6_scope_id uint32 + sin6_scope_id uint32 // nolint:unused } +// nolint:structcheck type in6_aliasreq struct { ifra_name [16]byte ifra_addr sockaddr_in6 - ifra_dstaddr sockaddr_in6 + ifra_dstaddr sockaddr_in6 // nolint:unused ifra_prefixmask sockaddr_in6 ifra_flags uint32 ifra_lifetime in6_addrlifetime diff --git a/src/tuntap/types.go b/src/tuntap/types.go index 4d8bba1f..a8084e0c 100644 --- a/src/tuntap/types.go +++ b/src/tuntap/types.go @@ -2,14 +2,14 @@ package tuntap // Out-of-band packet types const ( - typeKeyDummy = iota + typeKeyDummy = iota // nolint:deadcode,varcheck typeKeyLookup typeKeyResponse ) // In-band packet types const ( - typeSessionDummy = iota + typeSessionDummy = iota // nolint:deadcode,varcheck typeSessionTraffic typeSessionProto ) From ff751a5409deb83544c5348c1c3c45acfd5b9b5f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 2 Jun 2021 14:46:04 +0100 Subject: [PATCH 0704/1109] Fix lint error --- src/core/tcp_linux.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/tcp_linux.go b/src/core/tcp_linux.go index 39e3ba7a..558b4e56 100644 --- a/src/core/tcp_linux.go +++ b/src/core/tcp_linux.go @@ -36,7 +36,7 @@ func (t *tcp) getControl(sintf string) func(string, string, syscall.RawConn) err btd := func(fd uintptr) { err = unix.BindToDevice(int(fd), sintf) } - c.Control(btd) + _ = c.Control(btd) if err != nil { t.links.core.log.Debugln("Failed to set SO_BINDTODEVICE:", sintf) } From 414c100125ba0096bcf05a1882c4c8aceffff93e Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 5 Jun 2021 05:07:04 -0500 Subject: [PATCH 0705/1109] add public keys to multicast, public key pinning to multicast peering --- src/multicast/multicast.go | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 8f8054dd..97596380 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -2,6 +2,8 @@ package multicast import ( "context" + "crypto/ed25519" + "encoding/hex" "fmt" "net" "net/url" @@ -303,7 +305,12 @@ func (m *Multicast) _announce() { if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil { a.Zone = "" destAddr.Zone = iface.Name - msg := []byte(a.String()) + key, err := hex.DecodeString(m.config.PublicKey) + if err != nil { + panic(err) + } + msg := append([]byte(nil), key...) + msg = append(msg, a.String()...) _, _ = m.sock.WriteTo(msg, nil, destAddr) } if info.interval.Seconds() < 15 { @@ -342,7 +349,12 @@ func (m *Multicast) listen() { continue } } - anAddr := string(bs[:nBytes]) + if len(bs) < ed25519.PublicKeySize { + continue + } + var key ed25519.PublicKey + key = append(key, bs[:ed25519.PublicKeySize]...) + anAddr := string(bs[ed25519.PublicKeySize:nBytes]) addr, err := net.ResolveTCPAddr("tcp6", anAddr) if err != nil { continue @@ -357,7 +369,8 @@ func (m *Multicast) listen() { }) if _, ok := interfaces[from.Zone]; ok { addr.Zone = "" - u, err := url.Parse("tcp://" + addr.String()) + pin := fmt.Sprintf("/?ed25519=%s", hex.EncodeToString(key)) + u, err := url.Parse("tcp://" + addr.String() + pin) if err != nil { m.log.Debugln("Call from multicast failed, parse error:", addr.String(), err) } From e67ee9232d80fc77b02ffbeb56e7e4c8eff02c44 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 5 Jun 2021 06:00:33 -0500 Subject: [PATCH 0706/1109] fix nil pointer when attempting to access node config --- cmd/yggdrasil/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 0f830bb2..6e8580c8 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -271,7 +271,7 @@ func main() { // Setup the Yggdrasil node itself. The node{} type includes a Core, so we // don't need to create this manually. - n := node{} + n := node{config: cfg} // Now start Yggdrasil - this starts the DHT, router, switch and other core // components needed for Yggdrasil to operate if err = n.core.Start(cfg, logger); err != nil { From cb536a7322dc40f8c4050fb4d5ae1bb8ea7739c7 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 5 Jun 2021 20:55:08 +0100 Subject: [PATCH 0707/1109] Clean up util package --- src/util/util.go | 93 ------------------------------------------ src/util/workerpool.go | 29 ------------- 2 files changed, 122 deletions(-) delete mode 100644 src/util/workerpool.go diff --git a/src/util/util.go b/src/util/util.go index 06c2d2d4..507426d0 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -5,35 +5,9 @@ package util // These are misc. utility functions that didn't really fit anywhere else import ( - "runtime" - "strconv" - "strings" "time" ) -// Yield just executes runtime.Gosched(), and is included so we don't need to explicitly import runtime elsewhere. -func Yield() { - runtime.Gosched() -} - -// LockThread executes runtime.LockOSThread(), and is included so we don't need to explicitly import runtime elsewhere. -func LockThread() { - runtime.LockOSThread() -} - -// UnlockThread executes runtime.UnlockOSThread(), and is included so we don't need to explicitly import runtime elsewhere. -func UnlockThread() { - runtime.UnlockOSThread() -} - -// ResizeBytes returns a slice of the specified length. If the provided slice has sufficient capacity, it will be resized and returned rather than allocating a new slice. -func ResizeBytes(bs []byte, length int) []byte { - if cap(bs) >= length { - return bs[:length] - } - return make([]byte, length) -} - // TimerStop stops a timer and makes sure the channel is drained, returns true if the timer was stopped before firing. func TimerStop(t *time.Timer) bool { stopped := t.Stop() @@ -61,70 +35,3 @@ func FuncTimeout(timeout time.Duration, f func()) bool { return false } } - -// Difference loops over two strings and returns the elements of A which do not appear in B. -// This is somewhat useful when needing to determine which elements of a configuration file have changed. -func Difference(a, b []string) []string { - ab := []string{} - mb := map[string]bool{} - for _, x := range b { - mb[x] = true - } - for _, x := range a { - if !mb[x] { - ab = append(ab, x) - } - } - return ab -} - -// DecodeCoordString decodes a string representing coordinates in [1 2 3] format -// and returns a []uint64. -func DecodeCoordString(in string) (out []uint64) { - s := strings.Trim(in, "[]") - t := strings.Split(s, " ") - for _, a := range t { - if u, err := strconv.ParseUint(a, 0, 64); err == nil { - out = append(out, u) - } - } - return out -} - -// GetFlowKey takes an IP packet as an argument and returns some information about the traffic flow. -// For IPv4 packets, this is derived from the source and destination protocol and port numbers. -// For IPv6 packets, this is derived from the FlowLabel field of the packet if this was set, otherwise it's handled like IPv4. -// The FlowKey is then used internally by Yggdrasil for congestion control. -func GetFlowKey(bs []byte) uint64 { - // Work out the flowkey - this is used to determine which switch queue - // traffic will be pushed to in the event of congestion - var flowkey uint64 - // Get the IP protocol version from the packet - switch bs[0] & 0xf0 { - case 0x40: // IPv4 packet - ihl := (bs[0] & 0x0f) * 4 // whole IPv4 header length (min 20) - // 8 is minimum UDP packet length - if ihl >= 20 && len(bs)-int(ihl) >= 8 { - switch bs[9] /* protocol */ { - case 0x06 /* TCP */, 0x11 /* UDP */, 0x84 /* SCTP */ : - flowkey = uint64(bs[9])<<32 /* proto */ | - uint64(bs[ihl+0])<<24 | uint64(bs[ihl+1])<<16 /* sport */ | - uint64(bs[ihl+2])<<8 | uint64(bs[ihl+3]) /* dport */ - } - } - case 0x60: // IPv6 packet - // Check if the flowlabel was specified in the packet header - flowkey = uint64(bs[1]&0x0f)<<16 | uint64(bs[2])<<8 | uint64(bs[3]) - // If the flowlabel isn't present, make protokey from proto | sport | dport - // if the packet meets minimum UDP packet length - if flowkey == 0 && len(bs) >= 48 { - switch bs[9] /* protocol */ { - case 0x06 /* TCP */, 0x11 /* UDP */, 0x84 /* SCTP */ : - flowkey = uint64(bs[6])<<32 /* proto */ | - uint64(bs[40])<<24 | uint64(bs[41])<<16 /* sport */ | - uint64(bs[42])<<8 | uint64(bs[43]) /* dport */ - } - } - } - return flowkey -} diff --git a/src/util/workerpool.go b/src/util/workerpool.go deleted file mode 100644 index fd37f397..00000000 --- a/src/util/workerpool.go +++ /dev/null @@ -1,29 +0,0 @@ -package util - -import "runtime" - -var workerPool chan func() - -func init() { - maxProcs := runtime.GOMAXPROCS(0) - if maxProcs < 1 { - maxProcs = 1 - } - workerPool = make(chan func(), maxProcs) - for idx := 0; idx < maxProcs; idx++ { - go func() { - for f := range workerPool { - f() - } - }() - } -} - -// WorkerGo submits a job to a pool of GOMAXPROCS worker goroutines. -// This is meant for short non-blocking functions f() where you could just go f(), -// but you want some kind of backpressure to prevent spawning endless goroutines. -// WorkerGo returns as soon as the function is queued to run, not when it finishes. -// In Yggdrasil, these workers are used for certain cryptographic operations. -func WorkerGo(f func()) { - workerPool <- f -} From 99973b2757bbf3729fb2c4b005ee5b029ba7abf3 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 5 Jun 2021 20:57:03 +0100 Subject: [PATCH 0708/1109] Remove module package, it didn't really give us anything anyway --- cmd/yggdrasil/main.go | 15 +++++++-------- src/module/module.go | 19 ------------------- 2 files changed, 7 insertions(+), 27 deletions(-) delete mode 100644 src/module/module.go diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 6e8580c8..6619a956 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -27,7 +27,6 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/core" - "github.com/yggdrasil-network/yggdrasil-go/src/module" "github.com/yggdrasil-network/yggdrasil-go/src/multicast" "github.com/yggdrasil-network/yggdrasil-go/src/tuntap" "github.com/yggdrasil-network/yggdrasil-go/src/version" @@ -36,9 +35,9 @@ import ( type node struct { core core.Core config *config.NodeConfig - tuntap module.Module // tuntap.TunAdapter - multicast module.Module // multicast.Multicast - admin module.Module // admin.AdminSocket + tuntap *tuntap.TunAdapter + multicast *multicast.Multicast + admin *admin.AdminSocket } func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *config.NodeConfig { @@ -283,28 +282,28 @@ func main() { n.admin = &admin.AdminSocket{} n.multicast = &multicast.Multicast{} n.tuntap = &tuntap.TunAdapter{} - n.tuntap.(*tuntap.TunAdapter).SetSessionGatekeeper(n.sessionFirewall) + n.tuntap.SetSessionGatekeeper(n.sessionFirewall) // Start the admin socket if err := n.admin.Init(&n.core, cfg, logger, nil); err != nil { logger.Errorln("An error occured initialising admin socket:", err) } else if err := n.admin.Start(); err != nil { logger.Errorln("An error occurred starting admin socket:", err) } - n.admin.SetupAdminHandlers(n.admin.(*admin.AdminSocket)) + n.admin.SetupAdminHandlers(n.admin) // Start the multicast interface if err := n.multicast.Init(&n.core, cfg, logger, nil); err != nil { logger.Errorln("An error occured initialising multicast:", err) } else if err := n.multicast.Start(); err != nil { logger.Errorln("An error occurred starting multicast:", err) } - n.multicast.SetupAdminHandlers(n.admin.(*admin.AdminSocket)) + n.multicast.SetupAdminHandlers(n.admin) // Start the TUN/TAP interface if err := n.tuntap.Init(&n.core, cfg, logger, nil); err != nil { logger.Errorln("An error occurred initialising TUN/TAP:", err) } else if err := n.tuntap.Start(); err != nil { logger.Errorln("An error occurred starting TUN/TAP:", err) } - n.tuntap.SetupAdminHandlers(n.admin.(*admin.AdminSocket)) + n.tuntap.SetupAdminHandlers(n.admin) // Make some nice output that tells us what our IPv6 address and subnet are. // This is just logged to stdout for the user. address := n.core.Address() diff --git a/src/module/module.go b/src/module/module.go deleted file mode 100644 index 8f915005..00000000 --- a/src/module/module.go +++ /dev/null @@ -1,19 +0,0 @@ -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/core" -) - -// Module is an interface that defines which functions must be supported by a -// given Yggdrasil module. -type Module interface { - Init(core *core.Core, state *config.NodeConfig, log *log.Logger, options interface{}) error - Start() error - Stop() error - SetupAdminHandlers(a *admin.AdminSocket) - IsStarted() bool -} From ea15eeee7ed5ebd5138773d1453615561421dac6 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 5 Jun 2021 21:32:04 +0100 Subject: [PATCH 0709/1109] Ensure PublicKey option is unused, map old config options --- cmd/yggdrasil/main.go | 87 ++++++++++++++++++-------------------- cmd/yggdrasilctl/main.go | 7 ++- src/multicast/multicast.go | 6 +-- src/tuntap/iface.go | 2 +- src/tuntap/tun_darwin.go | 4 +- 5 files changed, 51 insertions(+), 55 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 6619a956..95502aac 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -40,7 +40,7 @@ type node struct { admin *admin.AdminSocket } -func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *config.NodeConfig { +func readConfig(log *log.Logger, useconf *bool, useconffile *string, normaliseconf *bool) *config.NodeConfig { // Use a configuration file. If -useconf, the configuration will be read // from stdin. If -useconffile, the configuration will be read from the // filesystem. @@ -78,27 +78,21 @@ func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *config if err := hjson.Unmarshal(conf, &dat); err != nil { panic(err) } - // Check for fields that have changed type recently, e.g. the Listen config - // option is now a []string rather than a string - if listen, ok := dat["Listen"].(string); ok { - dat["Listen"] = []string{listen} + // Check if we have old field names + if _, ok := dat["TunnelRouting"]; ok { + log.Warnln("WARNING: Tunnel routing is no longer supported") } - if tunnelrouting, ok := dat["TunnelRouting"].(map[string]interface{}); ok { - if c, ok := tunnelrouting["IPv4Sources"]; ok { - delete(tunnelrouting, "IPv4Sources") - tunnelrouting["IPv4LocalSubnets"] = c + if _, ok := dat["SigningPrivateKey"]; ok { + log.Warnln("WARNING: The configuration file is out of date, please take a backup and then use -normaliseconf") + log.Warnln("WARNING: The \"SigningPrivateKey\" configuration option has been renamed to \"PrivateKey\"") + if _, ok := dat["PrivateKey"]; !ok { + dat["PrivateKey"] = dat["SigningPrivateKey"] } - if c, ok := tunnelrouting["IPv6Sources"]; ok { - delete(tunnelrouting, "IPv6Sources") - tunnelrouting["IPv6LocalSubnets"] = c - } - if c, ok := tunnelrouting["IPv4Destinations"]; ok { - delete(tunnelrouting, "IPv4Destinations") - tunnelrouting["IPv4RemoteSubnets"] = c - } - if c, ok := tunnelrouting["IPv6Destinations"]; ok { - delete(tunnelrouting, "IPv6Destinations") - tunnelrouting["IPv6RemoteSubnets"] = c + } + if _, ok := dat["SigningPublicKey"]; ok { + log.Warnln("WARNING: The \"SigningPrivateKey\" configuration option has been renamed to \"PrivateKey\"") + if _, ok := dat["PublicKey"]; !ok { + dat["PublicKey"] = dat["SigningPublicKey"] } } // Sanitise the config @@ -176,6 +170,27 @@ func main() { loglevel := flag.String("loglevel", "info", "loglevel to enable") flag.Parse() + // Create a new logger that logs output to stdout. + var logger *log.Logger + switch *logto { + case "stdout": + logger = log.New(os.Stdout, "", log.Flags()) + case "syslog": + if syslogger, err := gsyslog.NewLogger(gsyslog.LOG_NOTICE, "DAEMON", version.BuildName()); err == nil { + logger = log.New(syslogger, "", log.Flags()) + } + default: + if logfd, err := os.OpenFile(*logto, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil { + logger = log.New(logfd, "", log.Flags()) + } + } + if logger == nil { + logger = log.New(os.Stdout, "", log.Flags()) + logger.Warnln("Logging defaulting to stdout") + } + + setLogLevel(*loglevel, logger) + var cfg *config.NodeConfig var err error switch { @@ -189,7 +204,7 @@ func main() { cfg = config.GenerateConfig() case *useconffile != "" || *useconf: // Read the configuration from either stdin or from the filesystem - cfg = readConfig(useconf, useconffile, normaliseconf) + cfg = readConfig(logger, useconf, useconffile, normaliseconf) // If the -normaliseconf option was specified then remarshal the above // configuration and print it back to stdout. This lets the user update // their configuration file with newly mapped names (like above) or to @@ -222,8 +237,8 @@ func main() { } // Have we been asked for the node address yet? If so, print it and then stop. getNodeKey := func() ed25519.PublicKey { - if pubkey, err := hex.DecodeString(cfg.PublicKey); err == nil { - return ed25519.PublicKey(pubkey) + if pubkey, err := hex.DecodeString(cfg.PrivateKey); err == nil { + return ed25519.PrivateKey(pubkey).Public().(ed25519.PublicKey) } return nil } @@ -247,26 +262,6 @@ func main() { return default: } - // Create a new logger that logs output to stdout. - var logger *log.Logger - switch *logto { - case "stdout": - logger = log.New(os.Stdout, "", log.Flags()) - case "syslog": - if syslogger, err := gsyslog.NewLogger(gsyslog.LOG_NOTICE, "DAEMON", version.BuildName()); err == nil { - logger = log.New(syslogger, "", log.Flags()) - } - default: - if logfd, err := os.OpenFile(*logto, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil { - logger = log.New(logfd, "", log.Flags()) - } - } - if logger == nil { - logger = log.New(os.Stdout, "", log.Flags()) - logger.Warnln("Logging defaulting to stdout") - } - - setLogLevel(*loglevel, logger) // Setup the Yggdrasil node itself. The node{} type includes a Core, so we // don't need to create this manually. @@ -285,14 +280,14 @@ func main() { n.tuntap.SetSessionGatekeeper(n.sessionFirewall) // Start the admin socket if err := n.admin.Init(&n.core, cfg, logger, nil); err != nil { - logger.Errorln("An error occured initialising admin socket:", err) + logger.Errorln("An error occurred initialising admin socket:", err) } else if err := n.admin.Start(); err != nil { logger.Errorln("An error occurred starting admin socket:", err) } n.admin.SetupAdminHandlers(n.admin) // Start the multicast interface if err := n.multicast.Init(&n.core, cfg, logger, nil); err != nil { - logger.Errorln("An error occured initialising multicast:", err) + logger.Errorln("An error occurred initialising multicast:", err) } else if err := n.multicast.Start(); err != nil { logger.Errorln("An error occurred starting multicast:", err) } @@ -308,6 +303,8 @@ func main() { // This is just logged to stdout for the user. address := n.core.Address() subnet := n.core.Subnet() + public := n.core.GetSelf().Key + logger.Infof("Your public key is %s", hex.EncodeToString(public[:])) logger.Infof("Your IPv6 address is %s", address.String()) logger.Infof("Your IPv6 subnet is %s", subnet.String()) // Catch interrupts from the operating system to exit gracefully. diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index 884656db..f7f4a69f 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -47,8 +47,11 @@ func run() int { fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] command [key=value] [key=value] ...\n\n", os.Args[0]) fmt.Println("Options:") flag.PrintDefaults() - fmt.Println("\nPlease note that options must always specified BEFORE the command\non the command line or they will be ignored.\n") // nolint:govet - fmt.Println("Commands:\n - Use \"list\" for a list of available commands\n") // nolint:govet + fmt.Println() + fmt.Println("Please note that options must always specified BEFORE the command\non the command line or they will be ignored.") + fmt.Println() + fmt.Println("Commands:\n - Use \"list\" for a list of available commands") + fmt.Println() fmt.Println("Examples:") fmt.Println(" - ", os.Args[0], "list") fmt.Println(" - ", os.Args[0], "getPeers") diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 97596380..59f64dc7 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -305,11 +305,7 @@ func (m *Multicast) _announce() { if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil { a.Zone = "" destAddr.Zone = iface.Name - key, err := hex.DecodeString(m.config.PublicKey) - if err != nil { - panic(err) - } - msg := append([]byte(nil), key...) + msg := append([]byte(nil), m.core.GetSelf().Key...) msg = append(msg, a.String()...) _, _ = m.sock.WriteTo(msg, nil, destAddr) } diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index da66e9d2..be9b6eba 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -48,7 +48,7 @@ func (tun *TunAdapter) read() { copy(srcSubnet[:], bs[8:]) copy(dstSubnet[:], bs[24:]) if srcAddr != tun.addr && srcSubnet != tun.subnet { - continue // Wrong soruce address + continue // Wrong source address } bs = buf[begin-1 : end] bs[0] = typeSessionTraffic diff --git a/src/tuntap/tun_darwin.go b/src/tuntap/tun_darwin.go index 060ad144..75938881 100644 --- a/src/tuntap/tun_darwin.go +++ b/src/tuntap/tun_darwin.go @@ -90,7 +90,7 @@ func (tun *TunAdapter) setupAddress(addr string) error { ar.ifra_prefixmask.sin6_len = uint8(unsafe.Sizeof(ar.ifra_prefixmask)) b := make([]byte, 16) binary.LittleEndian.PutUint16(b, uint16(0xFE00)) - ar.ifra_prefixmask.sin6_addr[0] = uint16(binary.BigEndian.Uint16(b)) + ar.ifra_prefixmask.sin6_addr[0] = binary.BigEndian.Uint16(b) ar.ifra_addr.sin6_len = uint8(unsafe.Sizeof(ar.ifra_addr)) ar.ifra_addr.sin6_family = unix.AF_INET6 @@ -99,7 +99,7 @@ func (tun *TunAdapter) setupAddress(addr string) error { addr, _ := strconv.ParseUint(parts[i], 16, 16) b := make([]byte, 16) binary.LittleEndian.PutUint16(b, uint16(addr)) - ar.ifra_addr.sin6_addr[i] = uint16(binary.BigEndian.Uint16(b)) + ar.ifra_addr.sin6_addr[i] = binary.BigEndian.Uint16(b) } ar.ifra_flags |= darwin_IN6_IFF_NODAD From 05ad5df8ab1cca26fc8f7d3822d6d0e0e2869225 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 5 Jun 2021 21:32:12 +0100 Subject: [PATCH 0710/1109] Run tests in CI --- .circleci/config.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8a486c38..6e41648c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,6 +16,11 @@ jobs: go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.31.0 golangci-lint run + - run: + name: Run Go tests + command: | + go test ./... + build-linux: docker: - image: circleci/golang:1.16 From 5cede61a34870ae214e93a3ba8a3329422b4ab0e Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 5 Jun 2021 21:32:18 +0100 Subject: [PATCH 0711/1109] Use git describe output for versions --- contrib/semver/version.sh | 47 +++------------------------------------ 1 file changed, 3 insertions(+), 44 deletions(-) diff --git a/contrib/semver/version.sh b/contrib/semver/version.sh index db96e339..7473c41f 100644 --- a/contrib/semver/version.sh +++ b/contrib/semver/version.sh @@ -1,46 +1,5 @@ #!/bin/sh -# Get the last tag -TAG=$(git describe --abbrev=0 --tags --match="v[0-9]*\.[0-9]*\.[0-9]*" 2>/dev/null) - -# Did getting the tag succeed? -if [ $? != 0 ] || [ -z "$TAG" ]; then - printf -- "unknown" - exit 0 -fi - -# Get the current branch -BRANCH=$(git symbolic-ref -q HEAD --short 2>/dev/null) - -# Did getting the branch succeed? -if [ $? != 0 ] || [ -z "$BRANCH" ]; then - BRANCH="master" -fi - -# Split out into major, minor and patch numbers -MAJOR=$(echo $TAG | cut -c 2- | cut -d "." -f 1) -MINOR=$(echo $TAG | cut -c 2- | cut -d "." -f 2) -PATCH=$(echo $TAG | cut -c 2- | cut -d "." -f 3) - -# Output in the desired format -if [ $((PATCH)) -eq 0 ]; then - printf '%s%d.%d' "$PREPEND" "$((MAJOR))" "$((MINOR))" -else - printf '%s%d.%d.%d' "$PREPEND" "$((MAJOR))" "$((MINOR))" "$((PATCH))" -fi - -# Add the build tag on non-master branches -if [ "$BRANCH" != "master" ]; then - BUILD=$(git rev-list --count $TAG..HEAD 2>/dev/null) - - # Did getting the count of commits since the tag succeed? - if [ $? != 0 ] || [ -z "$BUILD" ]; then - printf -- "-unknown" - exit 0 - fi - - # Is the build greater than zero? - if [ $((BUILD)) -gt 0 ]; then - printf -- "-%04d" "$((BUILD))" - fi -fi +# We'll just use the `git describe` version since it's reasonably +# easy to refer to a tag or commit using the describe output. +git describe --match="v[0-9]*\.[0-9]*\.[0-9]*" 2>/dev/null From 54cced0b89135b9f98227cec4c1a1e9204befc4c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 5 Jun 2021 21:40:58 +0100 Subject: [PATCH 0712/1109] Ensure PublicKey is correct when extracting from old config --- cmd/yggdrasil/main.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 95502aac..cf8710cc 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -82,17 +82,15 @@ func readConfig(log *log.Logger, useconf *bool, useconffile *string, normaliseco if _, ok := dat["TunnelRouting"]; ok { log.Warnln("WARNING: Tunnel routing is no longer supported") } - if _, ok := dat["SigningPrivateKey"]; ok { - log.Warnln("WARNING: The configuration file is out of date, please take a backup and then use -normaliseconf") + if old, ok := dat["SigningPrivateKey"]; ok { log.Warnln("WARNING: The \"SigningPrivateKey\" configuration option has been renamed to \"PrivateKey\"") if _, ok := dat["PrivateKey"]; !ok { - dat["PrivateKey"] = dat["SigningPrivateKey"] - } - } - if _, ok := dat["SigningPublicKey"]; ok { - log.Warnln("WARNING: The \"SigningPrivateKey\" configuration option has been renamed to \"PrivateKey\"") - if _, ok := dat["PublicKey"]; !ok { - dat["PublicKey"] = dat["SigningPublicKey"] + if privstr, err := hex.DecodeString(old.(string)); err == nil { + priv := ed25519.PrivateKey(privstr) + pub := priv.Public().(ed25519.PublicKey) + dat["PrivateKey"] = hex.EncodeToString(priv[:]) + dat["PublicKey"] = hex.EncodeToString(pub[:]) + } } } // Sanitise the config From 4a684e7caf82d5db553b4cb1bf4d50f581071173 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 5 Jun 2021 21:48:20 +0100 Subject: [PATCH 0713/1109] Don't add mutex to config output --- src/config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/config.go b/src/config/config.go index af47face..87d1af98 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -28,7 +28,7 @@ import ( // options that are necessary for an Yggdrasil node to run. You will need to // supply one of these structs to the Yggdrasil core when starting a node. type NodeConfig struct { - sync.RWMutex + sync.RWMutex `json:"-"` Peers []string `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j. These connections\nwill obey the operating system routing table, therefore you should\nuse this section when you may connect via different interfaces."` InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ tcp://a.b.c.d:e ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."` Listen []string `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntcp://0.0.0.0:0 or tcp://[::]:0 to listen on all interfaces."` From 80b6bf0c78abeaa34760ae7b0cc0e26fa4bc9d65 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 5 Jun 2021 21:49:11 +0100 Subject: [PATCH 0714/1109] Further tweaks to transition handling --- cmd/yggdrasil/main.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index cf8710cc..8455a1cd 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -90,6 +90,8 @@ func readConfig(log *log.Logger, useconf *bool, useconffile *string, normaliseco pub := priv.Public().(ed25519.PublicKey) dat["PrivateKey"] = hex.EncodeToString(priv[:]) dat["PublicKey"] = hex.EncodeToString(pub[:]) + } else { + log.Warnln("WARNING: The \"SigningPrivateKey\" configuration option contains an invalid value and will be ignored") } } } @@ -187,7 +189,11 @@ func main() { logger.Warnln("Logging defaulting to stdout") } - setLogLevel(*loglevel, logger) + if *normaliseconf { + setLogLevel("error", logger) + } else { + setLogLevel(*loglevel, logger) + } var cfg *config.NodeConfig var err error From d46a8830200e25beb5da28fa614f5a81ab0a0950 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 5 Jun 2021 21:54:05 +0100 Subject: [PATCH 0715/1109] Include public key in yggdrasilctl getSelf output for v0.4 nodes --- cmd/yggdrasilctl/main.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index f7f4a69f..91923392 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -291,6 +291,9 @@ func run() int { if subnet, ok := v.(map[string]interface{})["subnet"].(string); ok { fmt.Println("IPv6 subnet:", subnet) } + if boxSigKey, ok := v.(map[string]interface{})["key"].(string); ok { + fmt.Println("Public key:", boxSigKey) + } if coords, ok := v.(map[string]interface{})["coords"].(string); ok { fmt.Println("Coords:", coords) } From 2e2566d248b4678da958f5e047677b5e8dec8fd9 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 5 Jun 2021 21:56:31 +0100 Subject: [PATCH 0716/1109] Remove src/core/doc.go --- src/core/doc.go | 176 ------------------------------------------------ 1 file changed, 176 deletions(-) delete mode 100644 src/core/doc.go diff --git a/src/core/doc.go b/src/core/doc.go deleted file mode 100644 index 595af229..00000000 --- a/src/core/doc.go +++ /dev/null @@ -1,176 +0,0 @@ -/* -Package core implements the core functionality of the Yggdrasil Network. - -Introduction - -Yggdrasil is a proof-of-concept mesh network which provides end-to-end encrypted -communication between nodes in a decentralised fashion. The network is arranged -using a globally-agreed spanning tree which provides each node with a locator -(coordinates relative to the root) and a distributed hash table (DHT) mechanism -for finding other nodes. - -Each node also implements a router, which is responsible for encryption of -traffic, searches and connections, and a switch, which is responsible ultimately -for forwarding traffic across the network. - -While many Yggdrasil nodes in existence today are IP nodes - that is, they are -transporting IPv6 packets, like a kind of mesh VPN - it is also possible to -integrate Yggdrasil into your own applications and use it as a generic data -transport, similar to UDP. - -This library is what you need to integrate and use Yggdrasil in your own -application. - -Basics - -In order to start an Yggdrasil node, you should start by generating node -configuration, which amongst other things, includes encryption keypairs which -are used to generate the node's identity, and supply a logger which Yggdrasil's -output will be written to. - -This may look something like this: - - import ( - "os" - "github.com/gologme/log" - "github.com/yggdrasil-network/yggdrasil-go/src/config" - "github.com/yggdrasil-network/yggdrasil-go/src/core" - ) - - type node struct { - core core.Core - config *config.NodeConfig - log *log.Logger - } - -You then can supply node configuration and a logger: - - n := node{} - n.log = log.New(os.Stdout, "", log.Flags()) - n.config = config.GenerateConfig() - -In the above example, we ask the config package to supply new configuration each -time, which results in fresh encryption keys and therefore a new identity. It is -normally preferable in most cases to persist node configuration onto the -filesystem or into some configuration store so that the node's identity does not -change each time that the program starts. Note that Yggdrasil will automatically -fill in any missing configuration items with sane defaults. - -Once you have supplied a logger and some node configuration, you can then start -the node: - - n.core.Start(n.config, n.log) - -Add some peers to connect to the network: - - n.core.AddPeer("tcp://some-host.net:54321", "") - n.core.AddPeer("tcp://[2001::1:2:3]:54321", "") - n.core.AddPeer("tcp://1.2.3.4:54321", "") - -You can also ask the API for information about our node: - - n.log.Println("My node ID is", n.core.NodeID()) - n.log.Println("My public key is", n.core.EncryptionPublicKey()) - n.log.Println("My coords are", n.core.Coords()) - -Incoming Connections - -Once your node is started, you can then listen for connections from other nodes -by asking the API for a Listener: - - listener, err := n.core.ConnListen() - if err != nil { - // ... - } - -The Listener has a blocking Accept function which will wait for incoming -connections from remote nodes. It will return a Conn when a connection is -received. If the node never receives any incoming connections then this function -can block forever, so be prepared for that, perhaps by listening in a separate -goroutine. - -Assuming that you have defined a myConnectionHandler function to deal with -incoming connections: - - for { - conn, err := listener.Accept() - if err != nil { - // ... - } - - // We've got a new connection - go myConnectionHandler(conn) - } - -Outgoing Connections - -If you know the node ID of the remote node that you want to talk to, you can -dial an outbound connection to it. To do this, you should first ask the API for -a Dialer: - - dialer, err := n.core.ConnDialer() - if err != nil { - // ... - } - -You can then dial using the node's public key in hexadecimal format, for example: - - conn, err := dialer.Dial("curve25519", "55071be281f50d0abbda63aadc59755624280c44b2f1f47684317aa4e0325604") - if err != nil { - // ... - } - -Using Connections - -Conn objects are implementations of io.ReadWriteCloser, and as such, you can -Read, Write and Close them as necessary. - -Each Read or Write operation can deal with a buffer with a maximum size of 65535 -bytes - any bigger than this and the operation will return an error. - -For example, to write to the Conn from the supplied buffer: - - buf := []byte{1, 2, 3, 4, 5} - w, err := conn.Write(buf) - if err != nil { - // ... - } else { - // written w bytes - } - -Reading from the Conn into the supplied buffer: - - buf := make([]byte, 65535) - r, err := conn.Read(buf) - if err != nil { - // ... - } else { - // read r bytes - } - -When you are happy that a connection is no longer required, you can discard it: - - err := conn.Close() - if err != nil { - // ... - } - -Limitations - -You should be aware of the following limitations when working with the Yggdrasil -library: - -Individual messages written through Yggdrasil connections can not exceed 65535 -bytes in size. Yggdrasil has no concept of fragmentation, so if you try to send -a message that exceeds 65535 bytes in size, it will be dropped altogether and -an error will be returned. - -Yggdrasil connections are unreliable by nature. Messages are delivered on a -best-effort basis, and employs congestion control where appropriate to ensure -that congestion does not affect message transport, but Yggdrasil will not -retransmit any messages that have been lost. If reliable delivery is important -then you should manually implement acknowledgement and retransmission of -messages. - -*/ -package core From e827e5d313252936e292fc8b1b63599b6f4ea031 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 5 Jun 2021 22:09:15 +0100 Subject: [PATCH 0717/1109] Go back to old semver version for now --- contrib/semver/version.sh | 47 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/contrib/semver/version.sh b/contrib/semver/version.sh index 7473c41f..db96e339 100644 --- a/contrib/semver/version.sh +++ b/contrib/semver/version.sh @@ -1,5 +1,46 @@ #!/bin/sh -# We'll just use the `git describe` version since it's reasonably -# easy to refer to a tag or commit using the describe output. -git describe --match="v[0-9]*\.[0-9]*\.[0-9]*" 2>/dev/null +# Get the last tag +TAG=$(git describe --abbrev=0 --tags --match="v[0-9]*\.[0-9]*\.[0-9]*" 2>/dev/null) + +# Did getting the tag succeed? +if [ $? != 0 ] || [ -z "$TAG" ]; then + printf -- "unknown" + exit 0 +fi + +# Get the current branch +BRANCH=$(git symbolic-ref -q HEAD --short 2>/dev/null) + +# Did getting the branch succeed? +if [ $? != 0 ] || [ -z "$BRANCH" ]; then + BRANCH="master" +fi + +# Split out into major, minor and patch numbers +MAJOR=$(echo $TAG | cut -c 2- | cut -d "." -f 1) +MINOR=$(echo $TAG | cut -c 2- | cut -d "." -f 2) +PATCH=$(echo $TAG | cut -c 2- | cut -d "." -f 3) + +# Output in the desired format +if [ $((PATCH)) -eq 0 ]; then + printf '%s%d.%d' "$PREPEND" "$((MAJOR))" "$((MINOR))" +else + printf '%s%d.%d.%d' "$PREPEND" "$((MAJOR))" "$((MINOR))" "$((PATCH))" +fi + +# Add the build tag on non-master branches +if [ "$BRANCH" != "master" ]; then + BUILD=$(git rev-list --count $TAG..HEAD 2>/dev/null) + + # Did getting the count of commits since the tag succeed? + if [ $? != 0 ] || [ -z "$BUILD" ]; then + printf -- "-unknown" + exit 0 + fi + + # Is the build greater than zero? + if [ $((BUILD)) -gt 0 ]; then + printf -- "-%04d" "$((BUILD))" + fi +fi From c5529a3a388ef2b7d46129e326bf8366828d0926 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 5 Jun 2021 22:28:29 +0100 Subject: [PATCH 0718/1109] Use git describe again --- contrib/semver/version.sh | 46 ++++----------------------------------- 1 file changed, 4 insertions(+), 42 deletions(-) diff --git a/contrib/semver/version.sh b/contrib/semver/version.sh index db96e339..1a5c2fd9 100644 --- a/contrib/semver/version.sh +++ b/contrib/semver/version.sh @@ -1,46 +1,8 @@ #!/bin/sh -# Get the last tag -TAG=$(git describe --abbrev=0 --tags --match="v[0-9]*\.[0-9]*\.[0-9]*" 2>/dev/null) - -# Did getting the tag succeed? -if [ $? != 0 ] || [ -z "$TAG" ]; then - printf -- "unknown" - exit 0 -fi - -# Get the current branch -BRANCH=$(git symbolic-ref -q HEAD --short 2>/dev/null) - -# Did getting the branch succeed? -if [ $? != 0 ] || [ -z "$BRANCH" ]; then - BRANCH="master" -fi - -# Split out into major, minor and patch numbers -MAJOR=$(echo $TAG | cut -c 2- | cut -d "." -f 1) -MINOR=$(echo $TAG | cut -c 2- | cut -d "." -f 2) -PATCH=$(echo $TAG | cut -c 2- | cut -d "." -f 3) - -# Output in the desired format -if [ $((PATCH)) -eq 0 ]; then - printf '%s%d.%d' "$PREPEND" "$((MAJOR))" "$((MINOR))" +if [ "$1" == "--bare" ]; then + # Remove the "v" prefix + git describe --tags --match="v[0-9]*\.[0-9]*\.[0-9]*" | cut -c 2- else - printf '%s%d.%d.%d' "$PREPEND" "$((MAJOR))" "$((MINOR))" "$((PATCH))" -fi - -# Add the build tag on non-master branches -if [ "$BRANCH" != "master" ]; then - BUILD=$(git rev-list --count $TAG..HEAD 2>/dev/null) - - # Did getting the count of commits since the tag succeed? - if [ $? != 0 ] || [ -z "$BUILD" ]; then - printf -- "-unknown" - exit 0 - fi - - # Is the build greater than zero? - if [ $((BUILD)) -gt 0 ]; then - printf -- "-%04d" "$((BUILD))" - fi + git describe --tags --match="v[0-9]*\.[0-9]*\.[0-9]*" fi From 2b6aa3e2d74b7a927109b98c5252479e5c4a10d2 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 5 Jun 2021 22:38:37 +0100 Subject: [PATCH 0719/1109] Semver version fix --- contrib/semver/version.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/semver/version.sh b/contrib/semver/version.sh index 1a5c2fd9..c677e96b 100644 --- a/contrib/semver/version.sh +++ b/contrib/semver/version.sh @@ -1,6 +1,6 @@ #!/bin/sh -if [ "$1" == "--bare" ]; then +if [[ $* == *--bare* ]]; then # Remove the "v" prefix git describe --tags --match="v[0-9]*\.[0-9]*\.[0-9]*" | cut -c 2- else From ae196a7eded98ccaf729ea65e9525b81d9343a54 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 6 Jun 2021 00:52:03 -0500 Subject: [PATCH 0720/1109] update ironwood dependency --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 07d340e1..eb9065b8 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.16 require ( - github.com/Arceliar/ironwood v0.0.0-20210531083357-daeea6bc386a + github.com/Arceliar/ironwood v0.0.0-20210606054635-3bd9d71bce77 github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 github.com/cheggaaa/pb/v3 v3.0.6 github.com/fatih/color v1.10.0 // indirect diff --git a/go.sum b/go.sum index cafa8c84..9eb66d51 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/ironwood v0.0.0-20210531083357-daeea6bc386a h1:szuwvAlzD3r7PEcv4d3tin0V/4ZS/YQKL8RmLAXbE3E= -github.com/Arceliar/ironwood v0.0.0-20210531083357-daeea6bc386a/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= +github.com/Arceliar/ironwood v0.0.0-20210606054635-3bd9d71bce77 h1:KPX5rjNFU3ICdrOBP/TKSq2XWqkF5vqIH+rf/PYnH38= +github.com/Arceliar/ironwood v0.0.0-20210606054635-3bd9d71bce77/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= From 838bca083dc25df080786128956d3589b80e21f7 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 6 Jun 2021 02:33:11 -0500 Subject: [PATCH 0721/1109] remove bashisms for semver --- contrib/semver/version.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/contrib/semver/version.sh b/contrib/semver/version.sh index c677e96b..d2ec6ce1 100644 --- a/contrib/semver/version.sh +++ b/contrib/semver/version.sh @@ -1,8 +1,11 @@ #!/bin/sh -if [[ $* == *--bare* ]]; then +case "$*" in + *--bare*) # Remove the "v" prefix git describe --tags --match="v[0-9]*\.[0-9]*\.[0-9]*" | cut -c 2- -else + ;; + *) git describe --tags --match="v[0-9]*\.[0-9]*\.[0-9]*" -fi + ;; +esac From e7da3d72c494d6e9730cbd39e891327914a046dd Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 6 Jun 2021 02:35:02 -0500 Subject: [PATCH 0722/1109] remove session firewall, this can't prevent memory use so it's better to just use OS native tools --- cmd/yggdrasil/main.go | 60 ------------------------------------------ src/config/config.go | 15 ----------- src/tuntap/iface.go | 3 --- src/tuntap/keystore.go | 12 --------- src/tuntap/tun.go | 13 +++------ 5 files changed, 3 insertions(+), 100 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 8455a1cd..114e37f3 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -281,7 +281,6 @@ func main() { n.admin = &admin.AdminSocket{} n.multicast = &multicast.Multicast{} n.tuntap = &tuntap.TunAdapter{} - n.tuntap.SetSessionGatekeeper(n.sessionFirewall) // Start the admin socket if err := n.admin.Init(&n.core, cfg, logger, nil); err != nil { logger.Errorln("An error occurred initialising admin socket:", err) @@ -326,62 +325,3 @@ func (n *node) shutdown() { _ = n.tuntap.Stop() n.core.Stop() } - -func (n *node) sessionFirewall(pubkey ed25519.PublicKey, initiator bool) bool { - n.config.RLock() - defer n.config.RUnlock() - - // Allow by default if the session firewall is disabled - if !n.config.SessionFirewall.Enable { - return true - } - - // Reject blacklisted nodes - for _, b := range n.config.SessionFirewall.BlacklistPublicKeys { - key, err := hex.DecodeString(b) - if err == nil { - if bytes.Equal(key, pubkey) { - return false - } - } - } - - // Allow whitelisted nodes - for _, b := range n.config.SessionFirewall.WhitelistPublicKeys { - key, err := hex.DecodeString(b) - if err == nil { - if bytes.Equal(key, pubkey) { - return true - } - } - } - - // Allow outbound sessions if appropriate - if n.config.SessionFirewall.AlwaysAllowOutbound { - if initiator { - return true - } - } - - // Look and see if the pubkey is that of a direct peer - var isDirectPeer bool - for _, peer := range n.core.GetPeers() { - if bytes.Equal(peer.Key[:], pubkey[:]) { - isDirectPeer = true - break - } - } - - // Allow direct peers if appropriate - if n.config.SessionFirewall.AllowFromDirect && isDirectPeer { - return true - } - - // Allow remote nodes if appropriate - if n.config.SessionFirewall.AllowFromRemote && !isDirectPeer { - return true - } - - // Finally, default-deny if not matching any of the above rules - return false -} diff --git a/src/config/config.go b/src/config/config.go index 87d1af98..fa110122 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -40,21 +40,10 @@ type NodeConfig struct { LinkLocalTCPPort uint16 `comment:"The port number to be used for the link-local TCP listeners for the\nconfigured MulticastInterfaces. This option does not affect listeners\nspecified in the Listen option. Unless you plan to firewall link-local\ntraffic, it is best to leave this as the default value of 0. This\noption cannot currently be changed by reloading config during runtime."` IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."` IfMTU uint64 `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` - SessionFirewall SessionFirewall `comment:"The session firewall controls who can send/receive network traffic\nto/from. This is useful if you want to protect this node without\nresorting to using a real firewall. This does not affect traffic\nbeing routed via this node to somewhere else. Rules are prioritised as\nfollows: blacklist, whitelist, always allow outgoing, direct, remote."` NodeInfoPrivacy bool `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."` NodeInfo map[string]interface{} `comment:"Optional node info. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."` } -// SessionFirewall controls the session firewall configuration. -type SessionFirewall struct { - Enable bool `comment:"Enable or disable the session firewall. If disabled, network traffic\nfrom any node will be allowed. If enabled, the below rules apply."` - AllowFromDirect bool `comment:"Allow network traffic from directly connected peers."` - AllowFromRemote bool `comment:"Allow network traffic from remote nodes on the network that you are\nnot directly peered with."` - AlwaysAllowOutbound bool `comment:"Allow outbound network traffic regardless of AllowFromDirect or\nAllowFromRemote. This does allow a remote node to send unsolicited\ntraffic back to you for the length of the session."` - WhitelistPublicKeys []string `comment:"List of public keys from which network traffic is always accepted,\nregardless of AllowFromDirect or AllowFromRemote."` - BlacklistPublicKeys []string `comment:"List of public keys from which network traffic is always rejected,\nregardless of the whitelist, AllowFromDirect or AllowFromRemote."` -} - // Generates default configuration and returns a pointer to the resulting // NodeConfig. This is used when outputting the -genconf parameter and also when // using -autoconf. @@ -76,10 +65,6 @@ func GenerateConfig() *NodeConfig { cfg.MulticastInterfaces = defaults.GetDefaults().DefaultMulticastInterfaces cfg.IfName = defaults.GetDefaults().DefaultIfName cfg.IfMTU = defaults.GetDefaults().DefaultIfMTU - cfg.SessionFirewall.Enable = false - cfg.SessionFirewall.AllowFromDirect = true - cfg.SessionFirewall.AllowFromRemote = true - cfg.SessionFirewall.AlwaysAllowOutbound = true cfg.NodeInfoPrivacy = false return &cfg diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index be9b6eba..cb13690a 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -116,9 +116,6 @@ func (tun *TunAdapter) write() { continue // bad local address/subnet } info := tun.store.update(ed25519.PublicKey(from.(iwt.Addr))) - if info == nil { - continue // Blocked by the gatekeeper - } if srcAddr != info.address && srcSubnet != info.subnet { continue // bad remote address/subnet } diff --git a/src/tuntap/keystore.go b/src/tuntap/keystore.go index 03f429f7..2eb79202 100644 --- a/src/tuntap/keystore.go +++ b/src/tuntap/keystore.go @@ -113,18 +113,6 @@ func (k *keyStore) update(key ed25519.PublicKey) *keyInfo { info.key = kArray info.address = *address.AddrForKey(ed25519.PublicKey(info.key[:])) info.subnet = *address.SubnetForKey(ed25519.PublicKey(info.key[:])) - var isOutgoing bool - if k.addrBuffer[info.address] != nil { - isOutgoing = true - } - if k.subnetBuffer[info.subnet] != nil { - isOutgoing = true - } - if !k.tun.gatekeeper(key, isOutgoing) { - // Blocked by the gatekeeper, so don't create an entry for this - k.mutex.Unlock() - return nil - } k.keyToInfo[info.key] = info k.addrToInfo[info.address] = info k.subnetToInfo[info.subnet] = info diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index c1b90e75..f53f6dea 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -43,16 +43,9 @@ type TunAdapter struct { iface tun.Device phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below //mutex sync.RWMutex // Protects the below - isOpen bool - isEnabled bool // Used by the writer to drop sessionTraffic if not enabled - gatekeeper func(pubkey ed25519.PublicKey, initiator bool) bool - proto protoHandler -} - -func (tun *TunAdapter) SetSessionGatekeeper(gatekeeper func(pubkey ed25519.PublicKey, initiator bool) bool) { - phony.Block(tun, func() { - tun.gatekeeper = gatekeeper - }) + isOpen bool + isEnabled bool // Used by the writer to drop sessionTraffic if not enabled + proto protoHandler } // Gets the maximum supported MTU for the platform based on the defaults in From f7607557c18fb4002cf5abb38c3b5f0127b29202 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 6 Jun 2021 04:48:00 -0500 Subject: [PATCH 0723/1109] fix nBytes check in multicast code --- src/multicast/multicast.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 59f64dc7..220be7fd 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -345,7 +345,7 @@ func (m *Multicast) listen() { continue } } - if len(bs) < ed25519.PublicKeySize { + if nBytes < ed25519.PublicKeySize { continue } var key ed25519.PublicKey From acdc3dd3c0c1ca2a4d2e2cd89a8ff5a82b56c2f6 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 11 Jun 2021 21:12:27 +0100 Subject: [PATCH 0724/1109] Replace ?ed25519= with ?key= in peering URIs --- src/core/link.go | 2 +- src/multicast/multicast.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/link.go b/src/core/link.go index 51b679a6..c0eb509f 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -75,7 +75,7 @@ func (l *links) call(u *url.URL, sintf string) error { // return fmt.Errorf("peer %s is not correctly formatted (%s)", uri, err) //} tcpOpts := tcpOptions{} - if pubkeys, ok := u.Query()["ed25519"]; ok && len(pubkeys) > 0 { + if pubkeys, ok := u.Query()["key"]; ok && len(pubkeys) > 0 { tcpOpts.pinnedEd25519Keys = make(map[keyArray]struct{}) for _, pubkey := range pubkeys { if sigPub, err := hex.DecodeString(pubkey); err == nil { diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 220be7fd..6f0e8f98 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -365,7 +365,7 @@ func (m *Multicast) listen() { }) if _, ok := interfaces[from.Zone]; ok { addr.Zone = "" - pin := fmt.Sprintf("/?ed25519=%s", hex.EncodeToString(key)) + pin := fmt.Sprintf("/?key=%s", hex.EncodeToString(key)) u, err := url.Parse("tcp://" + addr.String() + pin) if err != nil { m.log.Debugln("Call from multicast failed, parse error:", addr.String(), err) From 3815b13ad5e03180a737b53afb4170a9d9d0aabd Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 12 Jun 2021 05:58:14 -0500 Subject: [PATCH 0725/1109] use DialContext --- src/core/tcp.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/core/tcp.go b/src/core/tcp.go index 21a3ba79..c5487d5d 100644 --- a/src/core/tcp.go +++ b/src/core/tcp.go @@ -270,7 +270,9 @@ func (t *tcp) call(saddr string, options tcpOptions, sintf string) { if err != nil { return } - conn, err = dialer.Dial("tcp", saddr) + ctx, done := context.WithTimeout(context.Background(), default_timeout) + conn, err = dialer.(proxy.ContextDialer).DialContext(ctx, "tcp", saddr) + done() if err != nil { return } @@ -292,7 +294,6 @@ func (t *tcp) call(saddr string, options tcpOptions, sintf string) { } dialer := net.Dialer{ Control: t.tcpContext, - Timeout: time.Second * 5, } if sintf != "" { dialer.Control = t.getControl(sintf) @@ -338,7 +339,9 @@ func (t *tcp) call(saddr string, options tcpOptions, sintf string) { } } } - conn, err = dialer.Dial("tcp", dst.String()) + ctx, done := context.WithTimeout(context.Background(), default_timeout) + conn, err = dialer.DialContext(ctx, "tcp", dst.String()) + done() if err != nil { t.links.core.log.Debugf("Failed to dial %s: %s", callproto, err) return From 5b6f730f18d3e6804aad9e2c9ffb3790dfc60265 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 12 Jun 2021 06:06:39 -0500 Subject: [PATCH 0726/1109] keep a context in the core, use it for listen/dial, cancel it when closing --- src/core/core.go | 17 ++++++++++++----- src/core/tcp.go | 6 +++--- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/core/core.go b/src/core/core.go index de916c1b..b17bf191 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -1,6 +1,7 @@ package core import ( + "context" "crypto/ed25519" "encoding/hex" "errors" @@ -31,6 +32,8 @@ type Core struct { links links log *log.Logger addPeerTimer *time.Timer + ctx context.Context + ctxCancel context.CancelFunc } func (c *Core) _init() error { @@ -57,6 +60,7 @@ func (c *Core) _init() error { // TODO check public against current.PublicKey, error if they don't match c.PacketConn, err = iw.NewPacketConn(c.secret) + c.ctx, c.ctxCancel = context.WithCancel(context.Background()) return err } @@ -67,6 +71,10 @@ func (c *Core) _addPeerLoop() { c.config.RLock() defer c.config.RUnlock() + if c.addPeerTimer == nil { + return + } + // Add peers from the Peers section for _, peer := range c.config.Peers { go func(peer string, intf string) { @@ -95,11 +103,9 @@ func (c *Core) _addPeerLoop() { } } - if c.addPeerTimer != nil { - c.addPeerTimer = time.AfterFunc(time.Minute, func() { - c.Act(nil, c._addPeerLoop) - }) - } + c.addPeerTimer = time.AfterFunc(time.Minute, func() { + c.Act(nil, c._addPeerLoop) + }) } // Start starts up Yggdrasil using the provided config.NodeConfig, and outputs @@ -152,6 +158,7 @@ func (c *Core) Stop() { // This function is unsafe and should only be ran by the core actor. func (c *Core) _stop() { + c.ctxCancel() c.PacketConn.Close() c.log.Infoln("Stopping...") if c.addPeerTimer != nil { diff --git a/src/core/tcp.go b/src/core/tcp.go index c5487d5d..c9bfd65f 100644 --- a/src/core/tcp.go +++ b/src/core/tcp.go @@ -153,7 +153,7 @@ func (t *tcp) listenURL(u *url.URL, sintf string) (*TcpListener, error) { func (t *tcp) listen(listenaddr string, upgrade *TcpUpgrade) (*TcpListener, error) { var err error - ctx := context.Background() + ctx := t.links.core.ctx lc := net.ListenConfig{ Control: t.tcpContext, } @@ -270,7 +270,7 @@ func (t *tcp) call(saddr string, options tcpOptions, sintf string) { if err != nil { return } - ctx, done := context.WithTimeout(context.Background(), default_timeout) + ctx, done := context.WithTimeout(t.links.core.ctx, default_timeout) conn, err = dialer.(proxy.ContextDialer).DialContext(ctx, "tcp", saddr) done() if err != nil { @@ -339,7 +339,7 @@ func (t *tcp) call(saddr string, options tcpOptions, sintf string) { } } } - ctx, done := context.WithTimeout(context.Background(), default_timeout) + ctx, done := context.WithTimeout(t.links.core.ctx, default_timeout) conn, err = dialer.DialContext(ctx, "tcp", dst.String()) done() if err != nil { From eeadffe4a5c08206ea30d70376fd1860397ebc7e Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 12 Jun 2021 06:07:33 -0500 Subject: [PATCH 0727/1109] move position of log line on shutdown --- src/core/core.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/core.go b/src/core/core.go index b17bf191..bd2478fc 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -158,9 +158,9 @@ func (c *Core) Stop() { // This function is unsafe and should only be ran by the core actor. func (c *Core) _stop() { + c.log.Infoln("Stopping...") c.ctxCancel() c.PacketConn.Close() - c.log.Infoln("Stopping...") if c.addPeerTimer != nil { c.addPeerTimer.Stop() c.addPeerTimer = nil From 91235980af18b036240eda6c1d11383f580352fc Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 12 Jun 2021 07:03:32 -0500 Subject: [PATCH 0728/1109] fix logging for socks --- src/core/tcp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/tcp.go b/src/core/tcp.go index c9bfd65f..5c3e506b 100644 --- a/src/core/tcp.go +++ b/src/core/tcp.go @@ -277,7 +277,7 @@ func (t *tcp) call(saddr string, options tcpOptions, sintf string) { return } t.waitgroup.Add(1) - options.socksPeerAddr = conn.RemoteAddr().String() + options.socksPeerAddr = saddr if ch := t.handler(conn, false, options); ch != nil { <-ch } From bb66851c2b4b37d89c03e051d7a17e3ae0355f5f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 12 Jun 2021 21:46:17 +0100 Subject: [PATCH 0729/1109] Update dependencies --- go.mod | 22 +++++++++++---------- go.sum | 62 +++++++++++++++++++++++++++++++--------------------------- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/go.mod b/go.mod index eb9065b8..1e27d93b 100644 --- a/go.mod +++ b/go.mod @@ -3,22 +3,24 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.16 require ( - github.com/Arceliar/ironwood v0.0.0-20210606054635-3bd9d71bce77 + github.com/Arceliar/ironwood v0.0.0-20210606094153-1bd43ce71198 github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 - github.com/cheggaaa/pb/v3 v3.0.6 - github.com/fatih/color v1.10.0 // indirect + github.com/VividCortex/ewma v1.2.0 // indirect + github.com/cheggaaa/pb/v3 v3.0.8 + github.com/fatih/color v1.12.0 // indirect github.com/gologme/log v1.2.0 github.com/hashicorp/go-syslog v1.0.0 github.com/hjson/hjson-go v3.1.0+incompatible github.com/kardianos/minwinsvc v1.0.0 - github.com/mattn/go-runewidth v0.0.10 // indirect + github.com/mattn/go-isatty v0.0.13 // indirect + github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mitchellh/mapstructure v1.4.1 - github.com/rivo/uniseg v0.2.0 // indirect github.com/vishvananda/netlink v1.1.0 github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect - golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 - golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b - golang.org/x/text v0.3.6-0.20210220033129-8f690f22cf1c - golang.zx2c4.com/wireguard v0.0.0-20210306175010-7e3b8371a1bf - golang.zx2c4.com/wireguard/windows v0.3.8 + golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect + golang.org/x/net v0.0.0-20210610132358-84b48f89b13b + golang.org/x/sys v0.0.0-20210611083646-a4fc73990273 + golang.org/x/text v0.3.7-0.20210503195748-5c7c50ebbd4f + golang.zx2c4.com/wireguard v0.0.0-20210604143328-f9b48a961cd2 + golang.zx2c4.com/wireguard/windows v0.3.14 ) diff --git a/go.sum b/go.sum index 9eb66d51..dab85269 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,15 @@ -github.com/Arceliar/ironwood v0.0.0-20210606054635-3bd9d71bce77 h1:KPX5rjNFU3ICdrOBP/TKSq2XWqkF5vqIH+rf/PYnH38= -github.com/Arceliar/ironwood v0.0.0-20210606054635-3bd9d71bce77/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= +github.com/Arceliar/ironwood v0.0.0-20210606094153-1bd43ce71198 h1:wLF+CSqm9DrPeT2dp1E4Xe5of8SyUxfJVxw8DHeT1YM= +github.com/Arceliar/ironwood v0.0.0-20210606094153-1bd43ce71198/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= -github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= -github.com/cheggaaa/pb/v3 v3.0.6 h1:ULPm1wpzvj60FvmCrX7bIaB80UgbhI+zSaQJKRfCbAs= -github.com/cheggaaa/pb/v3 v3.0.6/go.mod h1:X1L61/+36nz9bjIsrDU52qHKOQukUQe2Ge+YvGuquCw= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= +github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= +github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= +github.com/cheggaaa/pb/v3 v3.0.8 h1:bC8oemdChbke2FHIIGy9mn4DPJ2caZYQnfbRqwmdCoA= +github.com/cheggaaa/pb/v3 v3.0.8/go.mod h1:UICbiLec/XO6Hw6k+BHEtHeQFzzBH4i2/qk/ow1EJTA= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/gologme/log v1.2.0 h1:Ya5Ip/KD6FX7uH0S31QO87nCCSucKtF44TLbTtO7V4c= github.com/gologme/log v1.2.0/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= @@ -19,15 +20,14 @@ github.com/kardianos/minwinsvc v1.0.0 h1:+JfAi8IBJna0jY2dJGZqi7o15z13JelFIklJCAE github.com/kardianos/minwinsvc v1.0.0/go.mod h1:Bgd0oc+D0Qo3bBytmNtyRKVlp85dAloLKhfxanPFFRc= github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= -github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= +github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -40,14 +40,16 @@ github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvV github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210610132358-84b48f89b13b h1:k+E048sYJHyVnsr1GDrRZWQ32D2C7lWs9JRc0bel53A= +golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -56,20 +58,22 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225014209-683adc9d29d7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305215415-5cdee2b1b5a0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b h1:ggRgirZABFolTmi3sn6Ivd9SipZwLedQ5wR0aAKnFxU= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210611083646-a4fc73990273 h1:faDu4veV+8pcThn4fewv6TVlNCezafGoC1gM/mxQLbQ= +golang.org/x/sys v0.0.0-20210611083646-a4fc73990273/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6-0.20210220033129-8f690f22cf1c h1:SW/oilbeWd6f32u3ZvuYGqZ+wivcp//I3Dy/gByk7Wk= -golang.org/x/text v0.3.6-0.20210220033129-8f690f22cf1c/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7-0.20210503195748-5c7c50ebbd4f h1:yQJrRE0hDxDFmZLlRaw+3vusO4fwNHgHIjUOMO7bHYI= +golang.org/x/text v0.3.7-0.20210503195748-5c7c50ebbd4f/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.zx2c4.com/wireguard v0.0.0-20210225140808-70b7b7158fc9/go.mod h1:39ZQQ95hUxDxT7opsWy/rtfgvXXc8s30qfZ02df69Fo= -golang.zx2c4.com/wireguard v0.0.0-20210306175010-7e3b8371a1bf h1:AtdIMfzvVNPXN4kVY/yWS8mvpQogSwtCRJk2y/LBPpg= -golang.zx2c4.com/wireguard v0.0.0-20210306175010-7e3b8371a1bf/go.mod h1:ojGPy+9W6ZSM8anL+xC67fvh8zPQJwA6KpFOHyDWLX4= -golang.zx2c4.com/wireguard/windows v0.3.8 h1:FvfBEhdZZTwthLuPHdyP6zpivYL3enopxd4XpggAufM= -golang.zx2c4.com/wireguard/windows v0.3.8/go.mod h1:lm7dxHcBuzMNq706Ge1tZKZKw4+19vG9dLOhoDX05HQ= +golang.zx2c4.com/wireguard v0.0.0-20210510202332-9844c74f67ec/go.mod h1:a057zjmoc00UN7gVkaJt2sXVK523kMJcogDTEvPIasg= +golang.zx2c4.com/wireguard v0.0.0-20210604143328-f9b48a961cd2 h1:wfOOSvHgIzTZ9h5Vb6yUFZNn7uf3bT7PeYsHOO7tYDM= +golang.zx2c4.com/wireguard v0.0.0-20210604143328-f9b48a961cd2/go.mod h1:laHzsbfMhGSobUmruXWAyMKKHSqvIcrqZJMyHD+/3O8= +golang.zx2c4.com/wireguard/windows v0.3.14 h1:5yIDYyrQyGkLqV+tzY4ilMNeIvQeMXAz0glZz9u179A= +golang.zx2c4.com/wireguard/windows v0.3.14/go.mod h1:3P4IEAsb+BjlKZmpUXgy74c0iX9AVwwr3WcVJ8nPgME= From 1147ee19348af2df4dd0474cbe056d774710924b Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 13 Jun 2021 04:22:21 -0500 Subject: [PATCH 0730/1109] WIP moving IP-specific checks from tuntap to core --- src/core/api.go | 10 +- src/core/core.go | 27 ++- src/core/keystore.go | 295 +++++++++++++++++++++++++++++++ src/core/link.go | 4 +- src/{tuntap => core}/nodeinfo.go | 6 +- src/{tuntap => core}/proto.go | 20 +-- src/{tuntap => core}/types.go | 2 +- src/tuntap/keystore.go | 157 ---------------- src/tuntap/tun.go | 38 ---- 9 files changed, 339 insertions(+), 220 deletions(-) create mode 100644 src/core/keystore.go rename src/{tuntap => core}/nodeinfo.go (96%) rename src/{tuntap => core}/proto.go (95%) rename src/{tuntap => core}/types.go (96%) delete mode 100644 src/tuntap/keystore.go diff --git a/src/core/api.go b/src/core/api.go index f28b592a..bfccd66a 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -46,7 +46,7 @@ type Session struct { func (c *Core) GetSelf() Self { var self Self - s := c.PacketConn.PacketConn.Debug.GetSelf() + s := c.pc.PacketConn.Debug.GetSelf() self.Key = s.Key self.Root = s.Root self.Coords = s.Coords @@ -55,7 +55,7 @@ func (c *Core) GetSelf() Self { func (c *Core) GetPeers() []Peer { var peers []Peer - ps := c.PacketConn.PacketConn.Debug.GetPeers() + ps := c.pc.PacketConn.Debug.GetPeers() for _, p := range ps { var info Peer info.Key = p.Key @@ -69,7 +69,7 @@ func (c *Core) GetPeers() []Peer { func (c *Core) GetDHT() []DHTEntry { var dhts []DHTEntry - ds := c.PacketConn.PacketConn.Debug.GetDHT() + ds := c.pc.PacketConn.Debug.GetDHT() for _, d := range ds { var info DHTEntry info.Key = d.Key @@ -82,7 +82,7 @@ func (c *Core) GetDHT() []DHTEntry { func (c *Core) GetPaths() []PathEntry { var paths []PathEntry - ps := c.PacketConn.PacketConn.Debug.GetPaths() + ps := c.pc.PacketConn.Debug.GetPaths() for _, p := range ps { var info PathEntry info.Key = p.Key @@ -94,7 +94,7 @@ func (c *Core) GetPaths() []PathEntry { func (c *Core) GetSessions() []Session { var sessions []Session - ss := c.PacketConn.Debug.GetSessions() + ss := c.pc.Debug.GetSessions() for _, s := range ss { var info Session info.Key = s.Key diff --git a/src/core/core.go b/src/core/core.go index bd2478fc..cb34d894 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -25,11 +25,13 @@ type Core struct { // We're going to keep our own copy of the provided config - that way we can // guarantee that it will be covered by the mutex phony.Inbox - *iw.PacketConn + pc *iw.PacketConn config *config.NodeConfig // Config secret ed25519.PrivateKey public ed25519.PublicKey links links + proto protoHandler + store keyStore log *log.Logger addPeerTimer *time.Timer ctx context.Context @@ -59,8 +61,10 @@ func (c *Core) _init() error { c.public = c.secret.Public().(ed25519.PublicKey) // TODO check public against current.PublicKey, error if they don't match - c.PacketConn, err = iw.NewPacketConn(c.secret) + c.pc, err = iw.NewPacketConn(c.secret) c.ctx, c.ctxCancel = context.WithCancel(context.Background()) + c.store.init(c) + c.proto.init(c) return err } @@ -160,7 +164,7 @@ func (c *Core) Stop() { func (c *Core) _stop() { c.log.Infoln("Stopping...") c.ctxCancel() - c.PacketConn.Close() + c.pc.Close() if c.addPeerTimer != nil { c.addPeerTimer.Stop() c.addPeerTimer = nil @@ -173,3 +177,20 @@ func (c *Core) _stop() { */ c.log.Infoln("Stopped") } + +// Implement io.ReadWriteCloser + +func (c *Core) Read(p []byte) (n int, err error) { + n, err = c.store.readPC(p) + return +} + +func (c *Core) Write(p []byte) (n int, err error) { + n, err = c.store.writePC(p) + return +} + +func (c *Core) Close() error { + c.Stop() + return nil +} diff --git a/src/core/keystore.go b/src/core/keystore.go new file mode 100644 index 00000000..43424881 --- /dev/null +++ b/src/core/keystore.go @@ -0,0 +1,295 @@ +package core + +import ( + "crypto/ed25519" + "errors" + "fmt" + "sync" + "time" + + iwt "github.com/Arceliar/ironwood/types" + + "github.com/yggdrasil-network/yggdrasil-go/src/address" +) + +const keyStoreTimeout = 2 * time.Minute + +type keyArray [ed25519.PublicKeySize]byte + +type keyStore struct { + core *Core + address address.Address + subnet address.Subnet + mutex sync.Mutex + keyToInfo map[keyArray]*keyInfo + addrToInfo map[address.Address]*keyInfo + addrBuffer map[address.Address]*buffer + subnetToInfo map[address.Subnet]*keyInfo + subnetBuffer map[address.Subnet]*buffer + buf []byte // scratch space to prefix with typeSessionTraffic before sending +} + +type keyInfo struct { + key keyArray + address address.Address + subnet address.Subnet + timeout *time.Timer // From calling a time.AfterFunc to do cleanup +} + +type buffer struct { + packets [][]byte + timeout *time.Timer +} + +func (k *keyStore) init(core *Core) { + k.core = core + k.address = *address.AddrForKey(k.core.public) + k.subnet = *address.SubnetForKey(k.core.public) + k.core.pc.SetOutOfBandHandler(k.oobHandler) + k.keyToInfo = make(map[keyArray]*keyInfo) + k.addrToInfo = make(map[address.Address]*keyInfo) + k.addrBuffer = make(map[address.Address]*buffer) + k.subnetToInfo = make(map[address.Subnet]*keyInfo) + k.subnetBuffer = make(map[address.Subnet]*buffer) +} + +func (k *keyStore) sendToAddress(addr address.Address, bs []byte) { + k.mutex.Lock() + if info := k.addrToInfo[addr]; info != nil { + k.resetTimeout(info) + k.mutex.Unlock() + _, _ = k.core.pc.WriteTo(bs, iwt.Addr(info.key[:])) + } else { + var buf *buffer + if buf = k.addrBuffer[addr]; buf == nil { + buf = new(buffer) + k.addrBuffer[addr] = buf + } + msg := append([]byte(nil), bs...) + buf.packets = append(buf.packets, msg) + if buf.timeout != nil { + buf.timeout.Stop() + } + buf.timeout = time.AfterFunc(keyStoreTimeout, func() { + k.mutex.Lock() + defer k.mutex.Unlock() + if nbuf := k.addrBuffer[addr]; nbuf == buf { + delete(k.addrBuffer, addr) + } + }) + k.mutex.Unlock() + k.sendKeyLookup(addr.GetKey()) + } +} + +func (k *keyStore) sendToSubnet(subnet address.Subnet, bs []byte) { + k.mutex.Lock() + if info := k.subnetToInfo[subnet]; info != nil { + k.resetTimeout(info) + k.mutex.Unlock() + _, _ = k.core.pc.WriteTo(bs, iwt.Addr(info.key[:])) + } else { + var buf *buffer + if buf = k.subnetBuffer[subnet]; buf == nil { + buf = new(buffer) + k.subnetBuffer[subnet] = buf + } + msg := append([]byte(nil), bs...) + buf.packets = append(buf.packets, msg) + if buf.timeout != nil { + buf.timeout.Stop() + } + buf.timeout = time.AfterFunc(keyStoreTimeout, func() { + k.mutex.Lock() + defer k.mutex.Unlock() + if nbuf := k.subnetBuffer[subnet]; nbuf == buf { + delete(k.subnetBuffer, subnet) + } + }) + k.mutex.Unlock() + k.sendKeyLookup(subnet.GetKey()) + } +} + +func (k *keyStore) update(key ed25519.PublicKey) *keyInfo { + k.mutex.Lock() + var kArray keyArray + copy(kArray[:], key) + var info *keyInfo + if info = k.keyToInfo[kArray]; info == nil { + info = new(keyInfo) + info.key = kArray + info.address = *address.AddrForKey(ed25519.PublicKey(info.key[:])) + info.subnet = *address.SubnetForKey(ed25519.PublicKey(info.key[:])) + k.keyToInfo[info.key] = info + k.addrToInfo[info.address] = info + k.subnetToInfo[info.subnet] = info + k.resetTimeout(info) + k.mutex.Unlock() + if buf := k.addrBuffer[info.address]; buf != nil { + for _, bs := range buf.packets { + _, _ = k.core.pc.WriteTo(bs, iwt.Addr(info.key[:])) + } + delete(k.addrBuffer, info.address) + } + if buf := k.subnetBuffer[info.subnet]; buf != nil { + for _, bs := range buf.packets { + _, _ = k.core.pc.WriteTo(bs, iwt.Addr(info.key[:])) + } + delete(k.subnetBuffer, info.subnet) + } + } else { + k.resetTimeout(info) + k.mutex.Unlock() + } + return info +} + +func (k *keyStore) resetTimeout(info *keyInfo) { + if info.timeout != nil { + info.timeout.Stop() + } + info.timeout = time.AfterFunc(keyStoreTimeout, func() { + k.mutex.Lock() + defer k.mutex.Unlock() + if nfo := k.keyToInfo[info.key]; nfo == info { + delete(k.keyToInfo, info.key) + } + if nfo := k.addrToInfo[info.address]; nfo == info { + delete(k.addrToInfo, info.address) + } + if nfo := k.subnetToInfo[info.subnet]; nfo == info { + delete(k.subnetToInfo, info.subnet) + } + }) +} + +func (k *keyStore) oobHandler(fromKey, toKey ed25519.PublicKey, data []byte) { + if len(data) != 1+ed25519.SignatureSize { + return + } + sig := data[1:] + switch data[0] { + case typeKeyLookup: + snet := *address.SubnetForKey(toKey) + if snet == k.subnet && ed25519.Verify(fromKey, toKey[:], sig) { + // This is looking for at least our subnet (possibly our address) + // Send a response + k.sendKeyResponse(fromKey) + } + case typeKeyResponse: + // TODO keep a list of something to match against... + // Ignore the response if it doesn't match anything of interest... + if ed25519.Verify(fromKey, toKey[:], sig) { + k.update(fromKey) + } + } +} + +func (k *keyStore) sendKeyLookup(partial ed25519.PublicKey) { + sig := ed25519.Sign(k.core.secret, partial[:]) + bs := append([]byte{typeKeyLookup}, sig...) + _ = k.core.pc.SendOutOfBand(partial, bs) +} + +func (k *keyStore) sendKeyResponse(dest ed25519.PublicKey) { + sig := ed25519.Sign(k.core.secret, dest[:]) + bs := append([]byte{typeKeyResponse}, sig...) + _ = k.core.pc.SendOutOfBand(dest, bs) +} + +func (k *keyStore) maxSessionMTU() uint64 { + const sessionTypeOverhead = 1 + return k.core.pc.MTU() - sessionTypeOverhead +} + +func (k *keyStore) readPC(p []byte) (int, error) { + for { + bs := p + n, from, err := k.core.pc.ReadFrom(bs) + if err != nil { + return n, err + } + if n == 0 { + continue + } + switch bs[0] { + case typeSessionTraffic: + // This is what we want to handle here + case typeSessionProto: + var key keyArray + copy(key[:], from.(iwt.Addr)) + data := append([]byte(nil), bs[1:n]...) + k.core.proto.handleProto(nil, key, data) + continue + default: + continue + } + bs = bs[1:n] + if len(bs) == 0 { + continue + } + if bs[0]&0xf0 != 0x60 { + continue // not IPv6 + } + if len(bs) < 40 { + continue + } + /* TODO ICMP packet too big + if len(bs) > int(tun.MTU()) { + ptb := &icmp.PacketTooBig{ + MTU: int(tun.mtu), + Data: bs[:40], + } + if packet, err := CreateICMPv6(bs[8:24], bs[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb); err == nil { + _, _ = tun.core.WriteTo(packet, from) + } + continue + } + */ + var srcAddr, dstAddr address.Address + var srcSubnet, dstSubnet address.Subnet + copy(srcAddr[:], bs[8:]) + copy(dstAddr[:], bs[24:]) + copy(srcSubnet[:], bs[8:]) + copy(dstSubnet[:], bs[24:]) + if dstAddr != k.address && dstSubnet != k.subnet { + continue // bad local address/subnet + } + info := k.update(ed25519.PublicKey(from.(iwt.Addr))) + if srcAddr != info.address && srcSubnet != info.subnet { + continue // bad remote address/subnet + } + n = copy(p, bs) + return n, nil + } +} + +func (k *keyStore) writePC(bs []byte) (int, error) { + if bs[0]&0xf0 != 0x60 { + return 0, errors.New("not an IPv6 packet") // not IPv6 + } + if len(bs) < 40 { + strErr := fmt.Sprint("undersized IPv6 packet, length:", len(bs)) + return 0, errors.New(strErr) + } + var srcAddr, dstAddr address.Address + var srcSubnet, dstSubnet address.Subnet + copy(srcAddr[:], bs[8:]) + copy(dstAddr[:], bs[24:]) + copy(srcSubnet[:], bs[8:]) + copy(dstSubnet[:], bs[24:]) + if srcAddr != k.address && srcSubnet != k.subnet { + return 0, errors.New("wrong source address") + } + k.buf = append(k.buf[:0], typeSessionTraffic) + k.buf = append(k.buf, bs...) + if dstAddr.IsValid() { + k.sendToAddress(dstAddr, k.buf) + } else if dstSubnet.IsValid() { + k.sendToSubnet(dstSubnet, k.buf) + } else { + return 0, errors.New("invalid destination address") + } + return len(bs), nil +} diff --git a/src/core/link.go b/src/core/link.go index c0eb509f..295a8ad6 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -20,8 +20,6 @@ import ( //"github.com/Arceliar/phony" // TODO? use instead of mutexes ) -type keyArray [ed25519.PublicKeySize]byte - type links struct { core *Core mutex sync.RWMutex // protects links below @@ -231,7 +229,7 @@ func (intf *link) handler() (chan struct{}, error) { intf.links.core.log.Infof("Connected %s: %s, source %s", strings.ToUpper(intf.info.linkType), themString, intf.info.local) // Run the handler - err = intf.links.core.PacketConn.HandleConn(ed25519.PublicKey(intf.info.key[:]), intf.conn) + err = intf.links.core.pc.HandleConn(ed25519.PublicKey(intf.info.key[:]), intf.conn) // TODO don't report an error if it's just a 'use of closed network connection' if err != nil { intf.links.core.log.Infof("Disconnected %s: %s, source %s; error: %s", diff --git a/src/tuntap/nodeinfo.go b/src/core/nodeinfo.go similarity index 96% rename from src/tuntap/nodeinfo.go rename to src/core/nodeinfo.go index a61a4429..30644710 100644 --- a/src/tuntap/nodeinfo.go +++ b/src/core/nodeinfo.go @@ -1,4 +1,4 @@ -package tuntap +package core import ( "encoding/hex" @@ -129,7 +129,7 @@ func (m *nodeinfo) _sendReq(key keyArray, callback func(nodeinfo NodeInfoPayload if callback != nil { m._addCallback(key, callback) } - _, _ = m.proto.tun.core.WriteTo([]byte{typeSessionProto, typeProtoNodeInfoRequest}, iwt.Addr(key[:])) + _, _ = m.proto.core.pc.WriteTo([]byte{typeSessionProto, typeProtoNodeInfoRequest}, iwt.Addr(key[:])) } func (m *nodeinfo) handleReq(from phony.Actor, key keyArray) { @@ -146,7 +146,7 @@ func (m *nodeinfo) handleRes(from phony.Actor, key keyArray, info NodeInfoPayloa func (m *nodeinfo) _sendRes(key keyArray) { bs := append([]byte{typeSessionProto, typeProtoNodeInfoResponse}, m._getNodeInfo()...) - _, _ = m.proto.tun.core.WriteTo(bs, iwt.Addr(key[:])) + _, _ = m.proto.core.pc.WriteTo(bs, iwt.Addr(key[:])) } // Admin socket stuff diff --git a/src/tuntap/proto.go b/src/core/proto.go similarity index 95% rename from src/tuntap/proto.go rename to src/core/proto.go index 62b21563..24d54caf 100644 --- a/src/tuntap/proto.go +++ b/src/core/proto.go @@ -1,4 +1,4 @@ -package tuntap +package core import ( "encoding/hex" @@ -31,15 +31,15 @@ type reqInfo struct { type protoHandler struct { phony.Inbox - tun *TunAdapter + core *Core nodeinfo nodeinfo sreqs map[keyArray]*reqInfo preqs map[keyArray]*reqInfo dreqs map[keyArray]*reqInfo } -func (p *protoHandler) init(tun *TunAdapter) { - p.tun = tun +func (p *protoHandler) init(core *Core) { + p.core = core p.nodeinfo.init(p) p.sreqs = make(map[keyArray]*reqInfo) p.preqs = make(map[keyArray]*reqInfo) @@ -103,7 +103,7 @@ func (p *protoHandler) sendGetSelfRequest(key keyArray, callback func([]byte)) { } func (p *protoHandler) _handleGetSelfRequest(key keyArray) { - self := p.tun.core.GetSelf() + self := p.core.GetSelf() res := map[string]string{ "key": hex.EncodeToString(self.Key[:]), "coords": fmt.Sprintf("%v", self.Coords), @@ -144,12 +144,12 @@ func (p *protoHandler) sendGetPeersRequest(key keyArray, callback func([]byte)) } func (p *protoHandler) _handleGetPeersRequest(key keyArray) { - peers := p.tun.core.GetPeers() + peers := p.core.GetPeers() var bs []byte for _, pinfo := range peers { tmp := append(bs, pinfo.Key[:]...) const responseOverhead = 2 // 1 debug type, 1 getpeers type - if uint64(len(tmp))+responseOverhead > p.tun.maxSessionMTU() { + if uint64(len(tmp))+responseOverhead > p.core.store.maxSessionMTU() { break } bs = tmp @@ -186,12 +186,12 @@ func (p *protoHandler) sendGetDHTRequest(key keyArray, callback func([]byte)) { } func (p *protoHandler) _handleGetDHTRequest(key keyArray) { - dinfos := p.tun.core.GetDHT() + dinfos := p.core.GetDHT() var bs []byte for _, dinfo := range dinfos { tmp := append(bs, dinfo.Key[:]...) const responseOverhead = 2 // 1 debug type, 1 getdht type - if uint64(len(tmp))+responseOverhead > p.tun.maxSessionMTU() { + if uint64(len(tmp))+responseOverhead > p.core.store.maxSessionMTU() { break } bs = tmp @@ -209,7 +209,7 @@ func (p *protoHandler) _handleGetDHTResponse(key keyArray, bs []byte) { func (p *protoHandler) _sendDebug(key keyArray, dType uint8, data []byte) { bs := append([]byte{typeSessionProto, typeProtoDebug, dType}, data...) - _, _ = p.tun.core.WriteTo(bs, iwt.Addr(key[:])) + _, _ = p.core.pc.WriteTo(bs, iwt.Addr(key[:])) } // Admin socket stuff diff --git a/src/tuntap/types.go b/src/core/types.go similarity index 96% rename from src/tuntap/types.go rename to src/core/types.go index a8084e0c..e325b55e 100644 --- a/src/tuntap/types.go +++ b/src/core/types.go @@ -1,4 +1,4 @@ -package tuntap +package core // Out-of-band packet types const ( diff --git a/src/tuntap/keystore.go b/src/tuntap/keystore.go deleted file mode 100644 index 2eb79202..00000000 --- a/src/tuntap/keystore.go +++ /dev/null @@ -1,157 +0,0 @@ -package tuntap - -import ( - "crypto/ed25519" - "sync" - "time" - - iwt "github.com/Arceliar/ironwood/types" - - "github.com/yggdrasil-network/yggdrasil-go/src/address" -) - -const keyStoreTimeout = 2 * time.Minute - -type keyStore struct { - tun *TunAdapter - mutex sync.Mutex - keyToInfo map[keyArray]*keyInfo - addrToInfo map[address.Address]*keyInfo - addrBuffer map[address.Address]*buffer - subnetToInfo map[address.Subnet]*keyInfo - subnetBuffer map[address.Subnet]*buffer -} - -type keyArray [ed25519.PublicKeySize]byte - -type keyInfo struct { - key keyArray - address address.Address - subnet address.Subnet - timeout *time.Timer // From calling a time.AfterFunc to do cleanup -} - -type buffer struct { - packets [][]byte - timeout *time.Timer -} - -func (k *keyStore) init(tun *TunAdapter) { - k.tun = tun - k.keyToInfo = make(map[keyArray]*keyInfo) - k.addrToInfo = make(map[address.Address]*keyInfo) - k.addrBuffer = make(map[address.Address]*buffer) - k.subnetToInfo = make(map[address.Subnet]*keyInfo) - k.subnetBuffer = make(map[address.Subnet]*buffer) -} - -func (k *keyStore) sendToAddress(addr address.Address, bs []byte) { - k.mutex.Lock() - if info := k.addrToInfo[addr]; info != nil { - k.resetTimeout(info) - k.mutex.Unlock() - _, _ = k.tun.core.WriteTo(bs, iwt.Addr(info.key[:])) - } else { - var buf *buffer - if buf = k.addrBuffer[addr]; buf == nil { - buf = new(buffer) - k.addrBuffer[addr] = buf - } - msg := append([]byte(nil), bs...) - buf.packets = append(buf.packets, msg) - if buf.timeout != nil { - buf.timeout.Stop() - } - buf.timeout = time.AfterFunc(keyStoreTimeout, func() { - k.mutex.Lock() - defer k.mutex.Unlock() - if nbuf := k.addrBuffer[addr]; nbuf == buf { - delete(k.addrBuffer, addr) - } - }) - k.mutex.Unlock() - k.tun.sendKeyLookup(addr.GetKey()) - } -} - -func (k *keyStore) sendToSubnet(subnet address.Subnet, bs []byte) { - k.mutex.Lock() - if info := k.subnetToInfo[subnet]; info != nil { - k.resetTimeout(info) - k.mutex.Unlock() - _, _ = k.tun.core.WriteTo(bs, iwt.Addr(info.key[:])) - } else { - var buf *buffer - if buf = k.subnetBuffer[subnet]; buf == nil { - buf = new(buffer) - k.subnetBuffer[subnet] = buf - } - msg := append([]byte(nil), bs...) - buf.packets = append(buf.packets, msg) - if buf.timeout != nil { - buf.timeout.Stop() - } - buf.timeout = time.AfterFunc(keyStoreTimeout, func() { - k.mutex.Lock() - defer k.mutex.Unlock() - if nbuf := k.subnetBuffer[subnet]; nbuf == buf { - delete(k.subnetBuffer, subnet) - } - }) - k.mutex.Unlock() - k.tun.sendKeyLookup(subnet.GetKey()) - } -} - -func (k *keyStore) update(key ed25519.PublicKey) *keyInfo { - k.mutex.Lock() - var kArray keyArray - copy(kArray[:], key) - var info *keyInfo - if info = k.keyToInfo[kArray]; info == nil { - info = new(keyInfo) - info.key = kArray - info.address = *address.AddrForKey(ed25519.PublicKey(info.key[:])) - info.subnet = *address.SubnetForKey(ed25519.PublicKey(info.key[:])) - k.keyToInfo[info.key] = info - k.addrToInfo[info.address] = info - k.subnetToInfo[info.subnet] = info - k.resetTimeout(info) - k.mutex.Unlock() - if buf := k.addrBuffer[info.address]; buf != nil { - for _, bs := range buf.packets { - _, _ = k.tun.core.WriteTo(bs, iwt.Addr(info.key[:])) - } - delete(k.addrBuffer, info.address) - } - if buf := k.subnetBuffer[info.subnet]; buf != nil { - for _, bs := range buf.packets { - _, _ = k.tun.core.WriteTo(bs, iwt.Addr(info.key[:])) - } - delete(k.subnetBuffer, info.subnet) - } - } else { - k.resetTimeout(info) - k.mutex.Unlock() - } - return info -} - -func (k *keyStore) resetTimeout(info *keyInfo) { - if info.timeout != nil { - info.timeout.Stop() - } - info.timeout = time.AfterFunc(keyStoreTimeout, func() { - k.mutex.Lock() - defer k.mutex.Unlock() - if nfo := k.keyToInfo[info.key]; nfo == info { - delete(k.keyToInfo, info.key) - } - if nfo := k.addrToInfo[info.address]; nfo == info { - delete(k.addrToInfo, info.address) - } - if nfo := k.subnetToInfo[info.subnet]; nfo == info { - delete(k.subnetToInfo, info.subnet) - } - }) -} diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index f53f6dea..7b20bfb2 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -189,41 +189,3 @@ func (tun *TunAdapter) _stop() error { return nil } -func (tun *TunAdapter) oobHandler(fromKey, toKey ed25519.PublicKey, data []byte) { - if len(data) != 1+ed25519.SignatureSize { - return - } - sig := data[1:] - switch data[0] { - case typeKeyLookup: - snet := *address.SubnetForKey(toKey) - if snet == tun.subnet && ed25519.Verify(fromKey, toKey[:], sig) { - // This is looking for at least our subnet (possibly our address) - // Send a response - tun.sendKeyResponse(fromKey) - } - case typeKeyResponse: - // TODO keep a list of something to match against... - // Ignore the response if it doesn't match anything of interest... - if ed25519.Verify(fromKey, toKey[:], sig) { - tun.store.update(fromKey) - } - } -} - -func (tun *TunAdapter) sendKeyLookup(partial ed25519.PublicKey) { - sig := ed25519.Sign(tun.core.PrivateKey(), partial[:]) - bs := append([]byte{typeKeyLookup}, sig...) - _ = tun.core.SendOutOfBand(partial, bs) -} - -func (tun *TunAdapter) sendKeyResponse(dest ed25519.PublicKey) { - sig := ed25519.Sign(tun.core.PrivateKey(), dest[:]) - bs := append([]byte{typeKeyResponse}, sig...) - _ = tun.core.SendOutOfBand(dest, bs) -} - -func (tun *TunAdapter) maxSessionMTU() uint64 { - const sessionTypeOverhead = 1 - return tun.core.MTU() - sessionTypeOverhead -} From 45d6a1e6e57dbcd92ddeddb9e3d24bf3d47e1d7a Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 13 Jun 2021 10:42:31 +0100 Subject: [PATCH 0731/1109] Revert "Build MSIs for Windows using CircleCI (#766)" This reverts commit f0a5cd542cbf995ead8f3683adf427bf77978b60. --- .circleci/config.yml | 46 ++++-------------------- appveyor.yml | 20 +++++++++++ contrib/msi/build-msi.sh | 78 ++++++++++++++++++++++++++-------------- 3 files changed, 78 insertions(+), 66 deletions(-) create mode 100644 appveyor.yml diff --git a/.circleci/config.yml b/.circleci/config.yml index 6e41648c..5ebd26e2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -162,43 +162,6 @@ jobs: paths: - upload - build-windows: - docker: - - image: circleci/golang:1.16 - - steps: - - checkout - - - run: - name: Create artifact upload directory and set variables - command: | - mkdir /tmp/upload - echo 'export CINAME=$(sh contrib/semver/name.sh)' >> $BASH_ENV - echo 'export CIVERSION=$(sh contrib/semver/version.sh --bare)' >> $BASH_ENV - git config --global user.email "$(git log --format='%ae' HEAD -1)"; - git config --global user.name "$(git log --format='%an' HEAD -1)"; - - - run: - name: Install tools - command: | - sudo apt-get update - sudo apt-get -y install msitools wixl - - - run: - name: Build for Windows - command: | - rm -f {yggdrasil,yggdrasilctl} - GOOS=windows GOARCH=amd64 ./build && mv yggdrasil.exe /tmp/upload/$CINAME-$CIVERSION-windows-amd64.exe && mv yggdrasilctl.exe /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-windows-amd64.exe; - GOOS=windows GOARCH=386 ./build && mv yggdrasil.exe /tmp/upload/$CINAME-$CIVERSION-windows-i386.exe && mv yggdrasilctl.exe /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-windows-i386.exe; - bash contrib/msi/build-msi.sh x64 - bash contrib/msi/build-msi.sh x86 - mv *.msi /tmp/upload - - - persist_to_workspace: - root: /tmp - paths: - - upload - build-other: docker: - image: circleci/golang:1.16 @@ -229,6 +192,13 @@ jobs: GOOS=freebsd GOARCH=amd64 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-freebsd-amd64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-freebsd-amd64; GOOS=freebsd GOARCH=386 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-freebsd-i386 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-freebsd-i386; + - run: + name: Build for Windows + command: | + rm -f {yggdrasil,yggdrasilctl} + GOOS=windows GOARCH=amd64 ./build && mv yggdrasil.exe /tmp/upload/$CINAME-$CIVERSION-windows-amd64.exe && mv yggdrasilctl.exe /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-windows-amd64.exe; + GOOS=windows GOARCH=386 ./build && mv yggdrasil.exe /tmp/upload/$CINAME-$CIVERSION-windows-i386.exe && mv yggdrasilctl.exe /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-windows-i386.exe; + - persist_to_workspace: root: /tmp paths: @@ -252,11 +222,9 @@ workflows: - lint - build-linux - build-macos - - build-windows - build-other - upload: requires: - build-linux - build-macos - - build-windows - build-other diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..58724a24 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,20 @@ +version: '{build}' +pull_requests: + do_not_increment_build_number: true +os: Visual Studio 2017 +shallow_clone: false + +environment: + MSYS2_PATH_TYPE: inherit + CHERE_INVOKING: enabled_from_arguments + +build_script: +- cmd: >- + cd %APPVEYOR_BUILD_FOLDER% +- c:\msys64\usr\bin\bash -lc "./contrib/msi/build-msi.sh x64" +- c:\msys64\usr\bin\bash -lc "./contrib/msi/build-msi.sh x86" + +test: off + +artifacts: +- path: '*.msi' diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index 57d4e81d..421481cd 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -1,7 +1,9 @@ -#!/bin/bash +#!/bin/sh # This script generates an MSI file for Yggdrasil for a given architecture. It -# needs to run on Linux or macOS with Go 1.16, wixl and msitools installed. +# needs to run on Windows within MSYS2 and Go 1.13 or later must be installed on +# the system and within the PATH. This is ran currently by Appveyor (see +# appveyor.yml in the repository root) for both x86 and x64. # # Author: Neil Alexander @@ -9,7 +11,7 @@ PKGARCH=$1 if [ "${PKGARCH}" == "" ]; then - echo "tell me the architecture: x86, x64 or arm" + echo "tell me the architecture: x86 or x64" exit 1 fi @@ -26,11 +28,28 @@ then git checkout ${APPVEYOR_REPO_BRANCH} fi +# Install prerequisites within MSYS2 +pacman -S --needed --noconfirm unzip git curl + +# Download the wix tools! +if [ ! -d wixbin ]; +then + curl -LO https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip + if [ `md5sum wix311-binaries.zip | cut -f 1 -d " "` != "47a506f8ab6666ee3cc502fb07d0ee2a" ]; + then + echo "wix package didn't match expected checksum" + exit 1 + fi + mkdir -p wixbin + unzip -o wix311-binaries.zip -d wixbin || ( + echo "failed to unzip WiX" + exit 1 + ) +fi + # Build Yggdrasil! -[ "${PKGARCH}" == "x64" ] && GOOS=windows GOARCH=amd64 CGO_ENABLED=0 ./build -p -l "-aslr" -[ "${PKGARCH}" == "x86" ] && GOOS=windows GOARCH=386 CGO_ENABLED=0 ./build -p -l "-aslr" -[ "${PKGARCH}" == "arm" ] && GOOS=windows GOARCH=arm CGO_ENABLED=0 ./build -p -l "-aslr" -#[ "${PKGARCH}" == "arm64" ] && GOOS=windows GOARCH=arm64 CGO_ENABLED=0 ./build +[ "${PKGARCH}" == "x64" ] && GOOS=windows GOARCH=amd64 CGO_ENABLED=0 ./build +[ "${PKGARCH}" == "x86" ] && GOOS=windows GOARCH=386 CGO_ENABLED=0 ./build # Create the postinstall script cat > updateconfig.bat << EOF @@ -39,9 +58,7 @@ if not exist %ALLUSERSPROFILE%\\Yggdrasil ( ) if not exist %ALLUSERSPROFILE%\\Yggdrasil\\yggdrasil.conf ( if exist yggdrasil.exe ( - if not exist %ALLUSERSPROFILE%\\Yggdrasil\\yggdrasil.conf ( - yggdrasil.exe -genconf > %ALLUSERSPROFILE%\\Yggdrasil\\yggdrasil.conf - ) + yggdrasil.exe -genconf > %ALLUSERSPROFILE%\\Yggdrasil\\yggdrasil.conf ) ) EOF @@ -55,16 +72,12 @@ PKGVERSIONMS=$(echo $PKGVERSION | tr - .) PKGGUID="54a3294e-a441-4322-aefb-3bb40dd022bb" PKGINSTFOLDER="ProgramFilesFolder" # Download the Wintun driver -curl -o wintun.zip https://www.wintun.net/builds/wintun-0.10.2.zip -unzip wintun.zip if [ $PKGARCH = "x64" ]; then - PKGWINTUNDLL=wintun/bin/amd64/wintun.dll + PKGMSMNAME=wintun-x64.msm + curl -o ${PKGMSMNAME} https://www.wintun.net/builds/wintun-amd64-0.7.msm || (echo "couldn't get wintun"; exit 1) elif [ $PKGARCH = "x86" ]; then - PKGWINTUNDLL=wintun/bin/x86/wintun.dll -elif [ $PKGARCH = "arm" ]; then - PKGWINTUNDLL=wintun/bin/arm/wintun.dll -#elif [ $PKGARCH = "arm64" ]; then -# PKGWINTUNDLL=wintun/bin/arm64/wintun.dll + PKGMSMNAME=wintun-x86.msm + curl -o ${PKGMSMNAME} https://www.wintun.net/builds/wintun-x86-0.7.msm || (echo "couldn't get wintun"; exit 1) else echo "wasn't sure which architecture to get wintun for" exit 1 @@ -87,7 +100,6 @@ cat > wix.xml << EOF Language="1033" Codepage="1252" Version="${PKGVERSIONMS}" - Platform="${PKGARCH}" Manufacturer="github.com/yggdrasil-network"> wix.xml << EOF Source="yggdrasil.exe" KeyPath="yes" /> - - wix.xml << EOF + + @@ -178,6 +190,13 @@ cat > wix.xml << EOF + + + UPGRADINGPRODUCTCODE + + + + wix.xml << EOF + Before="StartServices"> + NOT Installed AND NOT REMOVE + @@ -197,4 +218,7 @@ cat > wix.xml << EOF EOF # Generate the MSI -wixl -v wix.xml -a ${PKGARCH} -o ${PKGNAME}-${PKGVERSION}-${PKGARCH}.msi +CANDLEFLAGS="-nologo" +LIGHTFLAGS="-nologo -spdb -sice:ICE71 -sice:ICE61" +wixbin/candle $CANDLEFLAGS -out ${PKGNAME}-${PKGVERSION}-${PKGARCH}.wixobj -arch ${PKGARCH} wix.xml && \ +wixbin/light $LIGHTFLAGS -ext WixUtilExtension.dll -out ${PKGNAME}-${PKGVERSION}-${PKGARCH}.msi ${PKGNAME}-${PKGVERSION}-${PKGARCH}.wixobj From 3b669a15edf01722b9a68c74e6c1eb0987c017e2 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 13 Jun 2021 10:47:14 +0100 Subject: [PATCH 0732/1109] Update build-msi.sh --- contrib/msi/build-msi.sh | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index 421481cd..85cd8209 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -11,7 +11,7 @@ PKGARCH=$1 if [ "${PKGARCH}" == "" ]; then - echo "tell me the architecture: x86 or x64" + echo "tell me the architecture: x86, x64 or arm" exit 1 fi @@ -48,8 +48,10 @@ then fi # Build Yggdrasil! -[ "${PKGARCH}" == "x64" ] && GOOS=windows GOARCH=amd64 CGO_ENABLED=0 ./build -[ "${PKGARCH}" == "x86" ] && GOOS=windows GOARCH=386 CGO_ENABLED=0 ./build +[ "${PKGARCH}" == "x64" ] && GOOS=windows GOARCH=amd64 CGO_ENABLED=0 ./build -p -l "-aslr" +[ "${PKGARCH}" == "x86" ] && GOOS=windows GOARCH=386 CGO_ENABLED=0 ./build -p -l "-aslr" +[ "${PKGARCH}" == "arm" ] && GOOS=windows GOARCH=arm CGO_ENABLED=0 ./build -p -l "-aslr" +#[ "${PKGARCH}" == "arm64" ] && GOOS=windows GOARCH=arm64 CGO_ENABLED=0 ./build # Create the postinstall script cat > updateconfig.bat << EOF @@ -72,12 +74,16 @@ PKGVERSIONMS=$(echo $PKGVERSION | tr - .) PKGGUID="54a3294e-a441-4322-aefb-3bb40dd022bb" PKGINSTFOLDER="ProgramFilesFolder" # Download the Wintun driver +curl -o wintun.zip https://www.wintun.net/builds/wintun-0.11.zip +unzip wintun.zip if [ $PKGARCH = "x64" ]; then - PKGMSMNAME=wintun-x64.msm - curl -o ${PKGMSMNAME} https://www.wintun.net/builds/wintun-amd64-0.7.msm || (echo "couldn't get wintun"; exit 1) + PKGWINTUNDLL=wintun/bin/amd64/wintun.dll elif [ $PKGARCH = "x86" ]; then - PKGMSMNAME=wintun-x86.msm - curl -o ${PKGMSMNAME} https://www.wintun.net/builds/wintun-x86-0.7.msm || (echo "couldn't get wintun"; exit 1) + PKGWINTUNDLL=wintun/bin/x86/wintun.dll +elif [ $PKGARCH = "arm" ]; then + PKGWINTUNDLL=wintun/bin/arm/wintun.dll +#elif [ $PKGARCH = "arm64" ]; then +# PKGWINTUNDLL=wintun/bin/arm64/wintun.dll else echo "wasn't sure which architecture to get wintun for" exit 1 @@ -100,6 +106,7 @@ cat > wix.xml << EOF Language="1033" Codepage="1252" Version="${PKGVERSIONMS}" + Platform="${PKGARCH}" Manufacturer="github.com/yggdrasil-network"> wix.xml << EOF Source="yggdrasil.exe" KeyPath="yes" /> + + wix.xml << EOF - - @@ -190,13 +197,6 @@ cat > wix.xml << EOF - - - UPGRADINGPRODUCTCODE - - - - Date: Sun, 13 Jun 2021 04:54:06 -0500 Subject: [PATCH 0733/1109] mostly finish migration of IP stuff to core, tuntap is still responsible for ICMP PacketTooBig --- src/core/api.go | 25 +++++++++ src/core/core.go | 31 ++++------- src/core/keystore.go | 124 ++++++++++++++++++++++--------------------- src/core/proto.go | 2 +- src/tuntap/admin.go | 8 +-- src/tuntap/iface.go | 88 +++--------------------------- src/tuntap/tun.go | 21 ++------ 7 files changed, 114 insertions(+), 185 deletions(-) diff --git a/src/core/api.go b/src/core/api.go index bfccd66a..c886edf6 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -222,3 +222,28 @@ func (c *Core) RemovePeer(addr string, sintf string) error { func (c *Core) CallPeer(u *url.URL, sintf string) error { return c.links.call(u, sintf) } + +func (c *Core) PublicKey() ed25519.PublicKey { + return c.public +} + +func (c *Core) MaxMTU() uint64 { + return c.store.maxSessionMTU() +} + +// Implement io.ReadWriteCloser + +func (c *Core) Read(p []byte) (n int, err error) { + n, err = c.store.readPC(p) + return +} + +func (c *Core) Write(p []byte) (n int, err error) { + n, err = c.store.writePC(p) + return +} + +func (c *Core) Close() error { + c.Stop() + return nil +} diff --git a/src/core/core.go b/src/core/core.go index cb34d894..89d49177 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -5,6 +5,7 @@ import ( "crypto/ed25519" "encoding/hex" "errors" + "fmt" "io/ioutil" "net/url" "time" @@ -25,13 +26,13 @@ type Core struct { // We're going to keep our own copy of the provided config - that way we can // guarantee that it will be covered by the mutex phony.Inbox - pc *iw.PacketConn + pc *iw.PacketConn config *config.NodeConfig // Config secret ed25519.PrivateKey public ed25519.PublicKey links links - proto protoHandler - store keyStore + proto protoHandler + store keyStore log *log.Logger addPeerTimer *time.Timer ctx context.Context @@ -43,13 +44,13 @@ func (c *Core) _init() error { // Init sets up structs // Start launches goroutines that depend on structs being set up // This is pretty much required to completely avoid race conditions + c.config.RLock() + defer c.config.RUnlock() if c.log == nil { c.log = log.New(ioutil.Discard, "", 0) } - c.config.RLock() sigPriv, err := hex.DecodeString(c.config.PrivateKey) - c.config.RUnlock() if err != nil { return err } @@ -65,6 +66,9 @@ func (c *Core) _init() error { c.ctx, c.ctxCancel = context.WithCancel(context.Background()) c.store.init(c) c.proto.init(c) + if err := c.proto.nodeinfo.setNodeInfo(c.config.NodeInfo, c.config.NodeInfoPrivacy); err != nil { + return fmt.Errorf("setNodeInfo: %w", err) + } return err } @@ -177,20 +181,3 @@ func (c *Core) _stop() { */ c.log.Infoln("Stopped") } - -// Implement io.ReadWriteCloser - -func (c *Core) Read(p []byte) (n int, err error) { - n, err = c.store.readPC(p) - return -} - -func (c *Core) Write(p []byte) (n int, err error) { - n, err = c.store.writePC(p) - return -} - -func (c *Core) Close() error { - c.Stop() - return nil -} diff --git a/src/core/keystore.go b/src/core/keystore.go index 43424881..d8a808a2 100644 --- a/src/core/keystore.go +++ b/src/core/keystore.go @@ -17,7 +17,7 @@ const keyStoreTimeout = 2 * time.Minute type keyArray [ed25519.PublicKeySize]byte type keyStore struct { - core *Core + core *Core address address.Address subnet address.Subnet mutex sync.Mutex @@ -26,7 +26,6 @@ type keyStore struct { addrBuffer map[address.Address]*buffer subnetToInfo map[address.Subnet]*keyInfo subnetBuffer map[address.Subnet]*buffer - buf []byte // scratch space to prefix with typeSessionTraffic before sending } type keyInfo struct { @@ -45,7 +44,10 @@ func (k *keyStore) init(core *Core) { k.core = core k.address = *address.AddrForKey(k.core.public) k.subnet = *address.SubnetForKey(k.core.public) - k.core.pc.SetOutOfBandHandler(k.oobHandler) + if err := k.core.pc.SetOutOfBandHandler(k.oobHandler); err != nil { + err = fmt.Errorf("tun.core.SetOutOfBandHander: %w", err) + panic(err) + } k.keyToInfo = make(map[keyArray]*keyInfo) k.addrToInfo = make(map[address.Address]*keyInfo) k.addrBuffer = make(map[address.Address]*buffer) @@ -204,38 +206,39 @@ func (k *keyStore) maxSessionMTU() uint64 { } func (k *keyStore) readPC(p []byte) (int, error) { - for { - bs := p - n, from, err := k.core.pc.ReadFrom(bs) - if err != nil { - return n, err - } - if n == 0 { - continue - } - switch bs[0] { - case typeSessionTraffic: - // This is what we want to handle here - case typeSessionProto: - var key keyArray - copy(key[:], from.(iwt.Addr)) - data := append([]byte(nil), bs[1:n]...) - k.core.proto.handleProto(nil, key, data) - continue - default: - continue - } - bs = bs[1:n] - if len(bs) == 0 { - continue - } - if bs[0]&0xf0 != 0x60 { - continue // not IPv6 - } - if len(bs) < 40 { - continue - } - /* TODO ICMP packet too big + buf := make([]byte, k.core.pc.MTU(), 65535) + for { + bs := buf + n, from, err := k.core.pc.ReadFrom(bs) + if err != nil { + return n, err + } + if n == 0 { + continue + } + switch bs[0] { + case typeSessionTraffic: + // This is what we want to handle here + case typeSessionProto: + var key keyArray + copy(key[:], from.(iwt.Addr)) + data := append([]byte(nil), bs[1:n]...) + k.core.proto.handleProto(nil, key, data) + continue + default: + continue + } + bs = bs[1:n] + if len(bs) == 0 { + continue + } + if bs[0]&0xf0 != 0x60 { + continue // not IPv6 + } + if len(bs) < 40 { + continue + } + /* TODO? ICMP packet too big, for now tuntap sends this when needed if len(bs) > int(tun.MTU()) { ptb := &icmp.PacketTooBig{ MTU: int(tun.mtu), @@ -246,32 +249,32 @@ func (k *keyStore) readPC(p []byte) (int, error) { } continue } - */ - var srcAddr, dstAddr address.Address - var srcSubnet, dstSubnet address.Subnet - copy(srcAddr[:], bs[8:]) - copy(dstAddr[:], bs[24:]) - copy(srcSubnet[:], bs[8:]) - copy(dstSubnet[:], bs[24:]) - if dstAddr != k.address && dstSubnet != k.subnet { - continue // bad local address/subnet - } - info := k.update(ed25519.PublicKey(from.(iwt.Addr))) - if srcAddr != info.address && srcSubnet != info.subnet { - continue // bad remote address/subnet - } - n = copy(p, bs) - return n, nil + */ + var srcAddr, dstAddr address.Address + var srcSubnet, dstSubnet address.Subnet + copy(srcAddr[:], bs[8:]) + copy(dstAddr[:], bs[24:]) + copy(srcSubnet[:], bs[8:]) + copy(dstSubnet[:], bs[24:]) + if dstAddr != k.address && dstSubnet != k.subnet { + continue // bad local address/subnet } + info := k.update(ed25519.PublicKey(from.(iwt.Addr))) + if srcAddr != info.address && srcSubnet != info.subnet { + continue // bad remote address/subnet + } + n = copy(p, bs) + return n, nil + } } func (k *keyStore) writePC(bs []byte) (int, error) { - if bs[0]&0xf0 != 0x60 { + if bs[0]&0xf0 != 0x60 { return 0, errors.New("not an IPv6 packet") // not IPv6 } if len(bs) < 40 { - strErr := fmt.Sprint("undersized IPv6 packet, length:", len(bs)) - return 0, errors.New(strErr) + strErr := fmt.Sprint("undersized IPv6 packet, length:", len(bs)) + return 0, errors.New(strErr) } var srcAddr, dstAddr address.Address var srcSubnet, dstSubnet address.Subnet @@ -280,16 +283,17 @@ func (k *keyStore) writePC(bs []byte) (int, error) { copy(srcSubnet[:], bs[8:]) copy(dstSubnet[:], bs[24:]) if srcAddr != k.address && srcSubnet != k.subnet { - return 0, errors.New("wrong source address") + return 0, errors.New("wrong source address") } - k.buf = append(k.buf[:0], typeSessionTraffic) - k.buf = append(k.buf, bs...) + buf := make([]byte, 1+len(bs), 65535) + buf[0] = typeSessionTraffic + copy(buf[1:], bs) if dstAddr.IsValid() { - k.sendToAddress(dstAddr, k.buf) + k.sendToAddress(dstAddr, buf) } else if dstSubnet.IsValid() { - k.sendToSubnet(dstSubnet, k.buf) + k.sendToSubnet(dstSubnet, buf) } else { - return 0, errors.New("invalid destination address") + return 0, errors.New("invalid destination address") } return len(bs), nil } diff --git a/src/core/proto.go b/src/core/proto.go index 24d54caf..557ac1d5 100644 --- a/src/core/proto.go +++ b/src/core/proto.go @@ -31,7 +31,7 @@ type reqInfo struct { type protoHandler struct { phony.Inbox - core *Core + core *Core nodeinfo nodeinfo sreqs map[keyArray]*reqInfo preqs map[keyArray]*reqInfo diff --git a/src/tuntap/admin.go b/src/tuntap/admin.go index 9c9ceb67..80b2df00 100644 --- a/src/tuntap/admin.go +++ b/src/tuntap/admin.go @@ -34,8 +34,8 @@ func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { } return res, nil }) - _ = a.AddHandler("getNodeInfo", []string{"key"}, t.proto.nodeinfo.nodeInfoAdminHandler) - _ = a.AddHandler("debug_remoteGetSelf", []string{"key"}, t.proto.getSelfHandler) - _ = a.AddHandler("debug_remoteGetPeers", []string{"key"}, t.proto.getPeersHandler) - _ = a.AddHandler("debug_remoteGetDHT", []string{"key"}, t.proto.getDHTHandler) + //_ = a.AddHandler("getNodeInfo", []string{"key"}, t.proto.nodeinfo.nodeInfoAdminHandler) + //_ = a.AddHandler("debug_remoteGetSelf", []string{"key"}, t.proto.getSelfHandler) + //_ = a.AddHandler("debug_remoteGetPeers", []string{"key"}, t.proto.getPeersHandler) + //_ = a.AddHandler("debug_remoteGetDHT", []string{"key"}, t.proto.getDHTHandler) } diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index cb13690a..8642a005 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -1,20 +1,8 @@ package tuntap import ( - "crypto/ed25519" - - "github.com/yggdrasil-network/yggdrasil-go/src/address" "golang.org/x/net/icmp" "golang.org/x/net/ipv6" - - //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" - //"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" - - //"golang.org/x/net/icmp" - //"golang.org/x/net/ipv6" - - iwt "github.com/Arceliar/ironwood/types" - //"github.com/Arceliar/phony" ) const TUN_OFFSET_BYTES = 4 @@ -34,28 +22,8 @@ func (tun *TunAdapter) read() { begin := TUN_OFFSET_BYTES end := begin + n bs := buf[begin:end] - if bs[0]&0xf0 != 0x60 { - continue // not IPv6 - } - if len(bs) < 40 { - tun.log.Traceln("TUN iface read undersized ipv6 packet, length:", len(bs)) - continue - } - var srcAddr, dstAddr address.Address - var srcSubnet, dstSubnet address.Subnet - copy(srcAddr[:], bs[8:]) - copy(dstAddr[:], bs[24:]) - copy(srcSubnet[:], bs[8:]) - copy(dstSubnet[:], bs[24:]) - if srcAddr != tun.addr && srcSubnet != tun.subnet { - continue // Wrong source address - } - bs = buf[begin-1 : end] - bs[0] = typeSessionTraffic - if dstAddr.IsValid() { - tun.store.sendToAddress(dstAddr, bs) - } else if dstSubnet.IsValid() { - tun.store.sendToSubnet(dstSubnet, bs) + if _, err := tun.core.Write(bs); err != nil { + tun.log.Errorln("Unable to send packet:", err) } } } @@ -63,63 +31,23 @@ func (tun *TunAdapter) read() { func (tun *TunAdapter) write() { var buf [TUN_OFFSET_BYTES + 65535]byte for { - bs := buf[TUN_OFFSET_BYTES-1:] - n, from, err := tun.core.ReadFrom(bs) + bs := buf[TUN_OFFSET_BYTES:] + n, err := tun.core.Read(bs) if err != nil { + tun.log.Errorln("Exiting tun writer due to core read error:", err) return } - if n == 0 { - continue - } - switch bs[0] { - case typeSessionTraffic: - // This is what we want to handle here - if !tun.isEnabled { - continue // Drop traffic if the tun is disabled - } - case typeSessionProto: - var key keyArray - copy(key[:], from.(iwt.Addr)) - data := append([]byte(nil), bs[1:n]...) - tun.proto.handleProto(nil, key, data) - continue - default: - continue - } - bs = bs[1:n] - if len(bs) == 0 { - continue - } - if bs[0]&0xf0 != 0x60 { - continue // not IPv6 - } - if len(bs) < 40 { - continue - } - if len(bs) > int(tun.MTU()) { + if n > int(tun.MTU()) { ptb := &icmp.PacketTooBig{ MTU: int(tun.mtu), Data: bs[:40], } if packet, err := CreateICMPv6(bs[8:24], bs[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb); err == nil { - _, _ = tun.core.WriteTo(packet, from) + _, _ = tun.core.Write(packet) } continue } - var srcAddr, dstAddr address.Address - var srcSubnet, dstSubnet address.Subnet - copy(srcAddr[:], bs[8:]) - copy(dstAddr[:], bs[24:]) - copy(srcSubnet[:], bs[8:]) - copy(dstSubnet[:], bs[24:]) - if dstAddr != tun.addr && dstSubnet != tun.subnet { - continue // bad local address/subnet - } - info := tun.store.update(ed25519.PublicKey(from.(iwt.Addr))) - if srcAddr != info.address && srcSubnet != info.subnet { - continue // bad remote address/subnet - } - bs = buf[:TUN_OFFSET_BYTES+len(bs)] + bs = buf[:TUN_OFFSET_BYTES+n] if _, err = tun.iface.Write(bs, TUN_OFFSET_BYTES); err != nil { tun.Act(nil, func() { if !tun.isOpen { diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 7b20bfb2..41da0070 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -9,7 +9,6 @@ package tuntap // TODO: Don't block in reader on writes that are pending searches import ( - "crypto/ed25519" "errors" "fmt" "net" @@ -34,7 +33,6 @@ type MTU uint16 // calling yggdrasil.Start(). type TunAdapter struct { core *core.Core - store keyStore config *config.NodeConfig log *log.Logger addr address.Address @@ -45,7 +43,6 @@ type TunAdapter struct { //mutex sync.RWMutex // Protects the below isOpen bool isEnabled bool // Used by the writer to drop sessionTraffic if not enabled - proto protoHandler } // Gets the maximum supported MTU for the platform based on the defaults in @@ -98,18 +95,8 @@ func MaximumMTU() uint64 { // the Yggdrasil core before this point and it must not be in use elsewhere. func (tun *TunAdapter) Init(core *core.Core, config *config.NodeConfig, log *log.Logger, options interface{}) error { tun.core = core - tun.store.init(tun) tun.config = config tun.log = log - tun.proto.init(tun) - tun.config.RLock() - if err := tun.proto.nodeinfo.setNodeInfo(tun.config.NodeInfo, tun.config.NodeInfoPrivacy); err != nil { - return fmt.Errorf("tun.proto.nodeinfo.setNodeInfo: %w", err) - } - tun.config.RUnlock() - if err := tun.core.SetOutOfBandHandler(tun.oobHandler); err != nil { - return fmt.Errorf("tun.core.SetOutOfBandHander: %w", err) - } return nil } @@ -132,8 +119,7 @@ func (tun *TunAdapter) _start() error { if tun.config == nil { return errors.New("no configuration available to TUN") } - sk := tun.core.PrivateKey() - pk := sk.Public().(ed25519.PublicKey) + pk := tun.core.PublicKey() tun.addr = *address.AddrForKey(pk) tun.subnet = *address.SubnetForKey(pk) addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1) @@ -144,8 +130,8 @@ func (tun *TunAdapter) _start() error { return nil } mtu := tun.config.IfMTU - if tun.maxSessionMTU() < mtu { - mtu = tun.maxSessionMTU() + if tun.core.MaxMTU() < mtu { + mtu = tun.core.MaxMTU() } if err := tun.setup(tun.config.IfName, addr, mtu); err != nil { return err @@ -188,4 +174,3 @@ func (tun *TunAdapter) _stop() error { } return nil } - From 63967462d98ef3f2adf55a7e8b5be604ef3fa361 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 13 Jun 2021 10:58:15 +0100 Subject: [PATCH 0734/1109] Update MSI build again --- contrib/msi/build-msi.sh | 9 ++++---- contrib/msi/msversion.sh | 46 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 contrib/msi/msversion.sh diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index 85cd8209..7818e7a7 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -48,9 +48,9 @@ then fi # Build Yggdrasil! -[ "${PKGARCH}" == "x64" ] && GOOS=windows GOARCH=amd64 CGO_ENABLED=0 ./build -p -l "-aslr" -[ "${PKGARCH}" == "x86" ] && GOOS=windows GOARCH=386 CGO_ENABLED=0 ./build -p -l "-aslr" -[ "${PKGARCH}" == "arm" ] && GOOS=windows GOARCH=arm CGO_ENABLED=0 ./build -p -l "-aslr" +[ "${PKGARCH}" == "x64" ] && GOOS=windows GOARCH=amd64 CGO_ENABLED=0 ./build -l "-aslr" +[ "${PKGARCH}" == "x86" ] && GOOS=windows GOARCH=386 CGO_ENABLED=0 ./build -l "-aslr" +[ "${PKGARCH}" == "arm" ] && GOOS=windows GOARCH=arm CGO_ENABLED=0 ./build -l "-aslr" #[ "${PKGARCH}" == "arm64" ] && GOOS=windows GOARCH=arm64 CGO_ENABLED=0 ./build # Create the postinstall script @@ -67,7 +67,7 @@ EOF # Work out metadata for the package info PKGNAME=$(sh contrib/semver/name.sh) -PKGVERSION=$(sh contrib/semver/version.sh --bare) +PKGVERSION=$(sh contrib/msi/msversion.sh --bare) PKGVERSIONMS=$(echo $PKGVERSION | tr - .) [ "${PKGARCH}" == "x64" ] && \ PKGGUID="77757838-1a23-40a5-a720-c3b43e0260cc" PKGINSTFOLDER="ProgramFiles64Folder" || \ @@ -119,7 +119,6 @@ cat > wix.xml << EOF InstallScope="perMachine" Languages="1033" Compressed="yes" - Platform="${PKGARCH}" SummaryCodepage="1252" /> /dev/null) + +# Did getting the tag succeed? +if [ $? != 0 ] || [ -z "$TAG" ]; then + printf -- "unknown" + exit 0 +fi + +# Get the current branch +BRANCH=$(git symbolic-ref -q HEAD --short 2>/dev/null) + +# Did getting the branch succeed? +if [ $? != 0 ] || [ -z "$BRANCH" ]; then + BRANCH="master" +fi + +# Split out into major, minor and patch numbers +MAJOR=$(echo $TAG | cut -c 2- | cut -d "." -f 1) +MINOR=$(echo $TAG | cut -c 2- | cut -d "." -f 2) +PATCH=$(echo $TAG | cut -c 2- | cut -d "." -f 3) + +# Output in the desired format +if [ $((PATCH)) -eq 0 ]; then + printf '%s%d.%d' "$PREPEND" "$((MAJOR))" "$((MINOR))" +else + printf '%s%d.%d.%d' "$PREPEND" "$((MAJOR))" "$((MINOR))" "$((PATCH))" +fi + +# Add the build tag on non-master branches +if [ "$BRANCH" != "master" ]; then + BUILD=$(git rev-list --count $TAG..HEAD 2>/dev/null) + + # Did getting the count of commits since the tag succeed? + if [ $? != 0 ] || [ -z "$BUILD" ]; then + printf -- "-unknown" + exit 0 + fi + + # Is the build greater than zero? + if [ $((BUILD)) -gt 0 ]; then + printf -- "-%04d" "$((BUILD))" + fi +fi \ No newline at end of file From 272670b85b3efa81f9f04ee20c1435f6be03d96c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 13 Jun 2021 11:03:01 +0100 Subject: [PATCH 0735/1109] Fix version numbers in MSI --- contrib/msi/build-msi.sh | 1 - contrib/msi/msversion.sh | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index 7818e7a7..8d40b2bb 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -106,7 +106,6 @@ cat > wix.xml << EOF Language="1033" Codepage="1252" Version="${PKGVERSIONMS}" - Platform="${PKGARCH}" Manufacturer="github.com/yggdrasil-network"> Date: Sun, 13 Jun 2021 11:04:27 +0100 Subject: [PATCH 0736/1109] Remove -aslr --- contrib/msi/build-msi.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index 8d40b2bb..001de313 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -48,9 +48,9 @@ then fi # Build Yggdrasil! -[ "${PKGARCH}" == "x64" ] && GOOS=windows GOARCH=amd64 CGO_ENABLED=0 ./build -l "-aslr" -[ "${PKGARCH}" == "x86" ] && GOOS=windows GOARCH=386 CGO_ENABLED=0 ./build -l "-aslr" -[ "${PKGARCH}" == "arm" ] && GOOS=windows GOARCH=arm CGO_ENABLED=0 ./build -l "-aslr" +[ "${PKGARCH}" == "x64" ] && GOOS=windows GOARCH=amd64 CGO_ENABLED=0 ./build +[ "${PKGARCH}" == "x86" ] && GOOS=windows GOARCH=386 CGO_ENABLED=0 ./build +[ "${PKGARCH}" == "arm" ] && GOOS=windows GOARCH=arm CGO_ENABLED=0 ./build #[ "${PKGARCH}" == "arm64" ] && GOOS=windows GOARCH=arm64 CGO_ENABLED=0 ./build # Create the postinstall script From 38e05b5f4c1199ac08f1630a502429cf79594304 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 13 Jun 2021 11:07:19 +0100 Subject: [PATCH 0737/1109] Download wintun on first pass --- contrib/msi/build-msi.sh | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index 001de313..72fd0733 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -74,19 +74,22 @@ PKGVERSIONMS=$(echo $PKGVERSION | tr - .) PKGGUID="54a3294e-a441-4322-aefb-3bb40dd022bb" PKGINSTFOLDER="ProgramFilesFolder" # Download the Wintun driver -curl -o wintun.zip https://www.wintun.net/builds/wintun-0.11.zip -unzip wintun.zip -if [ $PKGARCH = "x64" ]; then - PKGWINTUNDLL=wintun/bin/amd64/wintun.dll -elif [ $PKGARCH = "x86" ]; then - PKGWINTUNDLL=wintun/bin/x86/wintun.dll -elif [ $PKGARCH = "arm" ]; then - PKGWINTUNDLL=wintun/bin/arm/wintun.dll -#elif [ $PKGARCH = "arm64" ]; then -# PKGWINTUNDLL=wintun/bin/arm64/wintun.dll -else - echo "wasn't sure which architecture to get wintun for" - exit 1 +if [ ! -d wintun ]; +then + curl -o wintun.zip https://www.wintun.net/builds/wintun-0.11.zip + unzip wintun.zip + if [ $PKGARCH = "x64" ]; then + PKGWINTUNDLL=wintun/bin/amd64/wintun.dll + elif [ $PKGARCH = "x86" ]; then + PKGWINTUNDLL=wintun/bin/x86/wintun.dll + elif [ $PKGARCH = "arm" ]; then + PKGWINTUNDLL=wintun/bin/arm/wintun.dll + #elif [ $PKGARCH = "arm64" ]; then + # PKGWINTUNDLL=wintun/bin/arm64/wintun.dll + else + echo "wasn't sure which architecture to get wintun for" + exit 1 + fi fi if [ $PKGNAME != "master" ]; then From 9b68ac570222ab7936e07e3e8b9a7e0f798b5214 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 13 Jun 2021 11:13:02 +0100 Subject: [PATCH 0738/1109] Fix wintun hopefully --- contrib/msi/build-msi.sh | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index 72fd0733..fd4ac3f5 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -78,18 +78,18 @@ if [ ! -d wintun ]; then curl -o wintun.zip https://www.wintun.net/builds/wintun-0.11.zip unzip wintun.zip - if [ $PKGARCH = "x64" ]; then - PKGWINTUNDLL=wintun/bin/amd64/wintun.dll - elif [ $PKGARCH = "x86" ]; then - PKGWINTUNDLL=wintun/bin/x86/wintun.dll - elif [ $PKGARCH = "arm" ]; then - PKGWINTUNDLL=wintun/bin/arm/wintun.dll - #elif [ $PKGARCH = "arm64" ]; then - # PKGWINTUNDLL=wintun/bin/arm64/wintun.dll - else - echo "wasn't sure which architecture to get wintun for" - exit 1 - fi +fi +if [ $PKGARCH = "x64" ]; then + PKGWINTUNDLL=wintun/bin/amd64/wintun.dll +elif [ $PKGARCH = "x86" ]; then + PKGWINTUNDLL=wintun/bin/x86/wintun.dll +elif [ $PKGARCH = "arm" ]; then + PKGWINTUNDLL=wintun/bin/arm/wintun.dll +#elif [ $PKGARCH = "arm64" ]; then +# PKGWINTUNDLL=wintun/bin/arm64/wintun.dll +else + echo "wasn't sure which architecture to get wintun for" + exit 1 fi if [ $PKGNAME != "master" ]; then From 3393db8e77f752fdb3ed247d0461e5de693e7549 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 13 Jun 2021 05:25:13 -0500 Subject: [PATCH 0739/1109] move ICMP PacketTooBig sending into core --- src/core/api.go | 12 +++++++ src/core/keystore.go | 30 ++++++++++------- src/tuntap/icmpv6.go | 80 -------------------------------------------- src/tuntap/iface.go | 16 ++------- src/tuntap/tun.go | 1 + 5 files changed, 33 insertions(+), 106 deletions(-) delete mode 100644 src/tuntap/icmpv6.go diff --git a/src/core/api.go b/src/core/api.go index c886edf6..536b1a87 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -231,6 +231,18 @@ func (c *Core) MaxMTU() uint64 { return c.store.maxSessionMTU() } +// SetMTU can only safely be called after Init and before Start. +func (c *Core) SetMTU(mtu uint64) { + if mtu < 1280 { + mtu = 1280 + } + c.store.mtu = mtu +} + +func (c *Core) MTU() uint64 { + return c.store.mtu +} + // Implement io.ReadWriteCloser func (c *Core) Read(p []byte) (n int, err error) { diff --git a/src/core/keystore.go b/src/core/keystore.go index d8a808a2..d2c6c24a 100644 --- a/src/core/keystore.go +++ b/src/core/keystore.go @@ -7,6 +7,9 @@ import ( "sync" "time" + "golang.org/x/net/icmp" + "golang.org/x/net/ipv6" + iwt "github.com/Arceliar/ironwood/types" "github.com/yggdrasil-network/yggdrasil-go/src/address" @@ -26,6 +29,7 @@ type keyStore struct { addrBuffer map[address.Address]*buffer subnetToInfo map[address.Subnet]*keyInfo subnetBuffer map[address.Subnet]*buffer + mtu uint64 } type keyInfo struct { @@ -53,6 +57,7 @@ func (k *keyStore) init(core *Core) { k.addrBuffer = make(map[address.Address]*buffer) k.subnetToInfo = make(map[address.Subnet]*keyInfo) k.subnetBuffer = make(map[address.Subnet]*buffer) + k.mtu = 1280 // Default to something safe, expect user to set this } func (k *keyStore) sendToAddress(addr address.Address, bs []byte) { @@ -238,18 +243,19 @@ func (k *keyStore) readPC(p []byte) (int, error) { if len(bs) < 40 { continue } - /* TODO? ICMP packet too big, for now tuntap sends this when needed - if len(bs) > int(tun.MTU()) { - ptb := &icmp.PacketTooBig{ - MTU: int(tun.mtu), - Data: bs[:40], - } - if packet, err := CreateICMPv6(bs[8:24], bs[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb); err == nil { - _, _ = tun.core.WriteTo(packet, from) - } - continue - } - */ + if len(bs) > int(k.mtu) { + // Using bs would make it leak off the stack, so copy to buf + buf := make([]byte, 40) + copy(buf, bs) + ptb := &icmp.PacketTooBig{ + MTU: int(k.mtu), + Data: buf[:40], + } + if packet, err := CreateICMPv6(buf[8:24], buf[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb); err == nil { + _, _ = k.writePC(packet) + } + continue + } var srcAddr, dstAddr address.Address var srcSubnet, dstSubnet address.Subnet copy(srcAddr[:], bs[8:]) diff --git a/src/tuntap/icmpv6.go b/src/tuntap/icmpv6.go deleted file mode 100644 index b7461a2d..00000000 --- a/src/tuntap/icmpv6.go +++ /dev/null @@ -1,80 +0,0 @@ -package tuntap - -// The ICMPv6 module implements functions to easily create ICMPv6 -// packets. These functions, when mixed with the built-in Go IPv6 -// and ICMP libraries, can be used to send control messages back -// to the host. Examples include: -// - NDP messages, when running in TAP mode -// - Packet Too Big messages, when packets exceed the session MTU -// - Destination Unreachable messages, when a session prohibits -// incoming traffic - -import ( - "encoding/binary" - "net" - - "golang.org/x/net/icmp" - "golang.org/x/net/ipv6" -) - -type ICMPv6 struct{} - -// Marshal returns the binary encoding of h. -func ipv6Header_Marshal(h *ipv6.Header) ([]byte, error) { - b := make([]byte, 40) - b[0] |= byte(h.Version) << 4 - b[0] |= byte(h.TrafficClass) >> 4 - b[1] |= byte(h.TrafficClass) << 4 - b[1] |= byte(h.FlowLabel >> 16) - b[2] = byte(h.FlowLabel >> 8) - b[3] = byte(h.FlowLabel) - binary.BigEndian.PutUint16(b[4:6], uint16(h.PayloadLen)) - b[6] = byte(h.NextHeader) - b[7] = byte(h.HopLimit) - copy(b[8:24], h.Src) - copy(b[24:40], h.Dst) - return b, nil -} - -// Creates an ICMPv6 packet based on the given icmp.MessageBody and other -// parameters, complete with IP headers only, which can be written directly to -// a TUN adapter, or called directly by the CreateICMPv6L2 function when -// generating a message for TAP adapters. -func CreateICMPv6(dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) { - // Create the ICMPv6 message - icmpMessage := icmp.Message{ - Type: mtype, - Code: mcode, - Body: mbody, - } - - // Convert the ICMPv6 message into []byte - icmpMessageBuf, err := icmpMessage.Marshal(icmp.IPv6PseudoHeader(src, dst)) - if err != nil { - return nil, err - } - - // Create the IPv6 header - ipv6Header := ipv6.Header{ - Version: ipv6.Version, - NextHeader: 58, - PayloadLen: len(icmpMessageBuf), - HopLimit: 255, - Src: src, - Dst: dst, - } - - // Convert the IPv6 header into []byte - ipv6HeaderBuf, err := ipv6Header_Marshal(&ipv6Header) - if err != nil { - return nil, err - } - - // Construct the packet - responsePacket := make([]byte, ipv6.HeaderLen+ipv6Header.PayloadLen) - copy(responsePacket[:ipv6.HeaderLen], ipv6HeaderBuf) - copy(responsePacket[ipv6.HeaderLen:], icmpMessageBuf) - - // Send it back - return responsePacket, nil -} diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 8642a005..587c925e 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -1,10 +1,5 @@ package tuntap -import ( - "golang.org/x/net/icmp" - "golang.org/x/net/ipv6" -) - const TUN_OFFSET_BYTES = 4 func (tun *TunAdapter) read() { @@ -37,15 +32,8 @@ func (tun *TunAdapter) write() { tun.log.Errorln("Exiting tun writer due to core read error:", err) return } - if n > int(tun.MTU()) { - ptb := &icmp.PacketTooBig{ - MTU: int(tun.mtu), - Data: bs[:40], - } - if packet, err := CreateICMPv6(bs[8:24], bs[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb); err == nil { - _, _ = tun.core.Write(packet) - } - continue + if !tun.isEnabled { + continue // Nothing to do, the tun isn't enabled } bs = buf[:TUN_OFFSET_BYTES+n] if _, err = tun.iface.Write(bs, TUN_OFFSET_BYTES); err != nil { diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 41da0070..dbba018b 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -139,6 +139,7 @@ func (tun *TunAdapter) _start() error { if tun.MTU() != mtu { tun.log.Warnf("Warning: Interface MTU %d automatically adjusted to %d (supported range is 1280-%d)", tun.config.IfMTU, tun.MTU(), MaximumMTU()) } + tun.core.SetMTU(tun.MTU()) tun.isOpen = true tun.isEnabled = true go tun.read() From 48938282b7c43b03392597919235932001c13f9f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 13 Jun 2021 11:28:41 +0100 Subject: [PATCH 0740/1109] Upgrade appveyor runner 2017 -> 2019 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 58724a24..903014e7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,7 @@ version: '{build}' pull_requests: do_not_increment_build_number: true -os: Visual Studio 2017 +os: Visual Studio 2019 shallow_clone: false environment: From c8938a3527db8e3ff225d52f2232c106f6ef332c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 13 Jun 2021 11:34:59 +0100 Subject: [PATCH 0741/1109] Add missing icmpv6.go --- src/core/icmpv6.go | 80 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 src/core/icmpv6.go diff --git a/src/core/icmpv6.go b/src/core/icmpv6.go new file mode 100644 index 00000000..d15fbbcb --- /dev/null +++ b/src/core/icmpv6.go @@ -0,0 +1,80 @@ +package core + +// The ICMPv6 module implements functions to easily create ICMPv6 +// packets. These functions, when mixed with the built-in Go IPv6 +// and ICMP libraries, can be used to send control messages back +// to the host. Examples include: +// - NDP messages, when running in TAP mode +// - Packet Too Big messages, when packets exceed the session MTU +// - Destination Unreachable messages, when a session prohibits +// incoming traffic + +import ( + "encoding/binary" + "net" + + "golang.org/x/net/icmp" + "golang.org/x/net/ipv6" +) + +type ICMPv6 struct{} + +// Marshal returns the binary encoding of h. +func ipv6Header_Marshal(h *ipv6.Header) ([]byte, error) { + b := make([]byte, 40) + b[0] |= byte(h.Version) << 4 + b[0] |= byte(h.TrafficClass) >> 4 + b[1] |= byte(h.TrafficClass) << 4 + b[1] |= byte(h.FlowLabel >> 16) + b[2] = byte(h.FlowLabel >> 8) + b[3] = byte(h.FlowLabel) + binary.BigEndian.PutUint16(b[4:6], uint16(h.PayloadLen)) + b[6] = byte(h.NextHeader) + b[7] = byte(h.HopLimit) + copy(b[8:24], h.Src) + copy(b[24:40], h.Dst) + return b, nil +} + +// Creates an ICMPv6 packet based on the given icmp.MessageBody and other +// parameters, complete with IP headers only, which can be written directly to +// a TUN adapter, or called directly by the CreateICMPv6L2 function when +// generating a message for TAP adapters. +func CreateICMPv6(dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) { + // Create the ICMPv6 message + icmpMessage := icmp.Message{ + Type: mtype, + Code: mcode, + Body: mbody, + } + + // Convert the ICMPv6 message into []byte + icmpMessageBuf, err := icmpMessage.Marshal(icmp.IPv6PseudoHeader(src, dst)) + if err != nil { + return nil, err + } + + // Create the IPv6 header + ipv6Header := ipv6.Header{ + Version: ipv6.Version, + NextHeader: 58, + PayloadLen: len(icmpMessageBuf), + HopLimit: 255, + Src: src, + Dst: dst, + } + + // Convert the IPv6 header into []byte + ipv6HeaderBuf, err := ipv6Header_Marshal(&ipv6Header) + if err != nil { + return nil, err + } + + // Construct the packet + responsePacket := make([]byte, ipv6.HeaderLen+ipv6Header.PayloadLen) + copy(responsePacket[:ipv6.HeaderLen], ipv6HeaderBuf) + copy(responsePacket[ipv6.HeaderLen:], icmpMessageBuf) + + // Send it back + return responsePacket, nil +} From 8f91f0c050ae44188e73efcefb987df54bebb13d Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 13 Jun 2021 05:43:03 -0500 Subject: [PATCH 0742/1109] fix nodeinfo and debug admin functions, this is ugly / a hack, but it works i guess... --- src/admin/admin.go | 5 +++++ src/core/api.go | 25 +++++++++++++++++++++++++ src/tuntap/admin.go | 4 ---- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/admin/admin.go b/src/admin/admin.go index 7d5c66d1..d41a48e1 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -83,6 +83,7 @@ func (a *AdminSocket) Init(c *core.Core, nc *config.NodeConfig, log *log.Logger, } return res, nil }) + a.core.SetAdmin(a) return nil } @@ -142,6 +143,10 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { } return res, nil }) + //_ = a.AddHandler("getNodeInfo", []string{"key"}, t.proto.nodeinfo.nodeInfoAdminHandler) + //_ = a.AddHandler("debug_remoteGetSelf", []string{"key"}, t.proto.getSelfHandler) + //_ = a.AddHandler("debug_remoteGetPeers", []string{"key"}, t.proto.getPeersHandler) + //_ = a.AddHandler("debug_remoteGetDHT", []string{"key"}, t.proto.getDHTHandler) } // Start runs the admin API socket to listen for / respond to admin API calls. diff --git a/src/core/api.go b/src/core/api.go index 536b1a87..d1b931cf 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -3,6 +3,7 @@ package core import ( "crypto/ed25519" //"encoding/hex" + "encoding/json" //"errors" //"fmt" "net" @@ -259,3 +260,27 @@ func (c *Core) Close() error { c.Stop() return nil } + +// Hack to get the admin stuff working, TODO something cleaner + +type AddHandler interface { + AddHandler(name string, args []string, handlerfunc func(json.RawMessage) (interface{}, error)) error +} + +// SetAdmin must be called after Init and before Start. +// It sets the admin handler for NodeInfo and the Debug admin functions. +func (c *Core) SetAdmin(a AddHandler) error { + if err := a.AddHandler("getNodeInfo", []string{"key"}, c.proto.nodeinfo.nodeInfoAdminHandler); err != nil { + return err + } + if err := a.AddHandler("debug_remoteGetSelf", []string{"key"}, c.proto.getSelfHandler); err != nil { + return err + } + if err := a.AddHandler("debug_remoteGetPeers", []string{"key"}, c.proto.getPeersHandler); err != nil { + return err + } + if err := a.AddHandler("debug_remoteGetDHT", []string{"key"}, c.proto.getDHTHandler); err != nil { + return err + } + return nil +} diff --git a/src/tuntap/admin.go b/src/tuntap/admin.go index 80b2df00..862a3c66 100644 --- a/src/tuntap/admin.go +++ b/src/tuntap/admin.go @@ -34,8 +34,4 @@ func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { } return res, nil }) - //_ = a.AddHandler("getNodeInfo", []string{"key"}, t.proto.nodeinfo.nodeInfoAdminHandler) - //_ = a.AddHandler("debug_remoteGetSelf", []string{"key"}, t.proto.getSelfHandler) - //_ = a.AddHandler("debug_remoteGetPeers", []string{"key"}, t.proto.getPeersHandler) - //_ = a.AddHandler("debug_remoteGetDHT", []string{"key"}, t.proto.getDHTHandler) } From c6a7a077a36a99f852342b2060862c5dae119909 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 13 Jun 2021 09:25:08 -0500 Subject: [PATCH 0743/1109] add remote URI to GetPeers (fallback to net.Conn.RemoteAddr().String() if the uri is unknown) --- go.mod | 2 +- go.sum | 4 ++-- src/admin/getpeers.go | 2 ++ src/core/api.go | 11 +++++++++++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1e27d93b..72a9abc7 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.16 require ( - github.com/Arceliar/ironwood v0.0.0-20210606094153-1bd43ce71198 + github.com/Arceliar/ironwood v0.0.0-20210613142316-e2332dbd4e3f github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 github.com/VividCortex/ewma v1.2.0 // indirect github.com/cheggaaa/pb/v3 v3.0.8 diff --git a/go.sum b/go.sum index dab85269..e7135473 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/ironwood v0.0.0-20210606094153-1bd43ce71198 h1:wLF+CSqm9DrPeT2dp1E4Xe5of8SyUxfJVxw8DHeT1YM= -github.com/Arceliar/ironwood v0.0.0-20210606094153-1bd43ce71198/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= +github.com/Arceliar/ironwood v0.0.0-20210613142316-e2332dbd4e3f h1:fnjzLzu6/0/cyeDVJb9mYS2odsw+6B88D9gO6iRSvGw= +github.com/Arceliar/ironwood v0.0.0-20210613142316-e2332dbd4e3f/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= diff --git a/src/admin/getpeers.go b/src/admin/getpeers.go index 9f53cf23..9ccf9e2f 100644 --- a/src/admin/getpeers.go +++ b/src/admin/getpeers.go @@ -18,6 +18,7 @@ type PeerEntry struct { PublicKey string `json:"key"` Port uint64 `json:"port"` Coords []uint64 `json:"coords"` + Remote string `json:"remote"` } func (a *AdminSocket) getPeersHandler(req *GetPeersRequest, res *GetPeersResponse) error { @@ -29,6 +30,7 @@ func (a *AdminSocket) getPeersHandler(req *GetPeersRequest, res *GetPeersRespons PublicKey: hex.EncodeToString(p.Key), Port: p.Port, Coords: p.Coords, + Remote: p.Remote, } } return nil diff --git a/src/core/api.go b/src/core/api.go index d1b931cf..0e9d9d7a 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -28,6 +28,7 @@ type Peer struct { Root ed25519.PublicKey Coords []uint64 Port uint64 + Remote string } type DHTEntry struct { @@ -56,6 +57,12 @@ func (c *Core) GetSelf() Self { func (c *Core) GetPeers() []Peer { var peers []Peer + names := make(map[net.Conn]string) + c.links.mutex.Lock() + for _, info := range c.links.links { + names[info.conn] = info.lname + } + c.links.mutex.Unlock() ps := c.pc.PacketConn.Debug.GetPeers() for _, p := range ps { var info Peer @@ -63,6 +70,10 @@ func (c *Core) GetPeers() []Peer { info.Root = p.Root info.Coords = p.Coords info.Port = p.Port + info.Remote = p.Conn.RemoteAddr().String() + if name := names[p.Conn]; name != "" { + info.Remote = name + } peers = append(peers, info) } return peers From 2726dc0076a0e5ceb1c58437c53cc8a4144394e6 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 13 Jun 2021 09:51:53 -0500 Subject: [PATCH 0744/1109] don't return an error if the source address is wrong, since this happens very frequently for link-local traffic --- src/core/keystore.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/keystore.go b/src/core/keystore.go index d2c6c24a..cdc31aaa 100644 --- a/src/core/keystore.go +++ b/src/core/keystore.go @@ -289,7 +289,9 @@ func (k *keyStore) writePC(bs []byte) (int, error) { copy(srcSubnet[:], bs[8:]) copy(dstSubnet[:], bs[24:]) if srcAddr != k.address && srcSubnet != k.subnet { - return 0, errors.New("wrong source address") + // This happens all the time due to link-local traffic + // Don't send back an error, just drop it + return 0, nil } buf := make([]byte, 1+len(bs), 65535) buf[0] = typeSessionTraffic From da82308d7c306d46a0a9b7ea1e7d80dfcdd8477c Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 13 Jun 2021 10:30:16 -0500 Subject: [PATCH 0745/1109] update ironwood, fixes bug where sessions could become stuck after a node restarts --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 72a9abc7..6c104b09 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.16 require ( - github.com/Arceliar/ironwood v0.0.0-20210613142316-e2332dbd4e3f + github.com/Arceliar/ironwood v0.0.0-20210613152842-297306b677cc github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 github.com/VividCortex/ewma v1.2.0 // indirect github.com/cheggaaa/pb/v3 v3.0.8 diff --git a/go.sum b/go.sum index e7135473..84c2fad4 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/ironwood v0.0.0-20210613142316-e2332dbd4e3f h1:fnjzLzu6/0/cyeDVJb9mYS2odsw+6B88D9gO6iRSvGw= -github.com/Arceliar/ironwood v0.0.0-20210613142316-e2332dbd4e3f/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= +github.com/Arceliar/ironwood v0.0.0-20210613152842-297306b677cc h1:0eUsfi0FBobUVaBJEcC5x2/Y7Geq7Mpvx51rWmZN7NU= +github.com/Arceliar/ironwood v0.0.0-20210613152842-297306b677cc/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= From 108313153394637fa494f7f0bedccd615e7f3960 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 13 Jun 2021 16:52:14 +0100 Subject: [PATCH 0746/1109] Update build script for Android/iOS --- build | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/build b/build index 66f94403..583bf610 100755 --- a/build +++ b/build @@ -32,18 +32,16 @@ fi if [ $IOS ]; then echo "Building framework for iOS" - gomobile bind -target ios -tags mobile -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \ - github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil \ - github.com/yggdrasil-network/yggdrasil-go/src/config \ + go get golang.org/x/mobile/bind + gomobile bind -target ios -tags mobile -o Yggdrasil.framework -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \ github.com/yggdrasil-network/yggdrasil-extras/src/mobile \ - github.com/yggdrasil-network/yggdrasil-extras/src/dummy + github.com/yggdrasil-network/yggdrasil-go/src/config elif [ $ANDROID ]; then echo "Building aar for Android" - gomobile bind -target android -tags mobile -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \ - github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil \ - github.com/yggdrasil-network/yggdrasil-go/src/config \ + go get golang.org/x/mobile/bind + gomobile bind -target android -tags mobile -o yggdrasil.aar -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \ github.com/yggdrasil-network/yggdrasil-extras/src/mobile \ - github.com/yggdrasil-network/yggdrasil-extras/src/dummy + github.com/yggdrasil-network/yggdrasil-go/src/config else for CMD in yggdrasil yggdrasilctl ; do echo "Building: $CMD" From cb81be94ec15d29d666deb1a56ee6653bf75e5e2 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 13 Jun 2021 12:31:52 -0500 Subject: [PATCH 0747/1109] skip multicast packets sent from our own key --- src/multicast/multicast.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 6f0e8f98..2b631ca2 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -1,6 +1,7 @@ package multicast import ( + "bytes" "context" "crypto/ed25519" "encoding/hex" @@ -350,6 +351,9 @@ func (m *Multicast) listen() { } var key ed25519.PublicKey key = append(key, bs[:ed25519.PublicKeySize]...) + if bytes.Equal(key, m.core.GetSelf().Key) { + continue // don't bother trying to peer with self + } anAddr := string(bs[ed25519.PublicKeySize:nBytes]) addr, err := net.ResolveTCPAddr("tcp6", anAddr) if err != nil { From b34c3230f877a3b6fd0101e2c362d3fda7874037 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 13 Jun 2021 13:40:20 -0500 Subject: [PATCH 0748/1109] fix core_test.go and a race in setting/using mtu --- src/core/api.go | 8 ++++++-- src/core/core_test.go | 31 ++++++++++++++++++++++--------- src/core/keystore.go | 7 +++++-- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/core/api.go b/src/core/api.go index 0e9d9d7a..05d9f36f 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -243,16 +243,20 @@ func (c *Core) MaxMTU() uint64 { return c.store.maxSessionMTU() } -// SetMTU can only safely be called after Init and before Start. func (c *Core) SetMTU(mtu uint64) { if mtu < 1280 { mtu = 1280 } + c.store.mutex.Lock() c.store.mtu = mtu + c.store.mutex.Unlock() } func (c *Core) MTU() uint64 { - return c.store.mtu + c.store.mutex.Lock() + mtu := c.store.mtu + c.store.mutex.Unlock() + return mtu } // Implement io.ReadWriteCloser diff --git a/src/core/core_test.go b/src/core/core_test.go index 7e7f32d1..9a2f230f 100644 --- a/src/core/core_test.go +++ b/src/core/core_test.go @@ -43,11 +43,13 @@ func CreateAndConnectTwo(t testing.TB, verbose bool) (nodeA *Core, nodeB *Core) if err := nodeA.Start(GenerateConfig(), GetLoggerWithPrefix("A: ", verbose)); err != nil { t.Fatal(err) } + nodeA.SetMTU(1500) nodeB = new(Core) if err := nodeB.Start(GenerateConfig(), GetLoggerWithPrefix("B: ", verbose)); err != nil { t.Fatal(err) } + nodeB.SetMTU(1500) u, err := url.Parse("tcp://" + nodeA.links.tcp.getAddr().String()) if err != nil { @@ -89,8 +91,9 @@ func CreateEchoListener(t testing.TB, nodeA *Core, bufLen int, repeats int) chan done := make(chan struct{}) go func() { buf := make([]byte, bufLen) + res := make([]byte, bufLen) for i := 0; i < repeats; i++ { - n, from, err := nodeA.ReadFrom(buf) + n, err := nodeA.Read(buf) if err != nil { t.Error(err) return @@ -99,7 +102,10 @@ func CreateEchoListener(t testing.TB, nodeA *Core, bufLen int, repeats int) chan t.Error("missing data") return } - _, err = nodeA.WriteTo(buf, from) + copy(res, buf) + copy(res[8:24], buf[24:40]) + copy(res[24:40], buf[8:24]) + _, err = nodeA.Write(res) if err != nil { t.Error(err) } @@ -130,17 +136,20 @@ func TestCore_Start_Transfer(t *testing.T) { // Send msg := make([]byte, msgLen) - rand.Read(msg) - _, err := nodeB.WriteTo(msg, nodeA.LocalAddr()) + rand.Read(msg[40:]) + msg[0] = 0x60 + copy(msg[8:24], nodeB.Address()) + copy(msg[24:40], nodeA.Address()) + _, err := nodeB.Write(msg) if err != nil { t.Fatal(err) } buf := make([]byte, msgLen) - _, _, err = nodeB.ReadFrom(buf) + _, err = nodeB.Read(buf) if err != nil { t.Fatal(err) } - if !bytes.Equal(msg, buf) { + if !bytes.Equal(msg[40:], buf[40:]) { t.Fatal("expected echo") } <-done @@ -159,18 +168,22 @@ func BenchmarkCore_Start_Transfer(b *testing.B) { // Send msg := make([]byte, msgLen) - rand.Read(msg) + rand.Read(msg[40:]) + msg[0] = 0x60 + copy(msg[8:24], nodeB.Address()) + copy(msg[24:40], nodeA.Address()) + buf := make([]byte, msgLen) b.SetBytes(int64(msgLen)) b.ResetTimer() for i := 0; i < b.N; i++ { - _, err := nodeB.WriteTo(msg, nodeA.LocalAddr()) + _, err := nodeB.Write(msg) if err != nil { b.Fatal(err) } - _, _, err = nodeB.ReadFrom(buf) + _, err = nodeB.Read(buf) if err != nil { b.Fatal(err) } diff --git a/src/core/keystore.go b/src/core/keystore.go index cdc31aaa..82fa9317 100644 --- a/src/core/keystore.go +++ b/src/core/keystore.go @@ -243,12 +243,15 @@ func (k *keyStore) readPC(p []byte) (int, error) { if len(bs) < 40 { continue } - if len(bs) > int(k.mtu) { + k.mutex.Lock() + mtu := int(k.mtu) + k.mutex.Unlock() + if len(bs) > mtu { // Using bs would make it leak off the stack, so copy to buf buf := make([]byte, 40) copy(buf, bs) ptb := &icmp.PacketTooBig{ - MTU: int(k.mtu), + MTU: mtu, Data: buf[:40], } if packet, err := CreateICMPv6(buf[8:24], buf[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb); err == nil { From 1bf751a4746de8aa90e3a111ec46a20ef671b06a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 19 Jun 2021 07:44:37 -0500 Subject: [PATCH 0749/1109] update ironwood, only store 1 packet in the pre-session buffer --- go.mod | 2 +- go.sum | 4 ++-- src/core/keystore.go | 14 +++++--------- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 6c104b09..398504cb 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.16 require ( - github.com/Arceliar/ironwood v0.0.0-20210613152842-297306b677cc + github.com/Arceliar/ironwood v0.0.0-20210619124114-6ad55cae5031 github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 github.com/VividCortex/ewma v1.2.0 // indirect github.com/cheggaaa/pb/v3 v3.0.8 diff --git a/go.sum b/go.sum index 84c2fad4..5d11fad6 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/ironwood v0.0.0-20210613152842-297306b677cc h1:0eUsfi0FBobUVaBJEcC5x2/Y7Geq7Mpvx51rWmZN7NU= -github.com/Arceliar/ironwood v0.0.0-20210613152842-297306b677cc/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= +github.com/Arceliar/ironwood v0.0.0-20210619124114-6ad55cae5031 h1:DZVDfYhVdu+0wAiRHoY1olyNkKxIot9UjBnbQFzuUlM= +github.com/Arceliar/ironwood v0.0.0-20210619124114-6ad55cae5031/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= diff --git a/src/core/keystore.go b/src/core/keystore.go index 82fa9317..5062a675 100644 --- a/src/core/keystore.go +++ b/src/core/keystore.go @@ -40,7 +40,7 @@ type keyInfo struct { } type buffer struct { - packets [][]byte + packet []byte timeout *time.Timer } @@ -73,7 +73,7 @@ func (k *keyStore) sendToAddress(addr address.Address, bs []byte) { k.addrBuffer[addr] = buf } msg := append([]byte(nil), bs...) - buf.packets = append(buf.packets, msg) + buf.packet = msg if buf.timeout != nil { buf.timeout.Stop() } @@ -102,7 +102,7 @@ func (k *keyStore) sendToSubnet(subnet address.Subnet, bs []byte) { k.subnetBuffer[subnet] = buf } msg := append([]byte(nil), bs...) - buf.packets = append(buf.packets, msg) + buf.packet = msg if buf.timeout != nil { buf.timeout.Stop() } @@ -134,15 +134,11 @@ func (k *keyStore) update(key ed25519.PublicKey) *keyInfo { k.resetTimeout(info) k.mutex.Unlock() if buf := k.addrBuffer[info.address]; buf != nil { - for _, bs := range buf.packets { - _, _ = k.core.pc.WriteTo(bs, iwt.Addr(info.key[:])) - } + k.core.pc.WriteTo(buf.packet, iwt.Addr(info.key[:])) delete(k.addrBuffer, info.address) } if buf := k.subnetBuffer[info.subnet]; buf != nil { - for _, bs := range buf.packets { - _, _ = k.core.pc.WriteTo(bs, iwt.Addr(info.key[:])) - } + k.core.pc.WriteTo(buf.packet, iwt.Addr(info.key[:])) delete(k.subnetBuffer, info.subnet) } } else { From 5564de94ba7b54005bda5e572359ee0db45d6ae6 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 19 Jun 2021 09:53:11 -0500 Subject: [PATCH 0750/1109] when using tls, if no pinned key is set, pin the key from the cert. require that cert keys match a pinned key --- src/core/tcp.go | 4 ++-- src/core/tls.go | 40 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/core/tcp.go b/src/core/tcp.go index 5c3e506b..4dc36a08 100644 --- a/src/core/tcp.go +++ b/src/core/tcp.go @@ -54,7 +54,7 @@ type TcpListener struct { } type TcpUpgrade struct { - upgrade func(c net.Conn) (net.Conn, error) + upgrade func(c net.Conn, o *tcpOptions) (net.Conn, error) name string } @@ -361,7 +361,7 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options tcpOptions) chan str var upgraded bool if options.upgrade != nil { var err error - if sock, err = options.upgrade.upgrade(sock); err != nil { + if sock, err = options.upgrade.upgrade(sock, &options); err != nil { t.links.core.log.Errorln("TCP handler upgrade failed:", err) return nil } diff --git a/src/core/tls.go b/src/core/tls.go index 4fdcf997..4c25225b 100644 --- a/src/core/tls.go +++ b/src/core/tls.go @@ -9,6 +9,7 @@ import ( "crypto/x509/pkix" "encoding/hex" "encoding/pem" + "errors" "log" "math/big" "net" @@ -76,16 +77,47 @@ func (t *tcptls) init(tcp *tcp) { } } -func (t *tcptls) upgradeListener(c net.Conn) (net.Conn, error) { - conn := tls.Server(c, t.config) +func (t *tcptls) configForOptions(options *tcpOptions) *tls.Config { + config := *t.config + config.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) error { + if len(rawCerts) != 1 { + return errors.New("tls not exactly 1 cert") + } + cert, err := x509.ParseCertificate(rawCerts[0]) + if err != nil { + return errors.New("tls failed to parse cert") + } + if cert.PublicKeyAlgorithm != x509.Ed25519 { + return errors.New("tls wrong cert algorithm") + } + pk := cert.PublicKey.(ed25519.PublicKey) + var key keyArray + copy(key[:], pk) + // If options does not have a pinned key, then pin one now + if options.pinnedEd25519Keys == nil { + options.pinnedEd25519Keys = make(map[keyArray]struct{}) + options.pinnedEd25519Keys[key] = struct{}{} + } + if _, isIn := options.pinnedEd25519Keys[key]; !isIn { + return errors.New("tls key does not match pinned key") + } + return nil + } + return &config +} + +func (t *tcptls) upgradeListener(c net.Conn, options *tcpOptions) (net.Conn, error) { + config := t.configForOptions(options) + conn := tls.Server(c, 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) +func (t *tcptls) upgradeDialer(c net.Conn, options *tcpOptions) (net.Conn, error) { + config := t.configForOptions(options) + conn := tls.Client(c, config) if err := conn.Handshake(); err != nil { return c, err } From b7f57c0617478b521e81c488abddced2ea12d13f Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 19 Jun 2021 10:42:38 -0500 Subject: [PATCH 0751/1109] use TLS for multicast peers, fix TLS listener type in log output --- src/core/tcp.go | 8 ++++++-- src/multicast/multicast.go | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/core/tcp.go b/src/core/tcp.go index 4dc36a08..572fd652 100644 --- a/src/core/tcp.go +++ b/src/core/tcp.go @@ -185,17 +185,21 @@ func (t *tcp) listener(l *TcpListener, listenaddr string) { l.Listener.Close() return } + callproto := "TCP" + if l.opts.upgrade != nil { + callproto = strings.ToUpper(l.opts.upgrade.name) + } t.listeners[listenaddr] = l t.mutex.Unlock() // And here we go! defer func() { - t.links.core.log.Infoln("Stopping TCP listener on:", l.Listener.Addr().String()) + t.links.core.log.Infoln("Stopping", callproto, "listener on:", l.Listener.Addr().String()) l.Listener.Close() t.mutex.Lock() delete(t.listeners, listenaddr) t.mutex.Unlock() }() - t.links.core.log.Infoln("Listening for TCP on:", l.Listener.Addr().String()) + t.links.core.log.Infoln("Listening for", callproto, "on:", l.Listener.Addr().String()) go func() { <-l.stop l.Listener.Close() diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 2b631ca2..ef564bb3 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -277,7 +277,7 @@ func (m *Multicast) _announce() { var info *listenerInfo if nfo, ok := m.listeners[iface.Name]; !ok || nfo.listener.Listener == nil { // No listener was found - let's create one - urlString := fmt.Sprintf("tcp://[%s]:%d", addrIP, m.listenPort) + urlString := fmt.Sprintf("tls://[%s]:%d", addrIP, m.listenPort) u, err := url.Parse(urlString) if err != nil { panic(err) @@ -370,7 +370,7 @@ func (m *Multicast) listen() { if _, ok := interfaces[from.Zone]; ok { addr.Zone = "" pin := fmt.Sprintf("/?key=%s", hex.EncodeToString(key)) - u, err := url.Parse("tcp://" + addr.String() + pin) + u, err := url.Parse("tls://" + addr.String() + pin) if err != nil { m.log.Debugln("Call from multicast failed, parse error:", addr.String(), err) } From 39361af7892d5ccd8c567068bd46b5c17f2fa817 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 19 Jun 2021 17:51:11 +0100 Subject: [PATCH 0752/1109] Update config comments --- go.mod | 1 + go.sum | 28 ++++++++++++++++++++++++++++ src/config/config.go | 12 ++++++------ 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 398504cb..844dfaee 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/mitchellh/mapstructure v1.4.1 github.com/vishvananda/netlink v1.1.0 github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect + github.com/yggdrasil-network/yggdrasil-extras v0.0.0-20210616080244-468e4a33c3f6 // indirect golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect golang.org/x/net v0.0.0-20210610132358-84b48f89b13b golang.org/x/sys v0.0.0-20210611083646-a4fc73990273 diff --git a/go.sum b/go.sum index 5d11fad6..1fde4fd1 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,9 @@ +github.com/Arceliar/ironwood v0.0.0-20210613152842-297306b677cc/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/ironwood v0.0.0-20210619124114-6ad55cae5031 h1:DZVDfYhVdu+0wAiRHoY1olyNkKxIot9UjBnbQFzuUlM= github.com/Arceliar/ironwood v0.0.0-20210619124114-6ad55cae5031/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= @@ -38,18 +40,37 @@ github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYp github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/yggdrasil-network/yggdrasil-extras v0.0.0-20210616080244-468e4a33c3f6 h1:bwgqxLteeI2ruLUrVVSuIfYJY4vUno8Zne1unV9iLls= +github.com/yggdrasil-network/yggdrasil-extras v0.0.0-20210616080244-468e4a33c3f6/go.mod h1:+4d9w7U71su9wXzpOxwtxRmXgjnwpJjQpX6vi2rwpv4= +github.com/yggdrasil-network/yggdrasil-go v0.3.17-0.20210613153016-da82308d7c30/go.mod h1:ELFERsaKIevEZRjoynRDiovh06EZUCAhTAkZHkEGRIE= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20210527171505-7e972142eb43/go.mod h1:jFTmtFYCV0MFtXBU+J5V/+5AUeVS0ON/0WkE/KSrl6E= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b h1:k+E048sYJHyVnsr1GDrRZWQ32D2C7lWs9JRc0bel53A= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -59,6 +80,7 @@ golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -72,6 +94,12 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7-0.20210503195748-5c7c50ebbd4f h1:yQJrRE0hDxDFmZLlRaw+3vusO4fwNHgHIjUOMO7bHYI= golang.org/x/text v0.3.7-0.20210503195748-5c7c50ebbd4f/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wireguard v0.0.0-20210510202332-9844c74f67ec/go.mod h1:a057zjmoc00UN7gVkaJt2sXVK523kMJcogDTEvPIasg= golang.zx2c4.com/wireguard v0.0.0-20210604143328-f9b48a961cd2 h1:wfOOSvHgIzTZ9h5Vb6yUFZNn7uf3bT7PeYsHOO7tYDM= golang.zx2c4.com/wireguard v0.0.0-20210604143328-f9b48a961cd2/go.mod h1:laHzsbfMhGSobUmruXWAyMKKHSqvIcrqZJMyHD+/3O8= diff --git a/src/config/config.go b/src/config/config.go index fa110122..934c4091 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -29,14 +29,14 @@ import ( // supply one of these structs to the Yggdrasil core when starting a node. type NodeConfig struct { sync.RWMutex `json:"-"` - Peers []string `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j. These connections\nwill obey the operating system routing table, therefore you should\nuse this section when you may connect via different interfaces."` - InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ tcp://a.b.c.d:e ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."` - Listen []string `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntcp://0.0.0.0:0 or tcp://[::]:0 to listen on all interfaces."` + Peers []string `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tls://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j. These connections\nwill obey the operating system routing table, therefore you should\nuse this section when you may connect via different interfaces."` + InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ tls://a.b.c.d:e ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."` + Listen []string `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntls://0.0.0.0:0 or tls://[::]:0 to listen on all interfaces."` 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. To disable\nthe admin socket, use the value \"none\" instead."` MulticastInterfaces []string `comment:"Regular expressions for which interfaces multicast peer discovery\nshould be enabled on. If none specified, multicast peer discovery is\ndisabled. The default value is .* which uses all interfaces."` - AllowedPublicKeys []string `comment:"List of peer encryption public keys to allow incoming TCP peering\nconnections from. If left empty/undefined then all connections will\nbe allowed by default. This does not affect outgoing peerings, nor\ndoes it affect link-local peers discovered via multicast."` - PublicKey string `comment:"Your public signing key. Your peers may ask you for this to put\ninto their AllowedPublicKeys configuration."` - PrivateKey string `comment:"Your private signing key. DO NOT share this with anyone!"` + AllowedPublicKeys []string `comment:"List of peer public keys to allow incoming peering connections\nfrom. If left empty/undefined then all connections will be allowed\nby default. This does not affect outgoing peerings, nor does it\naffect link-local peers discovered via multicast."` + PublicKey string `comment:"Your public key. Your peers may ask you for this to put\ninto their AllowedPublicKeys configuration."` + PrivateKey string `comment:"Your private key. DO NOT share this with anyone!"` LinkLocalTCPPort uint16 `comment:"The port number to be used for the link-local TCP listeners for the\nconfigured MulticastInterfaces. This option does not affect listeners\nspecified in the Listen option. Unless you plan to firewall link-local\ntraffic, it is best to leave this as the default value of 0. This\noption cannot currently be changed by reloading config during runtime."` IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."` IfMTU uint64 `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` From 9b9ef2fad740df5b4c0d57eefd39b06ae51ae397 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 19 Jun 2021 11:56:03 -0500 Subject: [PATCH 0753/1109] tidy --- go.mod | 1 - go.sum | 28 ---------------------------- 2 files changed, 29 deletions(-) diff --git a/go.mod b/go.mod index 844dfaee..398504cb 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,6 @@ require ( github.com/mitchellh/mapstructure v1.4.1 github.com/vishvananda/netlink v1.1.0 github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect - github.com/yggdrasil-network/yggdrasil-extras v0.0.0-20210616080244-468e4a33c3f6 // indirect golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect golang.org/x/net v0.0.0-20210610132358-84b48f89b13b golang.org/x/sys v0.0.0-20210611083646-a4fc73990273 diff --git a/go.sum b/go.sum index 1fde4fd1..5d11fad6 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,7 @@ -github.com/Arceliar/ironwood v0.0.0-20210613152842-297306b677cc/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/ironwood v0.0.0-20210619124114-6ad55cae5031 h1:DZVDfYhVdu+0wAiRHoY1olyNkKxIot9UjBnbQFzuUlM= github.com/Arceliar/ironwood v0.0.0-20210619124114-6ad55cae5031/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= @@ -40,37 +38,18 @@ github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYp github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -github.com/yggdrasil-network/yggdrasil-extras v0.0.0-20210616080244-468e4a33c3f6 h1:bwgqxLteeI2ruLUrVVSuIfYJY4vUno8Zne1unV9iLls= -github.com/yggdrasil-network/yggdrasil-extras v0.0.0-20210616080244-468e4a33c3f6/go.mod h1:+4d9w7U71su9wXzpOxwtxRmXgjnwpJjQpX6vi2rwpv4= -github.com/yggdrasil-network/yggdrasil-go v0.3.17-0.20210613153016-da82308d7c30/go.mod h1:ELFERsaKIevEZRjoynRDiovh06EZUCAhTAkZHkEGRIE= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20210527171505-7e972142eb43/go.mod h1:jFTmtFYCV0MFtXBU+J5V/+5AUeVS0ON/0WkE/KSrl6E= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b h1:k+E048sYJHyVnsr1GDrRZWQ32D2C7lWs9JRc0bel53A= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -80,7 +59,6 @@ golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -94,12 +72,6 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7-0.20210503195748-5c7c50ebbd4f h1:yQJrRE0hDxDFmZLlRaw+3vusO4fwNHgHIjUOMO7bHYI= golang.org/x/text v0.3.7-0.20210503195748-5c7c50ebbd4f/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wireguard v0.0.0-20210510202332-9844c74f67ec/go.mod h1:a057zjmoc00UN7gVkaJt2sXVK523kMJcogDTEvPIasg= golang.zx2c4.com/wireguard v0.0.0-20210604143328-f9b48a961cd2 h1:wfOOSvHgIzTZ9h5Vb6yUFZNn7uf3bT7PeYsHOO7tYDM= golang.zx2c4.com/wireguard v0.0.0-20210604143328-f9b48a961cd2/go.mod h1:laHzsbfMhGSobUmruXWAyMKKHSqvIcrqZJMyHD+/3O8= From 50bd16d524e3dd8ef565a8ea6ffec94473fbec8f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 19 Jun 2021 18:02:38 +0100 Subject: [PATCH 0754/1109] Remove doc folder, out of date --- .gitmodules | 3 - doc/Whitepaper.md | 148 -------------------------------- doc/yggdrasil-network.github.io | 1 - 3 files changed, 152 deletions(-) delete mode 100644 .gitmodules delete mode 100644 doc/Whitepaper.md delete mode 160000 doc/yggdrasil-network.github.io diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e4e8b525..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "doc/yggdrasil-network.github.io"] - path = doc/yggdrasil-network.github.io - url = https://github.com/yggdrasil-network/yggdrasil-network.github.io/ diff --git a/doc/Whitepaper.md b/doc/Whitepaper.md deleted file mode 100644 index b65963d1..00000000 --- a/doc/Whitepaper.md +++ /dev/null @@ -1,148 +0,0 @@ -# Yggdrasil - -Note: This is a very rough early draft. - -Yggdrasil is an encrypted IPv6 network running in the [`200::/7` address range](https://en.wikipedia.org/wiki/Unique_local_address). -It is an experimental/toy network, so failure is acceptable, as long as it's instructive to see how it breaks if/when everything falls apart. - -IP addresses are derived from cryptographic keys, to reduce the need for public key infrastructure. -A form of locator/identifier separation (similar in goal to [LISP](https://en.wikipedia.org/wiki/Locator/Identifier_Separation_Protocol)) is used to map static identifiers (IP addresses) onto dynamic routing information (locators), using a [distributed hash table](https://en.wikipedia.org/wiki/Distributed_hash_table) (DHT). -Locators are used to approximate the distance between nodes in the network, where the approximate distance is the length of a real worst-case-scenario path through the network. -This is (arguably) easier to secure and requires less information about the network than commonly used routing schemes. - -While not technically a [compact routing scheme](https://arxiv.org/abs/0708.2309), tests on real-world networks suggest that routing in this style incurs stretch comparable to the name-dependent compact routing schemes designed for static networks. -Compared to compact routing schemes, Yggdrasil appears to have smaller average routing table sizes, works on dynamic networks, and is name-independent. -It currently lacks the provable bounds of compact routing schemes, and there's a serious argument to be made that it cheats by stretching the definition of some of the above terms, but the main point to be emphasized is that there are trade-offs between different concerns when trying to route traffic, and we'd rather make every part *good* than try to make any one part *perfect*. -In that sense, Yggdrasil seems to be competitive, on what are supposedly realistic networks, with compact routing schemes. - -## Addressing - -Yggdrasil uses a truncated version of a `NodeID` to assign addresses. -An address is assigned from the `200::/7` prefix, according to the following: - -1. Begin with `0x02` as the first byte of the address, or `0x03` if it's a `/64` prefix. -2. Count the number of leading `1` bits in the NodeID. -3. Set the second byte of the address to the number of leading `1` bits in the NodeID (8 bit unsigned integer, at most 255). -4. Append the NodeID to the remaining bits of the address, truncating the leading `1` bits and the first `0` bit, to a total address size of 128 bits. - -The last bit of the first byte is used to flag if an address is for a router (`200::/8`), or part of an advertised prefix (`300::/8`), where each router owns a `/64` that matches their address (except with the eight bit set to 1 instead of 0). -This allows the prefix to be advertised to the router's LAN, so unsupported devices can still connect to the network (e.g. network printers). - -The NodeID is a [sha512sum](https://en.wikipedia.org/wiki/SHA-512) of a node's public encryption key. -Addresses are checked that they match NodeID, to prevent address spoofing. -As such, while a 128 bit IPv6 address is likely too short to be considered secure by cryptographer standards, there is a significant cost in attempting to cause an address collision. -Addresses can be made more secure by brute force generating a large number of leading `1` bits in the NodeID. - -When connecting to a node, the IP address is unpacked into the known bits of the NodeID and a matching bitmask to track which bits are significant. -A node is only communicated with if its `NodeID` matches its public key and the known `NodeID` bits from the address. - -It is important to note that only `NodeID` is used internally for routing, so the addressing scheme could in theory be changed without breaking compatibility with intermediate routers. -This has been done once, when moving the address range from the `fd00::/8` ULA range to the reserved-but-[deprecated](https://tools.ietf.org/html/rfc4048) `200::/7` range. -Further addressing scheme changes could occur if, for example, an IPv7 format ever emerges. - -### Cryptography - -Public key encryption is done using the `golang.org/x/crypto/nacl/box`, which uses [Curve25519](https://en.wikipedia.org/wiki/Curve25519), [XSalsa20](https://en.wikipedia.org/wiki/Salsa20), and [Poly1305](https://en.wikipedia.org/wiki/Poly1305) for key exchange, encryption, and authentication (interoperable with [NaCl](https://en.wikipedia.org/wiki/NaCl_(software))). -Permanent keys are used only for protocol traffic, with random nonces generated on a per-packet basis using `crypto/rand` from Go's standard library. -Ephemeral session keys (for [forward secrecy](https://en.wikipedia.org/wiki/Forward_secrecy)) are generated for encapsulated IPv6 traffic, using the same set of primitives, with random initial nonces that are subsequently incremented. -A list of recently received session nonces is kept (as a bitmask) and checked to reject duplicated packets, in an effort to block duplicate packets and replay attacks. -A separate set of keys are generated and used for signing with [Ed25519](https://en.wikipedia.org/wiki/Ed25519), which is used by the routing layer to secure construction of a spanning tree. - -### Prefixes - -Recall that each node's address is in the lower half of the address range, I.e. `200::/8`. A `/64` prefix is made available to each node under `300::/8`, where the remaining bits of the prefix match the node's address under `200::/8`. -A node may optionally advertise a prefix on their local area network, which allows unsupported or legacy devices with IPv6 support to connect to the network. -Note that there are 64 fewer bits of `NodeID` available to check in each address from a routing prefix, so it makes sense to brute force a `NodeID` with more significant bits in the address if this approach is to be used. -Running `genkeys.go` will do this by default. - -## Locators and Routing - -Locators are generated using information from a spanning tree (described below). -The result is that each node has a set of [coordinates in a greedy metric space](https://en.wikipedia.org/wiki/Greedy_embedding). -These coordinates are used as a distance label. -Given the coordinates of any two nodes, it is possible to calculate the length of some real path through the network between the two nodes. - -Traffic is forwarded using a [greedy routing](https://en.wikipedia.org/wiki/Small-world_routing#Greedy_routing) scheme, where each node forwards the packet to a one-hop neighbor that is closer to the destination (according to this distance metric) than the current node. -In particular, when a packet needs to be forwarded, a node will forward it to whatever peer is closest to the destination in the greedy [metric space](https://en.wikipedia.org/wiki/Metric_space) used by the network, provided that the peer is closer to the destination than the current node. - -If no closer peers are idle, then the packet is queued in FIFO order, with separate queues per destination coords (currently, as a bit of a hack, IPv6 flow labels are embedded after the end of the significant part of the coords, so queues distinguish between different traffic streams with the same destination). -Whenever the node finishes forwarding a packet to a peer, it checks the queues, and will forward the first packet from the queue with the maximum `/`, i.e. the bandwidth the queue is attempting to use, subject to the constraint that the peer is a valid next hop (i.e. closer to the destination than the current node). -If no non-empty queue is available, then the peer is added to the idle set, forward packets when the need arises. - -This acts as a crude approximation of backpressure routing, where the remote queue sizes are assumed to be equal to the distance of a node from a destination (rather than communicating queue size information), and packets are never forwarded "backwards" through the network, but congestion on a local link is routed around when possible. -The queue selection strategy behaves similar to shortest-queue-first, in that a larger fraction of available bandwidth to sessions that attempt to use less bandwidth, and is loosely based on the rationale behind some proposed solutions to the [cake-cutting](https://en.wikipedia.org/wiki/Fair_cake-cutting) problem. - -The queue size is limited to 4 MB. If a packet is added to a queue and the total size of all queues is larger than this threshold, then a random queue is selected (with odds proportional to relative queue sizes), and the first packet from that queue is dropped, with the process repeated until the total queue size drops below the allowed threshold. - -Note that this forwarding procedure generalizes to nodes that are not one-hop neighbors, but the current implementation omits the use of more distant neighbors, as this is expected to be a minor optimization (it would add per-link control traffic to pass path-vector-like information about a subset of the network, which is a lot of overhead compared to the current setup). - -### Spanning Tree - -A [spanning tree](https://en.wikipedia.org/wiki/Spanning_tree) is constructed with the tree rooted at the highest TreeID, where TreeID is equal to a sha512sum of a node's public [Ed25519](https://en.wikipedia.org/wiki/Ed25519) key (used for signing). -A node sends periodic advertisement messages to each neighbor. -The advertisement contains the coords that match the path from the root through the node, plus one additional hop from the node to the neighbor being advertised to. -Each hop in this advertisement includes a matching ed25519 signature. -These signatures prevent nodes from forging arbitrary routing advertisements. - -The first hop, from the root, also includes a sequence number, which must be updated periodically. -A node will blacklist the current root (keeping a record of the last sequence number observed) if the root fails to update for longer than some timeout (currently hard coded at 1 minute). -Normally, a root node will update their sequence number for frequently than this (once every 30 seconds). -Nodes are throttled to ignore updates with a new sequence number for some period after updating their most recently seen sequence number (currently this cooldown is 15 seconds). -The implementation chooses to set the sequence number equal to the unix time on the root's clock, so that a new (higher) sequence number will be selected if the root is restarted and the clock is not set back. - -Other than the root node, every other node in the network must select one of its neighbors to use as their parent. -This selection is done by tracking when each neighbor first sends us a message with a new timestamp from the root, to determine the ordering of the latency of each path from the root, to each neighbor, and then to the node that's searching for a parent. -These relative latencies are tracked by, for each neighbor, keeping a score vs each other neighbor. -If a neighbor sends a message with an updated timestamp before another neighbor, then the faster neighbor's score is increased by 1. -If the neighbor sends a message slower, then the score is decreased by 2, to make sure that a node must be reliably faster (at least 2/3 of the time) to see a net score increase over time. -If a node begins to advertise new coordinates, then its score vs all other nodes is reset to 0. -A node switches to a new parent if a neighbor's score (vs the current parent) reaches some threshold, currently 240, which corresponds to about 2 hours of being a reliably faster path. -The intended outcome of this process is that stable connections from fixed infrastructure near the "core" of the network should (eventually) select parents that minimize latency from the root to themselves, while the more dynamic parts of the network, presumably more towards the edges, will try to favor reliability when selecting a parent. - -The distance metric between nodes is simply the distance between the nodes if they routed on the spanning tree. -This is equal to the sum of the distance from each node to the last common ancestor of the two nodes being compared. -The locator then consists of a root's key, timestamp, and coordinates representing each hop in the path from the root to the node. -In practice, only the coords are used for routing, while the root and timestamp, along with all the per-hop signatures, are needed to securely construct the spanning tree. - -## Name-independent routing - -A [Chord](https://en.wikipedia.org/wiki/Chord_(peer-to-peer))-like Distributed Hash Table (DHT) is used as a distributed database that maps NodeIDs onto coordinates in the spanning tree metric space. -The DHT is Chord-like in that it uses a successor/predecessor structure to do lookups in `O(n)` time with `O(1)` entries, then augments this with some additional information, adding roughly `O(logn)` additional entries, to reduce the lookup time to something around `O(logn)`. -In the long term, the idea is to favor spending our bandwidth making sure the minimum `O(1)` part is right, to prioritize correctness, and then try to conserve bandwidth (and power) by being a bit lazy about checking the remaining `O(logn)` portion when it's not in use. - -To be specific, the DHT stores the immediate successor of a node, plus the next node it manages to find which is strictly closer (by the tree hop-count metric) than all previous nodes. -The same process is repeated for predecessor nodes, and lookups walk the network in the predecessor direction, with each key being owned by its successor (to make sure defaulting to 0 for unknown bits of a `NodeID` doesn't cause us to overshoot the target during a lookup). -In addition, all of a node's one-hop neighbors are included in the DHT, since we get this information "for free", and we must include it in our DHT to ensure that the network doesn't diverge to a broken state (though I suspect that only adding parents or parent-child relationships may be sufficient -- worth trying to prove or disprove, if somebody's bored). -The DHT differs from Chord in that there are no values in the key:value store -- it only stores information about DHT peers -- and that it uses a [Kademlia](https://en.wikipedia.org/wiki/Kademlia)-inspired iterative-parallel lookup process. - -To summarize the entire routing procedure, when given only a node's IP address, the goal is to find a route to the destination. -That happens through 3 steps: - -1. The address is unpacked into the known bits of a NodeID and a bitmask to signal which bits of the NodeID are known (the unknown bits are ignored). -2. A DHT search is performed, which normally results in a response from the node closest in the DHT keyspace to the target `NodeID`. The response contains the node's curve25519 public key, which is checked to match the `NodeID` (and therefore the address), as well as the node's coordinates. -3. Using the keys and coords from the above step, an ephemeral key exchange occurs between the source and destination nodes. These ephemeral session keys are used to encrypt any ordinary IPv6 traffic that may be encapsulated and sent between the nodes. - -From that point, the session keys and coords are cached and used to encrypt and send traffic between nodes. This is *mostly* transparent to the user: the initial DHT lookup and key exchange takes at least 2 round trips, so there's some delay before session setup completes and normal IPv6 traffic can flow. This is similar to the delay caused by a DNS lookup, although it generally takes longer, as a DHT lookup requires multiple iterations to reach the destination. - -## Project Status and Plans - -The current (Go) implementation is considered alpha, so compatibility with future versions is neither guaranteed nor expected. -While users are discouraged from running anything truly critical on top of it, as of writing, it seems reliable enough for day-to-day use. - -As an "alpha" quality release, Yggdrasil *should* at least be able to detect incompatible versions when it sees them, and warn the users that an update may be needed. -A "beta" quality release should know enough to be compatible in the face of wire format changes, and reasonably feature complete. -A "stable" 1.0 release, if it ever happens, would probably be feature complete, with no expectation of future wire format changes, and free of known critical bugs. - -Roughly speaking, there are a few obvious ways the project could turn out: - -1. The developers could lose interest before it goes anywhere. -2. The project could be reasonably complete (beta or stable), but never gain a significant number of users. -3. The network may grow large enough that fundamental (non-fixable) design problems appear, which is hopefully a learning experience, but the project may die as a result. -4. The network may grow large, but never hit any design problems, in which case we need to think about either moving the important parts into other projects ([cjdns](https://github.com/cjdelisle/cjdns)) or rewriting compatible implementations that are better optimized for the target platforms (e.g. a linux kernel module). - -That last one is probably impossible, because the speed of light would *eventually* become a problem, for a sufficiently large network. -If the only thing limiting network growth turns out to be the underlying physics, then that arguably counts as a win. - -Also, note that some design decisions were made for ease-of-programming or ease-of-testing reasons, and likely need to be reconsidered at some point. -In particular, Yggdrasil currently uses TCP for connections with one-hop neighbors, which introduces an additional layer of buffering that can lead to increased and/or unstable latency in congested areas of the network. - diff --git a/doc/yggdrasil-network.github.io b/doc/yggdrasil-network.github.io deleted file mode 160000 index c876890a..00000000 --- a/doc/yggdrasil-network.github.io +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c876890a51d9140e68d5cec7fbeb2146c2562792 From 3b38ed082fc93b0dcc547bea7c381c19c26838ca Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 25 Jun 2021 21:15:40 -0500 Subject: [PATCH 0755/1109] make failed sends a debug log, instead of error --- src/core/keystore.go | 6 ++++-- src/tuntap/iface.go | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/core/keystore.go b/src/core/keystore.go index 5062a675..21fb8459 100644 --- a/src/core/keystore.go +++ b/src/core/keystore.go @@ -4,6 +4,7 @@ import ( "crypto/ed25519" "errors" "fmt" + "net" "sync" "time" @@ -278,7 +279,7 @@ func (k *keyStore) writePC(bs []byte) (int, error) { return 0, errors.New("not an IPv6 packet") // not IPv6 } if len(bs) < 40 { - strErr := fmt.Sprint("undersized IPv6 packet, length:", len(bs)) + strErr := fmt.Sprint("undersized IPv6 packet, length: ", len(bs)) return 0, errors.New(strErr) } var srcAddr, dstAddr address.Address @@ -290,7 +291,8 @@ func (k *keyStore) writePC(bs []byte) (int, error) { if srcAddr != k.address && srcSubnet != k.subnet { // This happens all the time due to link-local traffic // Don't send back an error, just drop it - return 0, nil + strErr := fmt.Sprint("incorrect source address: ", net.IP(srcAddr[:]).String()) + return 0, errors.New(strErr) } buf := make([]byte, 1+len(bs), 65535) buf[0] = typeSessionTraffic diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 587c925e..e72b091f 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -18,7 +18,7 @@ func (tun *TunAdapter) read() { end := begin + n bs := buf[begin:end] if _, err := tun.core.Write(bs); err != nil { - tun.log.Errorln("Unable to send packet:", err) + tun.log.Debugln("Unable to send packet:", err) } } } From d1dfe38683b354f15cb1b5c5c76fad4f43c89fed Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 25 Jun 2021 21:27:29 -0500 Subject: [PATCH 0756/1109] remove string from multicast announcement format --- src/multicast/multicast.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index ef564bb3..5d9f689c 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "crypto/ed25519" + "encoding/binary" "encoding/hex" "fmt" "net" @@ -307,7 +308,10 @@ func (m *Multicast) _announce() { a.Zone = "" destAddr.Zone = iface.Name msg := append([]byte(nil), m.core.GetSelf().Key...) - msg = append(msg, a.String()...) + msg = append(msg, a.IP...) + pbs := make([]byte, 2) + binary.BigEndian.PutUint16(pbs, uint16(a.Port)) + msg = append(msg, pbs...) _, _ = m.sock.WriteTo(msg, nil, destAddr) } if info.interval.Seconds() < 15 { @@ -354,13 +358,20 @@ func (m *Multicast) listen() { if bytes.Equal(key, m.core.GetSelf().Key) { continue // don't bother trying to peer with self } - anAddr := string(bs[ed25519.PublicKeySize:nBytes]) - addr, err := net.ResolveTCPAddr("tcp6", anAddr) + begin := ed25519.PublicKeySize + end := nBytes - 2 + if end <= begin { + continue // malformed address + } + ip := bs[begin:end] + port := binary.BigEndian.Uint16(bs[end:nBytes]) + anAddr := net.TCPAddr{IP: ip, Port: int(port)} + addr, err := net.ResolveTCPAddr("tcp6", anAddr.String()) if err != nil { continue } from := fromAddr.(*net.UDPAddr) - if addr.IP.String() != from.IP.String() { + if !from.IP.Equal(addr.IP) { continue } var interfaces map[string]interfaceInfo From 2db46c1250c8f1f8add58a75dab35adcbd966c7a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 25 Jun 2021 21:40:19 -0500 Subject: [PATCH 0757/1109] make socks connect to tls listeners, TODO make that configurable --- src/core/link.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/link.go b/src/core/link.go index 295a8ad6..165b18b2 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -93,6 +93,7 @@ func (l *links) call(u *url.URL, sintf string) error { tcpOpts.socksProxyAuth.User = u.User.Username() tcpOpts.socksProxyAuth.Password, _ = u.User.Password() } + tcpOpts.upgrade = l.tcp.tls.forDialer // TODO make this configurable pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/") l.tcp.call(pathtokens[0], tcpOpts, sintf) case "tls": From 2a7a53b6b6a826a81a49283b420a575416fc3084 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 27 Jun 2021 02:18:51 -0500 Subject: [PATCH 0758/1109] move GenerateConfig to defaults, to adjust dependency ordering, needed for stuff later --- cmd/yggdrasil/main.go | 7 ++++--- src/config/config.go | 28 ---------------------------- src/defaults/defaults.go | 22 ++++++++++++++++++++++ 3 files changed, 26 insertions(+), 31 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 114e37f3..79c243ba 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -25,6 +25,7 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/admin" "github.com/yggdrasil-network/yggdrasil-go/src/config" + "github.com/yggdrasil-network/yggdrasil-go/src/defaults" "github.com/yggdrasil-network/yggdrasil-go/src/core" "github.com/yggdrasil-network/yggdrasil-go/src/multicast" @@ -73,7 +74,7 @@ func readConfig(log *log.Logger, useconf *bool, useconffile *string, normaliseco // then parse the configuration we loaded above on top of it. The effect // of this is that any configuration item that is missing from the provided // configuration will use a sane default. - cfg := config.GenerateConfig() + cfg := defaults.GenerateConfig() var dat map[string]interface{} if err := hjson.Unmarshal(conf, &dat); err != nil { panic(err) @@ -114,7 +115,7 @@ func readConfig(log *log.Logger, useconf *bool, useconffile *string, normaliseco // Generates a new configuration and returns it in HJSON format. This is used // with -genconf. func doGenconf(isjson bool) string { - cfg := config.GenerateConfig() + cfg := defaults.GenerateConfig() var bs []byte var err error if isjson { @@ -205,7 +206,7 @@ func main() { case *autoconf: // Use an autoconf-generated config, this will give us random keys and // port numbers, and will use an automatically selected TUN/TAP interface. - cfg = config.GenerateConfig() + cfg = defaults.GenerateConfig() case *useconffile != "" || *useconf: // Read the configuration from either stdin or from the filesystem cfg = readConfig(logger, useconf, useconffile, normaliseconf) diff --git a/src/config/config.go b/src/config/config.go index 934c4091..767dc188 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -20,8 +20,6 @@ import ( "crypto/ed25519" "encoding/hex" "sync" - - "github.com/yggdrasil-network/yggdrasil-go/src/defaults" ) // NodeConfig is the main configuration structure, containing configuration @@ -44,32 +42,6 @@ type NodeConfig struct { NodeInfo map[string]interface{} `comment:"Optional node info. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."` } -// Generates default configuration and returns a pointer to the resulting -// NodeConfig. This is used when outputting the -genconf parameter and also when -// using -autoconf. -func GenerateConfig() *NodeConfig { - // Generate encryption keys. - spub, spriv, err := ed25519.GenerateKey(nil) - if err != nil { - panic(err) - } - // Create a node configuration and populate it. - cfg := NodeConfig{} - cfg.Listen = []string{} - cfg.AdminListen = defaults.GetDefaults().DefaultAdminListen - cfg.PublicKey = hex.EncodeToString(spub[:]) - cfg.PrivateKey = hex.EncodeToString(spriv[:]) - cfg.Peers = []string{} - cfg.InterfacePeers = map[string][]string{} - cfg.AllowedPublicKeys = []string{} - cfg.MulticastInterfaces = defaults.GetDefaults().DefaultMulticastInterfaces - cfg.IfName = defaults.GetDefaults().DefaultIfName - cfg.IfMTU = defaults.GetDefaults().DefaultIfMTU - cfg.NodeInfoPrivacy = false - - return &cfg -} - // NewSigningKeys replaces the signing keypair in the NodeConfig with a new // signing keypair. The signing keys are used by the switch to derive the // structure of the spanning tree. diff --git a/src/defaults/defaults.go b/src/defaults/defaults.go index a885c5dd..238a36d7 100644 --- a/src/defaults/defaults.go +++ b/src/defaults/defaults.go @@ -1,5 +1,7 @@ package defaults +import "github.com/yggdrasil-network/yggdrasil-go/src/config" + // 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. @@ -18,3 +20,23 @@ type platformDefaultParameters struct { DefaultIfMTU uint64 DefaultIfName string } + +// Generates default configuration and returns a pointer to the resulting +// NodeConfig. This is used when outputting the -genconf parameter and also when +// using -autoconf. +func GenerateConfig() *config.NodeConfig { + // Create a node configuration and populate it. + cfg := new(config.NodeConfig) + cfg.NewKeys() + cfg.Listen = []string{} + cfg.AdminListen = GetDefaults().DefaultAdminListen + cfg.Peers = []string{} + cfg.InterfacePeers = map[string][]string{} + cfg.AllowedPublicKeys = []string{} + cfg.MulticastInterfaces = GetDefaults().DefaultMulticastInterfaces + cfg.IfName = GetDefaults().DefaultIfName + cfg.IfMTU = GetDefaults().DefaultIfMTU + cfg.NodeInfoPrivacy = false + + return cfg +} From 2874ce1327d3c8079a0b1498742aefd3c347de57 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 27 Jun 2021 03:15:41 -0500 Subject: [PATCH 0759/1109] change multicast config format --- src/config/config.go | 32 +++++++++++-------- src/defaults/defaults.go | 4 ++- src/defaults/defaults_darwin.go | 6 ++-- src/defaults/defaults_freebsd.go | 4 +-- src/defaults/defaults_linux.go | 4 +-- src/defaults/defaults_openbsd.go | 4 +-- src/defaults/defaults_other.go | 4 +-- src/defaults/defaults_windows.go | 4 +-- src/multicast/multicast.go | 53 ++++++++++++++++++++------------ 9 files changed, 68 insertions(+), 47 deletions(-) diff --git a/src/config/config.go b/src/config/config.go index 767dc188..d81907f5 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -27,19 +27,25 @@ import ( // supply one of these structs to the Yggdrasil core when starting a node. type NodeConfig struct { sync.RWMutex `json:"-"` - Peers []string `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tls://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j. These connections\nwill obey the operating system routing table, therefore you should\nuse this section when you may connect via different interfaces."` - InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ tls://a.b.c.d:e ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."` - Listen []string `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntls://0.0.0.0:0 or tls://[::]:0 to listen on all interfaces."` - 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. To disable\nthe admin socket, use the value \"none\" instead."` - MulticastInterfaces []string `comment:"Regular expressions for which interfaces multicast peer discovery\nshould be enabled on. If none specified, multicast peer discovery is\ndisabled. The default value is .* which uses all interfaces."` - AllowedPublicKeys []string `comment:"List of peer public keys to allow incoming peering connections\nfrom. If left empty/undefined then all connections will be allowed\nby default. This does not affect outgoing peerings, nor does it\naffect link-local peers discovered via multicast."` - PublicKey string `comment:"Your public key. Your peers may ask you for this to put\ninto their AllowedPublicKeys configuration."` - PrivateKey string `comment:"Your private key. DO NOT share this with anyone!"` - LinkLocalTCPPort uint16 `comment:"The port number to be used for the link-local TCP listeners for the\nconfigured MulticastInterfaces. This option does not affect listeners\nspecified in the Listen option. Unless you plan to firewall link-local\ntraffic, it is best to leave this as the default value of 0. This\noption cannot currently be changed by reloading config during runtime."` - IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."` - IfMTU uint64 `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` - NodeInfoPrivacy bool `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."` - NodeInfo map[string]interface{} `comment:"Optional node info. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."` + Peers []string `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tls://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j. These connections\nwill obey the operating system routing table, therefore you should\nuse this section when you may connect via different interfaces."` + InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ tls://a.b.c.d:e ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."` + Listen []string `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntls://0.0.0.0:0 or tls://[::]:0 to listen on all interfaces."` + 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. To disable\nthe admin socket, use the value \"none\" instead."` + MulticastInterfaces []MulticastInterfaceConfig `comment:"Regular expressions for which interfaces multicast peer discovery\nshould be enabled on. If none specified, multicast peer discovery is\ndisabled. The default value is .* which uses all interfaces."` + AllowedPublicKeys []string `comment:"List of peer public keys to allow incoming peering connections\nfrom. If left empty/undefined then all connections will be allowed\nby default. This does not affect outgoing peerings, nor does it\naffect link-local peers discovered via multicast."` + PublicKey string `comment:"Your public key. Your peers may ask you for this to put\ninto their AllowedPublicKeys configuration."` + PrivateKey string `comment:"Your private key. DO NOT share this with anyone!"` + LinkLocalTCPPort uint16 `comment:"The port number to be used for the link-local TCP listeners for the\nconfigured MulticastInterfaces. This option does not affect listeners\nspecified in the Listen option. Unless you plan to firewall link-local\ntraffic, it is best to leave this as the default value of 0. This\noption cannot currently be changed by reloading config during runtime."` + IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."` + IfMTU uint64 `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` + NodeInfoPrivacy bool `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."` + NodeInfo map[string]interface{} `comment:"Optional node info. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."` +} + +type MulticastInterfaceConfig struct { + Regex string + Incoming bool + Outgoing bool } // NewSigningKeys replaces the signing keypair in the NodeConfig with a new diff --git a/src/defaults/defaults.go b/src/defaults/defaults.go index 238a36d7..7912fc76 100644 --- a/src/defaults/defaults.go +++ b/src/defaults/defaults.go @@ -2,6 +2,8 @@ package defaults import "github.com/yggdrasil-network/yggdrasil-go/src/config" +type MulticastInterfaceConfig = config.MulticastInterfaceConfig + // 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. @@ -13,7 +15,7 @@ type platformDefaultParameters struct { DefaultConfigFile string // Multicast interfaces - DefaultMulticastInterfaces []string + DefaultMulticastInterfaces []MulticastInterfaceConfig // TUN/TAP MaximumIfMTU uint64 diff --git a/src/defaults/defaults_darwin.go b/src/defaults/defaults_darwin.go index 9fbe6d8e..a21539f7 100644 --- a/src/defaults/defaults_darwin.go +++ b/src/defaults/defaults_darwin.go @@ -13,9 +13,9 @@ func GetDefaults() platformDefaultParameters { DefaultConfigFile: "/etc/yggdrasil.conf", // Multicast interfaces - DefaultMulticastInterfaces: []string{ - "en.*", - "bridge.*", + DefaultMulticastInterfaces: []MulticastInterfaceConfig{ + {Regex: "en.*", Incoming: true, Outgoing: true}, + {Regex: "bridge.*", Incoming: true, Outgoing: true}, }, // TUN/TAP diff --git a/src/defaults/defaults_freebsd.go b/src/defaults/defaults_freebsd.go index a57be2fa..400ae29a 100644 --- a/src/defaults/defaults_freebsd.go +++ b/src/defaults/defaults_freebsd.go @@ -13,8 +13,8 @@ func GetDefaults() platformDefaultParameters { DefaultConfigFile: "/usr/local/etc/yggdrasil.conf", // Multicast interfaces - DefaultMulticastInterfaces: []string{ - ".*", + DefaultMulticastInterfaces: []MulticastInterfaceConfig{ + {Regex: ".*", Incoming: true, Outgoing: true}, }, // TUN/TAP diff --git a/src/defaults/defaults_linux.go b/src/defaults/defaults_linux.go index 5f3f12a8..ac678d85 100644 --- a/src/defaults/defaults_linux.go +++ b/src/defaults/defaults_linux.go @@ -13,8 +13,8 @@ func GetDefaults() platformDefaultParameters { DefaultConfigFile: "/etc/yggdrasil.conf", // Multicast interfaces - DefaultMulticastInterfaces: []string{ - ".*", + DefaultMulticastInterfaces: []MulticastInterfaceConfig{ + {Regex: ".*", Incoming: true, Outgoing: true}, }, // TUN/TAP diff --git a/src/defaults/defaults_openbsd.go b/src/defaults/defaults_openbsd.go index 6700eb8f..f3e2376c 100644 --- a/src/defaults/defaults_openbsd.go +++ b/src/defaults/defaults_openbsd.go @@ -13,8 +13,8 @@ func GetDefaults() platformDefaultParameters { DefaultConfigFile: "/etc/yggdrasil.conf", // Multicast interfaces - DefaultMulticastInterfaces: []string{ - ".*", + DefaultMulticastInterfaces: []MulticastInterfaceConfig{ + {Regex: ".*", Incoming: true, Outgoing: true}, }, // TUN/TAP diff --git a/src/defaults/defaults_other.go b/src/defaults/defaults_other.go index 3b035537..d23c2ef0 100644 --- a/src/defaults/defaults_other.go +++ b/src/defaults/defaults_other.go @@ -13,8 +13,8 @@ func GetDefaults() platformDefaultParameters { DefaultConfigFile: "/etc/yggdrasil.conf", // Multicast interfaces - DefaultMulticastInterfaces: []string{ - ".*", + DefaultMulticastInterfaces: []MulticastInterfaceConfig{ + {Regex: ".*", Incoming: true, Outgoing: true}, }, // TUN/TAP diff --git a/src/defaults/defaults_windows.go b/src/defaults/defaults_windows.go index 305a2ffe..1e918c3b 100644 --- a/src/defaults/defaults_windows.go +++ b/src/defaults/defaults_windows.go @@ -13,8 +13,8 @@ func GetDefaults() platformDefaultParameters { DefaultConfigFile: "C:\\Program Files\\Yggdrasil\\yggdrasil.conf", // Multicast interfaces - DefaultMulticastInterfaces: []string{ - ".*", + DefaultMulticastInterfaces: []MulticastInterfaceConfig{ + {Regex: ".*", Incoming: true, Outgoing: true}, }, // TUN/TAP diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 5d9f689c..aea4e079 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -38,8 +38,10 @@ type Multicast struct { } type interfaceInfo struct { - iface net.Interface - addrs []net.Addr + iface net.Interface + addrs []net.Addr + incoming bool + outgoing bool } type listenerInfo struct { @@ -136,18 +138,16 @@ func (m *Multicast) _stop() error { } func (m *Multicast) _updateInterfaces() { - interfaces := make(map[string]interfaceInfo) - intfs := m.getAllowedInterfaces() - for _, intf := range intfs { - addrs, err := intf.Addrs() + interfaces := m.getAllowedInterfaces() + for name, info := range interfaces { + addrs, err := info.iface.Addrs() if err != nil { - m.log.Warnf("Failed up get addresses for interface %s: %s", intf.Name, err) + m.log.Warnf("Failed up get addresses for interface %s: %s", name, err) + delete(interfaces, name) continue } - interfaces[intf.Name] = interfaceInfo{ - iface: intf, - addrs: addrs, - } + info.addrs = addrs + interfaces[name] = info } m._interfaces = interfaces } @@ -163,10 +163,10 @@ func (m *Multicast) Interfaces() map[string]net.Interface { } // getAllowedInterfaces returns the currently known/enabled multicast interfaces. -func (m *Multicast) getAllowedInterfaces() map[string]net.Interface { - interfaces := make(map[string]net.Interface) +func (m *Multicast) getAllowedInterfaces() map[string]interfaceInfo { + interfaces := make(map[string]interfaceInfo) // Get interface expressions from config - exprs := m.config.MulticastInterfaces + ifcfgs := m.config.MulticastInterfaces // Ask the system for network interfaces allifaces, err := net.Interfaces() if err != nil { @@ -188,15 +188,23 @@ func (m *Multicast) getAllowedInterfaces() map[string]net.Interface { // Ignore point-to-point interfaces continue } - for _, expr := range exprs { + for _, ifcfg := range ifcfgs { // Compile each regular expression - e, err := regexp.Compile(expr) + e, err := regexp.Compile(ifcfg.Regex) if err != nil { panic(err) } // Does the interface match the regular expression? Store it if so if e.MatchString(iface.Name) { - interfaces[iface.Name] = iface + if ifcfg.Incoming || ifcfg.Outgoing { + info := interfaceInfo{ + iface: iface, + incoming: ifcfg.Incoming, + outgoing: ifcfg.Outgoing, + } + interfaces[iface.Name] = info + } + break } } } @@ -272,8 +280,13 @@ func (m *Multicast) _announce() { if !addrIP.IsLinkLocalUnicast() { continue } - // Join the multicast group - _ = m.sock.JoinGroup(&iface, groupAddr) + if info.outgoing { + // Join the multicast group, so we can listen for advertisements to open outgoing connections + _ = m.sock.JoinGroup(&iface, groupAddr) + } + if !info.incoming { + break // Don't send multicast advertisements if we don't accept incoming connections + } // Try and see if we already have a TCP listener for this interface var info *listenerInfo if nfo, ok := m.listeners[iface.Name]; !ok || nfo.listener.Listener == nil { @@ -378,7 +391,7 @@ func (m *Multicast) listen() { phony.Block(m, func() { interfaces = m._interfaces }) - if _, ok := interfaces[from.Zone]; ok { + if info, ok := interfaces[from.Zone]; ok && info.outgoing { addr.Zone = "" pin := fmt.Sprintf("/?key=%s", hex.EncodeToString(key)) u, err := url.Parse("tls://" + addr.String() + pin) From a42b77db84bf1488547a66147c8b5376520da784 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 27 Jun 2021 03:33:29 -0500 Subject: [PATCH 0760/1109] attempt to convert old multicast listen regexps into new struct format --- cmd/yggdrasil/main.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 79c243ba..67a822e4 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -96,6 +96,24 @@ func readConfig(log *log.Logger, useconf *bool, useconffile *string, normaliseco } } } + if oldmc, ok := dat["MulticastInterfaces"]; ok { + fmt.Println("DEBUG:", oldmc) + if oldmcvals, ok := oldmc.([]interface{}); ok { + var newmc []config.MulticastInterfaceConfig + for _, oldmcval := range oldmcvals { + if str, ok := oldmcval.(string); ok { + newmc = append(newmc, config.MulticastInterfaceConfig{ + Regex: str, + Incoming: true, + Outgoing: true, + }) + } + } + if newmc != nil { + dat["MulticastInterfaces"] = newmc + } + } + } // Sanitise the config confJson, err := json.Marshal(dat) if err != nil { From 4701f941a9f62f1e7234999052090358a1ccefd8 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 27 Jun 2021 09:42:46 +0100 Subject: [PATCH 0761/1109] Remove debug line --- cmd/yggdrasil/main.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 67a822e4..9572a177 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -97,7 +97,6 @@ func readConfig(log *log.Logger, useconf *bool, useconffile *string, normaliseco } } if oldmc, ok := dat["MulticastInterfaces"]; ok { - fmt.Println("DEBUG:", oldmc) if oldmcvals, ok := oldmc.([]interface{}); ok { var newmc []config.MulticastInterfaceConfig for _, oldmcval := range oldmcvals { From de853fed10adf946f1efbd4fad6aad87fc2c6d19 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 27 Jun 2021 17:24:46 -0500 Subject: [PATCH 0762/1109] multicast configuration changes --- cmd/yggdrasil/main.go | 14 +++++++-- src/config/config.go | 10 +++---- src/defaults/defaults_darwin.go | 4 +-- src/defaults/defaults_freebsd.go | 2 +- src/defaults/defaults_linux.go | 2 +- src/defaults/defaults_openbsd.go | 2 +- src/defaults/defaults_other.go | 2 +- src/defaults/defaults_windows.go | 2 +- src/multicast/multicast.go | 51 ++++++++++++++++---------------- 9 files changed, 49 insertions(+), 40 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 9572a177..55e6b261 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -102,13 +102,21 @@ func readConfig(log *log.Logger, useconf *bool, useconffile *string, normaliseco for _, oldmcval := range oldmcvals { if str, ok := oldmcval.(string); ok { newmc = append(newmc, config.MulticastInterfaceConfig{ - Regex: str, - Incoming: true, - Outgoing: true, + Regex: str, + Beacon: true, + Listen: true, }) } } if newmc != nil { + if oldport, ok := dat["LinkLocalTCPPort"]; ok { + // numbers parse to float64 by default + if port, ok := oldport.(float64); ok { + for idx := range newmc { + newmc[idx].Port = uint16(port) + } + } + } dat["MulticastInterfaces"] = newmc } } diff --git a/src/config/config.go b/src/config/config.go index d81907f5..041147b8 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -31,11 +31,10 @@ type NodeConfig struct { InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ tls://a.b.c.d:e ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."` Listen []string `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntls://0.0.0.0:0 or tls://[::]:0 to listen on all interfaces."` 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. To disable\nthe admin socket, use the value \"none\" instead."` - MulticastInterfaces []MulticastInterfaceConfig `comment:"Regular expressions for which interfaces multicast peer discovery\nshould be enabled on. If none specified, multicast peer discovery is\ndisabled. The default value is .* which uses all interfaces."` + MulticastInterfaces []MulticastInterfaceConfig `comment:"Configuration for which interfaces multicast peer discovery should be\nenabled on. Each entry in the list should be a json object which may\ncontain Regex, Beacon, Listen, and Port. Regex is a regular expression\nwhich is matched against an interface name, and interfaces use the\nfirst configuration that they match gainst. Beacon configures whether\nor not the node should send link-local multicast beacons to advertise\ntheir presence, while listening for incoming connections on Port.\nListen controls whether or not the node listens for multicast beacons\nand opens outgoing connections."` AllowedPublicKeys []string `comment:"List of peer public keys to allow incoming peering connections\nfrom. If left empty/undefined then all connections will be allowed\nby default. This does not affect outgoing peerings, nor does it\naffect link-local peers discovered via multicast."` PublicKey string `comment:"Your public key. Your peers may ask you for this to put\ninto their AllowedPublicKeys configuration."` PrivateKey string `comment:"Your private key. DO NOT share this with anyone!"` - LinkLocalTCPPort uint16 `comment:"The port number to be used for the link-local TCP listeners for the\nconfigured MulticastInterfaces. This option does not affect listeners\nspecified in the Listen option. Unless you plan to firewall link-local\ntraffic, it is best to leave this as the default value of 0. This\noption cannot currently be changed by reloading config during runtime."` IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."` IfMTU uint64 `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` NodeInfoPrivacy bool `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."` @@ -43,9 +42,10 @@ type NodeConfig struct { } type MulticastInterfaceConfig struct { - Regex string - Incoming bool - Outgoing bool + Regex string + Beacon bool + Listen bool + Port uint16 } // NewSigningKeys replaces the signing keypair in the NodeConfig with a new diff --git a/src/defaults/defaults_darwin.go b/src/defaults/defaults_darwin.go index a21539f7..e16f398f 100644 --- a/src/defaults/defaults_darwin.go +++ b/src/defaults/defaults_darwin.go @@ -14,8 +14,8 @@ func GetDefaults() platformDefaultParameters { // Multicast interfaces DefaultMulticastInterfaces: []MulticastInterfaceConfig{ - {Regex: "en.*", Incoming: true, Outgoing: true}, - {Regex: "bridge.*", Incoming: true, Outgoing: true}, + {Regex: "en.*", Beacon: true, Listen: true}, + {Regex: "bridge.*", Beacon: true, Listen: true}, }, // TUN/TAP diff --git a/src/defaults/defaults_freebsd.go b/src/defaults/defaults_freebsd.go index 400ae29a..6c3e1c60 100644 --- a/src/defaults/defaults_freebsd.go +++ b/src/defaults/defaults_freebsd.go @@ -14,7 +14,7 @@ func GetDefaults() platformDefaultParameters { // Multicast interfaces DefaultMulticastInterfaces: []MulticastInterfaceConfig{ - {Regex: ".*", Incoming: true, Outgoing: true}, + {Regex: ".*", Beacon: true, Listen: true}, }, // TUN/TAP diff --git a/src/defaults/defaults_linux.go b/src/defaults/defaults_linux.go index ac678d85..95c7ae95 100644 --- a/src/defaults/defaults_linux.go +++ b/src/defaults/defaults_linux.go @@ -14,7 +14,7 @@ func GetDefaults() platformDefaultParameters { // Multicast interfaces DefaultMulticastInterfaces: []MulticastInterfaceConfig{ - {Regex: ".*", Incoming: true, Outgoing: true}, + {Regex: ".*", Beacon: true, Listen: true}, }, // TUN/TAP diff --git a/src/defaults/defaults_openbsd.go b/src/defaults/defaults_openbsd.go index f3e2376c..ef339546 100644 --- a/src/defaults/defaults_openbsd.go +++ b/src/defaults/defaults_openbsd.go @@ -14,7 +14,7 @@ func GetDefaults() platformDefaultParameters { // Multicast interfaces DefaultMulticastInterfaces: []MulticastInterfaceConfig{ - {Regex: ".*", Incoming: true, Outgoing: true}, + {Regex: ".*", Beacon: true, Listen: true}, }, // TUN/TAP diff --git a/src/defaults/defaults_other.go b/src/defaults/defaults_other.go index d23c2ef0..d1417322 100644 --- a/src/defaults/defaults_other.go +++ b/src/defaults/defaults_other.go @@ -14,7 +14,7 @@ func GetDefaults() platformDefaultParameters { // Multicast interfaces DefaultMulticastInterfaces: []MulticastInterfaceConfig{ - {Regex: ".*", Incoming: true, Outgoing: true}, + {Regex: ".*", Beacon: true, Listen: true}, }, // TUN/TAP diff --git a/src/defaults/defaults_windows.go b/src/defaults/defaults_windows.go index 1e918c3b..e81d09cf 100644 --- a/src/defaults/defaults_windows.go +++ b/src/defaults/defaults_windows.go @@ -14,7 +14,7 @@ func GetDefaults() platformDefaultParameters { // Multicast interfaces DefaultMulticastInterfaces: []MulticastInterfaceConfig{ - {Regex: ".*", Incoming: true, Outgoing: true}, + {Regex: ".*", Beacon: true, Listen: true}, }, // TUN/TAP diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index aea4e079..882911df 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -32,22 +32,23 @@ type Multicast struct { sock *ipv6.PacketConn groupAddr string listeners map[string]*listenerInfo - listenPort uint16 isOpen bool _interfaces map[string]interfaceInfo } type interfaceInfo struct { - iface net.Interface - addrs []net.Addr - incoming bool - outgoing bool + iface net.Interface + addrs []net.Addr + beacon bool + listen bool + port uint16 } type listenerInfo struct { listener *core.TcpListener time time.Time interval time.Duration + port uint16 } // Init prepares the multicast interface for use. @@ -57,7 +58,6 @@ func (m *Multicast) Init(core *core.Core, nc *config.NodeConfig, log *log.Logger m.log = log m.listeners = make(map[string]*listenerInfo) m._interfaces = make(map[string]interfaceInfo) - m.listenPort = m.config.LinkLocalTCPPort m.groupAddr = "[ff02::114]:9001" return nil } @@ -196,11 +196,12 @@ func (m *Multicast) getAllowedInterfaces() map[string]interfaceInfo { } // Does the interface match the regular expression? Store it if so if e.MatchString(iface.Name) { - if ifcfg.Incoming || ifcfg.Outgoing { + if ifcfg.Beacon || ifcfg.Listen { info := interfaceInfo{ - iface: iface, - incoming: ifcfg.Incoming, - outgoing: ifcfg.Outgoing, + iface: iface, + beacon: ifcfg.Beacon, + listen: ifcfg.Listen, + port: ifcfg.Port, } interfaces[iface.Name] = info } @@ -280,18 +281,18 @@ func (m *Multicast) _announce() { if !addrIP.IsLinkLocalUnicast() { continue } - if info.outgoing { - // Join the multicast group, so we can listen for advertisements to open outgoing connections + if info.listen { + // Join the multicast group, so we can listen for beacons _ = m.sock.JoinGroup(&iface, groupAddr) } - if !info.incoming { - break // Don't send multicast advertisements if we don't accept incoming connections + if !info.beacon { + break // Don't send multicast beacons or accept incoming connections } // Try and see if we already have a TCP listener for this interface - var info *listenerInfo + var linfo *listenerInfo if nfo, ok := m.listeners[iface.Name]; !ok || nfo.listener.Listener == nil { // No listener was found - let's create one - urlString := fmt.Sprintf("tls://[%s]:%d", addrIP, m.listenPort) + urlString := fmt.Sprintf("tls://[%s]:%d", addrIP, info.port) u, err := url.Parse(urlString) if err != nil { panic(err) @@ -299,24 +300,24 @@ func (m *Multicast) _announce() { if li, err := m.core.Listen(u, iface.Name); err == nil { m.log.Debugln("Started multicasting on", iface.Name) // Store the listener so that we can stop it later if needed - info = &listenerInfo{listener: li, time: time.Now()} - m.listeners[iface.Name] = info + linfo = &listenerInfo{listener: li, time: time.Now()} + m.listeners[iface.Name] = linfo } else { m.log.Warnln("Not multicasting on", iface.Name, "due to error:", err) } } else { // An existing listener was found - info = m.listeners[iface.Name] + linfo = m.listeners[iface.Name] } // Make sure nothing above failed for some reason - if info == nil { + if linfo == nil { continue } - if time.Since(info.time) < info.interval { + if time.Since(linfo.time) < linfo.interval { continue } // Get the listener details and construct the multicast beacon - lladdr := info.listener.Listener.Addr().String() + lladdr := linfo.listener.Listener.Addr().String() if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil { a.Zone = "" destAddr.Zone = iface.Name @@ -327,8 +328,8 @@ func (m *Multicast) _announce() { msg = append(msg, pbs...) _, _ = m.sock.WriteTo(msg, nil, destAddr) } - if info.interval.Seconds() < 15 { - info.interval += time.Second + if linfo.interval.Seconds() < 15 { + linfo.interval += time.Second } break } @@ -391,7 +392,7 @@ func (m *Multicast) listen() { phony.Block(m, func() { interfaces = m._interfaces }) - if info, ok := interfaces[from.Zone]; ok && info.outgoing { + if info, ok := interfaces[from.Zone]; ok && info.listen { addr.Zone = "" pin := fmt.Sprintf("/?key=%s", hex.EncodeToString(key)) u, err := url.Parse("tls://" + addr.String() + pin) From 3646a8674c418098f13e68fbc3fe9086bcdb2941 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 28 Jun 2021 18:21:53 +0100 Subject: [PATCH 0763/1109] Yggdrasil v0.4.0rc4 --- src/core/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/version.go b/src/core/version.go index 6676c7b6..0bfbbcbc 100644 --- a/src/core/version.go +++ b/src/core/version.go @@ -22,7 +22,7 @@ func version_getBaseMetadata() version_metadata { return version_metadata{ meta: [4]byte{'m', 'e', 't', 'a'}, ver: 0, - minorVer: 0, + minorVer: 4, } } From 9b28f725e23120e86f63ed07b285c8592d977323 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 28 Jun 2021 18:28:56 +0100 Subject: [PATCH 0764/1109] Fix core_test.go --- src/core/core_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/core_test.go b/src/core/core_test.go index 9a2f230f..dd60af21 100644 --- a/src/core/core_test.go +++ b/src/core/core_test.go @@ -11,11 +11,12 @@ import ( "github.com/gologme/log" "github.com/yggdrasil-network/yggdrasil-go/src/config" + "github.com/yggdrasil-network/yggdrasil-go/src/defaults" ) // GenerateConfig produces default configuration with suitable modifications for tests. func GenerateConfig() *config.NodeConfig { - cfg := config.GenerateConfig() + cfg := defaults.GenerateConfig() cfg.AdminListen = "none" cfg.Listen = []string{"tcp://127.0.0.1:0"} cfg.IfName = "none" From ff44417decc3e7f9ad702b2c67b1cfa6d4739785 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 1 Jul 2021 08:04:01 -0500 Subject: [PATCH 0765/1109] listen for SIGHUP, restart node (reload config file, listen for stdin again, etc) if we receive one --- cmd/yggdrasil/main.go | 112 +++++++++++++++++++++++++++++++++--------- 1 file changed, 89 insertions(+), 23 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 55e6b261..84b0b11e 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "context" "crypto/ed25519" "encoding/hex" "encoding/json" @@ -41,15 +42,15 @@ type node struct { admin *admin.AdminSocket } -func readConfig(log *log.Logger, useconf *bool, useconffile *string, normaliseconf *bool) *config.NodeConfig { +func readConfig(log *log.Logger, useconf bool, useconffile string, normaliseconf bool) *config.NodeConfig { // Use a configuration file. If -useconf, the configuration will be read // from stdin. If -useconffile, the configuration will be read from the // filesystem. var conf []byte var err error - if *useconffile != "" { + if useconffile != "" { // Read the file from the filesystem - conf, err = ioutil.ReadFile(*useconffile) + conf, err = ioutil.ReadFile(useconffile) } else { // Read the file from stdin. conf, err = ioutil.ReadAll(os.Stdin) @@ -180,9 +181,21 @@ func setLogLevel(loglevel string, logger *log.Logger) { } } -// The main function is responsible for configuring and starting Yggdrasil. -func main() { - // Configure the command line parameters. +type yggArgs struct { + genconf bool + useconf bool + useconffile string + normaliseconf bool + confjson bool + autoconf bool + ver bool + logto string + getaddr bool + getsnet bool + loglevel string +} + +func getArgs() yggArgs { genconf := flag.Bool("genconf", false, "print a new config to stdout") useconf := flag.Bool("useconf", false, "read HJSON/JSON config from stdin") useconffile := flag.String("useconffile", "", "read HJSON/JSON config from specified file path") @@ -195,10 +208,43 @@ func main() { getsnet := flag.Bool("subnet", false, "returns the IPv6 subnet as derived from the supplied configuration") loglevel := flag.String("loglevel", "info", "loglevel to enable") flag.Parse() + return yggArgs{ + genconf: *genconf, + useconf: *useconf, + useconffile: *useconffile, + normaliseconf: *normaliseconf, + confjson: *confjson, + autoconf: *autoconf, + ver: *ver, + logto: *logto, + getaddr: *getaddr, + getsnet: *getsnet, + loglevel: *loglevel, + } +} + +// The main function is responsible for configuring and starting Yggdrasil. +func run(args yggArgs, ctx context.Context, done chan struct{}) { + defer close(done) + // Configure the command line parameters. + /* + genconf := flag.Bool("genconf", false, "print a new config to stdout") + useconf := flag.Bool("useconf", false, "read HJSON/JSON config from stdin") + useconffile := flag.String("useconffile", "", "read HJSON/JSON config from specified file path") + normaliseconf := flag.Bool("normaliseconf", false, "use in combination with either -useconf or -useconffile, outputs your configuration normalised") + confjson := flag.Bool("json", false, "print configuration from -genconf or -normaliseconf as JSON instead of HJSON") + autoconf := flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)") + ver := flag.Bool("version", false, "prints the version of this build") + logto := flag.String("logto", "stdout", "file path to log to, \"syslog\" or \"stdout\"") + getaddr := flag.Bool("address", false, "returns the IPv6 address as derived from the supplied configuration") + getsnet := flag.Bool("subnet", false, "returns the IPv6 subnet as derived from the supplied configuration") + loglevel := flag.String("loglevel", "info", "loglevel to enable") + flag.Parse() + */ // Create a new logger that logs output to stdout. var logger *log.Logger - switch *logto { + switch args.logto { case "stdout": logger = log.New(os.Stdout, "", log.Flags()) case "syslog": @@ -206,7 +252,7 @@ func main() { logger = log.New(syslogger, "", log.Flags()) } default: - if logfd, err := os.OpenFile(*logto, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil { + if logfd, err := os.OpenFile(args.logto, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil { logger = log.New(logfd, "", log.Flags()) } } @@ -215,33 +261,33 @@ func main() { logger.Warnln("Logging defaulting to stdout") } - if *normaliseconf { + if args.normaliseconf { setLogLevel("error", logger) } else { - setLogLevel(*loglevel, logger) + setLogLevel(args.loglevel, logger) } var cfg *config.NodeConfig var err error switch { - case *ver: + case args.ver: fmt.Println("Build name:", version.BuildName()) fmt.Println("Build version:", version.BuildVersion()) return - case *autoconf: + case args.autoconf: // Use an autoconf-generated config, this will give us random keys and // port numbers, and will use an automatically selected TUN/TAP interface. cfg = defaults.GenerateConfig() - case *useconffile != "" || *useconf: + case args.useconffile != "" || args.useconf: // Read the configuration from either stdin or from the filesystem - cfg = readConfig(logger, useconf, useconffile, normaliseconf) + cfg = readConfig(logger, args.useconf, args.useconffile, args.normaliseconf) // If the -normaliseconf option was specified then remarshal the above // configuration and print it back to stdout. This lets the user update // their configuration file with newly mapped names (like above) or to // convert from plain JSON to commented HJSON. - if *normaliseconf { + if args.normaliseconf { var bs []byte - if *confjson { + if args.confjson { bs, err = json.MarshalIndent(cfg, "", " ") } else { bs, err = hjson.Marshal(cfg) @@ -252,9 +298,9 @@ func main() { fmt.Println(string(bs)) return } - case *genconf: + case args.genconf: // Generate a new configuration and print it to stdout. - fmt.Println(doGenconf(*confjson)) + fmt.Println(doGenconf(args.confjson)) default: // No flags were provided, therefore print the list of flags to stdout. flag.PrintDefaults() @@ -273,14 +319,14 @@ func main() { return nil } switch { - case *getaddr: + case args.getaddr: if key := getNodeKey(); key != nil { addr := address.AddrForKey(key) ip := net.IP(addr[:]) fmt.Println(ip.String()) } return - case *getsnet: + case args.getsnet: if key := getNodeKey(); key != nil { snet := address.SubnetForKey(key) ipnet := net.IPNet{ @@ -337,10 +383,8 @@ func main() { logger.Infof("Your IPv6 address is %s", address.String()) logger.Infof("Your IPv6 subnet is %s", subnet.String()) // Catch interrupts from the operating system to exit gracefully. - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) + <-ctx.Done() // Capture the service being stopped on Windows. - <-c minwinsvc.SetOnExit(n.shutdown) n.shutdown() } @@ -351,3 +395,25 @@ func (n *node) shutdown() { _ = n.tuntap.Stop() n.core.Stop() } + +func main() { + args := getArgs() + hup := make(chan os.Signal, 1) + signal.Notify(hup, os.Interrupt, syscall.SIGHUP) + term := make(chan os.Signal, 1) + signal.Notify(term, os.Interrupt, syscall.SIGTERM) + for { + done := make(chan struct{}) + ctx, cancel := context.WithCancel(context.Background()) + go run(args, ctx, done) + select { + case <-hup: + cancel() + <-done + case <-term: + cancel() + <-done + return + } + } +} From df44b0227b743ef2fbfed01a2b32340843565f52 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 1 Jul 2021 08:54:14 -0500 Subject: [PATCH 0766/1109] disable SIGHUP handling for now --- cmd/yggdrasil/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 84b0b11e..c8165175 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -399,7 +399,7 @@ func (n *node) shutdown() { func main() { args := getArgs() hup := make(chan os.Signal, 1) - signal.Notify(hup, os.Interrupt, syscall.SIGHUP) + //signal.Notify(hup, os.Interrupt, syscall.SIGHUP) term := make(chan os.Signal, 1) signal.Notify(term, os.Interrupt, syscall.SIGTERM) for { From b07caa1e0ac7208be2a4b93c5a219e90caac77e5 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 1 Jul 2021 19:32:55 -0500 Subject: [PATCH 0767/1109] add first draft of changelog --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 856f1cea..50f91dd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,32 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> +## [0.4.0] - 2021-07-04 +### Added +- Connections to TLS peers will now pin the public ed25519 key used in the TLS handshake, or check that the handshake key matches the existing pinned key for that peer (if a key was pinned) + +### Changed +- This version is backwards incompatible with previous versions of Yggdrasil. The wire protocol version number, exchanged as part of the peer setup handshake, has been increased to 0.4. Nodes running this version will **not** be able to peer with earlier versions of Yggdrasil. Please note that **the network may be temporarily unstable** while infrastructure is being upgraded to the new release. Please also note that nodes may be removed from the public peers repository if they do not upgrade within some reasonable amount of time (think days or weeks, not months), and a large fraction of nodes on the public peers list may be unusable (offline or running an old and incompatible version) until that cleanup happens +- IP addresses are derived from ed25519 public (signing) keys. Previously, addresses were derived from a hash of X25519 (Diffie-Hellman) keys. Note that this means **all node addresses have changed with respect to previous releases**, so any existing services will be hosted at new addresses. The services page of the main repo has been updated to only list services in the new v0.4 network. Service operators are encouraged to submit a PR if they wish to be (re-)added to the list +- Link-local peers from multicast peer discovery will now connect over TLS. This is part of a general effort to encourage peering over TLS by default. Note that traffic is encrypted end-to-end regardless of how peer connections are established +- Multicast peer discovery is now more configurable. There are separate configuration options to control if beacons are sent, what port to listen on for incoming connections (if sending beacons), and whether or not to listen for beacons from other nodes (and open connections when receiving a beacon). Each configuration entry in the list specifies a regular expression to match against interface names. If an interface matches multiple regex in the list, it will use the settings for the first entry in the list that it matches with +- `socks://` peers now expect the destination endpoint to be a `tls://` listener +- The configuration file format has been updated. Among other things, there is now a single `PublicKey` and `PrivateKey` pair, both corresponding to ed25519 keys (since nodes no longer have a permanent X25519 key pair) +- Many of the admin functions available over `yggdrasilctl` have been changed or removed as part of rewrites to the code. The list of available functions will likely be expanded in future releases +- The session and routing code has been redesigned and rewritten as a [standalone library](https://github.com/Arceliar/ironwood). We expect to see reduced bandwidth use and improved reliability with the new design, especially in mobile networks. This is still an early work-in-progress, so the code hasn't been as well tested or optimized as the old code base. Please bear with us for these next few releases as we work through any bugs or issues +- Cryptographic sessions no longer use a single shared (ephemeral) secret for the entire life of the session. Keys are now rotated regularly for ongoing sessions (both nodes will rotate keys at least once per round trip exchange of traffic, which is arguably *too* aggressive, we may throttle this somehow in a future release) +- Source routing has been added. Under normal circumstances, this is what is used to forward session traffic (e.g. the user's IPv6 traffic) +- DHT-based routing has been added. This is used when the sender does not know a source route to the destination. Forwarding through the DHT is less efficient, but the only information that it requires the sender to know is the destination node's (static) key. This is primarily used during the key exchange at session setup, or as a temporary fallback when a source route fails due to changes in the network +- The greedy routing scheme, used to forward all traffic in previous releases, is now only used for protocol traffic (i.e. DHT setup and source route discovery) + +### Removed +- TunnelRouting (aka cryptokey routing) has been removed. We recommend tunneling an existing standard over Yggdrasil instead (e.g. `ip6gre` and `ip6gretap`) +- SessionFirewall has been removed. This was never a true firewall, it was simply a way to prevent a node from being flooded with unwanted sessions. The new code base needs to address that problem in other ways. Users who want a firewall or other packet filter should configure something supported by their OS (e.g. `ip6tables`) +- SIGHUP handling has been removed. SIGHUP will be handled normally (by exiting) instead of attempting to reload (parts of) the config file +- The whitepaper (and the rest of the doc folder) has been removed. This documentation was outdated since the routing code as been rewritten. New documentation will likely appear in a future release +- `cmd/yggrasilsim` has been removed. Since the routing code is now a separate library, it probably makes more sense to rewrite this as part of the library test code (or otherwise keep it separate from this repo) +- DHT lookups have been removed. This means there's nothing in the protocol that inherently makes it possible to crawl through the network. That said, `yggdrasilctl` exposes several remote `debug` functions, which make it possible to continue crawling the network. These will also be removed in a future release, if/when we're reasonably confident that things are working as intended + ## [0.3.16] - 2021-03-18 ### Added - New simulation code under `cmd/yggdrasilsim` (work-in-progress) From 9239ed70e472a8d7fc3bda279fd65e7be98dcf3b Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 1 Jul 2021 20:06:05 -0500 Subject: [PATCH 0768/1109] changelog revisions --- CHANGELOG.md | 68 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 49 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50f91dd9..c4c82d3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,29 +27,59 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [0.4.0] - 2021-07-04 ### Added -- Connections to TLS peers will now pin the public ed25519 key used in the TLS handshake, or check that the handshake key matches the existing pinned key for that peer (if a key was pinned) +- TLS connections now use public key pinning + - If no public key was already pinned, then the public key received as part of the TLS handshake is pinned to the connection + - The public key received as part of the handshake is checked against the pinned keys, and if no match is found, the connection is rejected ### Changed -- This version is backwards incompatible with previous versions of Yggdrasil. The wire protocol version number, exchanged as part of the peer setup handshake, has been increased to 0.4. Nodes running this version will **not** be able to peer with earlier versions of Yggdrasil. Please note that **the network may be temporarily unstable** while infrastructure is being upgraded to the new release. Please also note that nodes may be removed from the public peers repository if they do not upgrade within some reasonable amount of time (think days or weeks, not months), and a large fraction of nodes on the public peers list may be unusable (offline or running an old and incompatible version) until that cleanup happens -- IP addresses are derived from ed25519 public (signing) keys. Previously, addresses were derived from a hash of X25519 (Diffie-Hellman) keys. Note that this means **all node addresses have changed with respect to previous releases**, so any existing services will be hosted at new addresses. The services page of the main repo has been updated to only list services in the new v0.4 network. Service operators are encouraged to submit a PR if they wish to be (re-)added to the list -- Link-local peers from multicast peer discovery will now connect over TLS. This is part of a general effort to encourage peering over TLS by default. Note that traffic is encrypted end-to-end regardless of how peer connections are established -- Multicast peer discovery is now more configurable. There are separate configuration options to control if beacons are sent, what port to listen on for incoming connections (if sending beacons), and whether or not to listen for beacons from other nodes (and open connections when receiving a beacon). Each configuration entry in the list specifies a regular expression to match against interface names. If an interface matches multiple regex in the list, it will use the settings for the first entry in the list that it matches with -- `socks://` peers now expect the destination endpoint to be a `tls://` listener -- The configuration file format has been updated. Among other things, there is now a single `PublicKey` and `PrivateKey` pair, both corresponding to ed25519 keys (since nodes no longer have a permanent X25519 key pair) -- Many of the admin functions available over `yggdrasilctl` have been changed or removed as part of rewrites to the code. The list of available functions will likely be expanded in future releases -- The session and routing code has been redesigned and rewritten as a [standalone library](https://github.com/Arceliar/ironwood). We expect to see reduced bandwidth use and improved reliability with the new design, especially in mobile networks. This is still an early work-in-progress, so the code hasn't been as well tested or optimized as the old code base. Please bear with us for these next few releases as we work through any bugs or issues -- Cryptographic sessions no longer use a single shared (ephemeral) secret for the entire life of the session. Keys are now rotated regularly for ongoing sessions (both nodes will rotate keys at least once per round trip exchange of traffic, which is arguably *too* aggressive, we may throttle this somehow in a future release) -- Source routing has been added. Under normal circumstances, this is what is used to forward session traffic (e.g. the user's IPv6 traffic) -- DHT-based routing has been added. This is used when the sender does not know a source route to the destination. Forwarding through the DHT is less efficient, but the only information that it requires the sender to know is the destination node's (static) key. This is primarily used during the key exchange at session setup, or as a temporary fallback when a source route fails due to changes in the network -- The greedy routing scheme, used to forward all traffic in previous releases, is now only used for protocol traffic (i.e. DHT setup and source route discovery) +- This version is backwards incompatible with previous versions of Yggdrasil + - The wire protocol version number, exchanged as part of the peer setup handshake, has been increased to 0.4 + - Nodes running this version will **not** be able to peer with earlier versions of Yggdrasil + - Please note that **the network may be temporarily unstable** while infrastructure is being upgraded to the new release + - Please also note that nodes may be removed from the public peers repository if they do not upgrade within some time period after release (perhaps a couple of weeks) +- IP addresses are derived from ed25519 public (signing) keys + - Previously, addresses were derived from a hash of X25519 (Diffie-Hellman) keys + - Note that this means **all node addresses have changed with respect to previous releases**, so any existing services will be hosted at new addresses + - The services page of the main repo has been updated to only list services in the new v0.4 network + - Service operators are encouraged to submit a PR if they wish to be (re-)added to the list +- It is now recommended to peer over TLS + - Link-local peers from multicast peer discovery will now connect over TLS, with the key from the multicast announcement pinned to the connection + - `socks://` peers now expect the destination endpoint to be a `tls://` listener +- Multicast peer discovery is now more configurable + - There are separate configuration options to control if beacons are sent, what port to listen on for incoming connections (if sending beacons), and whether or not to listen for beacons from other nodes (and open connections when receiving a beacon) + - Each configuration entry in the list specifies a regular expression to match against interface names + - If an interface matches multiple regex in the list, it will use the settings for the first entry in the list that it matches with +- The session and routing code has been redesigned and rewritten: + - This is still an early work-in-progress, so the code hasn't been as well tested or optimized as the old code base. Please bear with us for these next few releases as we work through any bugs or issues + - Generally speaking, we expect to see reduced bandwidth use and improved reliability with the new design, especially in mobile networks + - Cryptographic sessions no longer use a single shared (ephemeral) secret for the entire life of the session. Keys are now rotated regularly for ongoing sessions (both nodes will rotate keys at least once per round trip exchange of traffic, which is arguably *too* aggressive, we may throttle this somehow in a future release) + - Source routing has been added. Under normal circumstances, this is what is used to forward session traffic (e.g. the user's IPv6 traffic) + - DHT-based routing has been added. This is used when the sender does not know a source route to the destination. Forwarding through the DHT is less efficient, but the only information that it requires the sender to know is the destination node's (static) key. This is primarily used during the key exchange at session setup, or as a temporary fallback when a source route fails due to changes in the network + - The new DHT design does not support crawling, and does not inherently allow nodes to look up the owner of an arbitrary key. In Yggdrasil, responding to lookups is implemented at the application level, and a response is only sent if the destination key matches the node's IP or /64 prefix + - The greedy routing scheme, used to forward all traffic in previous releases, is now only used for protocol traffic (i.e. DHT setup and source route discovery) + - The code now lives in a [standalone library](https://github.com/Arceliar/ironwood). You are encouraged **not** to use it, as it's still considered pre-alpha, but it's available for those who want to experiment with the new routing algorithm in other contexts +- Many of the admin functions available over `yggdrasilctl` have been changed or removed as part of rewrites to the code + - Several remote `debug` functions have been added temporarily, to allow for crawling and census gathering during the transition to the new version, but we intend to remove this at some point in the (possibly distant) future + - The list of available functions will likely be expanded in future releases +- The configuration file format has been updated in response to the changed/removed features ### Removed -- TunnelRouting (aka cryptokey routing) has been removed. We recommend tunneling an existing standard over Yggdrasil instead (e.g. `ip6gre` and `ip6gretap`) -- SessionFirewall has been removed. This was never a true firewall, it was simply a way to prevent a node from being flooded with unwanted sessions. The new code base needs to address that problem in other ways. Users who want a firewall or other packet filter should configure something supported by their OS (e.g. `ip6tables`) -- SIGHUP handling has been removed. SIGHUP will be handled normally (by exiting) instead of attempting to reload (parts of) the config file -- The whitepaper (and the rest of the doc folder) has been removed. This documentation was outdated since the routing code as been rewritten. New documentation will likely appear in a future release -- `cmd/yggrasilsim` has been removed. Since the routing code is now a separate library, it probably makes more sense to rewrite this as part of the library test code (or otherwise keep it separate from this repo) -- DHT lookups have been removed. This means there's nothing in the protocol that inherently makes it possible to crawl through the network. That said, `yggdrasilctl` exposes several remote `debug` functions, which make it possible to continue crawling the network. These will also be removed in a future release, if/when we're reasonably confident that things are working as intended +- TunnelRouting (aka cryptokey routing) has been removed + - It was too easy to accidentally break routing by capturing the route to peers with the tun adapter + - We recommend tunneling an existing standard over Yggdrasil instead (e.g. `ip6gre` and `ip6gretap`) +- SessionFirewall has been removed + - This was never a true firewall, it was simply a way to prevent a node from being flooded with unwanted sessions, so the name could be misleading and lead to a false sense of security + - Due to design changes, the new code needs to address the possible memory exhaustion attacks in other ways, and a single configurable list no longer makes sense + - Users who want a firewall or other packet filter mechansim should configure something supported by their OS instead (e.g. `ip6tables`) +- SIGHUP handling has been removed + - Previously, we set a custom SIGHUP handler, and used it to reload (parts of) the config file + - It was never obvious which parts could be reloaded while live, and which required the application to be killed and restarted to take effect + - Reloading the config without restarting was also a delicate and bug-prone process, and was distracting from more important developments + - SIGHUP will be handled normally (i.e. by exiting) +- The `doc` folder has been removed + - In light of the routing scheme's redesign and reimplementation, the documentation was out out-of-date + - New documentation may be added in a future release +- `cmd/yggrasilsim` has been removed, and is unlikely to return to this repository ## [0.3.16] - 2021-03-18 ### Added From 9391430bc08e91a99ca7dbc713c3cdcc4a12d66d Mon Sep 17 00:00:00 2001 From: Chris Hills Date: Fri, 2 Jul 2021 12:53:05 +0100 Subject: [PATCH 0769/1109] Update binary path in systemd service files to match the website. --- contrib/systemd/yggdrasil-default-config.service | 4 ++-- contrib/systemd/yggdrasil.service | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/systemd/yggdrasil-default-config.service b/contrib/systemd/yggdrasil-default-config.service index e9fe45be..b5af52f8 100644 --- a/contrib/systemd/yggdrasil-default-config.service +++ b/contrib/systemd/yggdrasil-default-config.service @@ -9,5 +9,5 @@ After=local-fs.target Type=oneshot Group=yggdrasil StandardOutput=file:/etc/yggdrasil.conf -ExecStart=/usr/bin/yggdrasil -genconf -ExecStartPost=/usr/bin/chmod 0640 /etc/yggdrasil.conf +ExecStart=/usr/local/bin/yggdrasil -genconf +ExecStartPost=/usr/local/bin/chmod 0640 /etc/yggdrasil.conf diff --git a/contrib/systemd/yggdrasil.service b/contrib/systemd/yggdrasil.service index 3002e61b..44afaf2b 100644 --- a/contrib/systemd/yggdrasil.service +++ b/contrib/systemd/yggdrasil.service @@ -12,7 +12,7 @@ ProtectSystem=true SyslogIdentifier=yggdrasil CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW ExecStartPre=+-/sbin/modprobe tun -ExecStart=/usr/bin/yggdrasil -useconffile /etc/yggdrasil.conf +ExecStart=/usr/local/bin/yggdrasil -useconffile /etc/yggdrasil.conf ExecReload=/bin/kill -HUP $MAINPID Restart=always TimeoutStopSec=5 From ccf03847fc701452ad95e11ea502e358c4b1901f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 2 Jul 2021 23:07:44 +0100 Subject: [PATCH 0770/1109] Update changelog --- CHANGELOG.md | 55 ++++++++++++++++++++++++---------------------------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4c82d3e..4487ba25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,58 +27,53 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [0.4.0] - 2021-07-04 ### Added +- New routing scheme, which is backwards incompatible with previous versions of Yggdrasil + - The wire protocol version number, exchanged as part of the peer setup handshake, has been increased to 0.4 + - Nodes running this new version **will not** be able to peer with earlier versions of Yggdrasil + - Please note that **the network may be temporarily unstable** while infrastructure is being upgraded to the new release - TLS connections now use public key pinning - If no public key was already pinned, then the public key received as part of the TLS handshake is pinned to the connection - The public key received as part of the handshake is checked against the pinned keys, and if no match is found, the connection is rejected ### Changed -- This version is backwards incompatible with previous versions of Yggdrasil - - The wire protocol version number, exchanged as part of the peer setup handshake, has been increased to 0.4 - - Nodes running this version will **not** be able to peer with earlier versions of Yggdrasil - - Please note that **the network may be temporarily unstable** while infrastructure is being upgraded to the new release - - Please also note that nodes may be removed from the public peers repository if they do not upgrade within some time period after release (perhaps a couple of weeks) -- IP addresses are derived from ed25519 public (signing) keys +- IP addresses are now derived from ed25519 public (signing) keys - Previously, addresses were derived from a hash of X25519 (Diffie-Hellman) keys - - Note that this means **all node addresses have changed with respect to previous releases**, so any existing services will be hosted at new addresses - - The services page of the main repo has been updated to only list services in the new v0.4 network - - Service operators are encouraged to submit a PR if they wish to be (re-)added to the list + - Importantly, this means that **all internal IPv6 addresses will change with this release** — this will affect anyone running public services or relying on Yggdrasil for remote access - It is now recommended to peer over TLS - - Link-local peers from multicast peer discovery will now connect over TLS, with the key from the multicast announcement pinned to the connection - - `socks://` peers now expect the destination endpoint to be a `tls://` listener + - Link-local peers from multicast peer discovery will now connect over TLS, with the key from the multicast beacon pinned to the connection + - `socks://` peers now expect the destination endpoint to be a `tls://` listener, instead of a `tcp://` listener - Multicast peer discovery is now more configurable - There are separate configuration options to control if beacons are sent, what port to listen on for incoming connections (if sending beacons), and whether or not to listen for beacons from other nodes (and open connections when receiving a beacon) - Each configuration entry in the list specifies a regular expression to match against interface names - If an interface matches multiple regex in the list, it will use the settings for the first entry in the list that it matches with -- The session and routing code has been redesigned and rewritten: - - This is still an early work-in-progress, so the code hasn't been as well tested or optimized as the old code base. Please bear with us for these next few releases as we work through any bugs or issues - - Generally speaking, we expect to see reduced bandwidth use and improved reliability with the new design, especially in mobile networks - - Cryptographic sessions no longer use a single shared (ephemeral) secret for the entire life of the session. Keys are now rotated regularly for ongoing sessions (both nodes will rotate keys at least once per round trip exchange of traffic, which is arguably *too* aggressive, we may throttle this somehow in a future release) +- The session and routing code has been entirely redesigned and rewritten + - This is still an early work-in-progress, so the code hasn't been as well tested or optimized as the old code base — please bear with us for these next few releases as we work through any bugs or issues + - Generally speaking, we expect to see reduced bandwidth use and improved reliability with the new design, especially in cases where nodes move around or change peerings frequently + - Cryptographic sessions no longer use a single shared (ephemeral) secret for the entire life of the session. Keys are now rotated regularly for ongoing sessions (currently rotated at least once per round trip exchange of traffic, subject to change in future releases) - Source routing has been added. Under normal circumstances, this is what is used to forward session traffic (e.g. the user's IPv6 traffic) - DHT-based routing has been added. This is used when the sender does not know a source route to the destination. Forwarding through the DHT is less efficient, but the only information that it requires the sender to know is the destination node's (static) key. This is primarily used during the key exchange at session setup, or as a temporary fallback when a source route fails due to changes in the network - - The new DHT design does not support crawling, and does not inherently allow nodes to look up the owner of an arbitrary key. In Yggdrasil, responding to lookups is implemented at the application level, and a response is only sent if the destination key matches the node's IP or /64 prefix + - The new DHT design is no longer RPC-based, does not support crawling and does not inherently allow nodes to look up the owner of an arbitrary key. Responding to lookups is now implemented at the application level and a response is only sent if the destination key matches the node's `/128` IP or `/64` prefix - The greedy routing scheme, used to forward all traffic in previous releases, is now only used for protocol traffic (i.e. DHT setup and source route discovery) - - The code now lives in a [standalone library](https://github.com/Arceliar/ironwood). You are encouraged **not** to use it, as it's still considered pre-alpha, but it's available for those who want to experiment with the new routing algorithm in other contexts + - The routing logic now lives in a [standalone library](https://github.com/Arceliar/ironwood). You are encouraged **not** to use it, as it's still considered pre-alpha, but it's available for those who want to experiment with the new routing algorithm in other contexts - Many of the admin functions available over `yggdrasilctl` have been changed or removed as part of rewrites to the code - Several remote `debug` functions have been added temporarily, to allow for crawling and census gathering during the transition to the new version, but we intend to remove this at some point in the (possibly distant) future - The list of available functions will likely be expanded in future releases - The configuration file format has been updated in response to the changed/removed features ### Removed -- TunnelRouting (aka cryptokey routing) has been removed - - It was too easy to accidentally break routing by capturing the route to peers with the tun adapter - - We recommend tunneling an existing standard over Yggdrasil instead (e.g. `ip6gre` and `ip6gretap`) -- SessionFirewall has been removed - - This was never a true firewall, it was simply a way to prevent a node from being flooded with unwanted sessions, so the name could be misleading and lead to a false sense of security - - Due to design changes, the new code needs to address the possible memory exhaustion attacks in other ways, and a single configurable list no longer makes sense +- Tunnel routing (a.k.a. crypto-key routing or "CKR") has been removed + - It was far too easy to accidentally break routing altogether by capturing the route to peers with the TUN adapter + - We recommend tunnelling an existing standard over Yggdrasil instead (e.g. `ip6gre`, `ip6gretap` or other encapsulations) + - All `TunnelRouting` configuration options will no longer take effect +- Session firewall has been removed + - This was never a true firewall — it didn't behave like a stateful IP firewall, often allowed return traffic unexpectedly and was simply a way to prevent a node from being flooded with unwanted sessions, so the name could be misleading and usually lead to a false sense of security + - Due to design changes, the new code needs to address the possible memory exhaustion attacks in other ways and a single configurable list no longer makes sense - Users who want a firewall or other packet filter mechansim should configure something supported by their OS instead (e.g. `ip6tables`) -- SIGHUP handling has been removed - - Previously, we set a custom SIGHUP handler, and used it to reload (parts of) the config file - - It was never obvious which parts could be reloaded while live, and which required the application to be killed and restarted to take effect + - All `SessionFirewall` configuration options will no longer take effect +- `SIGHUP` handling to reload the configuration at runtime has been removed + - It was not obvious which parts of the configuration could be reloaded at runtime, and which required the application to be killed and restarted to take effect - Reloading the config without restarting was also a delicate and bug-prone process, and was distracting from more important developments - - SIGHUP will be handled normally (i.e. by exiting) -- The `doc` folder has been removed - - In light of the routing scheme's redesign and reimplementation, the documentation was out out-of-date - - New documentation may be added in a future release + - `SIGHUP` will be handled normally (i.e. by exiting) - `cmd/yggrasilsim` has been removed, and is unlikely to return to this repository ## [0.3.16] - 2021-03-18 From 540e0bc2ce92202745ed49891d8bb3adbe36b749 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 2 Jul 2021 23:11:16 +0100 Subject: [PATCH 0771/1109] Update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4487ba25..bdb634f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - The new DHT design is no longer RPC-based, does not support crawling and does not inherently allow nodes to look up the owner of an arbitrary key. Responding to lookups is now implemented at the application level and a response is only sent if the destination key matches the node's `/128` IP or `/64` prefix - The greedy routing scheme, used to forward all traffic in previous releases, is now only used for protocol traffic (i.e. DHT setup and source route discovery) - The routing logic now lives in a [standalone library](https://github.com/Arceliar/ironwood). You are encouraged **not** to use it, as it's still considered pre-alpha, but it's available for those who want to experiment with the new routing algorithm in other contexts + - Session MTUs may be slightly lower now, in order to accommodate large packet headers if required - Many of the admin functions available over `yggdrasilctl` have been changed or removed as part of rewrites to the code - Several remote `debug` functions have been added temporarily, to allow for crawling and census gathering during the transition to the new version, but we intend to remove this at some point in the (possibly distant) future - The list of available functions will likely be expanded in future releases @@ -63,7 +64,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Removed - Tunnel routing (a.k.a. crypto-key routing or "CKR") has been removed - It was far too easy to accidentally break routing altogether by capturing the route to peers with the TUN adapter - - We recommend tunnelling an existing standard over Yggdrasil instead (e.g. `ip6gre`, `ip6gretap` or other encapsulations) + - We recommend tunnelling an existing standard over Yggdrasil instead (e.g. `ip6gre`, `ip6gretap` or other similar encapsulations, using Yggdrasil IPv6 addresses as the tunnel endpoints) - All `TunnelRouting` configuration options will no longer take effect - Session firewall has been removed - This was never a true firewall — it didn't behave like a stateful IP firewall, often allowed return traffic unexpectedly and was simply a way to prevent a node from being flooded with unwanted sessions, so the name could be misleading and usually lead to a false sense of security From 4d47ba8bf4815855d75cb2fcac3568aadb8cecad Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 2 Jul 2021 23:21:38 +0100 Subject: [PATCH 0772/1109] Update README.md --- README.md | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index d1d254a6..a709ae4e 100644 --- a/README.md +++ b/README.md @@ -20,25 +20,11 @@ internet-like topologies. ## Supported Platforms -We actively support the following platforms, and packages are available for -some of the below: +Yggdrasil works on a number of platforms, including Linux, macOS, Ubiquiti +EdgeRouter, VyOS, Windows, FreeBSD, OpenBSD and OpenWrt. -- Linux - - `.deb` and `.rpm` packages are built by CI for Debian and Red Hat-based - distributions - - Arch, Nix, Void packages also available within their respective repositories -- macOS - - `.pkg` packages are built by CI -- Ubiquiti EdgeOS - - `.deb` Vyatta packages are built by CI -- Windows -- FreeBSD -- OpenBSD -- OpenWrt - -Please see our [Platforms](https://yggdrasil-network.github.io/platforms.html) pages for more -specific information about each of our supported platforms, including -installation steps and caveats. +Please see our [Installation](https://yggdrasil-network.github.io/installation.html) +page for more information. You may also find other platform-specific wrappers, scripts or tools in the `contrib` folder. @@ -97,21 +83,18 @@ by giving the Yggdrasil binary the `CAP_NET_ADMIN` capability. ## Documentation -Documentation is available on our [GitHub -Pages](https://yggdrasil-network.github.io) site, or in the base submodule -repository within `doc/yggdrasil-network.github.io`. +Documentation is available on our [GitHub Pages](https://yggdrasil-network.github.io) +site. -- [Configuration file options](https://yggdrasil-network.github.io/configuration.html) -- [Platform-specific documentation](https://yggdrasil-network.github.io/platforms.html) +- [Configuration file](https://yggdrasil-network.github.io/configuration.html) - [Frequently asked questions](https://yggdrasil-network.github.io/faq.html) -- [Admin API documentation](https://yggdrasil-network.github.io/admin.html) - [Version changelog](CHANGELOG.md) ## Community Feel free to join us on our [Matrix channel](https://matrix.to/#/#yggdrasil:matrix.org) at `#yggdrasil:matrix.org` -or in the `#yggdrasil` IRC channel on Freenode. +or in the `#yggdrasil` IRC channel on [libera.chat](https://libera.chat). ## License From f7b91a8f939420f74ce8b911a6690fc961086033 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 2 Jul 2021 23:24:34 +0100 Subject: [PATCH 0773/1109] Update README.md --- README.md | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index a709ae4e..f1f71d11 100644 --- a/README.md +++ b/README.md @@ -11,23 +11,14 @@ allows pretty much any IPv6-capable application to communicate securely with other Yggdrasil nodes. Yggdrasil does not require you to have IPv6 Internet connectivity - it also works over IPv4. -Although Yggdrasil shares many similarities with -[cjdns](https://github.com/cjdelisle/cjdns), it employs a different routing -algorithm based on a globally-agreed spanning tree and greedy routing in a -metric space, and aims to implement some novel local backpressure routing -techniques. In theory, Yggdrasil should scale well on networks with -internet-like topologies. - ## Supported Platforms Yggdrasil works on a number of platforms, including Linux, macOS, Ubiquiti EdgeRouter, VyOS, Windows, FreeBSD, OpenBSD and OpenWrt. Please see our [Installation](https://yggdrasil-network.github.io/installation.html) -page for more information. - -You may also find other platform-specific wrappers, scripts or tools in the -`contrib` folder. +page for more information. You may also find other platform-specific wrappers, scripts +or tools in the `contrib` folder. ## Building @@ -83,10 +74,10 @@ by giving the Yggdrasil binary the `CAP_NET_ADMIN` capability. ## Documentation -Documentation is available on our [GitHub Pages](https://yggdrasil-network.github.io) -site. +Documentation is available [on our website](https://yggdrasil-network.github.io). -- [Configuration file](https://yggdrasil-network.github.io/configuration.html) +- [Installing Yggdrasil](https://yggdrasil-network.github.io/installation.html) +- [Configuring Yggdrasil](https://yggdrasil-network.github.io/configuration.html) - [Frequently asked questions](https://yggdrasil-network.github.io/faq.html) - [Version changelog](CHANGELOG.md) From 5844079f67de4909c8479221598de73c3a8b6132 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 3 Jul 2021 17:27:00 -0500 Subject: [PATCH 0774/1109] make sure genconf exits, clean up some commented out code --- cmd/yggdrasil/main.go | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index c8165175..d6d0d1a6 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -226,22 +226,6 @@ func getArgs() yggArgs { // The main function is responsible for configuring and starting Yggdrasil. func run(args yggArgs, ctx context.Context, done chan struct{}) { defer close(done) - // Configure the command line parameters. - /* - genconf := flag.Bool("genconf", false, "print a new config to stdout") - useconf := flag.Bool("useconf", false, "read HJSON/JSON config from stdin") - useconffile := flag.String("useconffile", "", "read HJSON/JSON config from specified file path") - normaliseconf := flag.Bool("normaliseconf", false, "use in combination with either -useconf or -useconffile, outputs your configuration normalised") - confjson := flag.Bool("json", false, "print configuration from -genconf or -normaliseconf as JSON instead of HJSON") - autoconf := flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)") - ver := flag.Bool("version", false, "prints the version of this build") - logto := flag.String("logto", "stdout", "file path to log to, \"syslog\" or \"stdout\"") - getaddr := flag.Bool("address", false, "returns the IPv6 address as derived from the supplied configuration") - getsnet := flag.Bool("subnet", false, "returns the IPv6 subnet as derived from the supplied configuration") - loglevel := flag.String("loglevel", "info", "loglevel to enable") - flag.Parse() - */ - // Create a new logger that logs output to stdout. var logger *log.Logger switch args.logto { @@ -301,6 +285,7 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { case args.genconf: // Generate a new configuration and print it to stdout. fmt.Println(doGenconf(args.confjson)) + return default: // No flags were provided, therefore print the list of flags to stdout. flag.PrintDefaults() @@ -414,6 +399,8 @@ func main() { cancel() <-done return + case <-done: + return } } } From 2fc34bbd5a17e002b323017de5d14a69c5940421 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 4 Jul 2021 09:26:17 +0100 Subject: [PATCH 0775/1109] Revert "Merge pull request #796 from Chaz6/update-systemd-files" This reverts commit 88bd098f91fdf7ca693018ab7982f680b5c9db92, reversing changes made to 4d798a34940af2eacba73f274ea9260fb57e8452. --- contrib/systemd/yggdrasil-default-config.service | 4 ++-- contrib/systemd/yggdrasil.service | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/systemd/yggdrasil-default-config.service b/contrib/systemd/yggdrasil-default-config.service index b5af52f8..e9fe45be 100644 --- a/contrib/systemd/yggdrasil-default-config.service +++ b/contrib/systemd/yggdrasil-default-config.service @@ -9,5 +9,5 @@ After=local-fs.target Type=oneshot Group=yggdrasil StandardOutput=file:/etc/yggdrasil.conf -ExecStart=/usr/local/bin/yggdrasil -genconf -ExecStartPost=/usr/local/bin/chmod 0640 /etc/yggdrasil.conf +ExecStart=/usr/bin/yggdrasil -genconf +ExecStartPost=/usr/bin/chmod 0640 /etc/yggdrasil.conf diff --git a/contrib/systemd/yggdrasil.service b/contrib/systemd/yggdrasil.service index 44afaf2b..3002e61b 100644 --- a/contrib/systemd/yggdrasil.service +++ b/contrib/systemd/yggdrasil.service @@ -12,7 +12,7 @@ ProtectSystem=true SyslogIdentifier=yggdrasil CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW ExecStartPre=+-/sbin/modprobe tun -ExecStart=/usr/local/bin/yggdrasil -useconffile /etc/yggdrasil.conf +ExecStart=/usr/bin/yggdrasil -useconffile /etc/yggdrasil.conf ExecReload=/bin/kill -HUP $MAINPID Restart=always TimeoutStopSec=5 From f990a56046646ef97322c910b8cd057e99ad035d Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 5 Jul 2021 13:14:12 -0500 Subject: [PATCH 0776/1109] have the core wrap and export the underlying PacketConn, move IPv6 ReadWriteCloser wrapper logic to a separate package --- cmd/yggdrasil/main.go | 4 +- src/core/api.go | 42 +------ src/core/core.go | 62 ++++++++- src/core/link.go | 2 +- src/core/nodeinfo.go | 4 +- src/core/proto.go | 9 +- src/core/types.go | 7 -- src/{core => ipv6rwc}/icmpv6.go | 2 +- src/{core/keystore.go => ipv6rwc/ipv6rwc.go} | 125 +++++++++++++------ src/tuntap/iface.go | 4 +- src/tuntap/tun.go | 19 ++- 11 files changed, 170 insertions(+), 110 deletions(-) rename src/{core => ipv6rwc}/icmpv6.go (99%) rename src/{core/keystore.go => ipv6rwc/ipv6rwc.go} (77%) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index d6d0d1a6..95d40151 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -29,6 +29,7 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/defaults" "github.com/yggdrasil-network/yggdrasil-go/src/core" + "github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc" "github.com/yggdrasil-network/yggdrasil-go/src/multicast" "github.com/yggdrasil-network/yggdrasil-go/src/tuntap" "github.com/yggdrasil-network/yggdrasil-go/src/version" @@ -353,7 +354,8 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { } n.multicast.SetupAdminHandlers(n.admin) // Start the TUN/TAP interface - if err := n.tuntap.Init(&n.core, cfg, logger, nil); err != nil { + rwc := ipv6rwc.NewReadWriteCloser(&n.core) + if err := n.tuntap.Init(rwc, cfg, logger, nil); err != nil { logger.Errorln("An error occurred initialising TUN/TAP:", err) } else if err := n.tuntap.Start(); err != nil { logger.Errorln("An error occurred starting TUN/TAP:", err) diff --git a/src/core/api.go b/src/core/api.go index 05d9f36f..6b219340 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -48,7 +48,7 @@ type Session struct { func (c *Core) GetSelf() Self { var self Self - s := c.pc.PacketConn.Debug.GetSelf() + s := c.PacketConn.PacketConn.Debug.GetSelf() self.Key = s.Key self.Root = s.Root self.Coords = s.Coords @@ -63,7 +63,7 @@ func (c *Core) GetPeers() []Peer { names[info.conn] = info.lname } c.links.mutex.Unlock() - ps := c.pc.PacketConn.Debug.GetPeers() + ps := c.PacketConn.PacketConn.Debug.GetPeers() for _, p := range ps { var info Peer info.Key = p.Key @@ -81,7 +81,7 @@ func (c *Core) GetPeers() []Peer { func (c *Core) GetDHT() []DHTEntry { var dhts []DHTEntry - ds := c.pc.PacketConn.Debug.GetDHT() + ds := c.PacketConn.PacketConn.Debug.GetDHT() for _, d := range ds { var info DHTEntry info.Key = d.Key @@ -94,7 +94,7 @@ func (c *Core) GetDHT() []DHTEntry { func (c *Core) GetPaths() []PathEntry { var paths []PathEntry - ps := c.pc.PacketConn.Debug.GetPaths() + ps := c.PacketConn.PacketConn.Debug.GetPaths() for _, p := range ps { var info PathEntry info.Key = p.Key @@ -106,7 +106,7 @@ func (c *Core) GetPaths() []PathEntry { func (c *Core) GetSessions() []Session { var sessions []Session - ss := c.pc.Debug.GetSessions() + ss := c.PacketConn.Debug.GetSessions() for _, s := range ss { var info Session info.Key = s.Key @@ -239,38 +239,6 @@ func (c *Core) PublicKey() ed25519.PublicKey { return c.public } -func (c *Core) MaxMTU() uint64 { - return c.store.maxSessionMTU() -} - -func (c *Core) SetMTU(mtu uint64) { - if mtu < 1280 { - mtu = 1280 - } - c.store.mutex.Lock() - c.store.mtu = mtu - c.store.mutex.Unlock() -} - -func (c *Core) MTU() uint64 { - c.store.mutex.Lock() - mtu := c.store.mtu - c.store.mutex.Unlock() - return mtu -} - -// Implement io.ReadWriteCloser - -func (c *Core) Read(p []byte) (n int, err error) { - n, err = c.store.readPC(p) - return -} - -func (c *Core) Write(p []byte) (n int, err error) { - n, err = c.store.writePC(p) - return -} - func (c *Core) Close() error { c.Stop() return nil diff --git a/src/core/core.go b/src/core/core.go index 89d49177..ac0ea1f0 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -7,10 +7,12 @@ import ( "errors" "fmt" "io/ioutil" + "net" "net/url" "time" - iw "github.com/Arceliar/ironwood/encrypted" + iwe "github.com/Arceliar/ironwood/encrypted" + iwt "github.com/Arceliar/ironwood/types" "github.com/Arceliar/phony" "github.com/gologme/log" @@ -26,13 +28,12 @@ type Core struct { // We're going to keep our own copy of the provided config - that way we can // guarantee that it will be covered by the mutex phony.Inbox - pc *iw.PacketConn + *iwe.PacketConn config *config.NodeConfig // Config secret ed25519.PrivateKey public ed25519.PublicKey links links proto protoHandler - store keyStore log *log.Logger addPeerTimer *time.Timer ctx context.Context @@ -62,9 +63,8 @@ func (c *Core) _init() error { c.public = c.secret.Public().(ed25519.PublicKey) // TODO check public against current.PublicKey, error if they don't match - c.pc, err = iw.NewPacketConn(c.secret) + c.PacketConn, err = iwe.NewPacketConn(c.secret) c.ctx, c.ctxCancel = context.WithCancel(context.Background()) - c.store.init(c) c.proto.init(c) if err := c.proto.nodeinfo.setNodeInfo(c.config.NodeInfo, c.config.NodeInfoPrivacy); err != nil { return fmt.Errorf("setNodeInfo: %w", err) @@ -168,7 +168,7 @@ func (c *Core) Stop() { func (c *Core) _stop() { c.log.Infoln("Stopping...") c.ctxCancel() - c.pc.Close() + c.PacketConn.Close() // TODO make c.Close() do the right thing (act like c.Stop()) if c.addPeerTimer != nil { c.addPeerTimer.Stop() c.addPeerTimer = nil @@ -181,3 +181,53 @@ func (c *Core) _stop() { */ c.log.Infoln("Stopped") } + +func (c *Core) MTU() uint64 { + const sessionTypeOverhead = 1 + return c.PacketConn.MTU() - sessionTypeOverhead +} + +func (c *Core) ReadFrom(p []byte) (n int, from net.Addr, err error) { + buf := make([]byte, c.PacketConn.MTU(), 65535) + for { + bs := buf + n, from, err = c.PacketConn.ReadFrom(bs) + if err != nil { + return 0, from, err + } + if n == 0 { + continue + } + switch bs[0] { + case typeSessionTraffic: + // This is what we want to handle here + case typeSessionProto: + var key keyArray + copy(key[:], from.(iwt.Addr)) + data := append([]byte(nil), bs[1:n]...) + c.proto.handleProto(nil, key, data) + continue + default: + continue + } + bs = bs[1:n] + copy(p, bs) + if len(p) < len(bs) { + n = len(p) + } else { + n = len(bs) + } + return + } +} + +func (c *Core) WriteTo(p []byte, addr net.Addr) (n int, err error) { + buf := make([]byte, 0, 65535) + buf = append(buf, typeSessionTraffic) + buf = append(buf, p...) + n, err = c.PacketConn.WriteTo(buf, addr) + if n > 0 { + n -= 1 + } + return +} diff --git a/src/core/link.go b/src/core/link.go index 165b18b2..ccab9219 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -230,7 +230,7 @@ func (intf *link) handler() (chan struct{}, error) { intf.links.core.log.Infof("Connected %s: %s, source %s", strings.ToUpper(intf.info.linkType), themString, intf.info.local) // Run the handler - err = intf.links.core.pc.HandleConn(ed25519.PublicKey(intf.info.key[:]), intf.conn) + err = intf.links.core.HandleConn(ed25519.PublicKey(intf.info.key[:]), intf.conn) // TODO don't report an error if it's just a 'use of closed network connection' if err != nil { intf.links.core.log.Infof("Disconnected %s: %s, source %s; error: %s", diff --git a/src/core/nodeinfo.go b/src/core/nodeinfo.go index 30644710..90153118 100644 --- a/src/core/nodeinfo.go +++ b/src/core/nodeinfo.go @@ -129,7 +129,7 @@ func (m *nodeinfo) _sendReq(key keyArray, callback func(nodeinfo NodeInfoPayload if callback != nil { m._addCallback(key, callback) } - _, _ = m.proto.core.pc.WriteTo([]byte{typeSessionProto, typeProtoNodeInfoRequest}, iwt.Addr(key[:])) + _, _ = m.proto.core.WriteTo([]byte{typeSessionProto, typeProtoNodeInfoRequest}, iwt.Addr(key[:])) } func (m *nodeinfo) handleReq(from phony.Actor, key keyArray) { @@ -146,7 +146,7 @@ func (m *nodeinfo) handleRes(from phony.Actor, key keyArray, info NodeInfoPayloa func (m *nodeinfo) _sendRes(key keyArray) { bs := append([]byte{typeSessionProto, typeProtoNodeInfoResponse}, m._getNodeInfo()...) - _, _ = m.proto.core.pc.WriteTo(bs, iwt.Addr(key[:])) + _, _ = m.proto.core.WriteTo(bs, iwt.Addr(key[:])) } // Admin socket stuff diff --git a/src/core/proto.go b/src/core/proto.go index 557ac1d5..f9d12c8a 100644 --- a/src/core/proto.go +++ b/src/core/proto.go @@ -1,6 +1,7 @@ package core import ( + "crypto/ed25519" "encoding/hex" "encoding/json" "errors" @@ -29,6 +30,8 @@ type reqInfo struct { timer *time.Timer // time.AfterFunc cleanup } +type keyArray [ed25519.PublicKeySize]byte + type protoHandler struct { phony.Inbox core *Core @@ -149,7 +152,7 @@ func (p *protoHandler) _handleGetPeersRequest(key keyArray) { for _, pinfo := range peers { tmp := append(bs, pinfo.Key[:]...) const responseOverhead = 2 // 1 debug type, 1 getpeers type - if uint64(len(tmp))+responseOverhead > p.core.store.maxSessionMTU() { + if uint64(len(tmp))+responseOverhead > p.core.MTU() { break } bs = tmp @@ -191,7 +194,7 @@ func (p *protoHandler) _handleGetDHTRequest(key keyArray) { for _, dinfo := range dinfos { tmp := append(bs, dinfo.Key[:]...) const responseOverhead = 2 // 1 debug type, 1 getdht type - if uint64(len(tmp))+responseOverhead > p.core.store.maxSessionMTU() { + if uint64(len(tmp))+responseOverhead > p.core.MTU() { break } bs = tmp @@ -209,7 +212,7 @@ func (p *protoHandler) _handleGetDHTResponse(key keyArray, bs []byte) { func (p *protoHandler) _sendDebug(key keyArray, dType uint8, data []byte) { bs := append([]byte{typeSessionProto, typeProtoDebug, dType}, data...) - _, _ = p.core.pc.WriteTo(bs, iwt.Addr(key[:])) + _, _ = p.core.WriteTo(bs, iwt.Addr(key[:])) } // Admin socket stuff diff --git a/src/core/types.go b/src/core/types.go index e325b55e..258563a1 100644 --- a/src/core/types.go +++ b/src/core/types.go @@ -1,12 +1,5 @@ package core -// Out-of-band packet types -const ( - typeKeyDummy = iota // nolint:deadcode,varcheck - typeKeyLookup - typeKeyResponse -) - // In-band packet types const ( typeSessionDummy = iota // nolint:deadcode,varcheck diff --git a/src/core/icmpv6.go b/src/ipv6rwc/icmpv6.go similarity index 99% rename from src/core/icmpv6.go rename to src/ipv6rwc/icmpv6.go index d15fbbcb..8faf1d51 100644 --- a/src/core/icmpv6.go +++ b/src/ipv6rwc/icmpv6.go @@ -1,4 +1,4 @@ -package core +package ipv6rwc // The ICMPv6 module implements functions to easily create ICMPv6 // packets. These functions, when mixed with the built-in Go IPv6 diff --git a/src/core/keystore.go b/src/ipv6rwc/ipv6rwc.go similarity index 77% rename from src/core/keystore.go rename to src/ipv6rwc/ipv6rwc.go index 21fb8459..1c715f0f 100644 --- a/src/core/keystore.go +++ b/src/ipv6rwc/ipv6rwc.go @@ -1,4 +1,4 @@ -package core +package ipv6rwc import ( "crypto/ed25519" @@ -14,14 +14,22 @@ import ( iwt "github.com/Arceliar/ironwood/types" "github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/core" ) const keyStoreTimeout = 2 * time.Minute +// Out-of-band packet types +const ( + typeKeyDummy = iota // nolint:deadcode,varcheck + typeKeyLookup + typeKeyResponse +) + type keyArray [ed25519.PublicKeySize]byte type keyStore struct { - core *Core + core *core.Core address address.Address subnet address.Subnet mutex sync.Mutex @@ -45,11 +53,11 @@ type buffer struct { timeout *time.Timer } -func (k *keyStore) init(core *Core) { - k.core = core - k.address = *address.AddrForKey(k.core.public) - k.subnet = *address.SubnetForKey(k.core.public) - if err := k.core.pc.SetOutOfBandHandler(k.oobHandler); err != nil { +func (k *keyStore) init(c *core.Core) { + k.core = c + k.address = *address.AddrForKey(k.core.PublicKey()) + k.subnet = *address.SubnetForKey(k.core.PublicKey()) + if err := k.core.SetOutOfBandHandler(k.oobHandler); err != nil { err = fmt.Errorf("tun.core.SetOutOfBandHander: %w", err) panic(err) } @@ -66,7 +74,7 @@ func (k *keyStore) sendToAddress(addr address.Address, bs []byte) { if info := k.addrToInfo[addr]; info != nil { k.resetTimeout(info) k.mutex.Unlock() - _, _ = k.core.pc.WriteTo(bs, iwt.Addr(info.key[:])) + _, _ = k.core.WriteTo(bs, iwt.Addr(info.key[:])) } else { var buf *buffer if buf = k.addrBuffer[addr]; buf == nil { @@ -95,7 +103,7 @@ func (k *keyStore) sendToSubnet(subnet address.Subnet, bs []byte) { if info := k.subnetToInfo[subnet]; info != nil { k.resetTimeout(info) k.mutex.Unlock() - _, _ = k.core.pc.WriteTo(bs, iwt.Addr(info.key[:])) + _, _ = k.core.WriteTo(bs, iwt.Addr(info.key[:])) } else { var buf *buffer if buf = k.subnetBuffer[subnet]; buf == nil { @@ -135,11 +143,11 @@ func (k *keyStore) update(key ed25519.PublicKey) *keyInfo { k.resetTimeout(info) k.mutex.Unlock() if buf := k.addrBuffer[info.address]; buf != nil { - k.core.pc.WriteTo(buf.packet, iwt.Addr(info.key[:])) + k.core.WriteTo(buf.packet, iwt.Addr(info.key[:])) delete(k.addrBuffer, info.address) } if buf := k.subnetBuffer[info.subnet]; buf != nil { - k.core.pc.WriteTo(buf.packet, iwt.Addr(info.key[:])) + k.core.WriteTo(buf.packet, iwt.Addr(info.key[:])) delete(k.subnetBuffer, info.subnet) } } else { @@ -191,46 +199,29 @@ func (k *keyStore) oobHandler(fromKey, toKey ed25519.PublicKey, data []byte) { } func (k *keyStore) sendKeyLookup(partial ed25519.PublicKey) { - sig := ed25519.Sign(k.core.secret, partial[:]) + sig := ed25519.Sign(k.core.PrivateKey(), partial[:]) bs := append([]byte{typeKeyLookup}, sig...) - _ = k.core.pc.SendOutOfBand(partial, bs) + _ = k.core.SendOutOfBand(partial, bs) } func (k *keyStore) sendKeyResponse(dest ed25519.PublicKey) { - sig := ed25519.Sign(k.core.secret, dest[:]) + sig := ed25519.Sign(k.core.PrivateKey(), dest[:]) bs := append([]byte{typeKeyResponse}, sig...) - _ = k.core.pc.SendOutOfBand(dest, bs) -} - -func (k *keyStore) maxSessionMTU() uint64 { - const sessionTypeOverhead = 1 - return k.core.pc.MTU() - sessionTypeOverhead + _ = k.core.SendOutOfBand(dest, bs) } func (k *keyStore) readPC(p []byte) (int, error) { - buf := make([]byte, k.core.pc.MTU(), 65535) + buf := make([]byte, k.core.MTU(), 65535) for { bs := buf - n, from, err := k.core.pc.ReadFrom(bs) + n, from, err := k.core.ReadFrom(bs) if err != nil { return n, err } if n == 0 { continue } - switch bs[0] { - case typeSessionTraffic: - // This is what we want to handle here - case typeSessionProto: - var key keyArray - copy(key[:], from.(iwt.Addr)) - data := append([]byte(nil), bs[1:n]...) - k.core.proto.handleProto(nil, key, data) - continue - default: - continue - } - bs = bs[1:n] + bs = bs[:n] if len(bs) == 0 { continue } @@ -294,15 +285,69 @@ func (k *keyStore) writePC(bs []byte) (int, error) { strErr := fmt.Sprint("incorrect source address: ", net.IP(srcAddr[:]).String()) return 0, errors.New(strErr) } - buf := make([]byte, 1+len(bs), 65535) - buf[0] = typeSessionTraffic - copy(buf[1:], bs) if dstAddr.IsValid() { - k.sendToAddress(dstAddr, buf) + k.sendToAddress(dstAddr, bs) } else if dstSubnet.IsValid() { - k.sendToSubnet(dstSubnet, buf) + k.sendToSubnet(dstSubnet, bs) } else { return 0, errors.New("invalid destination address") } return len(bs), nil } + +// Exported API + +func (k *keyStore) MaxMTU() uint64 { + return k.core.MTU() +} + +func (k *keyStore) SetMTU(mtu uint64) { + if mtu > k.MaxMTU() { + mtu = k.MaxMTU() + } + if mtu < 1280 { + mtu = 1280 + } + k.mutex.Lock() + k.mtu = mtu + k.mutex.Unlock() +} + +func (k *keyStore) MTU() uint64 { + k.mutex.Lock() + mtu := k.mtu + k.mutex.Unlock() + return mtu +} + +type ReadWriteCloser struct { + keyStore +} + +func NewReadWriteCloser(c *core.Core) *ReadWriteCloser { + rwc := new(ReadWriteCloser) + rwc.init(c) + return rwc +} + +func (rwc *ReadWriteCloser) Address() address.Address { + return rwc.address +} + +func (rwc *ReadWriteCloser) Subnet() address.Subnet { + return rwc.subnet +} + +func (rwc *ReadWriteCloser) Read(p []byte) (n int, err error) { + return rwc.readPC(p) +} + +func (rwc *ReadWriteCloser) Write(p []byte) (n int, err error) { + return rwc.writePC(p) +} + +func (rwc *ReadWriteCloser) Close() error { + err := rwc.core.Close() + rwc.core.Stop() + return err +} diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index e72b091f..f629399a 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -17,7 +17,7 @@ func (tun *TunAdapter) read() { begin := TUN_OFFSET_BYTES end := begin + n bs := buf[begin:end] - if _, err := tun.core.Write(bs); err != nil { + if _, err := tun.rwc.Write(bs); err != nil { tun.log.Debugln("Unable to send packet:", err) } } @@ -27,7 +27,7 @@ func (tun *TunAdapter) write() { var buf [TUN_OFFSET_BYTES + 65535]byte for { bs := buf[TUN_OFFSET_BYTES:] - n, err := tun.core.Read(bs) + n, err := tun.rwc.Read(bs) if err != nil { tun.log.Errorln("Exiting tun writer due to core read error:", err) return diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index dbba018b..4caefe4a 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -21,8 +21,8 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/config" - "github.com/yggdrasil-network/yggdrasil-go/src/core" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" + "github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc" ) type MTU uint16 @@ -32,7 +32,7 @@ type MTU uint16 // should pass this object to the yggdrasil.SetRouterAdapter() function before // calling yggdrasil.Start(). type TunAdapter struct { - core *core.Core + rwc *ipv6rwc.ReadWriteCloser config *config.NodeConfig log *log.Logger addr address.Address @@ -93,8 +93,8 @@ func MaximumMTU() uint64 { // Init initialises the TUN module. You must have acquired a Listener from // the Yggdrasil core before this point and it must not be in use elsewhere. -func (tun *TunAdapter) Init(core *core.Core, config *config.NodeConfig, log *log.Logger, options interface{}) error { - tun.core = core +func (tun *TunAdapter) Init(rwc *ipv6rwc.ReadWriteCloser, config *config.NodeConfig, log *log.Logger, options interface{}) error { + tun.rwc = rwc tun.config = config tun.log = log return nil @@ -119,9 +119,8 @@ func (tun *TunAdapter) _start() error { if tun.config == nil { return errors.New("no configuration available to TUN") } - pk := tun.core.PublicKey() - tun.addr = *address.AddrForKey(pk) - tun.subnet = *address.SubnetForKey(pk) + tun.addr = tun.rwc.Address() + tun.subnet = tun.rwc.Subnet() addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1) if tun.config.IfName == "none" || tun.config.IfName == "dummy" { tun.log.Debugln("Not starting TUN as ifname is none or dummy") @@ -130,8 +129,8 @@ func (tun *TunAdapter) _start() error { return nil } mtu := tun.config.IfMTU - if tun.core.MaxMTU() < mtu { - mtu = tun.core.MaxMTU() + if tun.rwc.MaxMTU() < mtu { + mtu = tun.rwc.MaxMTU() } if err := tun.setup(tun.config.IfName, addr, mtu); err != nil { return err @@ -139,7 +138,7 @@ func (tun *TunAdapter) _start() error { if tun.MTU() != mtu { tun.log.Warnf("Warning: Interface MTU %d automatically adjusted to %d (supported range is 1280-%d)", tun.config.IfMTU, tun.MTU(), MaximumMTU()) } - tun.core.SetMTU(tun.MTU()) + tun.rwc.SetMTU(tun.MTU()) tun.isOpen = true tun.isEnabled = true go tun.read() From e4ce2c79a9a6430f7145798a9a3e93c544bd869c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 5 Jul 2021 22:26:09 +0100 Subject: [PATCH 0777/1109] Add LocalAddr to complete net.PacketConn interface --- src/core/core.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/core/core.go b/src/core/core.go index ac0ea1f0..8c816d2b 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -231,3 +231,7 @@ func (c *Core) WriteTo(p []byte, addr net.Addr) (n int, err error) { } return } + +func (c *Core) LocalAddr() net.Addr { + return iwt.Addr(c.public) +} From e224c02d6d88cfd36a0f8598dccfd4cbd4e1f297 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 5 Jul 2021 22:35:46 +0100 Subject: [PATCH 0778/1109] Revert "Add LocalAddr to complete net.PacketConn interface" This reverts commit e4ce2c79a9a6430f7145798a9a3e93c544bd869c. --- src/core/core.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/core/core.go b/src/core/core.go index 8c816d2b..ac0ea1f0 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -231,7 +231,3 @@ func (c *Core) WriteTo(p []byte, addr net.Addr) (n int, err error) { } return } - -func (c *Core) LocalAddr() net.Addr { - return iwt.Addr(c.public) -} From 3704ebf4cbeab2b5818ca545e0bebfce2c7bd10a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Tue, 6 Jul 2021 19:45:12 -0500 Subject: [PATCH 0779/1109] fix debug rpcs and cleanup core.Close/core.Stop --- src/core/api.go | 5 ----- src/core/core.go | 26 ++++++++++++++++---------- src/core/nodeinfo.go | 4 ++-- src/core/proto.go | 2 +- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/core/api.go b/src/core/api.go index 6b219340..c312923d 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -239,11 +239,6 @@ func (c *Core) PublicKey() ed25519.PublicKey { return c.public } -func (c *Core) Close() error { - c.Stop() - return nil -} - // Hack to get the admin stuff working, TODO something cleaner type AddHandler interface { diff --git a/src/core/core.go b/src/core/core.go index ac0ea1f0..0332980b 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -161,25 +161,31 @@ func (c *Core) _start(nc *config.NodeConfig, log *log.Logger) error { // Stop shuts down the Yggdrasil node. func (c *Core) Stop() { - phony.Block(c, c._stop) + phony.Block(c, func() { + c.log.Infoln("Stopping...") + c._close() + c.log.Infoln("Stopped") + }) +} + +func (c *Core) Close() error { + var err error + phony.Block(c, func() { + err = c._close() + }) + return err } // This function is unsafe and should only be ran by the core actor. -func (c *Core) _stop() { - c.log.Infoln("Stopping...") +func (c *Core) _close() error { c.ctxCancel() - c.PacketConn.Close() // TODO make c.Close() do the right thing (act like c.Stop()) + err := c.PacketConn.Close() if c.addPeerTimer != nil { c.addPeerTimer.Stop() c.addPeerTimer = nil } _ = c.links.stop() - /* FIXME this deadlocks, need a waitgroup or something to coordinate shutdown - for _, peer := range c.GetPeers() { - c.DisconnectPeer(peer.Port) - } - */ - c.log.Infoln("Stopped") + return err } func (c *Core) MTU() uint64 { diff --git a/src/core/nodeinfo.go b/src/core/nodeinfo.go index 90153118..4ca21d73 100644 --- a/src/core/nodeinfo.go +++ b/src/core/nodeinfo.go @@ -129,7 +129,7 @@ func (m *nodeinfo) _sendReq(key keyArray, callback func(nodeinfo NodeInfoPayload if callback != nil { m._addCallback(key, callback) } - _, _ = m.proto.core.WriteTo([]byte{typeSessionProto, typeProtoNodeInfoRequest}, iwt.Addr(key[:])) + _, _ = m.proto.core.PacketConn.WriteTo([]byte{typeSessionProto, typeProtoNodeInfoRequest}, iwt.Addr(key[:])) } func (m *nodeinfo) handleReq(from phony.Actor, key keyArray) { @@ -146,7 +146,7 @@ func (m *nodeinfo) handleRes(from phony.Actor, key keyArray, info NodeInfoPayloa func (m *nodeinfo) _sendRes(key keyArray) { bs := append([]byte{typeSessionProto, typeProtoNodeInfoResponse}, m._getNodeInfo()...) - _, _ = m.proto.core.WriteTo(bs, iwt.Addr(key[:])) + _, _ = m.proto.core.PacketConn.WriteTo(bs, iwt.Addr(key[:])) } // Admin socket stuff diff --git a/src/core/proto.go b/src/core/proto.go index f9d12c8a..e60caeff 100644 --- a/src/core/proto.go +++ b/src/core/proto.go @@ -212,7 +212,7 @@ func (p *protoHandler) _handleGetDHTResponse(key keyArray, bs []byte) { func (p *protoHandler) _sendDebug(key keyArray, dType uint8, data []byte) { bs := append([]byte{typeSessionProto, typeProtoDebug, dType}, data...) - _, _ = p.core.WriteTo(bs, iwt.Addr(key[:])) + _, _ = p.core.PacketConn.WriteTo(bs, iwt.Addr(key[:])) } // Admin socket stuff From cd5383f7b799932b2536e4ee82e768558a073ec5 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 7 Jul 2021 18:36:51 -0500 Subject: [PATCH 0780/1109] fix core tests --- src/core/core_test.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/core/core_test.go b/src/core/core_test.go index dd60af21..fcfe2e31 100644 --- a/src/core/core_test.go +++ b/src/core/core_test.go @@ -44,13 +44,11 @@ func CreateAndConnectTwo(t testing.TB, verbose bool) (nodeA *Core, nodeB *Core) if err := nodeA.Start(GenerateConfig(), GetLoggerWithPrefix("A: ", verbose)); err != nil { t.Fatal(err) } - nodeA.SetMTU(1500) nodeB = new(Core) if err := nodeB.Start(GenerateConfig(), GetLoggerWithPrefix("B: ", verbose)); err != nil { t.Fatal(err) } - nodeB.SetMTU(1500) u, err := url.Parse("tcp://" + nodeA.links.tcp.getAddr().String()) if err != nil { @@ -94,7 +92,7 @@ func CreateEchoListener(t testing.TB, nodeA *Core, bufLen int, repeats int) chan buf := make([]byte, bufLen) res := make([]byte, bufLen) for i := 0; i < repeats; i++ { - n, err := nodeA.Read(buf) + n, from, err := nodeA.ReadFrom(buf) if err != nil { t.Error(err) return @@ -106,7 +104,7 @@ func CreateEchoListener(t testing.TB, nodeA *Core, bufLen int, repeats int) chan copy(res, buf) copy(res[8:24], buf[24:40]) copy(res[24:40], buf[8:24]) - _, err = nodeA.Write(res) + _, err = nodeA.WriteTo(res, from) if err != nil { t.Error(err) } @@ -141,12 +139,12 @@ func TestCore_Start_Transfer(t *testing.T) { msg[0] = 0x60 copy(msg[8:24], nodeB.Address()) copy(msg[24:40], nodeA.Address()) - _, err := nodeB.Write(msg) + _, err := nodeB.WriteTo(msg, nodeA.LocalAddr()) if err != nil { t.Fatal(err) } buf := make([]byte, msgLen) - _, err = nodeB.Read(buf) + _, _, err = nodeB.ReadFrom(buf) if err != nil { t.Fatal(err) } @@ -179,12 +177,13 @@ func BenchmarkCore_Start_Transfer(b *testing.B) { b.SetBytes(int64(msgLen)) b.ResetTimer() + addr := nodeA.LocalAddr() for i := 0; i < b.N; i++ { - _, err := nodeB.Write(msg) + _, err := nodeB.WriteTo(msg, addr) if err != nil { b.Fatal(err) } - _, err = nodeB.Read(buf) + _, _, err = nodeB.ReadFrom(buf) if err != nil { b.Fatal(err) } From 04ecdf60453846e0ab397f1a36497bb3b268f2bb Mon Sep 17 00:00:00 2001 From: Timur Demin Date: Tue, 6 Jul 2021 21:24:21 +0500 Subject: [PATCH 0781/1109] Preallocate memory when deriving address from key This makes src/address.AddrForKey preallocate 32 bytes before starting the address derivation. As benches in syg_go show, reallocating temp takes 20% of the function runtime. --- src/address/address.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/address/address.go b/src/address/address.go index 7add23ac..0e2400ed 100644 --- a/src/address/address.go +++ b/src/address/address.go @@ -64,7 +64,7 @@ func AddrForKey(publicKey ed25519.PublicKey) *Address { buf[idx] = ^buf[idx] } var addr Address - var temp []byte + var temp = make([]byte, 0, 32) done := false ones := byte(0) bits := byte(0) From 6a0ddc20efb5d5e79cc64630d3fdeddb8101149f Mon Sep 17 00:00:00 2001 From: cofob <49928332+cofob@users.noreply.github.com> Date: Wed, 21 Jul 2021 17:57:59 +0700 Subject: [PATCH 0782/1109] Allow yggdrasil bind to ports <1024 --- contrib/systemd/yggdrasil.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/systemd/yggdrasil.service b/contrib/systemd/yggdrasil.service index 3002e61b..27d27907 100644 --- a/contrib/systemd/yggdrasil.service +++ b/contrib/systemd/yggdrasil.service @@ -10,7 +10,7 @@ Group=yggdrasil ProtectHome=true ProtectSystem=true SyslogIdentifier=yggdrasil -CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW +CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE ExecStartPre=+-/sbin/modprobe tun ExecStart=/usr/bin/yggdrasil -useconffile /etc/yggdrasil.conf ExecReload=/bin/kill -HUP $MAINPID From d8df9755f2e8c41ee21110f77bc0b0b662f59414 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 28 Jul 2021 22:11:20 +0100 Subject: [PATCH 0783/1109] Allow specifying TLS SNI with ?sni= in peering URI --- src/core/link.go | 1 + src/core/tcp.go | 1 + src/core/tls.go | 13 ++++++++----- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/core/link.go b/src/core/link.go index ccab9219..f753156e 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -98,6 +98,7 @@ func (l *links) call(u *url.URL, sintf string) error { l.tcp.call(pathtokens[0], tcpOpts, sintf) case "tls": tcpOpts.upgrade = l.tcp.tls.forDialer + tcpOpts.tlsSNI = u.Query().Get("sni") l.tcp.call(u.Host, tcpOpts, sintf) default: return errors.New("unknown call scheme: " + u.Scheme) diff --git a/src/core/tcp.go b/src/core/tcp.go index 572fd652..7b1773b8 100644 --- a/src/core/tcp.go +++ b/src/core/tcp.go @@ -64,6 +64,7 @@ type tcpOptions struct { socksProxyAddr string socksProxyAuth *proxy.Auth socksPeerAddr string + tlsSNI string } func (l *TcpListener) Stop() { diff --git a/src/core/tls.go b/src/core/tls.go index 4c25225b..eb21fcbc 100644 --- a/src/core/tls.go +++ b/src/core/tls.go @@ -77,8 +77,8 @@ func (t *tcptls) init(tcp *tcp) { } } -func (t *tcptls) configForOptions(options *tcpOptions) *tls.Config { - config := *t.config +func (t *tcptls) configForOptions(options *tcpOptions, serverName string) *tls.Config { + config := t.config.Clone() config.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) error { if len(rawCerts) != 1 { return errors.New("tls not exactly 1 cert") @@ -103,11 +103,14 @@ func (t *tcptls) configForOptions(options *tcpOptions) *tls.Config { } return nil } - return &config + if serverName != "" { + config.ServerName = serverName + } + return config } func (t *tcptls) upgradeListener(c net.Conn, options *tcpOptions) (net.Conn, error) { - config := t.configForOptions(options) + config := t.configForOptions(options, "") conn := tls.Server(c, config) if err := conn.Handshake(); err != nil { return c, err @@ -116,7 +119,7 @@ func (t *tcptls) upgradeListener(c net.Conn, options *tcpOptions) (net.Conn, err } func (t *tcptls) upgradeDialer(c net.Conn, options *tcpOptions) (net.Conn, error) { - config := t.configForOptions(options) + config := t.configForOptions(options, options.tlsSNI) conn := tls.Client(c, config) if err := conn.Handshake(); err != nil { return c, err From f094cf34bf23bde6d6c8515c79f1ad14ea3a230c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 28 Jul 2021 22:23:33 +0100 Subject: [PATCH 0784/1109] Set SNI by default if the peering URI contains a DNS name --- src/core/link.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/core/link.go b/src/core/link.go index f753156e..98a7ab31 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -99,6 +99,13 @@ func (l *links) call(u *url.URL, sintf string) error { case "tls": tcpOpts.upgrade = l.tcp.tls.forDialer tcpOpts.tlsSNI = u.Query().Get("sni") + if tcpOpts.tlsSNI == "" { + // SNI headers must contain hostnames and not IP addresses, so we must make sure + // that we do not populate the SNI with an IP literal. + if host, _, err := net.SplitHostPort(u.Host); err == nil && net.ParseIP(host) == nil { + tcpOpts.tlsSNI = host + } + } l.tcp.call(u.Host, tcpOpts, sintf) default: return errors.New("unknown call scheme: " + u.Scheme) From bbdff033ce29d657c099f73f7946c1d5061a1b9c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 1 Aug 2021 21:36:51 +0100 Subject: [PATCH 0785/1109] Update SNI code --- src/core/link.go | 14 +++++++++++--- src/core/tls.go | 10 ++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/core/link.go b/src/core/link.go index 98a7ab31..755d38a3 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -98,10 +98,18 @@ func (l *links) call(u *url.URL, sintf string) error { l.tcp.call(pathtokens[0], tcpOpts, sintf) case "tls": tcpOpts.upgrade = l.tcp.tls.forDialer - tcpOpts.tlsSNI = u.Query().Get("sni") + // SNI headers must contain hostnames and not IP addresses, so we must make sure + // that we do not populate the SNI with an IP literal. We do this by splitting + // the host-port combo from the query option and then seeing if it parses to an + // IP address successfully or not. + if sni := u.Query().Get("sni"); sni != "" { + if host, _, err := net.SplitHostPort(sni); err == nil && net.ParseIP(host) == nil { + tcpOpts.tlsSNI = host + } + } + // If the SNI is not configured still because the above failed then we'll try + // again but this time we'll use the host part of the peering URI instead. if tcpOpts.tlsSNI == "" { - // SNI headers must contain hostnames and not IP addresses, so we must make sure - // that we do not populate the SNI with an IP literal. if host, _, err := net.SplitHostPort(u.Host); err == nil && net.ParseIP(host) == nil { tcpOpts.tlsSNI = host } diff --git a/src/core/tls.go b/src/core/tls.go index eb21fcbc..9e340ac4 100644 --- a/src/core/tls.go +++ b/src/core/tls.go @@ -77,7 +77,7 @@ func (t *tcptls) init(tcp *tcp) { } } -func (t *tcptls) configForOptions(options *tcpOptions, serverName string) *tls.Config { +func (t *tcptls) configForOptions(options *tcpOptions) *tls.Config { config := t.config.Clone() config.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) error { if len(rawCerts) != 1 { @@ -103,14 +103,11 @@ func (t *tcptls) configForOptions(options *tcpOptions, serverName string) *tls.C } return nil } - if serverName != "" { - config.ServerName = serverName - } return config } func (t *tcptls) upgradeListener(c net.Conn, options *tcpOptions) (net.Conn, error) { - config := t.configForOptions(options, "") + config := t.configForOptions(options) conn := tls.Server(c, config) if err := conn.Handshake(); err != nil { return c, err @@ -119,7 +116,8 @@ func (t *tcptls) upgradeListener(c net.Conn, options *tcpOptions) (net.Conn, err } func (t *tcptls) upgradeDialer(c net.Conn, options *tcpOptions) (net.Conn, error) { - config := t.configForOptions(options, options.tlsSNI) + config := t.configForOptions(options) + config.ServerName = options.tlsSNI conn := tls.Client(c, config) if err := conn.Handshake(); err != nil { return c, err From d1cd671bece1c69942f069d059174f01d5565406 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 1 Aug 2021 21:39:49 +0100 Subject: [PATCH 0786/1109] Fix bug --- src/core/link.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/link.go b/src/core/link.go index 755d38a3..8797b886 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -103,8 +103,8 @@ func (l *links) call(u *url.URL, sintf string) error { // the host-port combo from the query option and then seeing if it parses to an // IP address successfully or not. if sni := u.Query().Get("sni"); sni != "" { - if host, _, err := net.SplitHostPort(sni); err == nil && net.ParseIP(host) == nil { - tcpOpts.tlsSNI = host + if net.ParseIP(sni) == nil { + tcpOpts.tlsSNI = sni } } // If the SNI is not configured still because the above failed then we'll try From cbb6dc1b7d16616a27b31befcc5a9cf2b08b62c2 Mon Sep 17 00:00:00 2001 From: Alex Kotov Date: Tue, 3 Aug 2021 02:47:38 +0500 Subject: [PATCH 0787/1109] Split yggdrasilctl code into separate functions (refactoring) (#815) * Move yggdrasilctl responses to separate functions * Move yggdrasilctl request switch to separate function * Add empty lines * Create struct CmdLine for yggdrasilctl * Move yggdrasilctl command line parsing to separate func * Turn struct CmdLine into CmdLineEnv * Rename func parseCmdLine to parseFlagsAndArgs * Move yggdrasilctl endpoint setting logic into separate func * Function to create yggdrasilctl CmdLineEnv * Reorder code * Move struct fields into lines * Turn yggdrasilctl CmdLineEnv funcs to methods * Move yggdrasilctl connection code to separate func * Rename functions * Move yggdrasilctl command line env to separate mod * Move yggdrasilctl command line env to main mod * Run goimports Co-authored-by: Neil Alexander --- cmd/yggdrasilctl/cmd_line_env.go | 94 +++++ cmd/yggdrasilctl/main.go | 651 ++++++++++++++++--------------- 2 files changed, 422 insertions(+), 323 deletions(-) create mode 100644 cmd/yggdrasilctl/cmd_line_env.go diff --git a/cmd/yggdrasilctl/cmd_line_env.go b/cmd/yggdrasilctl/cmd_line_env.go new file mode 100644 index 00000000..bd6df8fc --- /dev/null +++ b/cmd/yggdrasilctl/cmd_line_env.go @@ -0,0 +1,94 @@ +package main + +import ( + "bytes" + "flag" + "fmt" + "io/ioutil" + "log" + "os" + + "github.com/hjson/hjson-go" + "golang.org/x/text/encoding/unicode" + + "github.com/yggdrasil-network/yggdrasil-go/src/defaults" +) + +type CmdLineEnv struct { + args []string + endpoint, server string + injson, verbose, ver bool +} + +func newCmdLineEnv() CmdLineEnv { + var cmdLineEnv CmdLineEnv + cmdLineEnv.endpoint = defaults.GetDefaults().DefaultAdminListen + return cmdLineEnv +} + +func (cmdLineEnv *CmdLineEnv) parseFlagsAndArgs() { + flag.Usage = func() { + fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] command [key=value] [key=value] ...\n\n", os.Args[0]) + fmt.Println("Options:") + flag.PrintDefaults() + fmt.Println() + fmt.Println("Please note that options must always specified BEFORE the command\non the command line or they will be ignored.") + fmt.Println() + fmt.Println("Commands:\n - Use \"list\" for a list of available commands") + fmt.Println() + fmt.Println("Examples:") + fmt.Println(" - ", os.Args[0], "list") + fmt.Println(" - ", os.Args[0], "getPeers") + fmt.Println(" - ", os.Args[0], "-v getSelf") + fmt.Println(" - ", os.Args[0], "setTunTap name=auto mtu=1500 tap_mode=false") + fmt.Println(" - ", os.Args[0], "-endpoint=tcp://localhost:9001 getDHT") + fmt.Println(" - ", os.Args[0], "-endpoint=unix:///var/run/ygg.sock getDHT") + } + + server := flag.String("endpoint", cmdLineEnv.endpoint, "Admin socket endpoint") + injson := flag.Bool("json", false, "Output in JSON format (as opposed to pretty-print)") + verbose := flag.Bool("v", false, "Verbose output (includes public keys)") + ver := flag.Bool("version", false, "Prints the version of this build") + + flag.Parse() + + cmdLineEnv.args = flag.Args() + cmdLineEnv.server = *server + cmdLineEnv.injson = *injson + cmdLineEnv.verbose = *verbose + cmdLineEnv.ver = *ver +} + +func (cmdLineEnv *CmdLineEnv) setEndpoint(logger *log.Logger) { + if cmdLineEnv.server == cmdLineEnv.endpoint { + if config, err := ioutil.ReadFile(defaults.GetDefaults().DefaultConfigFile); err == nil { + if bytes.Equal(config[0:2], []byte{0xFF, 0xFE}) || + bytes.Equal(config[0:2], []byte{0xFE, 0xFF}) { + utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM) + decoder := utf.NewDecoder() + config, err = decoder.Bytes(config) + if err != nil { + panic(err) + } + } + var dat map[string]interface{} + if err := hjson.Unmarshal(config, &dat); err != nil { + panic(err) + } + if ep, ok := dat["AdminListen"].(string); ok && (ep != "none" && ep != "") { + cmdLineEnv.endpoint = ep + logger.Println("Found platform default config file", defaults.GetDefaults().DefaultConfigFile) + logger.Println("Using endpoint", cmdLineEnv.endpoint, "from AdminListen") + } else { + logger.Println("Configuration file doesn't contain appropriate AdminListen option") + logger.Println("Falling back to platform default", defaults.GetDefaults().DefaultAdminListen) + } + } else { + logger.Println("Can't open config file from default location", defaults.GetDefaults().DefaultConfigFile) + logger.Println("Falling back to platform default", defaults.GetDefaults().DefaultAdminListen) + } + } else { + cmdLineEnv.endpoint = cmdLineEnv.server + logger.Println("Using endpoint", cmdLineEnv.endpoint, "from command line") + } +} diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index 91923392..788b4f19 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -6,7 +6,6 @@ import ( "errors" "flag" "fmt" - "io/ioutil" "log" "net" "net/url" @@ -15,10 +14,6 @@ import ( "strconv" "strings" - "golang.org/x/text/encoding/unicode" - - "github.com/hjson/hjson-go" - "github.com/yggdrasil-network/yggdrasil-go/src/defaults" "github.com/yggdrasil-network/yggdrasil-go/src/version" ) @@ -32,6 +27,7 @@ func main() { func run() int { logbuffer := &bytes.Buffer{} logger := log.New(logbuffer, "", log.Flags()) + defer func() int { if r := recover(); r != nil { logger.Println("Fatal error:", r) @@ -41,97 +37,24 @@ func run() int { return 0 }() - endpoint := defaults.GetDefaults().DefaultAdminListen + cmdLineEnv := newCmdLineEnv() + cmdLineEnv.parseFlagsAndArgs() - flag.Usage = func() { - fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] command [key=value] [key=value] ...\n\n", os.Args[0]) - fmt.Println("Options:") - flag.PrintDefaults() - fmt.Println() - fmt.Println("Please note that options must always specified BEFORE the command\non the command line or they will be ignored.") - fmt.Println() - fmt.Println("Commands:\n - Use \"list\" for a list of available commands") - fmt.Println() - fmt.Println("Examples:") - fmt.Println(" - ", os.Args[0], "list") - fmt.Println(" - ", os.Args[0], "getPeers") - fmt.Println(" - ", os.Args[0], "-v getSelf") - fmt.Println(" - ", os.Args[0], "setTunTap name=auto mtu=1500 tap_mode=false") - fmt.Println(" - ", os.Args[0], "-endpoint=tcp://localhost:9001 getDHT") - fmt.Println(" - ", os.Args[0], "-endpoint=unix:///var/run/ygg.sock getDHT") - } - server := flag.String("endpoint", endpoint, "Admin socket endpoint") - injson := flag.Bool("json", false, "Output in JSON format (as opposed to pretty-print)") - verbose := flag.Bool("v", false, "Verbose output (includes public keys)") - ver := flag.Bool("version", false, "Prints the version of this build") - flag.Parse() - args := flag.Args() - - if *ver { + if cmdLineEnv.ver { fmt.Println("Build name:", version.BuildName()) fmt.Println("Build version:", version.BuildVersion()) fmt.Println("To get the version number of the running Yggdrasil node, run", os.Args[0], "getSelf") return 0 } - if len(args) == 0 { + if len(cmdLineEnv.args) == 0 { flag.Usage() return 0 } - if *server == endpoint { - if config, err := ioutil.ReadFile(defaults.GetDefaults().DefaultConfigFile); err == nil { - if bytes.Equal(config[0:2], []byte{0xFF, 0xFE}) || - bytes.Equal(config[0:2], []byte{0xFE, 0xFF}) { - utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM) - decoder := utf.NewDecoder() - config, err = decoder.Bytes(config) - if err != nil { - panic(err) - } - } - var dat map[string]interface{} - if err := hjson.Unmarshal(config, &dat); err != nil { - panic(err) - } - if ep, ok := dat["AdminListen"].(string); ok && (ep != "none" && ep != "") { - endpoint = ep - logger.Println("Found platform default config file", defaults.GetDefaults().DefaultConfigFile) - logger.Println("Using endpoint", endpoint, "from AdminListen") - } else { - logger.Println("Configuration file doesn't contain appropriate AdminListen option") - logger.Println("Falling back to platform default", defaults.GetDefaults().DefaultAdminListen) - } - } else { - logger.Println("Can't open config file from default location", defaults.GetDefaults().DefaultConfigFile) - logger.Println("Falling back to platform default", defaults.GetDefaults().DefaultAdminListen) - } - } else { - endpoint = *server - logger.Println("Using endpoint", endpoint, "from command line") - } + cmdLineEnv.setEndpoint(logger) - var conn net.Conn - u, err := url.Parse(endpoint) - if err == nil { - switch strings.ToLower(u.Scheme) { - case "unix": - logger.Println("Connecting to UNIX socket", endpoint[7:]) - conn, err = net.Dial("unix", endpoint[7:]) - case "tcp": - logger.Println("Connecting to TCP socket", u.Host) - conn, err = net.Dial("tcp", u.Host) - default: - logger.Println("Unknown protocol or malformed address - check your endpoint") - err = errors.New("protocol not supported") - } - } else { - logger.Println("Connecting to TCP socket", u.Host) - conn, err = net.Dial("tcp", endpoint) - } - if err != nil { - panic(err) - } + conn := connect(cmdLineEnv.endpoint, logger) logger.Println("Connected") defer conn.Close() @@ -140,7 +63,7 @@ func run() int { send := make(admin_info) recv := make(admin_info) - for c, a := range args { + for c, a := range cmdLineEnv.args { if c == 0 { if strings.HasPrefix(a, "-") { logger.Printf("Ignoring flag %s as it should be specified before other parameters\n", a) @@ -176,7 +99,9 @@ func run() int { if err := encoder.Encode(&send); err != nil { panic(err) } + logger.Printf("Request sent") + if err := decoder.Decode(&recv); err == nil { logger.Printf("Response received") if recv["status"] == "error" { @@ -195,252 +120,16 @@ func run() int { fmt.Println("Missing response body (malformed response?)") return 1 } - req := recv["request"].(map[string]interface{}) res := recv["response"].(map[string]interface{}) - if *injson { + if cmdLineEnv.injson { if json, err := json.MarshalIndent(res, "", " "); err == nil { fmt.Println(string(json)) } return 0 } - switch strings.ToLower(req["request"].(string)) { - case "dot": - fmt.Println(res["dot"]) - case "list", "getpeers", "getswitchpeers", "getdht", "getsessions", "dhtping": - maxWidths := make(map[string]int) - var keyOrder []string - keysOrdered := false - - for _, tlv := range res { - for slk, slv := range tlv.(map[string]interface{}) { - if !keysOrdered { - for k := range slv.(map[string]interface{}) { - if !*verbose { - if k == "box_pub_key" || k == "box_sig_key" || k == "nodeinfo" || k == "was_mtu_fixed" { - continue - } - } - keyOrder = append(keyOrder, fmt.Sprint(k)) - } - sort.Strings(keyOrder) - keysOrdered = true - } - for k, v := range slv.(map[string]interface{}) { - if len(fmt.Sprint(slk)) > maxWidths["key"] { - maxWidths["key"] = len(fmt.Sprint(slk)) - } - if len(fmt.Sprint(v)) > maxWidths[k] { - maxWidths[k] = len(fmt.Sprint(v)) - if maxWidths[k] < len(k) { - maxWidths[k] = len(k) - } - } - } - } - - if len(keyOrder) > 0 { - fmt.Printf("%-"+fmt.Sprint(maxWidths["key"])+"s ", "") - for _, v := range keyOrder { - fmt.Printf("%-"+fmt.Sprint(maxWidths[v])+"s ", v) - } - fmt.Println() - } - - for slk, slv := range tlv.(map[string]interface{}) { - fmt.Printf("%-"+fmt.Sprint(maxWidths["key"])+"s ", slk) - for _, k := range keyOrder { - preformatted := slv.(map[string]interface{})[k] - var formatted string - switch k { - case "bytes_sent", "bytes_recvd": - formatted = fmt.Sprintf("%d", uint(preformatted.(float64))) - case "uptime", "last_seen": - seconds := uint(preformatted.(float64)) % 60 - minutes := uint(preformatted.(float64)/60) % 60 - hours := uint(preformatted.(float64) / 60 / 60) - formatted = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) - default: - formatted = fmt.Sprint(preformatted) - } - fmt.Printf("%-"+fmt.Sprint(maxWidths[k])+"s ", formatted) - } - fmt.Println() - } - } - case "gettuntap", "settuntap": - for k, v := range res { - fmt.Println("Interface name:", k) - if mtu, ok := v.(map[string]interface{})["mtu"].(float64); ok { - fmt.Println("Interface MTU:", mtu) - } - if tap_mode, ok := v.(map[string]interface{})["tap_mode"].(bool); ok { - fmt.Println("TAP mode:", tap_mode) - } - } - case "getself": - for k, v := range res["self"].(map[string]interface{}) { - if buildname, ok := v.(map[string]interface{})["build_name"].(string); ok && buildname != "unknown" { - fmt.Println("Build name:", buildname) - } - if buildversion, ok := v.(map[string]interface{})["build_version"].(string); ok && buildversion != "unknown" { - fmt.Println("Build version:", buildversion) - } - fmt.Println("IPv6 address:", k) - if subnet, ok := v.(map[string]interface{})["subnet"].(string); ok { - fmt.Println("IPv6 subnet:", subnet) - } - if boxSigKey, ok := v.(map[string]interface{})["key"].(string); ok { - fmt.Println("Public key:", boxSigKey) - } - if coords, ok := v.(map[string]interface{})["coords"].(string); ok { - fmt.Println("Coords:", coords) - } - if *verbose { - if nodeID, ok := v.(map[string]interface{})["node_id"].(string); ok { - fmt.Println("Node ID:", nodeID) - } - if boxPubKey, ok := v.(map[string]interface{})["box_pub_key"].(string); ok { - fmt.Println("Public encryption key:", boxPubKey) - } - if boxSigKey, ok := v.(map[string]interface{})["box_sig_key"].(string); ok { - fmt.Println("Public signing key:", boxSigKey) - } - } - } - case "getswitchqueues": - maximumqueuesize := float64(4194304) - portqueues := make(map[float64]float64) - portqueuesize := make(map[float64]float64) - portqueuepackets := make(map[float64]float64) - v := res["switchqueues"].(map[string]interface{}) - if queuecount, ok := v["queues_count"].(float64); ok { - fmt.Printf("Active queue count: %d queues\n", uint(queuecount)) - } - if queuesize, ok := v["queues_size"].(float64); ok { - fmt.Printf("Active queue size: %d bytes\n", uint(queuesize)) - } - if highestqueuecount, ok := v["highest_queues_count"].(float64); ok { - fmt.Printf("Highest queue count: %d queues\n", uint(highestqueuecount)) - } - if highestqueuesize, ok := v["highest_queues_size"].(float64); ok { - fmt.Printf("Highest queue size: %d bytes\n", uint(highestqueuesize)) - } - if m, ok := v["maximum_queues_size"].(float64); ok { - maximumqueuesize = m - fmt.Printf("Maximum queue size: %d bytes\n", uint(maximumqueuesize)) - } - if queues, ok := v["queues"].([]interface{}); ok { - if len(queues) != 0 { - fmt.Println("Active queues:") - for _, v := range queues { - queueport := v.(map[string]interface{})["queue_port"].(float64) - queuesize := v.(map[string]interface{})["queue_size"].(float64) - queuepackets := v.(map[string]interface{})["queue_packets"].(float64) - queueid := v.(map[string]interface{})["queue_id"].(string) - portqueues[queueport]++ - portqueuesize[queueport] += queuesize - portqueuepackets[queueport] += queuepackets - queuesizepercent := (100 / maximumqueuesize) * queuesize - fmt.Printf("- Switch port %d, Stream ID: %v, size: %d bytes (%d%% full), %d packets\n", - uint(queueport), []byte(queueid), uint(queuesize), - uint(queuesizepercent), uint(queuepackets)) - } - } - } - if len(portqueuesize) > 0 && len(portqueuepackets) > 0 { - fmt.Println("Aggregated statistics by switchport:") - for k, v := range portqueuesize { - queuesizepercent := (100 / (portqueues[k] * maximumqueuesize)) * v - fmt.Printf("- Switch port %d, size: %d bytes (%d%% full), %d packets\n", - uint(k), uint(v), uint(queuesizepercent), uint(portqueuepackets[k])) - } - } - case "addpeer", "removepeer", "addallowedencryptionpublickey", "removeallowedencryptionpublickey", "addsourcesubnet", "addroute", "removesourcesubnet", "removeroute": - if _, ok := res["added"]; ok { - for _, v := range res["added"].([]interface{}) { - fmt.Println("Added:", fmt.Sprint(v)) - } - } - if _, ok := res["not_added"]; ok { - for _, v := range res["not_added"].([]interface{}) { - fmt.Println("Not added:", fmt.Sprint(v)) - } - } - if _, ok := res["removed"]; ok { - for _, v := range res["removed"].([]interface{}) { - fmt.Println("Removed:", fmt.Sprint(v)) - } - } - if _, ok := res["not_removed"]; ok { - for _, v := range res["not_removed"].([]interface{}) { - fmt.Println("Not removed:", fmt.Sprint(v)) - } - } - case "getallowedencryptionpublickeys": - if _, ok := res["allowed_box_pubs"]; !ok { - fmt.Println("All connections are allowed") - } else if res["allowed_box_pubs"] == nil { - fmt.Println("All connections are allowed") - } else { - fmt.Println("Connections are allowed only from the following public box keys:") - for _, v := range res["allowed_box_pubs"].([]interface{}) { - fmt.Println("-", v) - } - } - case "getmulticastinterfaces": - if _, ok := res["multicast_interfaces"]; !ok { - fmt.Println("No multicast interfaces found") - } else if res["multicast_interfaces"] == nil { - fmt.Println("No multicast interfaces found") - } else { - fmt.Println("Multicast peer discovery is active on:") - for _, v := range res["multicast_interfaces"].([]interface{}) { - fmt.Println("-", v) - } - } - case "getsourcesubnets": - if _, ok := res["source_subnets"]; !ok { - fmt.Println("No source subnets found") - } else if res["source_subnets"] == nil { - fmt.Println("No source subnets found") - } else { - fmt.Println("Source subnets:") - for _, v := range res["source_subnets"].([]interface{}) { - fmt.Println("-", v) - } - } - case "getroutes": - if routes, ok := res["routes"].(map[string]interface{}); !ok { - fmt.Println("No routes found") - } else { - if res["routes"] == nil || len(routes) == 0 { - fmt.Println("No routes found") - } else { - fmt.Println("Routes:") - for k, v := range routes { - if pv, ok := v.(string); ok { - fmt.Println("-", k, " via ", pv) - } - } - } - } - case "settunnelrouting": - fallthrough - case "gettunnelrouting": - if enabled, ok := res["enabled"].(bool); !ok { - fmt.Println("Tunnel routing is disabled") - } else if !enabled { - fmt.Println("Tunnel routing is disabled") - } else { - fmt.Println("Tunnel routing is enabled") - } - default: - if json, err := json.MarshalIndent(recv["response"], "", " "); err == nil { - fmt.Println(string(json)) - } - } + handleAll(recv, cmdLineEnv.verbose) } else { logger.Println("Error receiving response:", err) } @@ -448,5 +137,321 @@ func run() int { if v, ok := recv["status"]; ok && v != "success" { return 1 } + return 0 } + +func connect(endpoint string, logger *log.Logger) net.Conn { + var conn net.Conn + + u, err := url.Parse(endpoint) + + if err == nil { + switch strings.ToLower(u.Scheme) { + case "unix": + logger.Println("Connecting to UNIX socket", endpoint[7:]) + conn, err = net.Dial("unix", endpoint[7:]) + case "tcp": + logger.Println("Connecting to TCP socket", u.Host) + conn, err = net.Dial("tcp", u.Host) + default: + logger.Println("Unknown protocol or malformed address - check your endpoint") + err = errors.New("protocol not supported") + } + } else { + logger.Println("Connecting to TCP socket", u.Host) + conn, err = net.Dial("tcp", endpoint) + } + + if err != nil { + panic(err) + } + + return conn +} + +func handleAll(recv map[string]interface{}, verbose bool) { + req := recv["request"].(map[string]interface{}) + res := recv["response"].(map[string]interface{}) + + switch strings.ToLower(req["request"].(string)) { + case "dot": + handleDot(res) + case "list", "getpeers", "getswitchpeers", "getdht", "getsessions", "dhtping": + handleVariousInfo(res, verbose) + case "gettuntap", "settuntap": + handleGetAndSetTunTap(res) + case "getself": + handleGetSelf(res, verbose) + case "getswitchqueues": + handleGetSwitchQueues(res) + case "addpeer", "removepeer", "addallowedencryptionpublickey", "removeallowedencryptionpublickey", "addsourcesubnet", "addroute", "removesourcesubnet", "removeroute": + handleAddsAndRemoves(res) + case "getallowedencryptionpublickeys": + handleGetAllowedEncryptionPublicKeys(res) + case "getmulticastinterfaces": + handleGetMulticastInterfaces(res) + case "getsourcesubnets": + handleGetSourceSubnets(res) + case "getroutes": + handleGetRoutes(res) + case "settunnelrouting": + fallthrough + case "gettunnelrouting": + handleGetTunnelRouting(res) + default: + if json, err := json.MarshalIndent(recv["response"], "", " "); err == nil { + fmt.Println(string(json)) + } + } +} + +func handleDot(res map[string]interface{}) { + fmt.Println(res["dot"]) +} + +func handleVariousInfo(res map[string]interface{}, verbose bool) { + maxWidths := make(map[string]int) + var keyOrder []string + keysOrdered := false + + for _, tlv := range res { + for slk, slv := range tlv.(map[string]interface{}) { + if !keysOrdered { + for k := range slv.(map[string]interface{}) { + if !verbose { + if k == "box_pub_key" || k == "box_sig_key" || k == "nodeinfo" || k == "was_mtu_fixed" { + continue + } + } + keyOrder = append(keyOrder, fmt.Sprint(k)) + } + sort.Strings(keyOrder) + keysOrdered = true + } + for k, v := range slv.(map[string]interface{}) { + if len(fmt.Sprint(slk)) > maxWidths["key"] { + maxWidths["key"] = len(fmt.Sprint(slk)) + } + if len(fmt.Sprint(v)) > maxWidths[k] { + maxWidths[k] = len(fmt.Sprint(v)) + if maxWidths[k] < len(k) { + maxWidths[k] = len(k) + } + } + } + } + + if len(keyOrder) > 0 { + fmt.Printf("%-"+fmt.Sprint(maxWidths["key"])+"s ", "") + for _, v := range keyOrder { + fmt.Printf("%-"+fmt.Sprint(maxWidths[v])+"s ", v) + } + fmt.Println() + } + + for slk, slv := range tlv.(map[string]interface{}) { + fmt.Printf("%-"+fmt.Sprint(maxWidths["key"])+"s ", slk) + for _, k := range keyOrder { + preformatted := slv.(map[string]interface{})[k] + var formatted string + switch k { + case "bytes_sent", "bytes_recvd": + formatted = fmt.Sprintf("%d", uint(preformatted.(float64))) + case "uptime", "last_seen": + seconds := uint(preformatted.(float64)) % 60 + minutes := uint(preformatted.(float64)/60) % 60 + hours := uint(preformatted.(float64) / 60 / 60) + formatted = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) + default: + formatted = fmt.Sprint(preformatted) + } + fmt.Printf("%-"+fmt.Sprint(maxWidths[k])+"s ", formatted) + } + fmt.Println() + } + } +} + +func handleGetAndSetTunTap(res map[string]interface{}) { + for k, v := range res { + fmt.Println("Interface name:", k) + if mtu, ok := v.(map[string]interface{})["mtu"].(float64); ok { + fmt.Println("Interface MTU:", mtu) + } + if tap_mode, ok := v.(map[string]interface{})["tap_mode"].(bool); ok { + fmt.Println("TAP mode:", tap_mode) + } + } +} + +func handleGetSelf(res map[string]interface{}, verbose bool) { + for k, v := range res["self"].(map[string]interface{}) { + if buildname, ok := v.(map[string]interface{})["build_name"].(string); ok && buildname != "unknown" { + fmt.Println("Build name:", buildname) + } + if buildversion, ok := v.(map[string]interface{})["build_version"].(string); ok && buildversion != "unknown" { + fmt.Println("Build version:", buildversion) + } + fmt.Println("IPv6 address:", k) + if subnet, ok := v.(map[string]interface{})["subnet"].(string); ok { + fmt.Println("IPv6 subnet:", subnet) + } + if boxSigKey, ok := v.(map[string]interface{})["key"].(string); ok { + fmt.Println("Public key:", boxSigKey) + } + if coords, ok := v.(map[string]interface{})["coords"].(string); ok { + fmt.Println("Coords:", coords) + } + if verbose { + if nodeID, ok := v.(map[string]interface{})["node_id"].(string); ok { + fmt.Println("Node ID:", nodeID) + } + if boxPubKey, ok := v.(map[string]interface{})["box_pub_key"].(string); ok { + fmt.Println("Public encryption key:", boxPubKey) + } + if boxSigKey, ok := v.(map[string]interface{})["box_sig_key"].(string); ok { + fmt.Println("Public signing key:", boxSigKey) + } + } + } +} + +func handleGetSwitchQueues(res map[string]interface{}) { + maximumqueuesize := float64(4194304) + portqueues := make(map[float64]float64) + portqueuesize := make(map[float64]float64) + portqueuepackets := make(map[float64]float64) + v := res["switchqueues"].(map[string]interface{}) + if queuecount, ok := v["queues_count"].(float64); ok { + fmt.Printf("Active queue count: %d queues\n", uint(queuecount)) + } + if queuesize, ok := v["queues_size"].(float64); ok { + fmt.Printf("Active queue size: %d bytes\n", uint(queuesize)) + } + if highestqueuecount, ok := v["highest_queues_count"].(float64); ok { + fmt.Printf("Highest queue count: %d queues\n", uint(highestqueuecount)) + } + if highestqueuesize, ok := v["highest_queues_size"].(float64); ok { + fmt.Printf("Highest queue size: %d bytes\n", uint(highestqueuesize)) + } + if m, ok := v["maximum_queues_size"].(float64); ok { + maximumqueuesize = m + fmt.Printf("Maximum queue size: %d bytes\n", uint(maximumqueuesize)) + } + if queues, ok := v["queues"].([]interface{}); ok { + if len(queues) != 0 { + fmt.Println("Active queues:") + for _, v := range queues { + queueport := v.(map[string]interface{})["queue_port"].(float64) + queuesize := v.(map[string]interface{})["queue_size"].(float64) + queuepackets := v.(map[string]interface{})["queue_packets"].(float64) + queueid := v.(map[string]interface{})["queue_id"].(string) + portqueues[queueport]++ + portqueuesize[queueport] += queuesize + portqueuepackets[queueport] += queuepackets + queuesizepercent := (100 / maximumqueuesize) * queuesize + fmt.Printf("- Switch port %d, Stream ID: %v, size: %d bytes (%d%% full), %d packets\n", + uint(queueport), []byte(queueid), uint(queuesize), + uint(queuesizepercent), uint(queuepackets)) + } + } + } + if len(portqueuesize) > 0 && len(portqueuepackets) > 0 { + fmt.Println("Aggregated statistics by switchport:") + for k, v := range portqueuesize { + queuesizepercent := (100 / (portqueues[k] * maximumqueuesize)) * v + fmt.Printf("- Switch port %d, size: %d bytes (%d%% full), %d packets\n", + uint(k), uint(v), uint(queuesizepercent), uint(portqueuepackets[k])) + } + } +} + +func handleAddsAndRemoves(res map[string]interface{}) { + if _, ok := res["added"]; ok { + for _, v := range res["added"].([]interface{}) { + fmt.Println("Added:", fmt.Sprint(v)) + } + } + if _, ok := res["not_added"]; ok { + for _, v := range res["not_added"].([]interface{}) { + fmt.Println("Not added:", fmt.Sprint(v)) + } + } + if _, ok := res["removed"]; ok { + for _, v := range res["removed"].([]interface{}) { + fmt.Println("Removed:", fmt.Sprint(v)) + } + } + if _, ok := res["not_removed"]; ok { + for _, v := range res["not_removed"].([]interface{}) { + fmt.Println("Not removed:", fmt.Sprint(v)) + } + } +} + +func handleGetAllowedEncryptionPublicKeys(res map[string]interface{}) { + if _, ok := res["allowed_box_pubs"]; !ok { + fmt.Println("All connections are allowed") + } else if res["allowed_box_pubs"] == nil { + fmt.Println("All connections are allowed") + } else { + fmt.Println("Connections are allowed only from the following public box keys:") + for _, v := range res["allowed_box_pubs"].([]interface{}) { + fmt.Println("-", v) + } + } +} + +func handleGetMulticastInterfaces(res map[string]interface{}) { + if _, ok := res["multicast_interfaces"]; !ok { + fmt.Println("No multicast interfaces found") + } else if res["multicast_interfaces"] == nil { + fmt.Println("No multicast interfaces found") + } else { + fmt.Println("Multicast peer discovery is active on:") + for _, v := range res["multicast_interfaces"].([]interface{}) { + fmt.Println("-", v) + } + } +} + +func handleGetSourceSubnets(res map[string]interface{}) { + if _, ok := res["source_subnets"]; !ok { + fmt.Println("No source subnets found") + } else if res["source_subnets"] == nil { + fmt.Println("No source subnets found") + } else { + fmt.Println("Source subnets:") + for _, v := range res["source_subnets"].([]interface{}) { + fmt.Println("-", v) + } + } +} + +func handleGetRoutes(res map[string]interface{}) { + if routes, ok := res["routes"].(map[string]interface{}); !ok { + fmt.Println("No routes found") + } else { + if res["routes"] == nil || len(routes) == 0 { + fmt.Println("No routes found") + } else { + fmt.Println("Routes:") + for k, v := range routes { + if pv, ok := v.(string); ok { + fmt.Println("-", k, " via ", pv) + } + } + } + } +} + +func handleGetTunnelRouting(res map[string]interface{}) { + if enabled, ok := res["enabled"].(bool); !ok { + fmt.Println("Tunnel routing is disabled") + } else if !enabled { + fmt.Println("Tunnel routing is disabled") + } else { + fmt.Println("Tunnel routing is enabled") + } +} From ebe366ef3bcb1b22a31225b06d140d013e463647 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 7 Aug 2021 10:17:21 +0100 Subject: [PATCH 0788/1109] Add IPReadWriteCloser interface --- src/tuntap/tun.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 4caefe4a..8676ff99 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -11,6 +11,7 @@ package tuntap import ( "errors" "fmt" + "io" "net" //"sync" @@ -22,17 +23,23 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" - "github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc" ) -type MTU uint16 +type IPReadWriteCloser interface { + io.ReadWriteCloser + MaxMTU() uint64 + SetMTU(mtu uint64) + MTU() uint64 + Address() address.Address + Subnet() address.Subnet +} // TunAdapter represents a running TUN interface and extends the // yggdrasil.Adapter type. In order to use the TUN adapter with Yggdrasil, you // should pass this object to the yggdrasil.SetRouterAdapter() function before // calling yggdrasil.Start(). type TunAdapter struct { - rwc *ipv6rwc.ReadWriteCloser + rwc IPReadWriteCloser config *config.NodeConfig log *log.Logger addr address.Address @@ -93,7 +100,7 @@ func MaximumMTU() uint64 { // Init initialises the TUN module. You must have acquired a Listener from // the Yggdrasil core before this point and it must not be in use elsewhere. -func (tun *TunAdapter) Init(rwc *ipv6rwc.ReadWriteCloser, config *config.NodeConfig, log *log.Logger, options interface{}) error { +func (tun *TunAdapter) Init(rwc IPReadWriteCloser, config *config.NodeConfig, log *log.Logger, options interface{}) error { tun.rwc = rwc tun.config = config tun.log = log From 3613614b418ddb7d99b931ce0c6a40e89ed04fc8 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 7 Aug 2021 12:56:36 -0500 Subject: [PATCH 0789/1109] Revert "Add IPReadWriteCloser interface" This reverts commit ebe366ef3bcb1b22a31225b06d140d013e463647. --- src/tuntap/tun.go | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 8676ff99..4caefe4a 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -11,7 +11,6 @@ package tuntap import ( "errors" "fmt" - "io" "net" //"sync" @@ -23,23 +22,17 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" + "github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc" ) -type IPReadWriteCloser interface { - io.ReadWriteCloser - MaxMTU() uint64 - SetMTU(mtu uint64) - MTU() uint64 - Address() address.Address - Subnet() address.Subnet -} +type MTU uint16 // TunAdapter represents a running TUN interface and extends the // yggdrasil.Adapter type. In order to use the TUN adapter with Yggdrasil, you // should pass this object to the yggdrasil.SetRouterAdapter() function before // calling yggdrasil.Start(). type TunAdapter struct { - rwc IPReadWriteCloser + rwc *ipv6rwc.ReadWriteCloser config *config.NodeConfig log *log.Logger addr address.Address @@ -100,7 +93,7 @@ func MaximumMTU() uint64 { // Init initialises the TUN module. You must have acquired a Listener from // the Yggdrasil core before this point and it must not be in use elsewhere. -func (tun *TunAdapter) Init(rwc IPReadWriteCloser, config *config.NodeConfig, log *log.Logger, options interface{}) error { +func (tun *TunAdapter) Init(rwc *ipv6rwc.ReadWriteCloser, config *config.NodeConfig, log *log.Logger, options interface{}) error { tun.rwc = rwc tun.config = config tun.log = log From 538ee13669bed5c3637888d0b3ae9e49d833d20c Mon Sep 17 00:00:00 2001 From: Alex Kotov Date: Wed, 1 Sep 2021 06:16:57 +0500 Subject: [PATCH 0790/1109] Add type core.AddHandlerFunc --- src/admin/admin.go | 6 +++--- src/core/api.go | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/admin/admin.go b/src/admin/admin.go index d41a48e1..ce831395 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -38,8 +38,8 @@ type AdminSocketResponse struct { } type handler struct { - args []string // List of human-readable argument names - handler func(json.RawMessage) (interface{}, error) // First is input map, second is output + args []string // List of human-readable argument names + handler core.AddHandlerFunc // First is input map, second is output } type ListResponse struct { @@ -51,7 +51,7 @@ type ListEntry struct { } // AddHandler is called for each admin function to add the handler and help documentation to the API. -func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc func(json.RawMessage) (interface{}, error)) error { +func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc core.AddHandlerFunc) error { if _, ok := a.handlers[strings.ToLower(name)]; ok { return errors.New("handler already exists") } diff --git a/src/core/api.go b/src/core/api.go index c312923d..ae13d499 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -242,9 +242,11 @@ func (c *Core) PublicKey() ed25519.PublicKey { // Hack to get the admin stuff working, TODO something cleaner type AddHandler interface { - AddHandler(name string, args []string, handlerfunc func(json.RawMessage) (interface{}, error)) error + AddHandler(name string, args []string, handlerfunc AddHandlerFunc) error } +type AddHandlerFunc func(json.RawMessage) (interface{}, error) + // SetAdmin must be called after Init and before Start. // It sets the admin handler for NodeInfo and the Debug admin functions. func (c *Core) SetAdmin(a AddHandler) error { From a5f2ba80a2d4aecd61f06e85bc1e69995d821f75 Mon Sep 17 00:00:00 2001 From: Alex Kotov Date: Wed, 1 Sep 2021 06:24:25 +0500 Subject: [PATCH 0791/1109] Organize code in "src/core/proto.go" --- src/core/proto.go | 83 ++++++++++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 34 deletions(-) diff --git a/src/core/proto.go b/src/core/proto.go index e60caeff..da9d9b99 100644 --- a/src/core/proto.go +++ b/src/core/proto.go @@ -34,21 +34,26 @@ type keyArray [ed25519.PublicKeySize]byte type protoHandler struct { phony.Inbox - core *Core + nodeinfo nodeinfo - sreqs map[keyArray]*reqInfo - preqs map[keyArray]*reqInfo - dreqs map[keyArray]*reqInfo + core *Core + + getSelfRequests map[keyArray]*reqInfo + getPeersRequests map[keyArray]*reqInfo + getDHTRequests map[keyArray]*reqInfo } func (p *protoHandler) init(core *Core) { p.core = core p.nodeinfo.init(p) - p.sreqs = make(map[keyArray]*reqInfo) - p.preqs = make(map[keyArray]*reqInfo) - p.dreqs = make(map[keyArray]*reqInfo) + + p.getSelfRequests = make(map[keyArray]*reqInfo) + p.getPeersRequests = make(map[keyArray]*reqInfo) + p.getDHTRequests = make(map[keyArray]*reqInfo) } +// Common functions + func (p *protoHandler) handleProto(from phony.Actor, key keyArray, bs []byte) { if len(bs) == 0 { return @@ -85,22 +90,29 @@ func (p *protoHandler) _handleDebug(key keyArray, bs []byte) { } } +func (p *protoHandler) _sendDebug(key keyArray, dType uint8, data []byte) { + bs := append([]byte{typeSessionProto, typeProtoDebug, dType}, data...) + _, _ = p.core.PacketConn.WriteTo(bs, iwt.Addr(key[:])) +} + +// Get self + func (p *protoHandler) sendGetSelfRequest(key keyArray, callback func([]byte)) { p.Act(nil, func() { - if info := p.sreqs[key]; info != nil { + if info := p.getSelfRequests[key]; info != nil { info.timer.Stop() - delete(p.sreqs, key) + delete(p.getSelfRequests, key) } info := new(reqInfo) info.callback = callback info.timer = time.AfterFunc(time.Minute, func() { p.Act(nil, func() { - if p.sreqs[key] == info { - delete(p.sreqs, key) + if p.getSelfRequests[key] == info { + delete(p.getSelfRequests, key) } }) }) - p.sreqs[key] = info + p.getSelfRequests[key] = info p._sendDebug(key, typeDebugGetSelfRequest, nil) }) } @@ -119,29 +131,31 @@ func (p *protoHandler) _handleGetSelfRequest(key keyArray) { } func (p *protoHandler) _handleGetSelfResponse(key keyArray, bs []byte) { - if info := p.sreqs[key]; info != nil { + if info := p.getSelfRequests[key]; info != nil { info.timer.Stop() info.callback(bs) - delete(p.sreqs, key) + delete(p.getSelfRequests, key) } } +// Get peers + func (p *protoHandler) sendGetPeersRequest(key keyArray, callback func([]byte)) { p.Act(nil, func() { - if info := p.preqs[key]; info != nil { + if info := p.getPeersRequests[key]; info != nil { info.timer.Stop() - delete(p.preqs, key) + delete(p.getPeersRequests, key) } info := new(reqInfo) info.callback = callback info.timer = time.AfterFunc(time.Minute, func() { p.Act(nil, func() { - if p.preqs[key] == info { - delete(p.preqs, key) + if p.getPeersRequests[key] == info { + delete(p.getPeersRequests, key) } }) }) - p.preqs[key] = info + p.getPeersRequests[key] = info p._sendDebug(key, typeDebugGetPeersRequest, nil) }) } @@ -161,29 +175,31 @@ func (p *protoHandler) _handleGetPeersRequest(key keyArray) { } func (p *protoHandler) _handleGetPeersResponse(key keyArray, bs []byte) { - if info := p.preqs[key]; info != nil { + if info := p.getPeersRequests[key]; info != nil { info.timer.Stop() info.callback(bs) - delete(p.preqs, key) + delete(p.getPeersRequests, key) } } +// Get DHT + func (p *protoHandler) sendGetDHTRequest(key keyArray, callback func([]byte)) { p.Act(nil, func() { - if info := p.dreqs[key]; info != nil { + if info := p.getDHTRequests[key]; info != nil { info.timer.Stop() - delete(p.dreqs, key) + delete(p.getDHTRequests, key) } info := new(reqInfo) info.callback = callback info.timer = time.AfterFunc(time.Minute, func() { p.Act(nil, func() { - if p.dreqs[key] == info { - delete(p.dreqs, key) + if p.getDHTRequests[key] == info { + delete(p.getDHTRequests, key) } }) }) - p.dreqs[key] = info + p.getDHTRequests[key] = info p._sendDebug(key, typeDebugGetDHTRequest, nil) }) } @@ -203,19 +219,14 @@ func (p *protoHandler) _handleGetDHTRequest(key keyArray) { } func (p *protoHandler) _handleGetDHTResponse(key keyArray, bs []byte) { - if info := p.dreqs[key]; info != nil { + if info := p.getDHTRequests[key]; info != nil { info.timer.Stop() info.callback(bs) - delete(p.dreqs, key) + delete(p.getDHTRequests, key) } } -func (p *protoHandler) _sendDebug(key keyArray, dType uint8, data []byte) { - bs := append([]byte{typeSessionProto, typeProtoDebug, dType}, data...) - _, _ = p.core.PacketConn.WriteTo(bs, iwt.Addr(key[:])) -} - -// Admin socket stuff +// Admin socket stuff for "Get self" type DebugGetSelfRequest struct { Key string `json:"key"` @@ -255,6 +266,8 @@ func (p *protoHandler) getSelfHandler(in json.RawMessage) (interface{}, error) { } } +// Admin socket stuff for "Get peers" + type DebugGetPeersRequest struct { Key string `json:"key"` } @@ -303,6 +316,8 @@ func (p *protoHandler) getPeersHandler(in json.RawMessage) (interface{}, error) } } +// Admin socket stuff for "Get DHT" + type DebugGetDHTRequest struct { Key string `json:"key"` } From 3c89781057bed6e87daf3f40523c21d0a569d59b Mon Sep 17 00:00:00 2001 From: Alex Kotov Date: Wed, 1 Sep 2021 07:57:45 +0500 Subject: [PATCH 0792/1109] Align and reorder code for lesser diff --- src/core/proto.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/proto.go b/src/core/proto.go index da9d9b99..1151d006 100644 --- a/src/core/proto.go +++ b/src/core/proto.go @@ -35,8 +35,8 @@ type keyArray [ed25519.PublicKeySize]byte type protoHandler struct { phony.Inbox + core *Core nodeinfo nodeinfo - core *Core getSelfRequests map[keyArray]*reqInfo getPeersRequests map[keyArray]*reqInfo From 571186ca772e96b1f4226b2dd56252b9aa3e5a30 Mon Sep 17 00:00:00 2001 From: Alex Kotov Date: Fri, 3 Sep 2021 01:45:30 +0500 Subject: [PATCH 0793/1109] Rename protohandler attributes --- src/core/proto.go | 54 +++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/core/proto.go b/src/core/proto.go index 1151d006..3045972e 100644 --- a/src/core/proto.go +++ b/src/core/proto.go @@ -38,18 +38,18 @@ type protoHandler struct { core *Core nodeinfo nodeinfo - getSelfRequests map[keyArray]*reqInfo - getPeersRequests map[keyArray]*reqInfo - getDHTRequests map[keyArray]*reqInfo + selfRequests map[keyArray]*reqInfo + peersRequests map[keyArray]*reqInfo + dhtRequests map[keyArray]*reqInfo } func (p *protoHandler) init(core *Core) { p.core = core p.nodeinfo.init(p) - p.getSelfRequests = make(map[keyArray]*reqInfo) - p.getPeersRequests = make(map[keyArray]*reqInfo) - p.getDHTRequests = make(map[keyArray]*reqInfo) + p.selfRequests = make(map[keyArray]*reqInfo) + p.peersRequests = make(map[keyArray]*reqInfo) + p.dhtRequests = make(map[keyArray]*reqInfo) } // Common functions @@ -99,20 +99,20 @@ func (p *protoHandler) _sendDebug(key keyArray, dType uint8, data []byte) { func (p *protoHandler) sendGetSelfRequest(key keyArray, callback func([]byte)) { p.Act(nil, func() { - if info := p.getSelfRequests[key]; info != nil { + if info := p.selfRequests[key]; info != nil { info.timer.Stop() - delete(p.getSelfRequests, key) + delete(p.selfRequests, key) } info := new(reqInfo) info.callback = callback info.timer = time.AfterFunc(time.Minute, func() { p.Act(nil, func() { - if p.getSelfRequests[key] == info { - delete(p.getSelfRequests, key) + if p.selfRequests[key] == info { + delete(p.selfRequests, key) } }) }) - p.getSelfRequests[key] = info + p.selfRequests[key] = info p._sendDebug(key, typeDebugGetSelfRequest, nil) }) } @@ -131,10 +131,10 @@ func (p *protoHandler) _handleGetSelfRequest(key keyArray) { } func (p *protoHandler) _handleGetSelfResponse(key keyArray, bs []byte) { - if info := p.getSelfRequests[key]; info != nil { + if info := p.selfRequests[key]; info != nil { info.timer.Stop() info.callback(bs) - delete(p.getSelfRequests, key) + delete(p.selfRequests, key) } } @@ -142,20 +142,20 @@ func (p *protoHandler) _handleGetSelfResponse(key keyArray, bs []byte) { func (p *protoHandler) sendGetPeersRequest(key keyArray, callback func([]byte)) { p.Act(nil, func() { - if info := p.getPeersRequests[key]; info != nil { + if info := p.peersRequests[key]; info != nil { info.timer.Stop() - delete(p.getPeersRequests, key) + delete(p.peersRequests, key) } info := new(reqInfo) info.callback = callback info.timer = time.AfterFunc(time.Minute, func() { p.Act(nil, func() { - if p.getPeersRequests[key] == info { - delete(p.getPeersRequests, key) + if p.peersRequests[key] == info { + delete(p.peersRequests, key) } }) }) - p.getPeersRequests[key] = info + p.peersRequests[key] = info p._sendDebug(key, typeDebugGetPeersRequest, nil) }) } @@ -175,10 +175,10 @@ func (p *protoHandler) _handleGetPeersRequest(key keyArray) { } func (p *protoHandler) _handleGetPeersResponse(key keyArray, bs []byte) { - if info := p.getPeersRequests[key]; info != nil { + if info := p.peersRequests[key]; info != nil { info.timer.Stop() info.callback(bs) - delete(p.getPeersRequests, key) + delete(p.peersRequests, key) } } @@ -186,20 +186,20 @@ func (p *protoHandler) _handleGetPeersResponse(key keyArray, bs []byte) { func (p *protoHandler) sendGetDHTRequest(key keyArray, callback func([]byte)) { p.Act(nil, func() { - if info := p.getDHTRequests[key]; info != nil { + if info := p.dhtRequests[key]; info != nil { info.timer.Stop() - delete(p.getDHTRequests, key) + delete(p.dhtRequests, key) } info := new(reqInfo) info.callback = callback info.timer = time.AfterFunc(time.Minute, func() { p.Act(nil, func() { - if p.getDHTRequests[key] == info { - delete(p.getDHTRequests, key) + if p.dhtRequests[key] == info { + delete(p.dhtRequests, key) } }) }) - p.getDHTRequests[key] = info + p.dhtRequests[key] = info p._sendDebug(key, typeDebugGetDHTRequest, nil) }) } @@ -219,10 +219,10 @@ func (p *protoHandler) _handleGetDHTRequest(key keyArray) { } func (p *protoHandler) _handleGetDHTResponse(key keyArray, bs []byte) { - if info := p.getDHTRequests[key]; info != nil { + if info := p.dhtRequests[key]; info != nil { info.timer.Stop() info.callback(bs) - delete(p.getDHTRequests, key) + delete(p.dhtRequests, key) } } From 52345a2de49d47bfad23b5cf45435cd84fd971bc Mon Sep 17 00:00:00 2001 From: Fyodor Ustinov Date: Tue, 21 Sep 2021 23:19:25 +0300 Subject: [PATCH 0794/1109] Check tun.config is not equal to nil before usage (#830) We have to check tun.config is not nil before first use, not after. --- src/tuntap/tun.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 4caefe4a..eddccbcd 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -114,11 +114,11 @@ func (tun *TunAdapter) _start() error { if tun.isOpen { return errors.New("TUN module is already started") } - tun.config.RLock() - defer tun.config.RUnlock() if tun.config == nil { return errors.New("no configuration available to TUN") } + tun.config.RLock() + defer tun.config.RUnlock() tun.addr = tun.rwc.Address() tun.subnet = tun.rwc.Subnet() addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1) From 1c7deb72db461ee4c1ec96137b9242fb6acd1837 Mon Sep 17 00:00:00 2001 From: Paul Dee <647633+systemcrash@users.noreply.github.com> Date: Tue, 21 Sep 2021 22:19:40 +0200 Subject: [PATCH 0795/1109] Align struct elements to byte boundaries: reduce memory footprint. (#834) --- cmd/yggdrasil/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 95d40151..58b8230d 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -185,14 +185,14 @@ func setLogLevel(loglevel string, logger *log.Logger) { type yggArgs struct { genconf bool useconf bool - useconffile string normaliseconf bool confjson bool autoconf bool ver bool - logto string getaddr bool getsnet bool + useconffile string + logto string loglevel string } From 529a33034becaf0f44fc5b3069fb33f46e4c2bcd Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 23 Sep 2021 04:34:58 -0500 Subject: [PATCH 0796/1109] gofmt to add new build comments --- src/core/debug.go | 1 + src/core/tcp_darwin.go | 1 + src/core/tcp_linux.go | 1 + src/core/tcp_other.go | 1 + src/defaults/defaults_darwin.go | 1 + src/defaults/defaults_freebsd.go | 1 + src/defaults/defaults_linux.go | 1 + src/defaults/defaults_openbsd.go | 1 + src/defaults/defaults_other.go | 1 + src/defaults/defaults_windows.go | 1 + src/multicast/multicast_darwin.go | 1 + src/multicast/multicast_other.go | 1 + src/multicast/multicast_unix.go | 1 + src/multicast/multicast_windows.go | 1 + src/tuntap/tun_bsd.go | 1 + src/tuntap/tun_darwin.go | 1 + src/tuntap/tun_linux.go | 1 + src/tuntap/tun_other.go | 1 + src/tuntap/tun_windows.go | 1 + 19 files changed, 19 insertions(+) diff --git a/src/core/debug.go b/src/core/debug.go index 0fc08259..eb406798 100644 --- a/src/core/debug.go +++ b/src/core/debug.go @@ -1,3 +1,4 @@ +//go:build debug // +build debug package core diff --git a/src/core/tcp_darwin.go b/src/core/tcp_darwin.go index 6b85c621..2ea3abc8 100644 --- a/src/core/tcp_darwin.go +++ b/src/core/tcp_darwin.go @@ -1,3 +1,4 @@ +//go:build darwin // +build darwin package core diff --git a/src/core/tcp_linux.go b/src/core/tcp_linux.go index 558b4e56..e59c3121 100644 --- a/src/core/tcp_linux.go +++ b/src/core/tcp_linux.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package core diff --git a/src/core/tcp_other.go b/src/core/tcp_other.go index 97b81ed1..8dd76f28 100644 --- a/src/core/tcp_other.go +++ b/src/core/tcp_other.go @@ -1,3 +1,4 @@ +//go:build !darwin && !linux // +build !darwin,!linux package core diff --git a/src/defaults/defaults_darwin.go b/src/defaults/defaults_darwin.go index e16f398f..060ce814 100644 --- a/src/defaults/defaults_darwin.go +++ b/src/defaults/defaults_darwin.go @@ -1,3 +1,4 @@ +//go:build darwin // +build darwin package defaults diff --git a/src/defaults/defaults_freebsd.go b/src/defaults/defaults_freebsd.go index 6c3e1c60..84df48ad 100644 --- a/src/defaults/defaults_freebsd.go +++ b/src/defaults/defaults_freebsd.go @@ -1,3 +1,4 @@ +//go:build freebsd // +build freebsd package defaults diff --git a/src/defaults/defaults_linux.go b/src/defaults/defaults_linux.go index 95c7ae95..c7f5f119 100644 --- a/src/defaults/defaults_linux.go +++ b/src/defaults/defaults_linux.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package defaults diff --git a/src/defaults/defaults_openbsd.go b/src/defaults/defaults_openbsd.go index ef339546..0ec877ca 100644 --- a/src/defaults/defaults_openbsd.go +++ b/src/defaults/defaults_openbsd.go @@ -1,3 +1,4 @@ +//go:build openbsd // +build openbsd package defaults diff --git a/src/defaults/defaults_other.go b/src/defaults/defaults_other.go index d1417322..37637425 100644 --- a/src/defaults/defaults_other.go +++ b/src/defaults/defaults_other.go @@ -1,3 +1,4 @@ +//go:build !linux && !darwin && !windows && !openbsd && !freebsd // +build !linux,!darwin,!windows,!openbsd,!freebsd package defaults diff --git a/src/defaults/defaults_windows.go b/src/defaults/defaults_windows.go index e81d09cf..c1ea9689 100644 --- a/src/defaults/defaults_windows.go +++ b/src/defaults/defaults_windows.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package defaults diff --git a/src/multicast/multicast_darwin.go b/src/multicast/multicast_darwin.go index e7075c0a..7ad0c543 100644 --- a/src/multicast/multicast_darwin.go +++ b/src/multicast/multicast_darwin.go @@ -1,3 +1,4 @@ +//go:build darwin // +build darwin package multicast diff --git a/src/multicast/multicast_other.go b/src/multicast/multicast_other.go index dfcf625f..9977951c 100644 --- a/src/multicast/multicast_other.go +++ b/src/multicast/multicast_other.go @@ -1,3 +1,4 @@ +//go:build !linux && !darwin && !netbsd && !freebsd && !openbsd && !dragonflybsd && !windows // +build !linux,!darwin,!netbsd,!freebsd,!openbsd,!dragonflybsd,!windows package multicast diff --git a/src/multicast/multicast_unix.go b/src/multicast/multicast_unix.go index 1ff48b17..9c822fcf 100644 --- a/src/multicast/multicast_unix.go +++ b/src/multicast/multicast_unix.go @@ -1,3 +1,4 @@ +//go:build linux || netbsd || freebsd || openbsd || dragonflybsd // +build linux netbsd freebsd openbsd dragonflybsd package multicast diff --git a/src/multicast/multicast_windows.go b/src/multicast/multicast_windows.go index 3666faaa..515412a4 100644 --- a/src/multicast/multicast_windows.go +++ b/src/multicast/multicast_windows.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package multicast diff --git a/src/tuntap/tun_bsd.go b/src/tuntap/tun_bsd.go index 75158857..fe36266b 100644 --- a/src/tuntap/tun_bsd.go +++ b/src/tuntap/tun_bsd.go @@ -1,3 +1,4 @@ +//go:build openbsd || freebsd // +build openbsd freebsd package tuntap diff --git a/src/tuntap/tun_darwin.go b/src/tuntap/tun_darwin.go index 75938881..6f6e2528 100644 --- a/src/tuntap/tun_darwin.go +++ b/src/tuntap/tun_darwin.go @@ -1,3 +1,4 @@ +//go:build !mobile // +build !mobile package tuntap diff --git a/src/tuntap/tun_linux.go b/src/tuntap/tun_linux.go index 0a845368..f849c00f 100644 --- a/src/tuntap/tun_linux.go +++ b/src/tuntap/tun_linux.go @@ -1,3 +1,4 @@ +//go:build !mobile // +build !mobile package tuntap diff --git a/src/tuntap/tun_other.go b/src/tuntap/tun_other.go index c0321267..8ce24953 100644 --- a/src/tuntap/tun_other.go +++ b/src/tuntap/tun_other.go @@ -1,3 +1,4 @@ +//go:build !linux && !darwin && !windows && !openbsd && !freebsd && !mobile // +build !linux,!darwin,!windows,!openbsd,!freebsd,!mobile package tuntap diff --git a/src/tuntap/tun_windows.go b/src/tuntap/tun_windows.go index 7b7ee710..8dce7274 100644 --- a/src/tuntap/tun_windows.go +++ b/src/tuntap/tun_windows.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package tuntap From 86e5306eec42324a7e68b4a5e37b7b9d89e98c83 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 23 Sep 2021 04:35:31 -0500 Subject: [PATCH 0797/1109] fix race from mutex that wasn't held long enough --- src/ipv6rwc/ipv6rwc.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/ipv6rwc/ipv6rwc.go b/src/ipv6rwc/ipv6rwc.go index 1c715f0f..f71c638e 100644 --- a/src/ipv6rwc/ipv6rwc.go +++ b/src/ipv6rwc/ipv6rwc.go @@ -129,6 +129,7 @@ func (k *keyStore) sendToSubnet(subnet address.Subnet, bs []byte) { func (k *keyStore) update(key ed25519.PublicKey) *keyInfo { k.mutex.Lock() + defer k.mutex.Unlock() var kArray keyArray copy(kArray[:], key) var info *keyInfo @@ -140,8 +141,6 @@ func (k *keyStore) update(key ed25519.PublicKey) *keyInfo { k.keyToInfo[info.key] = info k.addrToInfo[info.address] = info k.subnetToInfo[info.subnet] = info - k.resetTimeout(info) - k.mutex.Unlock() if buf := k.addrBuffer[info.address]; buf != nil { k.core.WriteTo(buf.packet, iwt.Addr(info.key[:])) delete(k.addrBuffer, info.address) @@ -150,10 +149,8 @@ func (k *keyStore) update(key ed25519.PublicKey) *keyInfo { k.core.WriteTo(buf.packet, iwt.Addr(info.key[:])) delete(k.subnetBuffer, info.subnet) } - } else { - k.resetTimeout(info) - k.mutex.Unlock() } + k.resetTimeout(info) return info } From e5d638ff4bdd1bf9847e6e645b658b14f60278ae Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 23 Sep 2021 04:39:12 -0500 Subject: [PATCH 0798/1109] better way to empty ipv6rwc buffer --- src/ipv6rwc/ipv6rwc.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/ipv6rwc/ipv6rwc.go b/src/ipv6rwc/ipv6rwc.go index f71c638e..fc2a688f 100644 --- a/src/ipv6rwc/ipv6rwc.go +++ b/src/ipv6rwc/ipv6rwc.go @@ -129,10 +129,10 @@ func (k *keyStore) sendToSubnet(subnet address.Subnet, bs []byte) { func (k *keyStore) update(key ed25519.PublicKey) *keyInfo { k.mutex.Lock() - defer k.mutex.Unlock() var kArray keyArray copy(kArray[:], key) var info *keyInfo + var packets [][]byte if info = k.keyToInfo[kArray]; info == nil { info = new(keyInfo) info.key = kArray @@ -142,15 +142,19 @@ func (k *keyStore) update(key ed25519.PublicKey) *keyInfo { k.addrToInfo[info.address] = info k.subnetToInfo[info.subnet] = info if buf := k.addrBuffer[info.address]; buf != nil { - k.core.WriteTo(buf.packet, iwt.Addr(info.key[:])) + packets = append(packets, buf.packet) delete(k.addrBuffer, info.address) } if buf := k.subnetBuffer[info.subnet]; buf != nil { - k.core.WriteTo(buf.packet, iwt.Addr(info.key[:])) + packets = append(packets, buf.packet) delete(k.subnetBuffer, info.subnet) } } k.resetTimeout(info) + k.mutex.Unlock() + for _, packet := range packets { + k.core.WriteTo(packet, iwt.Addr(info.key[:])) + } return info } From 9a1d1df85edb5442ec75382334fd5a265382800f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 23 Sep 2021 12:11:03 +0100 Subject: [PATCH 0799/1109] Use newer Xcode image for macOS builds in CircleCI --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5ebd26e2..fa49c34d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -105,7 +105,7 @@ jobs: build-macos: macos: - xcode: "10.0.0" + xcode: "13.0.0" working_directory: ~/go/src/github.com/yggdrasil-network/yggdrasil-go From 99227b60cedfb278b1b6d6ac9ae965f47fce5ccf Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 28 Sep 2021 11:02:15 +0100 Subject: [PATCH 0800/1109] Update CI to use Go 1.17, produce Apple Silicon builds (closes #844) --- .circleci/config.yml | 16 ++++--- contrib/macos/create-pkg.sh | 4 ++ src/multicast/multicast_darwin.go | 40 ++-------------- src/multicast/multicast_darwin_cgo.go | 69 +++++++++++++++++++++++++++ src/multicast/multicast_other.go | 4 +- 5 files changed, 89 insertions(+), 44 deletions(-) create mode 100644 src/multicast/multicast_darwin_cgo.go diff --git a/.circleci/config.yml b/.circleci/config.yml index fa49c34d..065f2b0b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ version: 2.1 jobs: lint: docker: - - image: circleci/golang:1.16 + - image: circleci/golang:1.17 steps: - checkout @@ -23,7 +23,7 @@ jobs: build-linux: docker: - - image: circleci/golang:1.16 + - image: circleci/golang:1.17 steps: - checkout @@ -124,11 +124,11 @@ jobs: echo -e "Host *\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config - run: - name: Install Go 1.16 + name: Install Go 1.17 command: | cd /tmp - curl -LO https://dl.google.com/go/go1.16.darwin-amd64.pkg - sudo installer -pkg /tmp/go1.16.darwin-amd64.pkg -target / + curl -LO https://dl.google.com/go/go1.17.darwin-amd64.pkg + sudo installer -pkg /tmp/go1.17.darwin-amd64.pkg -target / #- run: # name: Install Gomobile @@ -142,11 +142,15 @@ jobs: GO111MODULE=on GOOS=darwin GOARCH=amd64 ./build cp yggdrasil /tmp/upload/$CINAME-$CIVERSION-darwin-amd64 cp yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-darwin-amd64; + GO111MODULE=on GOOS=darwin GOARCH=arm64 ./build + cp yggdrasil /tmp/upload/$CINAME-$CIVERSION-darwin-arm64 + cp yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-darwin-arm64; - run: name: Build for macOS (.pkg format) command: | PKGARCH=amd64 sh contrib/macos/create-pkg.sh + PKGARCH=arm64 sh contrib/macos/create-pkg.sh mv *.pkg /tmp/upload/ #- run: @@ -164,7 +168,7 @@ jobs: build-other: docker: - - image: circleci/golang:1.16 + - image: circleci/golang:1.17 steps: - checkout diff --git a/contrib/macos/create-pkg.sh b/contrib/macos/create-pkg.sh index cc9a74f7..919c45ed 100755 --- a/contrib/macos/create-pkg.sh +++ b/contrib/macos/create-pkg.sh @@ -15,6 +15,10 @@ command -v mkbom >/dev/null 2>&1 || ( sudo make install || (echo "Failed to build mkbom"; exit 1) ) +# Build Yggdrasil +echo "running GO111MODULE=on GOOS=darwin GOARCH=${PKGARCH-amd64} ./build" +GO111MODULE=on GOOS=darwin GOARCH=${PKGARCH-amd64} ./build + # Check if we can find the files we need - they should # exist if you are running this script from the root of # the yggdrasil-go repo and you have ran ./build diff --git a/src/multicast/multicast_darwin.go b/src/multicast/multicast_darwin.go index 7ad0c543..22cf3998 100644 --- a/src/multicast/multicast_darwin.go +++ b/src/multicast/multicast_darwin.go @@ -1,49 +1,17 @@ -//go:build darwin -// +build darwin +//go:build !cgo && (darwin || ios) +// +build !cgo +// +build darwin ios package multicast -/* -#cgo CFLAGS: -x objective-c -#cgo LDFLAGS: -framework Foundation -#import -NSNetServiceBrowser *serviceBrowser; -void StartAWDLBrowsing() { - if (serviceBrowser == nil) { - serviceBrowser = [[NSNetServiceBrowser alloc] init]; - serviceBrowser.includesPeerToPeer = YES; - } - [serviceBrowser searchForServicesOfType:@"_yggdrasil._tcp" inDomain:@""]; -} -void StopAWDLBrowsing() { - if (serviceBrowser == nil) { - return; - } - [serviceBrowser stop]; -} -*/ -import "C" import ( "syscall" - "time" "golang.org/x/sys/unix" ) func (m *Multicast) _multicastStarted() { - if !m.isOpen { - return - } - C.StopAWDLBrowsing() - for intf := range m._interfaces { - if intf == "awdl0" { - C.StartAWDLBrowsing() - break - } - } - time.AfterFunc(time.Minute, func() { - m.Act(nil, m._multicastStarted) - }) + } func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error { diff --git a/src/multicast/multicast_darwin_cgo.go b/src/multicast/multicast_darwin_cgo.go new file mode 100644 index 00000000..b7d7358c --- /dev/null +++ b/src/multicast/multicast_darwin_cgo.go @@ -0,0 +1,69 @@ +//go:build (darwin && cgo) || (ios && cgo) +// +build darwin,cgo ios,cgo + +package multicast + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation +#import +NSNetServiceBrowser *serviceBrowser; +void StartAWDLBrowsing() { + if (serviceBrowser == nil) { + serviceBrowser = [[NSNetServiceBrowser alloc] init]; + serviceBrowser.includesPeerToPeer = YES; + } + [serviceBrowser searchForServicesOfType:@"_yggdrasil._tcp" inDomain:@""]; +} +void StopAWDLBrowsing() { + if (serviceBrowser == nil) { + return; + } + [serviceBrowser stop]; +} +*/ +import "C" +import ( + "syscall" + "time" + + "golang.org/x/sys/unix" +) + +func (m *Multicast) _multicastStarted() { + if !m.isOpen { + return + } + C.StopAWDLBrowsing() + for intf := range m._interfaces { + if intf == "awdl0" { + C.StartAWDLBrowsing() + break + } + } + time.AfterFunc(time.Minute, func() { + m.Act(nil, m._multicastStarted) + }) +} + +func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error { + var control error + var reuseport error + var recvanyif error + + control = c.Control(func(fd uintptr) { + reuseport = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) + + // sys/socket.h: #define SO_RECV_ANYIF 0x1104 + recvanyif = unix.SetsockoptInt(int(fd), syscall.SOL_SOCKET, 0x1104, 1) + }) + + switch { + case reuseport != nil: + return reuseport + case recvanyif != nil: + return recvanyif + default: + return control + } +} diff --git a/src/multicast/multicast_other.go b/src/multicast/multicast_other.go index 9977951c..263f4ffc 100644 --- a/src/multicast/multicast_other.go +++ b/src/multicast/multicast_other.go @@ -1,5 +1,5 @@ -//go:build !linux && !darwin && !netbsd && !freebsd && !openbsd && !dragonflybsd && !windows -// +build !linux,!darwin,!netbsd,!freebsd,!openbsd,!dragonflybsd,!windows +//go:build !linux && !darwin && !ios && !netbsd && !freebsd && !openbsd && !dragonflybsd && !windows +// +build !linux,!darwin,!ios,!netbsd,!freebsd,!openbsd,!dragonflybsd,!windows package multicast From 4859accbb038f8db3c3a541d0f3f3f76de75b9fb Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 2 Nov 2021 18:03:16 +0000 Subject: [PATCH 0801/1109] Fix panic in `address.GetKey()` (fixes #860) --- src/address/address.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/address/address.go b/src/address/address.go index 0e2400ed..c7f2eb46 100644 --- a/src/address/address.go +++ b/src/address/address.go @@ -129,7 +129,11 @@ func (a *Address) GetKey() ed25519.PublicKey { bits <<= byte(idx % 8) keyIdx := keyOffset + (idx - addrOffset) bits >>= byte(keyIdx % 8) - key[keyIdx/8] |= bits + idx := keyIdx / 8 + if idx >= len(key) { + break + } + key[idx] |= bits } for idx := range key { key[idx] = ^key[idx] From 5c19f3f88c6b664456721a933c4e37c85214504d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 3 Nov 2021 10:33:00 +0000 Subject: [PATCH 0802/1109] Update dependencies --- go.mod | 11 +++++------ go.sum | 52 ++++++++++++++++++++++++++++++++-------------------- 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/go.mod b/go.mod index 398504cb..05044531 100644 --- a/go.mod +++ b/go.mod @@ -17,10 +17,9 @@ require ( github.com/mitchellh/mapstructure v1.4.1 github.com/vishvananda/netlink v1.1.0 github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect - golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect - golang.org/x/net v0.0.0-20210610132358-84b48f89b13b - golang.org/x/sys v0.0.0-20210611083646-a4fc73990273 - golang.org/x/text v0.3.7-0.20210503195748-5c7c50ebbd4f - golang.zx2c4.com/wireguard v0.0.0-20210604143328-f9b48a961cd2 - golang.zx2c4.com/wireguard/windows v0.3.14 + golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 + golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 + golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b + golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a + golang.zx2c4.com/wireguard/windows v0.4.12 ) diff --git a/go.sum b/go.sum index 5d11fad6..e6df746b 100644 --- a/go.sum +++ b/go.sum @@ -38,42 +38,54 @@ github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYp github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210610132358-84b48f89b13b h1:k+E048sYJHyVnsr1GDrRZWQ32D2C7lWs9JRc0bel53A= -golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210927181540-4e4d966f7476/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211011170408-caeb26a5c8c0/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 h1:VrJZAjbekhoRn7n5FBujY31gboH+iB3pdLxn3gE9FjU= +golang.org/x/net v0.0.0-20211101193420-4a448f8816b3/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210611083646-a4fc73990273 h1:faDu4veV+8pcThn4fewv6TVlNCezafGoC1gM/mxQLbQ= -golang.org/x/sys v0.0.0-20210611083646-a4fc73990273/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 h1:7zYaz3tjChtpayGDzu6H0hDAUM5zIGA2XW7kRNgQ0jc= +golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7-0.20210503195748-5c7c50ebbd4f h1:yQJrRE0hDxDFmZLlRaw+3vusO4fwNHgHIjUOMO7bHYI= -golang.org/x/text v0.3.7-0.20210503195748-5c7c50ebbd4f/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b h1:NXqSWXSRUSCaFuvitrWtU169I3876zRTalMRbfd6LL0= +golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.zx2c4.com/wireguard v0.0.0-20210510202332-9844c74f67ec/go.mod h1:a057zjmoc00UN7gVkaJt2sXVK523kMJcogDTEvPIasg= -golang.zx2c4.com/wireguard v0.0.0-20210604143328-f9b48a961cd2 h1:wfOOSvHgIzTZ9h5Vb6yUFZNn7uf3bT7PeYsHOO7tYDM= -golang.zx2c4.com/wireguard v0.0.0-20210604143328-f9b48a961cd2/go.mod h1:laHzsbfMhGSobUmruXWAyMKKHSqvIcrqZJMyHD+/3O8= -golang.zx2c4.com/wireguard/windows v0.3.14 h1:5yIDYyrQyGkLqV+tzY4ilMNeIvQeMXAz0glZz9u179A= -golang.zx2c4.com/wireguard/windows v0.3.14/go.mod h1:3P4IEAsb+BjlKZmpUXgy74c0iX9AVwwr3WcVJ8nPgME= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.zx2c4.com/wireguard v0.0.0-20211012062646-82d2aa87aa62/go.mod h1:id8Oh3eCCmpj9uVGWVjsUAl6UPX5ysMLzu6QxJU2UOU= +golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a h1:tTbyylK9/D3u/wEP26Vx7L700UpY48nhioJWZM1vhZw= +golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a/go.mod h1:id8Oh3eCCmpj9uVGWVjsUAl6UPX5ysMLzu6QxJU2UOU= +golang.zx2c4.com/wireguard/windows v0.4.12 h1:CUmbdWKVNzTSsVb4yUAiEwL3KsabdJkEPdDjCHxBlhA= +golang.zx2c4.com/wireguard/windows v0.4.12/go.mod h1:PW4y+d9oY83XU9rRwRwrJDwEMuhVjMxu2gfD1cfzS7w= From 4f3117d81d76d5219063016e458c4d2f748ff05d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 3 Nov 2021 17:40:06 +0000 Subject: [PATCH 0803/1109] Use `network-online.target` instead of `network.target` for systemd service unit --- contrib/systemd/yggdrasil.service | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/systemd/yggdrasil.service b/contrib/systemd/yggdrasil.service index 27d27907..f8c2dd22 100644 --- a/contrib/systemd/yggdrasil.service +++ b/contrib/systemd/yggdrasil.service @@ -1,8 +1,8 @@ [Unit] Description=yggdrasil -Wants=network.target +Wants=network-online.target Wants=yggdrasil-default-config.service -After=network.target +After=network-online.target After=yggdrasil-default-config.service [Service] From 1f64319712a83aaa7f79789ab247db9542b04dbf Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 3 Nov 2021 17:53:35 +0000 Subject: [PATCH 0804/1109] Version 0.4.1 --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdb634f3..05291f85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> +## [0.4.1] - 2021-11-03 +### Added +- TLS peerings now support Server Name Indication (SNI) + - The SNI is sent automatically if the peering URI contains a DNS name + - A custom SNI can be specified by adding the `?sni=domain.com` parameter to the peering URI +- A new `ipv6rwc` API package now implements the IPv6-specific logic separate from the `tun` package + +### Fixed +- A crash when calculating the partial public key for very high IPv6 addresses has been fixed +- A crash due to a concurrent map write has been fixed +- A crash due to missing TUN configuration has been fixed +- A race condition in the keystore code has been fixed + ## [0.4.0] - 2021-07-04 ### Added - New routing scheme, which is backwards incompatible with previous versions of Yggdrasil From 03a5cce5bb739d5f3ad5d9877c38817cf88eccd5 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 3 Nov 2021 20:03:27 +0000 Subject: [PATCH 0805/1109] Revert Wireguard update This reverts commit 5c19f3f88c6b664456721a933c4e37c85214504d. --- go.mod | 11 ++++++----- go.sum | 52 ++++++++++++++++++++-------------------------------- 2 files changed, 26 insertions(+), 37 deletions(-) diff --git a/go.mod b/go.mod index 05044531..398504cb 100644 --- a/go.mod +++ b/go.mod @@ -17,9 +17,10 @@ require ( github.com/mitchellh/mapstructure v1.4.1 github.com/vishvananda/netlink v1.1.0 github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect - golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 - golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 - golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b - golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a - golang.zx2c4.com/wireguard/windows v0.4.12 + golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect + golang.org/x/net v0.0.0-20210610132358-84b48f89b13b + golang.org/x/sys v0.0.0-20210611083646-a4fc73990273 + golang.org/x/text v0.3.7-0.20210503195748-5c7c50ebbd4f + golang.zx2c4.com/wireguard v0.0.0-20210604143328-f9b48a961cd2 + golang.zx2c4.com/wireguard/windows v0.3.14 ) diff --git a/go.sum b/go.sum index e6df746b..5d11fad6 100644 --- a/go.sum +++ b/go.sum @@ -38,54 +38,42 @@ github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYp github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210927181540-4e4d966f7476/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211011170408-caeb26a5c8c0/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 h1:VrJZAjbekhoRn7n5FBujY31gboH+iB3pdLxn3gE9FjU= -golang.org/x/net v0.0.0-20211101193420-4a448f8816b3/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210610132358-84b48f89b13b h1:k+E048sYJHyVnsr1GDrRZWQ32D2C7lWs9JRc0bel53A= +golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 h1:7zYaz3tjChtpayGDzu6H0hDAUM5zIGA2XW7kRNgQ0jc= -golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210611083646-a4fc73990273 h1:faDu4veV+8pcThn4fewv6TVlNCezafGoC1gM/mxQLbQ= +golang.org/x/sys v0.0.0-20210611083646-a4fc73990273/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b h1:NXqSWXSRUSCaFuvitrWtU169I3876zRTalMRbfd6LL0= -golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0= +golang.org/x/text v0.3.7-0.20210503195748-5c7c50ebbd4f h1:yQJrRE0hDxDFmZLlRaw+3vusO4fwNHgHIjUOMO7bHYI= +golang.org/x/text v0.3.7-0.20210503195748-5c7c50ebbd4f/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.zx2c4.com/wireguard v0.0.0-20211012062646-82d2aa87aa62/go.mod h1:id8Oh3eCCmpj9uVGWVjsUAl6UPX5ysMLzu6QxJU2UOU= -golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a h1:tTbyylK9/D3u/wEP26Vx7L700UpY48nhioJWZM1vhZw= -golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a/go.mod h1:id8Oh3eCCmpj9uVGWVjsUAl6UPX5ysMLzu6QxJU2UOU= -golang.zx2c4.com/wireguard/windows v0.4.12 h1:CUmbdWKVNzTSsVb4yUAiEwL3KsabdJkEPdDjCHxBlhA= -golang.zx2c4.com/wireguard/windows v0.4.12/go.mod h1:PW4y+d9oY83XU9rRwRwrJDwEMuhVjMxu2gfD1cfzS7w= +golang.zx2c4.com/wireguard v0.0.0-20210510202332-9844c74f67ec/go.mod h1:a057zjmoc00UN7gVkaJt2sXVK523kMJcogDTEvPIasg= +golang.zx2c4.com/wireguard v0.0.0-20210604143328-f9b48a961cd2 h1:wfOOSvHgIzTZ9h5Vb6yUFZNn7uf3bT7PeYsHOO7tYDM= +golang.zx2c4.com/wireguard v0.0.0-20210604143328-f9b48a961cd2/go.mod h1:laHzsbfMhGSobUmruXWAyMKKHSqvIcrqZJMyHD+/3O8= +golang.zx2c4.com/wireguard/windows v0.3.14 h1:5yIDYyrQyGkLqV+tzY4ilMNeIvQeMXAz0glZz9u179A= +golang.zx2c4.com/wireguard/windows v0.3.14/go.mod h1:3P4IEAsb+BjlKZmpUXgy74c0iX9AVwwr3WcVJ8nPgME= From e4e58831bf6b3d2a194c12c4e8dc3fd7351e6165 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 3 Nov 2021 22:16:53 +0000 Subject: [PATCH 0806/1109] Version 0.4.2 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05291f85..bb418b11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> +## [0.4.2] - 2021-11-03 +### Fixed +- Reverted a dependency update which resulted in problems building with Go 1.16 and running on Windows + ## [0.4.1] - 2021-11-03 ### Added - TLS peerings now support Server Name Indication (SNI) From 87e936195ebaa1d11475d6fb3a73958c654be977 Mon Sep 17 00:00:00 2001 From: Alex Kotov Date: Thu, 4 Nov 2021 13:05:53 +0500 Subject: [PATCH 0807/1109] Add some tests (#828) * Add tests * Add tests * Add tests * Add tests * Fix code style * Remove unnecessary tests --- src/address/address_test.go | 114 ++++++++++++++++++++++++++++++++++++ src/config/config_test.go | 54 +++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 src/address/address_test.go create mode 100644 src/config/config_test.go diff --git a/src/address/address_test.go b/src/address/address_test.go new file mode 100644 index 00000000..a7939e0f --- /dev/null +++ b/src/address/address_test.go @@ -0,0 +1,114 @@ +package address + +import ( + "bytes" + "crypto/ed25519" + "math/rand" + "testing" +) + +func TestAddress_Address_IsValid(t *testing.T) { + var address Address + rand.Read(address[:]) + + address[0] = 0 + + if address.IsValid() { + t.Fatal("invalid address marked as valid") + } + + address[0] = 0x03 + + if address.IsValid() { + t.Fatal("invalid address marked as valid") + } + + address[0] = 0x02 + + if !address.IsValid() { + t.Fatal("valid address marked as invalid") + } +} + +func TestAddress_Subnet_IsValid(t *testing.T) { + var subnet Subnet + rand.Read(subnet[:]) + + subnet[0] = 0 + + if subnet.IsValid() { + t.Fatal("invalid subnet marked as valid") + } + + subnet[0] = 0x02 + + if subnet.IsValid() { + t.Fatal("invalid subnet marked as valid") + } + + subnet[0] = 0x03 + + if !subnet.IsValid() { + t.Fatal("valid subnet marked as invalid") + } +} + +func TestAddress_AddrForKey(t *testing.T) { + publicKey := ed25519.PublicKey{ + 189, 186, 207, 216, 34, 64, 222, 61, 205, 18, 57, 36, 203, 181, 82, 86, + 251, 141, 171, 8, 170, 152, 227, 5, 82, 138, 184, 79, 65, 158, 110, 251, + } + + expectedAddress := Address{ + 2, 0, 132, 138, 96, 79, 187, 126, 67, 132, 101, 219, 141, 182, 104, 149, + } + + if *AddrForKey(publicKey) != expectedAddress { + t.Fatal("invalid address returned") + } +} + +func TestAddress_SubnetForKey(t *testing.T) { + publicKey := ed25519.PublicKey{ + 189, 186, 207, 216, 34, 64, 222, 61, 205, 18, 57, 36, 203, 181, 82, 86, + 251, 141, 171, 8, 170, 152, 227, 5, 82, 138, 184, 79, 65, 158, 110, 251, + } + + expectedSubnet := Subnet{3, 0, 132, 138, 96, 79, 187, 126} + + if *SubnetForKey(publicKey) != expectedSubnet { + t.Fatal("invalid subnet returned") + } +} + +func TestAddress_Address_GetKey(t *testing.T) { + address := Address{ + 2, 0, 132, 138, 96, 79, 187, 126, 67, 132, 101, 219, 141, 182, 104, 149, + } + + expectedPublicKey := ed25519.PublicKey{ + 189, 186, 207, 216, 34, 64, 222, 61, + 205, 18, 57, 36, 203, 181, 127, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + } + + if !bytes.Equal(address.GetKey(), expectedPublicKey) { + t.Fatal("invalid public key returned") + } +} + +func TestAddress_Subnet_GetKey(t *testing.T) { + subnet := Subnet{3, 0, 132, 138, 96, 79, 187, 126} + + expectedPublicKey := ed25519.PublicKey{ + 189, 186, 207, 216, 34, 64, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + } + + if !bytes.Equal(subnet.GetKey(), expectedPublicKey) { + t.Fatal("invalid public key returned") + } +} diff --git a/src/config/config_test.go b/src/config/config_test.go new file mode 100644 index 00000000..8b6e14e1 --- /dev/null +++ b/src/config/config_test.go @@ -0,0 +1,54 @@ +package config + +import ( + "bytes" + "encoding/hex" + "testing" +) + +func TestConfig_Keys(t *testing.T) { + var nodeConfig NodeConfig + nodeConfig.NewKeys() + + publicKey1, err := hex.DecodeString(nodeConfig.PublicKey) + + if err != nil { + t.Fatal("can not decode generated public key") + } + + if len(publicKey1) == 0 { + t.Fatal("empty public key generated") + } + + privateKey1, err := hex.DecodeString(nodeConfig.PrivateKey) + + if err != nil { + t.Fatal("can not decode generated private key") + } + + if len(privateKey1) == 0 { + t.Fatal("empty private key generated") + } + + nodeConfig.NewKeys() + + publicKey2, err := hex.DecodeString(nodeConfig.PublicKey) + + if err != nil { + t.Fatal("can not decode generated public key") + } + + if bytes.Equal(publicKey2, publicKey1) { + t.Fatal("same public key generated") + } + + privateKey2, err := hex.DecodeString(nodeConfig.PrivateKey) + + if err != nil { + t.Fatal("can not decode generated private key") + } + + if bytes.Equal(privateKey2, privateKey1) { + t.Fatal("same private key generated") + } +} From 408d381591e99c273bf8db520a185478cdd8024f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 6 Dec 2021 11:19:58 +0000 Subject: [PATCH 0808/1109] Set `hostArchitectures` in macOS `.pkg` installer --- contrib/macos/create-pkg.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/macos/create-pkg.sh b/contrib/macos/create-pkg.sh index 919c45ed..773f01ee 100755 --- a/contrib/macos/create-pkg.sh +++ b/contrib/macos/create-pkg.sh @@ -79,6 +79,7 @@ PKGNAME=$(sh contrib/semver/name.sh) PKGVERSION=$(sh contrib/semver/version.sh --bare) PKGARCH=${PKGARCH-amd64} PAYLOADSIZE=$(( $(wc -c pkgbuild/flat/base.pkg/Payload | awk '{ print $1 }') / 1024 )) +[ "$PKGARCH" = "amd64" ] && PKGHOSTARCH="x86_64" || PKGHOSTARCH=${PKGARCH} # Create the PackageInfo file cat > pkgbuild/flat/base.pkg/PackageInfo << EOF @@ -98,7 +99,7 @@ cat > pkgbuild/flat/Distribution << EOF Yggdrasil (${PKGNAME}-${PKGVERSION}) - +