diff --git a/.circleci/config.yml b/.circleci/config.yml index 99088d1c..9cf7d95b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -24,11 +24,11 @@ jobs: command: | sudo apt-get install -y alien - - run: - name: Test debug builds - command: | - ./build -d - test -f yggdrasil && test -f yggdrasilctl + # - run: + # name: Test debug builds + # command: | + # ./build -d + # test -f yggdrasil && test -f yggdrasilctl - run: name: Build for Linux (including Debian packages and RPMs) diff --git a/build b/build index f6c72466..f76ee7b2 100755 --- a/build +++ b/build @@ -28,10 +28,16 @@ fi if [ $IOS ]; then echo "Building framework for iOS" - gomobile bind -target ios -tags mobile -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil + 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/yggdrasil + 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/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 8c8340f0..fd8cd7b6 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -19,6 +19,8 @@ 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" ) @@ -26,7 +28,9 @@ type nodeConfig = config.NodeConfig type Core = yggdrasil.Core type node struct { - core Core + core Core + tuntap tuntap.TunAdapter + multicast multicast.Multicast } func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *nodeConfig { @@ -185,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 @@ -244,13 +248,20 @@ 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. - if err := n.core.Start(cfg, logger); err != nil { + // 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") panic(err) } + // Start the multicast interface + n.multicast.Init(&n.core, state, logger, nil) + 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() { @@ -258,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/config/config.go b/src/config/config.go index a9007585..8137cac9 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -2,11 +2,37 @@ 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. 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, s.Previous +} + // 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/dummy/dummy.go b/src/dummy/dummy.go new file mode 100644 index 00000000..ca6bb7b7 --- /dev/null +++ b/src/dummy/dummy.go @@ -0,0 +1,61 @@ +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/yggdrasil/awdl.go b/src/mobile/awdl.go similarity index 98% rename from src/yggdrasil/awdl.go rename to src/mobile/awdl.go index 5e8cce16..9a073ef7 100644 --- a/src/yggdrasil/awdl.go +++ b/src/mobile/awdl.go @@ -1,5 +1,6 @@ -package yggdrasil +package mobile +/* import ( "errors" "io" @@ -104,3 +105,4 @@ func (a *awdl) shutdown(identity string) error { } return errors.New("Interface not found or already closed") } +*/ diff --git a/src/mobile/mobile.go b/src/mobile/mobile.go new file mode 100644 index 00000000..02bd30fe --- /dev/null +++ b/src/mobile/mobile.go @@ -0,0 +1,149 @@ +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 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 + 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 { + 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/yggdrasil/mobile_android.go b/src/mobile/mobile_android.go similarity index 90% rename from src/yggdrasil/mobile_android.go rename to src/mobile/mobile_android.go index 24764840..f3206aca 100644 --- a/src/yggdrasil/mobile_android.go +++ b/src/mobile/mobile_android.go @@ -1,6 +1,6 @@ // +build android -package yggdrasil +package mobile import "log" diff --git a/src/yggdrasil/mobile_ios.go b/src/mobile/mobile_ios.go similarity index 92% rename from src/yggdrasil/mobile_ios.go rename to src/mobile/mobile_ios.go index 7b089992..26b219cc 100644 --- a/src/yggdrasil/mobile_ios.go +++ b/src/mobile/mobile_ios.go @@ -1,6 +1,6 @@ -// +build mobile,darwin +// +build darwin -package yggdrasil +package mobile /* #cgo CFLAGS: -x objective-c @@ -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/multicast.go b/src/multicast/multicast.go similarity index 72% rename from src/yggdrasil/multicast.go rename to src/multicast/multicast.go index ca3a1f75..4e5bc4b1 100644 --- a/src/yggdrasil/multicast.go +++ b/src/multicast/multicast.go @@ -1,4 +1,4 @@ -package yggdrasil +package multicast import ( "context" @@ -7,25 +7,37 @@ 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 +// 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 + 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) { +// Init prepares the multicast interface for use. +func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) error { m.core = core + m.config = state + m.log = log m.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() + m.listeners = make(map[string]*yggdrasil.TcpListener) + current, _ := m.config.Get() + m.listenPort = current.LinkLocalTCPPort go func() { for { e := <-m.reconfigure @@ -35,15 +47,19 @@ 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)") } + return nil } -func (m *multicast) start() error { +// 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.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 @@ -68,11 +84,15 @@ func (m *multicast) start() error { return nil } -func (m *multicast) interfaces() map[string]net.Interface { +// Stop is not implemented for multicast yet. +func (m *Multicast) Stop() error { + return nil +} + +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.config.Get() + exprs := current.MulticastInterfaces // Ask the system for network interfaces interfaces := make(map[string]net.Interface) allifaces, err := net.Interfaces() @@ -108,7 +128,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) @@ -124,9 +144,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 @@ -137,7 +157,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 @@ -186,17 +206,17 @@ 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.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 @@ -207,7 +227,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 @@ -221,7 +241,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) @@ -253,8 +273,8 @@ 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.CallPeer("tcp://"+addr.String(), from.Zone); err != nil { + m.log.Debugln("Call from multicast failed:", err) } } } diff --git a/src/yggdrasil/multicast_darwin.go b/src/multicast/multicast_darwin.go similarity index 86% rename from src/yggdrasil/multicast_darwin.go rename to src/multicast/multicast_darwin.go index 53646113..900354c7 100644 --- a/src/yggdrasil/multicast_darwin.go +++ b/src/multicast/multicast_darwin.go @@ -1,6 +1,6 @@ // +build darwin -package yggdrasil +package multicast /* #cgo CFLAGS: -x objective-c @@ -31,11 +31,11 @@ import ( var awdlGoroutineStarted bool -func (m *multicast) multicastStarted() { +func (m *Multicast) multicastStarted() { if awdlGoroutineStarted { return } - m.core.log.Infoln("Multicast discovery will wake up AWDL if required") + m.log.Infoln("Multicast discovery will wake up AWDL if required") awdlGoroutineStarted = true for { C.StopAWDLBrowsing() @@ -49,7 +49,7 @@ func (m *multicast) multicastStarted() { } } -func (m *multicast) multicastReuse(network string, address string, c syscall.RawConn) error { +func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error { var control error var reuseport error var recvanyif error diff --git a/src/yggdrasil/multicast_other.go b/src/multicast/multicast_other.go similarity index 53% rename from src/yggdrasil/multicast_other.go rename to src/multicast/multicast_other.go index e20bbda3..16ea15d6 100644 --- a/src/yggdrasil/multicast_other.go +++ b/src/multicast/multicast_other.go @@ -1,13 +1,13 @@ // +build !linux,!darwin,!netbsd,!freebsd,!openbsd,!dragonflybsd,!windows -package yggdrasil +package multicast import "syscall" -func (m *multicast) multicastStarted() { +func (m *Multicast) multicastStarted() { } -func (m *multicast) multicastReuse(network string, address string, c syscall.RawConn) error { +func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error { return nil } diff --git a/src/yggdrasil/multicast_unix.go b/src/multicast/multicast_unix.go similarity index 75% rename from src/yggdrasil/multicast_unix.go rename to src/multicast/multicast_unix.go index 3da1ab4e..9d6a01a8 100644 --- a/src/yggdrasil/multicast_unix.go +++ b/src/multicast/multicast_unix.go @@ -1,15 +1,15 @@ // +build linux netbsd freebsd openbsd dragonflybsd -package yggdrasil +package multicast import "syscall" import "golang.org/x/sys/unix" -func (m *multicast) multicastStarted() { +func (m *Multicast) multicastStarted() { } -func (m *multicast) multicastReuse(network string, address string, c syscall.RawConn) error { +func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error { var control error var reuseport error diff --git a/src/yggdrasil/multicast_windows.go b/src/multicast/multicast_windows.go similarity index 75% rename from src/yggdrasil/multicast_windows.go rename to src/multicast/multicast_windows.go index 3e07f6cc..7a846b1e 100644 --- a/src/yggdrasil/multicast_windows.go +++ b/src/multicast/multicast_windows.go @@ -1,15 +1,15 @@ // +build windows -package yggdrasil +package multicast import "syscall" import "golang.org/x/sys/windows" -func (m *multicast) multicastStarted() { +func (m *Multicast) multicastStarted() { } -func (m *multicast) multicastReuse(network string, address string, c syscall.RawConn) error { +func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error { var control error var reuseaddr error diff --git a/src/yggdrasil/icmpv6.go b/src/tuntap/icmpv6.go similarity index 84% rename from src/yggdrasil/icmpv6.go rename to src/tuntap/icmpv6.go index 52ca50c6..55c3280a 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 { @@ -97,18 +97,18 @@ func (i *icmpv6) parse_packet(datain []byte) { } // 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/tuntap/tun.go b/src/tuntap/tun.go new file mode 100644 index 00000000..c93b1163 --- /dev/null +++ b/src/tuntap/tun.go @@ -0,0 +1,361 @@ +package tuntap + +// This manages the tun driver to send/recv packets to/from applications + +import ( + "bytes" + "errors" + "fmt" + "net" + "sync" + "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/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 + +// 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 + 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 +// defaults.GetDefaults(). +func getSupportedMTU(mtu int) int { + if mtu > defaults.GetDefaults().MaximumIfMTU { + return defaults.GetDefaults().MaximumIfMTU + } + return mtu +} + +// 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() +} + +// 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) +} + +// IsTAP returns true if the adapter is a TAP adapter (Layer 2) or false if it +// is a TUN adapter (Layer 3). +func (tun *TunAdapter) IsTAP() bool { + return tun.iface.IsTAP() +} + +// DefaultName gets the default TUN/TAP interface name for your platform. +func DefaultName() string { + return defaults.GetDefaults().DefaultIfName +} + +// 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 +} + +// DefaultIsTAP returns true if the default adapter mode for the current +// platform is TAP (Layer 2) and returns false for TUN (Layer 3). +func DefaultIsTAP() bool { + return defaults.GetDefaults().DefaultIfTAPMode +} + +// MaximumMTU returns the maximum supported TUN/TAP interface MTU for your +// platform. This can be as high as 65535, depending on platform, but is never +// lower than 1280. +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 + } + } + }() +} + +// 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 { + 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 + } + } + 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.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 { + if _, ok := tun.icmpv6.peermacs[tun.addr]; ok { + break + } + request, err := tun.icmpv6.CreateNDPL2(tun.addr) + if err != nil { + panic(err) + } + if _, err := tun.iface.Write(request); err != nil { + panic(err) + } + time.Sleep(time.Second) + } + }() + } + return nil +} + +// 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 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 + } + 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") + } + 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]) { + 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) + } + } + } + } 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() +} diff --git a/src/yggdrasil/tun_bsd.go b/src/tuntap/tun_bsd.go similarity index 82% rename from src/yggdrasil/tun_bsd.go rename to src/tuntap/tun_bsd.go index 81e2c46c..27c9bb2f 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,20 +103,20 @@ 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 // 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/yggdrasil/tun_darwin.go b/src/tuntap/tun_darwin.go similarity index 81% rename from src/yggdrasil/tun_darwin.go rename to src/tuntap/tun_darwin.go index 7ec1b8b9..60786b81 100644 --- a/src/yggdrasil/tun_darwin.go +++ b/src/tuntap/tun_darwin.go @@ -1,6 +1,6 @@ // +build !mobile -package yggdrasil +package tuntap // The darwin platform specific tun parts @@ -16,9 +16,9 @@ import ( ) // Configures the "utun" adapter with the correct IPv6 address and MTU. -func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { +func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { if iftapmode { - tun.core.log.Warnln("TAP mode is not supported on this platform, defaulting to TUN") + tun.Log.Warnln("TAP mode is not supported on this platform, defaulting to TUN") } config := water.Config{DeviceType: water.TUN} iface, err := water.New(config) @@ -64,12 +64,12 @@ type ifreq struct { // Sets the IPv6 address of the utun adapter. On Darwin/macOS this is done using // a system socket and making direct syscalls to the kernel. -func (tun *tunAdapter) setupAddress(addr string) error { +func (tun *TunAdapter) setupAddress(addr string) error { var fd int var err error if fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, 0); err != nil { - tun.core.log.Printf("Create AF_SYSTEM socket failed: %v.", err) + tun.Log.Printf("Create AF_SYSTEM socket failed: %v.", err) return err } @@ -98,19 +98,19 @@ func (tun *tunAdapter) setupAddress(addr string) error { copy(ir.ifr_name[:], tun.iface.Name()) ir.ifru_mtu = uint32(tun.mtu) - tun.core.log.Infof("Interface name: %s", ar.ifra_name) - tun.core.log.Infof("Interface IPv6: %s", addr) - tun.core.log.Infof("Interface MTU: %d", ir.ifru_mtu) + tun.Log.Infof("Interface name: %s", ar.ifra_name) + tun.Log.Infof("Interface IPv6: %s", addr) + tun.Log.Infof("Interface MTU: %d", ir.ifru_mtu) if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(darwin_SIOCAIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 { err = errno - tun.core.log.Errorf("Error in darwin_SIOCAIFADDR_IN6: %v", errno) + tun.Log.Errorf("Error in darwin_SIOCAIFADDR_IN6: %v", errno) return err } if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 { err = errno - tun.core.log.Errorf("Error in SIOCSIFMTU: %v", errno) + tun.Log.Errorf("Error in SIOCSIFMTU: %v", errno) return err } diff --git a/src/yggdrasil/tun_dummy.go b/src/tuntap/tun_dummy.go similarity index 79% rename from src/yggdrasil/tun_dummy.go rename to src/tuntap/tun_dummy.go index 234ab1de..04e65257 100644 --- a/src/yggdrasil/tun_dummy.go +++ b/src/tuntap/tun_dummy.go @@ -1,19 +1,19 @@ // +build mobile -package yggdrasil +package tuntap // This is to catch unsupported platforms // If your platform supports tun devices, you could try configuring it manually // Creates the TUN/TAP adapter, if supported by the Water library. Note that // no guarantees are made at this point on an unsupported platform. -func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { +func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { tun.mtu = getSupportedMTU(mtu) return tun.setupAddress(addr) } // We don't know how to set the IPv6 address on an unknown platform, therefore // write about it to stdout and don't try to do anything further. -func (tun *tunAdapter) setupAddress(addr string) error { +func (tun *TunAdapter) setupAddress(addr string) error { return nil } diff --git a/src/yggdrasil/tun_linux.go b/src/tuntap/tun_linux.go similarity index 86% rename from src/yggdrasil/tun_linux.go rename to src/tuntap/tun_linux.go index 30ada235..7d228574 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} @@ -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) } @@ -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 78% rename from src/yggdrasil/tun_other.go rename to src/tuntap/tun_other.go index 07ec25fd..bb302d19 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 { - tun.core.log.Warnln("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr) +func (tun *TunAdapter) setupAddress(addr string) error { + tun.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 64% rename from src/yggdrasil/tun_windows.go rename to src/tuntap/tun_windows.go index 1c89a437..5a158b14 100644 --- a/src/yggdrasil/tun_windows.go +++ b/src/tuntap/tun_windows.go @@ -1,4 +1,4 @@ -package yggdrasil +package tuntap import ( "fmt" @@ -13,9 +13,9 @@ 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") + 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,41 +58,41 @@ 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) } // 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()), 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 } // 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()), 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 diff --git a/src/yggdrasil/adapter.go b/src/yggdrasil/adapter.go index 3ce80d2b..8fadb195 100644 --- a/src/yggdrasil/adapter.go +++ b/src/yggdrasil/adapter.go @@ -1,18 +1,47 @@ package yggdrasil -// Defines the minimum required struct members for an adapter type (this is -// now the base type for tunAdapter in tun.go) +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 { - core *Core - send chan<- []byte - recv <-chan []byte - reconfigure chan chan error + adapterImplementation + Core *Core + Config *config.NodeState + Log *log.Logger + Send chan<- []byte + Recv <-chan []byte + Reject <-chan RejectedPacket + Reconfigure chan chan 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) +// 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/admin.go b/src/yggdrasil/admin.go index a0854f22..a9595f8c 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,54 +168,54 @@ 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("getMulticastInterfaces", []string{}, func(in admin_info) (admin_info, error) { + }) + 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() { 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 }) @@ -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 { @@ -643,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/ckr.go b/src/yggdrasil/ckr.go index 03bc5718..dc4f1b90 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -55,25 +55,24 @@ func (c *cryptokey) init(core *Core) { // Configure the CKR routes - this must only ever be called from the router // goroutine, e.g. through router.doAdmin func (c *cryptokey) configure() error { - c.core.configMutex.RLock() - defer c.core.configMutex.RUnlock() + current, _ := c.core.config.Get() // Set enabled/disabled state - c.setEnabled(c.core.config.TunnelRouting.Enable) + c.setEnabled(current.TunnelRouting.Enable) // Clear out existing routes c.ipv6routes = make([]cryptokey_route, 0) c.ipv4routes = make([]cryptokey_route, 0) // Add IPv6 routes - for ipv6, pubkey := range c.core.config.TunnelRouting.IPv6Destinations { + for ipv6, pubkey := range current.TunnelRouting.IPv6Destinations { if err := c.addRoute(ipv6, pubkey); err != nil { return err } } // Add IPv4 routes - for ipv4, pubkey := range c.core.config.TunnelRouting.IPv4Destinations { + for ipv4, pubkey := range current.TunnelRouting.IPv4Destinations { if err := c.addRoute(ipv4, pubkey); err != nil { return err } @@ -85,7 +84,7 @@ func (c *cryptokey) configure() error { // Add IPv6 sources c.ipv6sources = make([]net.IPNet, 0) - for _, source := range c.core.config.TunnelRouting.IPv6Sources { + for _, source := range current.TunnelRouting.IPv6Sources { if err := c.addSourceSubnet(source); err != nil { return err } @@ -93,7 +92,7 @@ func (c *cryptokey) configure() error { // Add IPv4 sources c.ipv4sources = make([]net.IPNet, 0) - for _, source := range c.core.config.TunnelRouting.IPv4Sources { + for _, source := range current.TunnelRouting.IPv4Sources { if err := c.addSourceSubnet(source); err != nil { return err } diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 12ff14fc..037ef094 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -2,9 +2,9 @@ package yggdrasil import ( "encoding/hex" + "errors" "io/ioutil" "net" - "sync" "time" "github.com/gologme/log" @@ -12,26 +12,18 @@ 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 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 { // 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 @@ -43,7 +35,6 @@ type Core struct { dht dht admin admin searches searches - multicast multicast link link log *log.Logger } @@ -57,19 +48,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 } @@ -83,7 +76,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? @@ -96,20 +89,17 @@ func (c *Core) init() error { // be reconnected with. 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() + // the peers from the config - these could change! + 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) @@ -121,15 +111,13 @@ 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...") - c.configMutex.Lock() - c.configOld = c.config - c.config = *config - c.configMutex.Unlock() + c.config.Replace(*config) errors := 0 @@ -140,11 +128,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 { @@ -163,190 +149,219 @@ 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 } -// Starts up Yggdrasil using the provided 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. -func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { +// 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, +// 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 - if name := GetBuildName(); name != "unknown" { + c.config = config.NodeState{ + Current: *nc, + Previous: *nc, + } + + 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) } 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 { c.log.Errorln("Failed to start link interfaces") - return err + return nil, 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") - 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.multicast.start(); err != nil { - c.log.Errorln("Failed to start multicast interface") - return err - } - - if err := c.router.tun.start(); err != nil { - c.log.Errorln("Failed to start TUN/TAP") - return 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") - return nil + return &c.config, nil } -// Stops the Yggdrasil node. +// Stop shuts down the Yggdrasil node. func (c *Core) Stop() { c.log.Infoln("Stopping...") - c.router.tun.close() + if c.router.adapter != nil { + c.router.adapter.Close() + } c.admin.close() } -// Generates a new encryption keypair. The encryption keys are used to -// encrypt traffic and to derive the IPv6 address/subnet of the node. +// 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() } -// 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 { +// NodeID gets the node ID. +func (c *Core) NodeID() *crypto.NodeID { return crypto.GetNodeID(&c.boxPub) } -// Gets the tree ID. -func (c *Core) GetTreeID() *crypto.TreeID { +// TreeID gets the tree ID. +func (c *Core) TreeID() *crypto.TreeID { return crypto.GetTreeID(&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())[:]) +// 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 } -// 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)} } -// Gets the nodeinfo. -func (c *Core) GetNodeInfo() nodeinfoPayload { +// 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() } -// 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 +// 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 { - 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 } -// Adds an allowed public key. This allow peerings to be restricted only to -// keys that you have selected. +// 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) } - -// 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() -} - -// Gets the current TUN/TAP interface MTU. -func (c *Core) GetTUNIfMTU() int { - return c.router.tun.mtu -} diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 5427aca9..b081c92d 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -86,7 +86,7 @@ func (t *dht) init(c *Core) { e <- nil } }() - t.nodeID = *t.core.GetNodeID() + t.nodeID = *t.core.NodeID() t.peers = make(chan *dhtInfo, 1024) t.callbacks = make(map[dhtReqKey]dht_callbackInfo) t.reset() diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index bfec714b..de90fd94 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -23,8 +23,7 @@ type link struct { reconfigure chan chan error mutex sync.RWMutex // protects interfaces below interfaces map[linkInfo]*linkInterface - awdl awdl // AWDL interface support - tcp tcp // TCP interface support + tcp tcp // TCP interface support // TODO timeout (to remove from switch), read from config.ReadTimeout } @@ -68,26 +67,15 @@ func (l *link) init(c *Core) error { return err } - if err := l.awdl.init(l); err != nil { - c.log.Errorln("Failed to start AWDL interface") - return err - } - go func() { for { e := <-l.reconfigure tcpresponse := make(chan error) - awdlresponse := make(chan error) l.tcp.reconfigure <- tcpresponse if err := <-tcpresponse; err != nil { e <- err continue } - l.awdl.reconfigure <- awdlresponse - if err := <-awdlresponse; err != nil { - e <- err - continue - } e <- nil } }() diff --git a/src/yggdrasil/mobile.go b/src/yggdrasil/mobile.go deleted file mode 100644 index 81aa47f1..00000000 --- a/src/yggdrasil/mobile.go +++ /dev/null @@ -1,130 +0,0 @@ -// +build mobile - -package yggdrasil - -import ( - "encoding/hex" - "encoding/json" - "os" - "time" - - "github.com/gologme/log" - - hjson "github.com/hjson/hjson-go" - "github.com/mitchellh/mapstructure" - "github.com/yggdrasil-network/yggdrasil-go/src/config" - "github.com/yggdrasil-network/yggdrasil-go/src/util" -) - -// This file is meant to "plug the gap" for mobile support, as Gomobile will -// not create headers for Swift/Obj-C etc if they have complex (non-native) -// types. Therefore for iOS we will expose some nice simple functions. Note -// that in the case of iOS we handle reading/writing to/from TUN in Swift -// therefore we use the "dummy" TUN interface instead. - -func (c *Core) addStaticPeers(cfg *config.NodeConfig) { - if len(cfg.Peers) == 0 && len(cfg.InterfacePeers) == 0 { - return - } - for { - for _, peer := range cfg.Peers { - c.AddPeer(peer, "") - time.Sleep(time.Second) - } - for intf, intfpeers := range cfg.InterfacePeers { - for _, peer := range intfpeers { - c.AddPeer(peer, intf) - time.Sleep(time.Second) - } - } - time.Sleep(time.Minute) - } -} - -// Starts a node with a randomly generated config. -func (c *Core) StartAutoconfigure() error { - mobilelog := MobileLogger{} - logger := log.New(mobilelog, "", 0) - nc := config.GenerateConfig() - nc.IfName = "dummy" - nc.AdminListen = "tcp://localhost:9001" - nc.Peers = []string{} - if hostname, err := os.Hostname(); err == nil { - nc.NodeInfo = map[string]interface{}{"name": hostname} - } - if err := c.Start(nc, logger); err != nil { - return err - } - go c.addStaticPeers(nc) - return nil -} - -// Starts a node with the given JSON config. You can get JSON config (rather -// than HJSON) by using the GenerateConfigJSON() function. -func (c *Core) StartJSON(configjson []byte) error { - mobilelog := MobileLogger{} - logger := log.New(mobilelog, "", 0) - nc := config.GenerateConfig() - var dat map[string]interface{} - if err := hjson.Unmarshal(configjson, &dat); err != nil { - return err - } - if err := mapstructure.Decode(dat, &nc); err != nil { - return err - } - nc.IfName = "dummy" - if err := c.Start(nc, logger); err != nil { - return err - } - go c.addStaticPeers(nc) - return nil -} - -// Generates mobile-friendly configuration in JSON format. -func GenerateConfigJSON() []byte { - nc := config.GenerateConfig() - nc.IfName = "dummy" - if json, err := json.Marshal(nc); err == nil { - return json - } else { - return nil - } -} - -// Gets the node's IPv6 address. -func (c *Core) GetAddressString() string { - return c.GetAddress().String() -} - -// Gets the node's IPv6 subnet in CIDR notation. -func (c *Core) GetSubnetString() string { - return c.GetSubnet().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.tun.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.tun.send <- packet - return nil -} diff --git a/src/yggdrasil/nodeinfo.go b/src/yggdrasil/nodeinfo.go index 963a2fc2..89b8b89f 100644 --- a/src/yggdrasil/nodeinfo.go +++ b/src/yggdrasil/nodeinfo.go @@ -101,8 +101,8 @@ func (m *nodeinfo) setNodeInfo(given interface{}, privacy bool) error { m.myNodeInfoMutex.Lock() defer m.myNodeInfoMutex.Unlock() defaults := map[string]interface{}{ - "buildname": GetBuildName(), - "buildversion": GetBuildVersion(), + "buildname": BuildName(), + "buildversion": BuildVersion(), "buildplatform": runtime.GOOS, "buildarch": runtime.GOARCH, } diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index ce359368..06201f9b 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -44,42 +44,42 @@ func (ps *peers) init(c *Core) { // because the key is in the whitelist or because the whitelist is empty. func (ps *peers) isAllowedEncryptionPublicKey(box *crypto.BoxPubKey) bool { boxstr := hex.EncodeToString(box[:]) - ps.core.configMutex.RLock() - defer ps.core.configMutex.RUnlock() - for _, v := range ps.core.config.AllowedEncryptionPublicKeys { + ps.core.config.Mutex.RLock() + defer ps.core.config.Mutex.RUnlock() + for _, v := range ps.core.config.Current.AllowedEncryptionPublicKeys { if v == boxstr { return true } } - return len(ps.core.config.AllowedEncryptionPublicKeys) == 0 + return len(ps.core.config.Current.AllowedEncryptionPublicKeys) == 0 } // Adds a key to the whitelist. func (ps *peers) addAllowedEncryptionPublicKey(box string) { - ps.core.configMutex.RLock() - defer ps.core.configMutex.RUnlock() - ps.core.config.AllowedEncryptionPublicKeys = - append(ps.core.config.AllowedEncryptionPublicKeys, box) + ps.core.config.Mutex.RLock() + defer ps.core.config.Mutex.RUnlock() + ps.core.config.Current.AllowedEncryptionPublicKeys = + append(ps.core.config.Current.AllowedEncryptionPublicKeys, box) } // Removes a key from the whitelist. func (ps *peers) removeAllowedEncryptionPublicKey(box string) { - ps.core.configMutex.RLock() - defer ps.core.configMutex.RUnlock() - for k, v := range ps.core.config.AllowedEncryptionPublicKeys { + ps.core.config.Mutex.RLock() + defer ps.core.config.Mutex.RUnlock() + for k, v := range ps.core.config.Current.AllowedEncryptionPublicKeys { if v == box { - ps.core.config.AllowedEncryptionPublicKeys = - append(ps.core.config.AllowedEncryptionPublicKeys[:k], - ps.core.config.AllowedEncryptionPublicKeys[k+1:]...) + ps.core.config.Current.AllowedEncryptionPublicKeys = + append(ps.core.config.Current.AllowedEncryptionPublicKeys[:k], + ps.core.config.Current.AllowedEncryptionPublicKeys[k+1:]...) } } } // Gets the whitelist of allowed keys for incoming connections. func (ps *peers) getAllowedEncryptionPublicKeys() []string { - ps.core.configMutex.RLock() - defer ps.core.configMutex.RUnlock() - return ps.core.config.AllowedEncryptionPublicKeys + ps.core.config.Mutex.RLock() + defer ps.core.config.Mutex.RUnlock() + return ps.core.config.Current.AllowedEncryptionPublicKeys } // Atomically gets a map[switchPort]*peer of known peers. diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 1d4c0771..a3d3d68c 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,21 +20,18 @@ 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" "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" ) -// 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 @@ -44,23 +41,44 @@ 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 - recv chan<- []byte // place where the tun pulls received packets from - send <-chan []byte // place where the tun puts outgoing packets + 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 } -// Initializes the router struct, which includes setting up channels to/from the tun/tap. +// 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{} +} + +// 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) @@ -107,16 +125,20 @@ 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) - 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) + if r.adapter != nil { + r.adapter.Init(&r.core.config, r.core.log, send, recv, reject) + } } // Starts the mainLoop goroutine. @@ -126,7 +148,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. @@ -157,9 +179,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) } } } @@ -168,7 +189,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 @@ -313,18 +334,11 @@ func (r *router) sendPacket(bs []byte) { window = int(sinfo.getMTU()) } - // Create the Packet Too Big response - ptb := &icmp.PacketTooBig{ - MTU: int(sinfo.getMTU()), - Data: bs[:window], - } - - // Create the ICMPv6 response from it - icmpv6Buf, err := r.tun.icmpv6.create_icmpv6_tun( - bs[8:24], bs[24:40], - ipv6.ICMPTypePacketTooBig, 0, ptb) - if err == nil { - r.recv <- icmpv6Buf + // 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 @@ -335,7 +349,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 { @@ -398,7 +412,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{} @@ -432,7 +446,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 012af579..74255c09 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,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.tun.mtu) + if ss.core.router.adapter != nil { + sinfo.myMTU = uint16(ss.core.router.adapter.MTU()) + } now := time.Now() sinfo.time = now sinfo.mtuTime = now diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 490b15f3..0e164b9a 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -188,9 +188,7 @@ func (t *switchTable) init(core *Core) { now := time.Now() t.core = core t.reconfigure = make(chan chan error, 1) - t.core.configMutex.RLock() t.key = t.core.sigPub - t.core.configMutex.RUnlock() locator := switchLocator{root: t.key, tstamp: now.Unix()} peers := make(map[switchPort]peerInfo) t.data = switchData{locator: locator, peers: peers} diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 8acf9c17..4361ec82 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -36,14 +36,18 @@ 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 +// 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 } // Wrapper function to set additional options for specific connection types. @@ -64,7 +68,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,16 +80,16 @@ 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() { 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://" { @@ -103,7 +107,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() } @@ -115,9 +119,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 } @@ -129,7 +133,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 +142,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 +154,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 +162,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 +171,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 +196,7 @@ func (t *tcp) listener(l *tcpListener, listenaddr string) { return } go t.handler(sock, true, nil) - case <-l.stop: + case <-l.Stop: return } } diff --git a/src/yggdrasil/tun.go b/src/yggdrasil/tun.go deleted file mode 100644 index 465cbb1c..00000000 --- a/src/yggdrasil/tun.go +++ /dev/null @@ -1,266 +0,0 @@ -package yggdrasil - -// This manages the tun driver to send/recv packets to/from applications - -import ( - "bytes" - "errors" - "fmt" - "net" - "sync" - "time" - - "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/defaults" - "github.com/yggdrasil-network/yggdrasil-go/src/util" -) - -const tun_IPv6_HEADER_LENGTH = 40 -const tun_ETHER_HEADER_LENGTH = 14 - -// Represents a running TUN/TAP interface. -type tunAdapter struct { - Adapter - 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 -// defaults.GetDefaults(). -func getSupportedMTU(mtu int) int { - if mtu > defaults.GetDefaults().MaximumIfMTU { - return defaults.GetDefaults().MaximumIfMTU - } - return mtu -} - -// 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) - 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() - if updated { - tun.core.log.Warnln("Reconfiguring TUN/TAP is not supported yet") - e <- nil - } else { - e <- nil - } - } - }() -} - -// 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() - if ifname != "none" { - if err := tun.setup(ifname, iftapmode, addr, mtu); err != nil { - return err - } - } - if ifname == "none" || ifname == "dummy" { - return nil - } - 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()) }() - if iftapmode { - go func() { - for { - if _, ok := tun.icmpv6.peermacs[tun.core.router.addr]; ok { - break - } - request, err := tun.icmpv6.create_ndp_tap(tun.core.router.addr) - if err != nil { - panic(err) - } - if _, err := tun.iface.Write(request); err != nil { - panic(err) - } - time.Sleep(time.Second) - } - }() - } - return nil -} - -// 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 { - 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...") - } - 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...") - } - 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.create_ndp_tap(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.core.router.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 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 { - peermac = neighbor.mac - peerknown = true - sendndp(destAddr) - } else { - sendndp(tun.core.router.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 { - // panic(err) - 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.parse_packet(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() -}