diff --git a/build b/build index e463c852..a3a679f2 100755 --- a/build +++ b/build @@ -6,12 +6,13 @@ PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)} LDFLAGS="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER" -while getopts "udtc:l:" option +while getopts "udmtc:l:" option do case "${option}" in u) UPX=true;; d) DEBUG=true;; + m) MOBILE=true;; t) TABLES=true;; c) GCFLAGS="$GCFLAGS $OPTARG";; l) LDFLAGS="$LDFLAGS $OPTARG";; @@ -25,7 +26,9 @@ fi for CMD in `ls cmd/` ; do echo "Building: $CMD" - if [ $DEBUG ]; then + if [ $MOBILE ]; then + go build -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" -tags mobile -v ./cmd/$CMD + elif [ $DEBUG ]; then go build -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" -tags debug -v ./cmd/$CMD else go build -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" -v ./cmd/$CMD diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 2b6d2f04..43cf77a7 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -2,13 +2,11 @@ package main import ( "bytes" - "encoding/hex" "encoding/json" "flag" "fmt" "io/ioutil" "log" - "math/rand" "os" "os/signal" "regexp" @@ -23,7 +21,6 @@ import ( "github.com/mitchellh/mapstructure" "github.com/yggdrasil-network/yggdrasil-go/src/config" - "github.com/yggdrasil-network/yggdrasil-go/src/defaults" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) @@ -34,52 +31,10 @@ type node struct { core Core } -// Generates default configuration. This is used when outputting the -genconf -// parameter and also when using -autoconf. The isAutoconf flag is used to -// determine whether the operating system should select a free port by itself -// (which guarantees that there will not be a conflict with any other services) -// or whether to generate a random port number. The only side effect of setting -// isAutoconf is that the TCP and UDP ports will likely end up with different -// port numbers. -func generateConfig(isAutoconf bool) *nodeConfig { - // Create a new core. - core := Core{} - // Generate encryption keys. - bpub, bpriv := core.NewEncryptionKeys() - spub, spriv := core.NewSigningKeys() - // Create a node configuration and populate it. - cfg := nodeConfig{} - if isAutoconf { - cfg.Listen = "[::]:0" - } else { - r1 := rand.New(rand.NewSource(time.Now().UnixNano())) - cfg.Listen = fmt.Sprintf("[::]:%d", r1.Intn(65534-32768)+32768) - } - cfg.AdminListen = defaults.GetDefaults().DefaultAdminListen - cfg.EncryptionPublicKey = hex.EncodeToString(bpub[:]) - cfg.EncryptionPrivateKey = hex.EncodeToString(bpriv[:]) - cfg.SigningPublicKey = hex.EncodeToString(spub[:]) - cfg.SigningPrivateKey = hex.EncodeToString(spriv[:]) - cfg.Peers = []string{} - cfg.InterfacePeers = map[string][]string{} - cfg.AllowedEncryptionPublicKeys = []string{} - cfg.MulticastInterfaces = []string{".*"} - cfg.IfName = defaults.GetDefaults().DefaultIfName - cfg.IfMTU = defaults.GetDefaults().DefaultIfMTU - cfg.IfTAPMode = defaults.GetDefaults().DefaultIfTAPMode - cfg.SessionFirewall.Enable = false - cfg.SessionFirewall.AllowFromDirect = true - cfg.SessionFirewall.AllowFromRemote = true - cfg.SwitchOptions.MaxTotalQueueSize = yggdrasil.SwitchQueueTotalMinSize - cfg.NodeInfoPrivacy = false - - return &cfg -} - // Generates a new configuration and returns it in HJSON format. This is used // with -genconf. func doGenconf(isjson bool) string { - cfg := generateConfig(false) + cfg := config.GenerateConfig(false) var bs []byte var err error if isjson { @@ -114,19 +69,19 @@ func main() { case *autoconf: // Use an autoconf-generated config, this will give us random keys and // port numbers, and will use an automatically selected TUN/TAP interface. - cfg = generateConfig(true) + cfg = config.GenerateConfig(true) case *useconffile != "" || *useconf: // Use a configuration file. If -useconf, the configuration will be read // from stdin. If -useconffile, the configuration will be read from the // filesystem. - var config []byte + var configjson []byte var err error if *useconffile != "" { // Read the file from the filesystem - config, err = ioutil.ReadFile(*useconffile) + configjson, err = ioutil.ReadFile(*useconffile) } else { // Read the file from stdin. - config, err = ioutil.ReadAll(os.Stdin) + configjson, err = ioutil.ReadAll(os.Stdin) } if err != nil { panic(err) @@ -135,11 +90,11 @@ func main() { // throwing everywhere when it's converting things into UTF-16 for the hell // of it - remove it and decode back down into UTF-8. This is necessary // because hjson doesn't know what to do with UTF-16 and will panic - if bytes.Compare(config[0:2], []byte{0xFF, 0xFE}) == 0 || - bytes.Compare(config[0:2], []byte{0xFE, 0xFF}) == 0 { + if bytes.Compare(configjson[0:2], []byte{0xFF, 0xFE}) == 0 || + bytes.Compare(configjson[0:2], []byte{0xFE, 0xFF}) == 0 { utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM) decoder := utf.NewDecoder() - config, err = decoder.Bytes(config) + configjson, err = decoder.Bytes(configjson) if err != nil { panic(err) } @@ -148,9 +103,9 @@ func main() { // then parse the configuration we loaded above on top of it. The effect // of this is that any configuration item that is missing from the provided // configuration will use a sane default. - cfg = generateConfig(false) + cfg = config.GenerateConfig(false) var dat map[string]interface{} - if err := hjson.Unmarshal(config, &dat); err != nil { + if err := hjson.Unmarshal(configjson, &dat); err != nil { panic(err) } confJson, err := json.Marshal(dat) diff --git a/src/config/config.go b/src/config/config.go index 192f435f..192b94e5 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -1,5 +1,15 @@ package config +import ( + "encoding/hex" + "fmt" + "math/rand" + "time" + + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/defaults" +) + // NodeConfig defines all configuration values needed to run a signle yggdrasil node type NodeConfig struct { Listen string `comment:"Listen address for peer connections. Default is to listen for all\nTCP connections over IPv4 and IPv6 with a random port."` @@ -53,3 +63,45 @@ type TunnelRouting struct { type SwitchOptions struct { MaxTotalQueueSize uint64 `comment:"Maximum size of all switch queues combined (in bytes)."` } + +// Generates default configuration. This is used when outputting the -genconf +// parameter and also when using -autoconf. The isAutoconf flag is used to +// determine whether the operating system should select a free port by itself +// (which guarantees that there will not be a conflict with any other services) +// or whether to generate a random port number. The only side effect of setting +// isAutoconf is that the TCP and UDP ports will likely end up with different +// port numbers. +func GenerateConfig(isAutoconf bool) *NodeConfig { + // Create a new core. + //core := Core{} + // Generate encryption keys. + bpub, bpriv := crypto.NewBoxKeys() + spub, spriv := crypto.NewSigKeys() + // Create a node configuration and populate it. + cfg := NodeConfig{} + if isAutoconf { + cfg.Listen = "[::]:0" + } else { + r1 := rand.New(rand.NewSource(time.Now().UnixNano())) + cfg.Listen = fmt.Sprintf("[::]:%d", r1.Intn(65534-32768)+32768) + } + cfg.AdminListen = defaults.GetDefaults().DefaultAdminListen + cfg.EncryptionPublicKey = hex.EncodeToString(bpub[:]) + cfg.EncryptionPrivateKey = hex.EncodeToString(bpriv[:]) + cfg.SigningPublicKey = hex.EncodeToString(spub[:]) + cfg.SigningPrivateKey = hex.EncodeToString(spriv[:]) + cfg.Peers = []string{} + cfg.InterfacePeers = map[string][]string{} + cfg.AllowedEncryptionPublicKeys = []string{} + cfg.MulticastInterfaces = []string{".*"} + cfg.IfName = defaults.GetDefaults().DefaultIfName + cfg.IfMTU = defaults.GetDefaults().DefaultIfMTU + cfg.IfTAPMode = defaults.GetDefaults().DefaultIfTAPMode + cfg.SessionFirewall.Enable = false + cfg.SessionFirewall.AllowFromDirect = true + cfg.SessionFirewall.AllowFromRemote = true + cfg.SwitchOptions.MaxTotalQueueSize = 4 * 1024 * 1024 + cfg.NodeInfoPrivacy = false + + return &cfg +} diff --git a/src/yggdrasil/adapter.go b/src/yggdrasil/adapter.go index 4a432092..7fb6a19e 100644 --- a/src/yggdrasil/adapter.go +++ b/src/yggdrasil/adapter.go @@ -1,17 +1,8 @@ package yggdrasil -// Defines the minimum required functions for an adapter type. -type AdapterInterface interface { - init(core *Core, send chan<- []byte, recv <-chan []byte) - read() error - write() error - close() error -} - // Defines the minimum required struct members for an adapter type (this is // now the base type for tunAdapter in tun.go) type Adapter struct { - AdapterInterface core *Core send chan<- []byte recv <-chan []byte diff --git a/src/yggdrasil/awdl.go b/src/yggdrasil/awdl.go new file mode 100644 index 00000000..65e84cb5 --- /dev/null +++ b/src/yggdrasil/awdl.go @@ -0,0 +1,147 @@ +package yggdrasil + +import ( + "errors" + "fmt" + "sync" + "sync/atomic" + "time" + + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/util" +) + +type awdl struct { + core *Core + mutex sync.RWMutex // protects interfaces below + interfaces map[string]*awdlInterface +} + +type awdlInterface struct { + awdl *awdl + fromAWDL chan []byte + toAWDL chan []byte + shutdown chan bool + peer *peer +} + +func (l *awdl) init(c *Core) error { + l.core = c + l.mutex.Lock() + l.interfaces = make(map[string]*awdlInterface) + l.mutex.Unlock() + + return nil +} + +func (l *awdl) create(fromAWDL chan []byte, toAWDL chan []byte, boxPubKey *crypto.BoxPubKey, sigPubKey *crypto.SigPubKey, name string) (*awdlInterface, error) { + /* + myLinkPub, myLinkPriv := crypto.NewBoxKeys() + meta := version_getBaseMetadata() + meta.box = l.core.boxPub + meta.sig = l.core.sigPub + meta.link = *myLinkPub + metaBytes := meta.encode() + l.core.log.Println("toAWDL <- metaBytes") + toAWDL <- metaBytes + l.core.log.Println("metaBytes = <-fromAWDL") + metaBytes = <-fromAWDL + l.core.log.Println("version_metadata{}") + meta = version_metadata{} + if !meta.decode(metaBytes) || !meta.check() { + return nil, errors.New("Metadata decode failure") + } + base := version_getBaseMetadata() + if meta.ver > base.ver || meta.ver == base.ver && meta.minorVer > base.minorVer { + return nil, errors.New("Failed to connect to node: " + name + " version: " + fmt.Sprintf("%d.%d", meta.ver, meta.minorVer)) + } + shared := crypto.GetSharedKey(myLinkPriv, &meta.link) + */ + shared := crypto.GetSharedKey(&l.core.boxPriv, boxPubKey) + intf := awdlInterface{ + awdl: l, + fromAWDL: fromAWDL, + toAWDL: toAWDL, + shutdown: make(chan bool), + peer: l.core.peers.newPeer(boxPubKey, sigPubKey, shared, name), + //peer: l.core.peers.newPeer(&meta.box, &meta.sig, shared, name), + } + if intf.peer != nil { + l.mutex.Lock() + l.interfaces[name] = &intf + l.mutex.Unlock() + intf.peer.linkOut = make(chan []byte, 1) // protocol traffic + intf.peer.out = func(msg []byte) { + defer func() { recover() }() + intf.toAWDL <- msg + } // called by peer.sendPacket() + l.core.switchTable.idleIn <- intf.peer.port // notify switch that we're idle + intf.peer.close = func() { + close(intf.fromAWDL) + close(intf.toAWDL) + } + go intf.handler() + go intf.peer.linkLoop() + return &intf, nil + } + return nil, errors.New("l.core.peers.newPeer failed") +} + +func (l *awdl) getInterface(identity string) *awdlInterface { + l.mutex.RLock() + defer l.mutex.RUnlock() + if intf, ok := l.interfaces[identity]; ok { + return intf + } + return nil +} + +func (l *awdl) shutdown(identity string) error { + if intf, ok := l.interfaces[identity]; ok { + intf.shutdown <- true + l.core.peers.removePeer(intf.peer.port) + l.mutex.Lock() + delete(l.interfaces, identity) + l.mutex.Unlock() + return nil + } else { + return errors.New(fmt.Sprintf("Interface '%s' doesn't exist or already shutdown", identity)) + } +} + +func (ai *awdlInterface) handler() { + send := func(msg []byte) { + ai.toAWDL <- msg + atomic.AddUint64(&ai.peer.bytesSent, uint64(len(msg))) + util.PutBytes(msg) + } + for { + timerInterval := tcp_ping_interval + timer := time.NewTimer(timerInterval) + defer timer.Stop() + select { + case p := <-ai.peer.linkOut: + send(p) + continue + default: + } + timer.Stop() + select { + case <-timer.C: + default: + } + timer.Reset(timerInterval) + select { + case _ = <-timer.C: + send([]byte{}) + case p := <-ai.peer.linkOut: + send(p) + continue + case r := <-ai.fromAWDL: + ai.peer.handlePacket(r) + ai.awdl.core.switchTable.idleIn <- ai.peer.port + case <-ai.shutdown: + return + } + } +} diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index e38274fa..a1c2127d 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -35,6 +35,7 @@ type Core struct { multicast multicast nodeinfo nodeinfo tcp tcpInterface + awdl awdl log *log.Logger ifceExpr []*regexp.Regexp // the zone of link-local IPv6 peers must match this } @@ -132,6 +133,11 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { return err } + if err := c.awdl.init(c); err != nil { + c.log.Println("Failed to start AWDL interface") + return err + } + if nc.SwitchOptions.MaxTotalQueueSize >= SwitchQueueTotalMinSize { c.switchTable.queueTotalMaxSize = nc.SwitchOptions.MaxTotalQueueSize } diff --git a/src/yggdrasil/mobile.go b/src/yggdrasil/mobile.go new file mode 100644 index 00000000..300d132a --- /dev/null +++ b/src/yggdrasil/mobile.go @@ -0,0 +1,167 @@ +// +build mobile + +package yggdrasil + +import ( + "encoding/hex" + "encoding/json" + "errors" + "log" + "os" + "regexp" + + 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/crypto" + "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) StartAutoconfigure() error { + mobilelog := MobileLogger{} + logger := log.New(mobilelog, "", 0) + nc := config.GenerateConfig(true) + 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} + } + ifceExpr, err := regexp.Compile(".*") + if err == nil { + c.ifceExpr = append(c.ifceExpr, ifceExpr) + } + if err := c.Start(nc, logger); err != nil { + return err + } + return nil +} + +func (c *Core) StartJSON(configjson []byte) error { + mobilelog := MobileLogger{} + logger := log.New(mobilelog, "", 0) + nc := config.GenerateConfig(false) + 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" + for _, ll := range nc.MulticastInterfaces { + ifceExpr, err := regexp.Compile(ll) + if err != nil { + panic(err) + } + c.AddMulticastInterfaceExpr(ifceExpr) + } + if err := c.Start(nc, logger); err != nil { + return err + } + return nil +} + +func GenerateConfigJSON() []byte { + nc := config.GenerateConfig(false) + nc.IfName = "dummy" + if json, err := json.Marshal(nc); err == nil { + return json + } else { + return nil + } +} + +func (c *Core) GetAddressString() string { + return c.GetAddress().String() +} + +func (c *Core) GetSubnetString() string { + return c.GetSubnet().String() +} + +func (c *Core) RouterRecvPacket() ([]byte, error) { + packet := <-c.router.tun.recv + return packet, nil +} + +func (c *Core) RouterSendPacket(buf []byte) error { + packet := append(util.GetBytes(), buf[:]...) + c.router.tun.send <- packet + return nil +} + +func (c *Core) AWDLCreateInterface(boxPubKey string, sigPubKey string, name string) error { + fromAWDL := make(chan []byte, 32) + toAWDL := make(chan []byte, 32) + + var boxPub crypto.BoxPubKey + var sigPub crypto.SigPubKey + boxPubHex, err := hex.DecodeString(boxPubKey) + if err != nil { + c.log.Println(err) + return err + } + sigPubHex, err := hex.DecodeString(sigPubKey) + if err != nil { + c.log.Println(err) + return err + } + copy(boxPub[:], boxPubHex) + copy(sigPub[:], sigPubHex) + + if intf, err := c.awdl.create(fromAWDL, toAWDL, &boxPub, &sigPub, name); err == nil { + if intf != nil { + c.log.Println(err) + return err + } else { + c.log.Println("c.awdl.create didn't return an interface") + return errors.New("c.awdl.create didn't return an interface") + } + } else { + c.log.Println(err) + return err + } +} + +func (c *Core) AWDLCreateInterfaceFromContext(context []byte, name string) error { + if len(context) < crypto.BoxPubKeyLen+crypto.SigPubKeyLen { + return errors.New("Not enough bytes in context") + } + boxPubKey := hex.EncodeToString(context[:crypto.BoxPubKeyLen]) + sigPubKey := hex.EncodeToString(context[crypto.BoxPubKeyLen:]) + return c.AWDLCreateInterface(boxPubKey, sigPubKey, name) +} + +func (c *Core) AWDLShutdownInterface(name string) error { + return c.awdl.shutdown(name) +} + +func (c *Core) AWDLRecvPacket(identity string) ([]byte, error) { + if intf := c.awdl.getInterface(identity); intf != nil { + return <-intf.toAWDL, nil + } + return nil, errors.New("AWDLRecvPacket identity not known: " + identity) +} + +func (c *Core) AWDLSendPacket(identity string, buf []byte) error { + packet := append(util.GetBytes(), buf[:]...) + if intf := c.awdl.getInterface(identity); intf != nil { + intf.fromAWDL <- packet + return nil + } + return errors.New("AWDLSendPacket identity not known: " + identity) +} + +func (c *Core) AWDLConnectionContext() []byte { + var context []byte + context = append(context, c.boxPub[:]...) + context = append(context, c.sigPub[:]...) + return context +} diff --git a/src/yggdrasil/mobile_ios.go b/src/yggdrasil/mobile_ios.go new file mode 100644 index 00000000..4abc6e9f --- /dev/null +++ b/src/yggdrasil/mobile_ios.go @@ -0,0 +1,25 @@ +// +build mobile,darwin + +package yggdrasil + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation +#import +void Log(const char *text) { + NSString *nss = [NSString stringWithUTF8String:text]; + NSLog(@"%@", nss); +} +*/ +import "C" +import "unsafe" + +type MobileLogger struct { +} + +func (nsl MobileLogger) Write(p []byte) (n int, err error) { + p = append(p, 0) + cstr := (*C.char)(unsafe.Pointer(&p[0])) + C.Log(cstr) + return len(p), nil +} diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index a2b94b67..333561e5 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -217,6 +217,7 @@ func (p *peer) handlePacket(packet []byte) { default: util.PutBytes(packet) } + return } // Called to handle traffic or protocolTraffic packets. diff --git a/src/yggdrasil/tun.go b/src/yggdrasil/tun.go index 8ed53332..8c0f91d5 100644 --- a/src/yggdrasil/tun.go +++ b/src/yggdrasil/tun.go @@ -47,11 +47,13 @@ func (tun *tunAdapter) init(core *Core, send chan<- []byte, recv <-chan []byte) // Starts the setup process for the TUN/TAP adapter, and if successful, starts // the read/write goroutines to handle packets on that interface. func (tun *tunAdapter) start(ifname string, iftapmode bool, addr string, mtu int) error { - if ifname == "none" { - return nil + if ifname != "none" { + if err := tun.setup(ifname, iftapmode, addr, mtu); err != nil { + return err + } } - 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 diff --git a/src/yggdrasil/tun_darwin.go b/src/yggdrasil/tun_darwin.go index 943468e6..828c01ea 100644 --- a/src/yggdrasil/tun_darwin.go +++ b/src/yggdrasil/tun_darwin.go @@ -1,3 +1,5 @@ +// +build !mobile + package yggdrasil // The darwin platform specific tun parts diff --git a/src/yggdrasil/tun_dummy.go b/src/yggdrasil/tun_dummy.go new file mode 100644 index 00000000..234ab1de --- /dev/null +++ b/src/yggdrasil/tun_dummy.go @@ -0,0 +1,19 @@ +// +build mobile + +package yggdrasil + +// This is to catch unsupported platforms +// If your platform supports tun devices, you could try configuring it manually + +// Creates the TUN/TAP adapter, if supported by the Water library. Note that +// no guarantees are made at this point on an unsupported platform. +func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { + tun.mtu = getSupportedMTU(mtu) + return tun.setupAddress(addr) +} + +// We don't know how to set the IPv6 address on an unknown platform, therefore +// write about it to stdout and don't try to do anything further. +func (tun *tunAdapter) setupAddress(addr string) error { + return nil +} diff --git a/src/yggdrasil/tun_linux.go b/src/yggdrasil/tun_linux.go index 7a7c9cb7..8ccdd30b 100644 --- a/src/yggdrasil/tun_linux.go +++ b/src/yggdrasil/tun_linux.go @@ -1,3 +1,5 @@ +// +build !mobile + package yggdrasil // The linux platform specific tun parts diff --git a/src/yggdrasil/tun_other.go b/src/yggdrasil/tun_other.go index 625f9cd5..22058c11 100644 --- a/src/yggdrasil/tun_other.go +++ b/src/yggdrasil/tun_other.go @@ -1,4 +1,4 @@ -// +build !linux,!darwin,!windows,!openbsd,!freebsd,!netbsd +// +build !linux,!darwin,!windows,!openbsd,!freebsd,!netbsd,!mobile package yggdrasil