From 3f824ee99c9381ca139a6725d9fbda3ca03ced58 Mon Sep 17 00:00:00 2001 From: Paul Spooren Date: Tue, 19 Mar 2019 15:54:49 +0100 Subject: [PATCH 001/177] README: add OpenWrt as supported platform --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e9e0c7d..3f07a2b 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 002/177] 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 8c8340f..cec7705 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 a900758..861e57a 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 52ca50c..f4f9efe 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 465cbb1..7dab31c 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 81e2c46..95c13af 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 7ec1b8b..5dfca13 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 234ab1d..04e6525 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 30ada23..051287e 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 07ec25f..17dfa2b 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 1c89a43..f5cebe5 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 3ce80d2..7437eb0 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 a0854f2..002f974 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 03bc571..dc4f1b9 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 12ff14f..a583d58 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 ca3a1f7..ab1b158 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 ce35936..06201f9 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 1d4c077..04f5ee5 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 012af57..b3563e0 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 490b15f..0e164b9 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 8acf9c1..f25ac41 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 003/177] 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 f4f9efe..55c3280 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 7dab31c..780043f 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 7437eb0..4103d1e 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 004/177] 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 780043f..f85d8bb 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 4103d1e..6be3ef0 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 04f5ee5..aa2cd54 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 005/177] 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 95c13af..996f314 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 051287e..c9c03c0 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 17dfa2b..48276b4 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 f5cebe5..8a66ac6 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 006/177] 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 cec7705..fe84849 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 ab1b158..71bdbc8 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 5364611..900354c 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 e20bbda..16ea15d 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 3da1ab4..9d6a01a 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 3e07f6c..7a846b1 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 002f974..2db7ad4 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 a583d58..461e8e9 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 81aa47f..bad1424 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 f25ac41..b9681fc 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 007/177] 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 99088d1..9cf7d95 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 461e8e9..5d45a48 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 008/177] 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 fe84849..72f0284 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 71bdbc8..c675680 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 5d45a48..dbc893a 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 aa2cd54..6314cb1 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 b3563e0..8deff95 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 009/177] 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 72f0284..9278867 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 dbc893a..0e9b251 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 6314cb1..bef564f 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 010/177] 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 0e9b251..523ab5e 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 011/177] 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 f85d8bb..5a247bf 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 6be3ef0..d373894 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 523ab5e..8444050 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 bef564f..9130f55 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 012/177] 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 c675680..4e5bc4b 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 5a247bf..600d3d7 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 9130f55..aee9224 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 b9681fc..4361ec8 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 013/177] 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 9278867..fd8cd7b 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 2db7ad4..a9595f8 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 8444050..c0548e2 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 5427aca..b081c92 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 963a2fc..89b8b89 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 014/177] 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 600d3d7..5d9f4eb 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 d373894..e5b1707 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 c0548e2..9634a42 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 bfec714..de90fd9 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 bad1424..0c6686d 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 7b08999..96d2fc0 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 aee9224..a3d3d68 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 015/177] 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 f6c7246..127af75 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 0000000..a199eee --- /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 0000000..ecc4d65 --- /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 2476484..f3206ac 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 96d2fc0..26b219c 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 9634a42..fcee124 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 0c6686d..0000000 --- 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 016/177] 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 127af75..f76ee7b 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 a199eee..ca6bb7b 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 ecc4d65..5eec96e 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 fcee124..037ef09 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 8deff95..74255c0 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 017/177] 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 5d9f4eb..c93b116 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 996f314..27c9bb2 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 5dfca13..60786b8 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 c9c03c0..7d22857 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 48276b4..bb302d1 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 8a66ac6..5a158b1 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 e5b1707..8fadb19 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 018/177] 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 5e8cce1..4fb4386 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 019/177] 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 4fb4386..9a073ef 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 5eec96e..02bd30f 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 020/177] 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 8891f1a..1bb4d90 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 021/177] 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 4433a9d..00f6536 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 022/177] 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 00f6536..90707a7 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 023/177] 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 861e57a..8137cac 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 024/177] 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 c974a3c..b47db18 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 0000000..9181269 --- /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 025/177] 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 fd8cd7b..36866b3 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 b8864dc..b70bbe9 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 a9595f8..0f91cd1 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 9181269..0b38525 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 a3d3d68..d7923f5 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 c391dda..e81a972 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 74255c0..fd3a985 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 026/177] 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 36866b3..8a810bc 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 0b38525..41ef8c0 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 fd3a985..4c896f2 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 027/177] 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 41ef8c0..0ce626c 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 037ef09..22caf08 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 4c896f2..a183545 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 028/177] 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 0ce626c..bcf38ed 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 029/177] 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 8a810bc..fd8cd7b 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 bcf38ed..7aa4655 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 a183545..e860dc8 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 030/177] 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 7aa4655..fd65743 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 e860dc8..e46761d 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 031/177] 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 fd65743..e337b34 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 e46761d..c5319bc 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 032/177] 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 e337b34..a151537 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 22caf08..dba1c64 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 0000000..268d8b7 --- /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 c5319bc..64b5d29 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 033/177] 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 dba1c64..b5d74e8 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 268d8b7..6222541 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 034/177] 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 a151537..df45d12 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 64b5d29..40259d9 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 035/177] 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 fd8cd7b..2267d62 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 036/177] 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 2267d62..0e3aa35 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 df45d12..b936d4f 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 037/177] 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 b936d4f..874a7a9 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 b5d74e8..cfba833 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 d7923f5..693fba4 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 40259d9..9c09532 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 038/177] Allow building with race detector --- build | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/build b/build index f76ee7b..bad287f 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 039/177] 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 0e3aa35..f3d933c 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 b47db18..d5b467e 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 c93b116..3010fae 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 27c9bb2..996f314 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 60786b8..5dfca13 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 7d22857..c9c03c0 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 bb302d1..48276b4 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 5a158b1..8a66ac6 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 8fadb19..0000000 --- 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 874a7a9..6334eef 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 cfba833..81be1b2 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 0000000..7042bd0 --- /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 693fba4..19b62f9 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 9c09532..3bf69a8 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 040/177] 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 3010fae..4b72b46 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 6334eef..9566cae 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 7042bd0..fee355e 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 e81a972..dcf0c81 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 041/177] 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 4b72b46..9e46124 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 9566cae..daba298 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 fee355e..4a3d816 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 19b62f9..7da5162 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 dcf0c81..e81a972 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 3bf69a8..b0bba2d 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 042/177] 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 f3d933c..9634983 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 9e46124..acdcf3a 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 daba298..ed9eb6c 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 043/177] 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 acdcf3a..d9e0e77 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 ed9eb6c..977c742 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 044/177] 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 977c742..7898a12 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 4a3d816..49ce0a9 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 7da5162..348a1ed 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 b0bba2d..15d346a 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 045/177] 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 d9e0e77..912998c 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 046/177] 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 e81a972..3460fd4 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 047/177] 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 912998c..5f87c20 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 7898a12..9308d34 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 15d346a..55d4a0c 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 048/177] 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 5f87c20..746293d 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 049/177] 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 746293d..3bdbde2 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 49ce0a9..bd4b87e 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 050/177] 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 3bdbde2..07264da 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 9308d34..0accf16 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 bd4b87e..8901610 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 3460fd4..0a64336 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 55d4a0c..e356f61 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 051/177] 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 07264da..c65c15e 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 0accf16..903152d 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 e356f61..967d5f6 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 052/177] 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 c65c15e..36e2965 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 903152d..a5702d3 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 053/177] 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 36e2965..709f705 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 a5702d3..4ffa4b1 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 348a1ed..6870072 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 054/177] 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 4ffa4b1..8d36a60 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 8901610..325c6b7 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 967d5f6..44c8387 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 055/177] 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 709f705..58eca27 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 056/177] 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 0f91cd1..a24a585 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 dc4f1b9..0000000 --- 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 81be1b2..ac2b494 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 6870072..cacf025 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 057/177] 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 58eca27..eb06cc9 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 058/177] 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 55c3280..8159e0f 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 eb06cc9..fed6563 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 059/177] 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 fed6563..01cafaa 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 44c8387..bf35e8c 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 060/177] 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 01cafaa..c19764c 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 8d36a60..08591c4 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 325c6b7..1943c85 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 bf35e8c..724152d 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 061/177] 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 08591c4..a019908 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 062/177] 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 a019908..51f352f 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 063/177] 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 51f352f..5c71bb9 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 cacf025..2e32fb6 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 064/177] 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 49e0207..94bd5d6 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 5c71bb9..aad1dcd 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 065/177] 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 0000000..e59e560 --- /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 0000000..d837633 --- /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 c19764c..cfc8a66 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 066/177] 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 e59e560..ae8fcc0 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 d837633..5b8e77b 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 067/177] 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 0000000..037dde6 --- /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 0000000..2549ece --- /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 0000000..aa2adc8 --- /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 0000000..f7b6876 --- /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 0000000..fd17e49 --- /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 0000000..f0b6f55 --- /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 0000000..51e9aef --- /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 0000000..8e39e0f --- /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 068/177] 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 2549ece..68a49da 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 f0b6f55..a592f38 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 51e9aef..02218e5 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 069/177] 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 68a49da..2d61b40 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 070/177] 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 ae8fcc0..50860f3 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 5b8e77b..efccd07 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 e575b72..c4eed63 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 071/177] 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 0e164b9..1bc4050 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 072/177] 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 0000000..971f0a3 --- /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 efccd07..d70a130 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 cfc8a66..a73dde1 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 073/177] 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 9634983..52ddfe9 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 4e5bc4b..8d18889 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 a73dde1..310e421 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 ac2b494..63d33d9 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 074/177] 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 52ddfe9..ccb7061 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 075/177] 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 8d18889..cbde7fd 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 076/177] 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 037dde6..0000000 --- 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 077/177] 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 7aec419..0000000 --- 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 4361ec8..dfb4151 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 078/177] 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 0000000..b527810 --- /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 63d33d9..41bb11f 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 079/177] 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 b527810..d41b5f5 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 080/177] 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 ccb7061..756947d 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 a24a585..a7e9ac7 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 d41b5f5..e7050c7 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 41bb11f..3a7f9f1 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 081/177] 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 756947d..5dba3a5 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 a7e9ac7..ff5476e 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 0000000..672b7ca --- /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 0000000..1d0b587 --- /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 082/177] 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 ff5476e..7cf5f33 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 083/177] 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 7cf5f33..27dabdc 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 e7050c7..40e5a2b 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 084/177] 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 1d0b587..21c7048 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 971f0a3..c996c39 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 085/177] 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 50860f3..ce4645f 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 310e421..b9ff04e 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 aad1dcd..1290ad0 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 086/177] 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 ce4645f..66c1011 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 b9ff04e..683b83a 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 087/177] 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 b70bbe9..51f4fa5 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 27dabdc..c3f140a 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 40e5a2b..5e58ffa 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 724152d..3a4eb2e 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 088/177] 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 3a4eb2e..68b9095 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 089/177] 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 66c1011..f303e0c 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 1290ad0..9175aa5 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 090/177] 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 f303e0c..d6bb7a7 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 9175aa5..d2458db 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 091/177] 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 c996c39..c9233e6 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 092/177] 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 d2458db..3df7c79 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 093/177] 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 3df7c79..9ce5563 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 094/177] 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 8137cac..9f8f3f5 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 3834990..a578419 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 9bac3aa..26fd1f2 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 df1a3c6..0e52348 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 2f3459c..67404e2 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 40476dc..52a487b 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 cd6d202..d44e571 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 a01ab7a..0ba825c 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 3b04783..6d53225 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 095/177] 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 5e58ffa..9d64099 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 68b9095..46628c3 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 096/177] 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 5dba3a5..fb6cccb 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 097/177] 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 9d64099..25f9869 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 098/177] 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 9cf7d95..03244ce 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 099/177] 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 26fd1f2..47683bf 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 67404e2..b0aaf85 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 100/177] 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 46628c3..2211847 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 101/177] 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 672b7ca..40e28af 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 cbde7fd..f0b1a9a 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 900354c..213bff3 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 102/177] 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 25f9869..b98df3b 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 9ce5563..7216be9 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 b081c92..b53e29c 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 0a64336..576034b 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 103/177] 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 fb6cccb..32069c0 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 25f9869..462353c 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 104/177] 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 40e28af..cafee07 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 f0b1a9a..3c0d8c0 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 213bff3..c88b4a8 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 105/177] 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 32069c0..6af2772 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 106/177] 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 d70a130..f6cfec9 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 683b83a..a21f871 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 7216be9..5c9a413 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 1943c85..1e3e0d6 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 576034b..b43f0e4 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 107/177] 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 a21f871..ed5d2d4 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 108/177] 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 2211847..55b0ed4 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 109/177] 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 5c9a413..a4036c7 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 b43f0e4..d8c9049 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 55b0ed4..dc3f01e 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 110/177] 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 a4036c7..0357cca 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 111/177] 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 995e54c..eec583a 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 92dfe88..4be88b2 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 112/177] 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 0357cca..53d2551 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 1e3e0d6..6b24cfb 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 2e32fb6..514d14f 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 dc3f01e..98a12c7 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 113/177] 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 53d2551..38e4df9 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 98a12c7..4cc059e 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 114/177] 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 f6cfec9..16a3b65 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 115/177] 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 38e4df9..b4f68e1 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 116/177] 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 864e7f3..7270bc2 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 117/177] 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 7270bc2..1bec983 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 118/177] 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 4cc059e..53836c3 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 119/177] 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 b4f68e1..5d1e77a 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 120/177] 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 6af2772..fd8828c 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 3c0d8c0..ba1f18f 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 16a3b65..60c814c 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 ed5d2d4..b7b4cfa 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 121/177] 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 fd8828c..7944684 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 122/177] 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 3a7f9f1..62d89a8 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 123/177] 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 7944684..129b01d 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 124/177] 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 c9233e6..0032039 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 8159e0f..ea1a785 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 60c814c..be3988a 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 b7b4cfa..15530d2 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 125/177] 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 ea1a785..fe80dfb 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 be3988a..9ffde85 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 126/177] 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 0032039..52c1159 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 15530d2..cc12497 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 8a66ac6..002c354 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 127/177] 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 c3f140a..4c933dd 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 128/177] 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 26d49a5..d7f1327 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 129/177] 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 d5b467e..75736ba 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 62d89a8..35a86df 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 130/177] 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 03244ce..38b6b03 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 131/177] 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 38b6b03..1a2898c 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 132/177] 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 d6bb7a7..f45e4a1 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 133/177] 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 f45e4a1..c9afa6e 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 5d1e77a..2a286b0 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 134/177] 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 c9afa6e..c5e6e81 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 135/177] 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 c5e6e81..4cc880c 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 cc12497..72bdd2f 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 136/177] 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 4cc880c..c5e6e81 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 72bdd2f..cc12497 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 137/177] 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 c5e6e81..24d862e 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 2a286b0..0e18078 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 c4eed63..5cb7c46 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 138/177] 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 24d862e..0d63fde 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 139/177] 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 0d63fde..234c34f 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 140/177] 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 0e18078..1d686f8 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 141/177] 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 0000000..1433c73 --- /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 142/177] 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 1433c73..fa98008 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 143/177] 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 fa98008..2a78c19 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 1d686f8..bc884fb 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 144/177] 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 eec583a..2804453 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 4be88b2..c4b8686 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 002c354..98508ef 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 145/177] 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 2804453..d943045 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 c4b8686..28b5fb3 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 146/177] 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 1a2898c..2bd5b03 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 147/177] 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 d943045..0d58244 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 28b5fb3..4335302 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 148/177] 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 98508ef..d46b7e5 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 149/177] 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 9ffde85..4ddaf0b 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 150/177] 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 0d58244..f076905 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 4335302..c6fc16c 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 fe80dfb..e601acb 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 4ddaf0b..637715d 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 d46b7e5..a826c7a 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 151/177] 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 5cb7c46..ff3bbe7 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 152/177] 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 4c933dd..fc8a6a5 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 153/177] 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 234c34f..193c623 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 154/177] 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 f076905..dc2d522 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 c6fc16c..059d845 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 155/177] 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 d8c9049..4c31fd6 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 156/177] 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 5dfca13..d7b4653 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 157/177] 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 2bd5b03..0bf62c5 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 158/177] 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 0bf62c5..36fd678 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 159/177] 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 36fd678..f9cd072 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 160/177] 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 fc8a6a5..7d6f16b 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 9f8f3f5..a7bbbac 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 ca6bb7b..0000000 --- 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 02bd30f..08a8c1a 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 ba1f18f..5ab9673 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 52c1159..80e3e42 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 193c623..1d47b37 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 cc12497..eef05b8 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 04e6525..0000000 --- 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 1bec983..bd0d098 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 bc884fb..cd61e24 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 35a86df..2aa7834 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 514d14f..abd31cb 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 161/177] 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 abd31cb..c5e1dde 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 162/177] 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 059d845..050a3e5 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 2a78c19..af4721b 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 94bd5d6..4596474 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 1bec983..2bad5a0 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 bc884fb..ee9cbb3 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 53836c3..eca3bb0 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 163/177] 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 ee9cbb3..e04108f 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 164/177] 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 e04108f..30519f6 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 165/177] 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 637715d..00cf2df 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 166/177] 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 7d6f16b..2b73764 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 bd0d098..f42a208 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 89b8b89..f1c7ed0 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 1bb4d90..5aa354d 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 167/177] 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 f42a208..bf64324 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 168/177] 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 9a073ef..0000000 --- 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 08a8c1a..0000000 --- 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 f3206ac..0000000 --- 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 26b219c..0000000 --- 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 169/177] Fix build -i --- build | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build b/build index bad287f..383b91a 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 170/177] 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 00cf2df..a95dfae 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 171/177] Update build script --- build | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/build b/build index 383b91a..a787965 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 172/177] Don't strip debug builds --- build | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build b/build index a787965..046296e 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 173/177] Try to be more POSIX-compliant --- build | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/build b/build index 046296e..80a971f 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 174/177] Update CHANGELOG.md --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5894598..ee50dcc 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 175/177] 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 dc2d522..325f55c 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 050a3e5..409913e 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 176/177] 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 325f55c..84025df 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 409913e..2e81ccc 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 177/177] more updates to go.sum --- go.sum | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.sum b/go.sum index 2e81ccc..81d337e 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=