diff --git a/build b/build index 583bf610..c932cb18 100755 --- a/build +++ b/build @@ -33,15 +33,17 @@ fi if [ $IOS ]; then echo "Building framework for iOS" go get golang.org/x/mobile/bind - gomobile bind -target ios -tags mobile -o Yggdrasil.framework -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \ - github.com/yggdrasil-network/yggdrasil-extras/src/mobile \ - github.com/yggdrasil-network/yggdrasil-go/src/config + gomobile bind \ + -target ios -tags mobile -o Yggdrasil.xcframework \ + -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \ + ./src/mobile ./src/config elif [ $ANDROID ]; then echo "Building aar for Android" go get golang.org/x/mobile/bind - gomobile bind -target android -tags mobile -o yggdrasil.aar -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \ - github.com/yggdrasil-network/yggdrasil-extras/src/mobile \ - github.com/yggdrasil-network/yggdrasil-go/src/config + gomobile bind \ + -target android -tags mobile -o yggdrasil.aar \ + -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \ + ./src/mobile ./src/config else for CMD in yggdrasil yggdrasilctl ; do echo "Building: $CMD" diff --git a/src/mobile/mobile.go b/src/mobile/mobile.go new file mode 100644 index 00000000..ba7cfdf6 --- /dev/null +++ b/src/mobile/mobile.go @@ -0,0 +1,178 @@ +package mobile + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "net" + + "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/core" + "github.com/yggdrasil-network/yggdrasil-go/src/defaults" + "github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc" + "github.com/yggdrasil-network/yggdrasil-go/src/multicast" + "github.com/yggdrasil-network/yggdrasil-go/src/version" + + _ "golang.org/x/mobile/bind" +) + +// 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 core.Core + iprwc *ipv6rwc.ReadWriteCloser + config *config.NodeConfig + multicast multicast.Multicast + log MobileLogger +} + +// StartAutoconfigure starts a node with a randomly generated config +func (m *Yggdrasil) StartAutoconfigure() error { + return m.StartJSON([]byte("{}")) +} + +// 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") + m.config = defaults.GenerateConfig() + if err := json.Unmarshal(configjson, &m.config); err != nil { + return err + } + m.config.IfName = "none" + if err := m.core.Start(m.config, logger); err != nil { + logger.Errorln("An error occured starting Yggdrasil:", err) + return err + } + mtu := m.config.IfMTU + m.iprwc = ipv6rwc.NewReadWriteCloser(&m.core) + if m.iprwc.MaxMTU() < mtu { + mtu = m.iprwc.MaxMTU() + } + m.iprwc.SetMTU(mtu) + if len(m.config.MulticastInterfaces) > 0 { + if err := m.multicast.Init(&m.core, m.config, logger, nil); err != nil { + logger.Errorln("An error occurred initialising multicast:", err) + return err + } + if err := m.multicast.Start(); err != nil { + logger.Errorln("An error occurred starting multicast:", err) + return err + } + } + return nil +} + +// Send sends a packet to Yggdrasil. It should be a fully formed +// IPv6 packet +func (m *Yggdrasil) Send(p []byte) error { + if m.iprwc == nil { + return nil + } + _, _ = m.iprwc.Write(p) + return nil +} + +// Recv waits for and reads a packet coming from Yggdrasil. It +// will be a fully formed IPv6 packet +func (m *Yggdrasil) Recv() ([]byte, error) { + if m.iprwc == nil { + return nil, nil + } + var buf [65535]byte + n, _ := m.iprwc.Read(buf[:]) + return buf[:n], nil +} + +// Stop the mobile Yggdrasil instance +func (m *Yggdrasil) Stop() error { + logger := log.New(m.log, "", 0) + logger.EnableLevel("info") + logger.Infof("Stop the mobile Yggdrasil instance %s", "") + if err := m.multicast.Stop(); err != nil { + return err + } + m.core.Stop() + return nil +} + +// GenerateConfigJSON generates mobile-friendly configuration in JSON format +func GenerateConfigJSON() []byte { + nc := defaults.GenerateConfig() + nc.IfName = "none" + if json, err := json.Marshal(nc); err == nil { + return json + } + return nil +} + +// GetAddressString gets the node's IPv6 address +func (m *Yggdrasil) GetAddressString() string { + ip := m.core.Address() + return ip.String() +} + +// GetSubnetString gets the node's IPv6 subnet in CIDR notation +func (m *Yggdrasil) GetSubnetString() string { + subnet := m.core.Subnet() + return subnet.String() +} + +// GetPublicKeyString gets the node's public key in hex form +func (m *Yggdrasil) GetPublicKeyString() string { + return hex.EncodeToString(m.core.GetSelf().Key) +} + +// GetCoordsString gets the node's coordinates +func (m *Yggdrasil) GetCoordsString() string { + return fmt.Sprintf("%v", m.core.GetSelf().Coords) +} + +func (m *Yggdrasil) GetPeersJSON() (result string) { + peers := []struct { + core.Peer + IP string + }{} + for _, v := range m.core.GetPeers() { + a := address.AddrForKey(v.Key) + ip := net.IP(a[:]).String() + peers = append(peers, struct { + core.Peer + IP string + }{ + Peer: v, + IP: ip, + }) + } + if res, err := json.Marshal(peers); err == nil { + return string(res) + } else { + return "{}" + } +} + +func (m *Yggdrasil) GetDHTJSON() (result string) { + if res, err := json.Marshal(m.core.GetDHT()); err == nil { + return string(res) + } else { + return "{}" + } +} + +// GetMTU returns the configured node MTU. This must be called AFTER Start. +func (m *Yggdrasil) GetMTU() int { + return int(m.core.MTU()) +} + +func GetVersion() string { + return version.BuildVersion() +} diff --git a/src/mobile/mobile_android.go b/src/mobile/mobile_android.go new file mode 100644 index 00000000..f3206aca --- /dev/null +++ b/src/mobile/mobile_android.go @@ -0,0 +1,12 @@ +// +build android + +package mobile + +import "log" + +type MobileLogger struct{} + +func (nsl MobileLogger) Write(p []byte) (n int, err error) { + log.Println(string(p)) + return len(p), nil +} diff --git a/src/mobile/mobile_ios.go b/src/mobile/mobile_ios.go new file mode 100644 index 00000000..253bbc0b --- /dev/null +++ b/src/mobile/mobile_ios.go @@ -0,0 +1,27 @@ +// +build ios + +package mobile + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation +#import +void Log(const char *text) { + NSString *nss = [NSString stringWithUTF8String:text]; + NSLog(@"%@", nss); +} +*/ +import "C" +import ( + "unsafe" +) + +type MobileLogger struct { +} + +func (nsl MobileLogger) Write(p []byte) (n int, err error) { + p = append(p, 0) + cstr := (*C.char)(unsafe.Pointer(&p[0])) + C.Log(cstr) + return len(p), nil +} diff --git a/src/mobile/mobile_other.go b/src/mobile/mobile_other.go new file mode 100644 index 00000000..7b7ed74a --- /dev/null +++ b/src/mobile/mobile_other.go @@ -0,0 +1,13 @@ +// +build !android,!ios + +package mobile + +import "fmt" + +type MobileLogger struct { +} + +func (nsl MobileLogger) Write(p []byte) (n int, err error) { + fmt.Print(string(p)) + return len(p), nil +} diff --git a/src/mobile/mobile_test.go b/src/mobile/mobile_test.go new file mode 100644 index 00000000..19916407 --- /dev/null +++ b/src/mobile/mobile_test.go @@ -0,0 +1,16 @@ +package mobile + +import "testing" + +func TestStartYggdrasil(t *testing.T) { + ygg := &Yggdrasil{} + if err := ygg.StartAutoconfigure(); err != nil { + t.Fatalf("Failed to start Yggdrasil: %s", err) + } + t.Log("Address:", ygg.GetAddressString()) + t.Log("Subnet:", ygg.GetSubnetString()) + t.Log("Coords:", ygg.GetCoordsString()) + if err := ygg.Stop(); err != nil { + t.Fatalf("Failed to stop Yggdrasil: %s", err) + } +}