From 02a6b15e6b211d2ef5e9e72b219bc6802e3445df Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Fri, 20 Sep 2019 19:51:57 -0700 Subject: [PATCH] Moar Go --- go/native/GoGlue.cpp | 23 +++--- go/native/GoGlue.h | 6 +- go/pkg/zerotier/address.go | 54 ++++++++++++++ go/pkg/zerotier/errors.go | 5 +- go/pkg/zerotier/mac.go | 2 +- go/pkg/zerotier/nativetap.go | 137 +++++++++++++++++++++++++++++++++++ go/pkg/zerotier/network.go | 58 +++++++++++++-- go/pkg/zerotier/node.go | 26 +++---- go/pkg/zerotier/tap.go | 26 +++++++ 9 files changed, 297 insertions(+), 40 deletions(-) create mode 100644 go/pkg/zerotier/address.go create mode 100644 go/pkg/zerotier/nativetap.go create mode 100644 go/pkg/zerotier/tap.go diff --git a/go/native/GoGlue.cpp b/go/native/GoGlue.cpp index f3cc94dd4..b94b54578 100644 --- a/go/native/GoGlue.cpp +++ b/go/native/GoGlue.cpp @@ -575,24 +575,24 @@ extern "C" void ZT_GoTap_setEnabled(ZT_GoTap *tap,int enabled) reinterpret_cast(tap)->setEnabled(enabled != 0); } -extern "C" int ZT_GoTap_addIp(ZT_GoTap *tap,int af,const void *ip,int port) +extern "C" int ZT_GoTap_addIp(ZT_GoTap *tap,int af,const void *ip,int netmaskBits) { switch(af) { case AF_INET: - return (reinterpret_cast(tap)->addIp(InetAddress(ip,4,(unsigned int)port)) ? 1 : 0); + return (reinterpret_cast(tap)->addIp(InetAddress(ip,4,(unsigned int)netmaskBits)) ? 1 : 0); case AF_INET6: - return (reinterpret_cast(tap)->addIp(InetAddress(ip,16,(unsigned int)port)) ? 1 : 0); + return (reinterpret_cast(tap)->addIp(InetAddress(ip,16,(unsigned int)netmaskBits)) ? 1 : 0); } return 0; } -extern "C" int ZT_GoTap_removeIp(ZT_GoTap *tap,int af,const void *ip,int port) +extern "C" int ZT_GoTap_removeIp(ZT_GoTap *tap,int af,const void *ip,int netmaskBits) { switch(af) { case AF_INET: - return (reinterpret_cast(tap)->removeIp(InetAddress(ip,4,(unsigned int)port)) ? 1 : 0); + return (reinterpret_cast(tap)->removeIp(InetAddress(ip,4,(unsigned int)netmaskBits)) ? 1 : 0); case AF_INET6: - return (reinterpret_cast(tap)->removeIp(InetAddress(ip,16,(unsigned int)port)) ? 1 : 0); + return (reinterpret_cast(tap)->removeIp(InetAddress(ip,16,(unsigned int)netmaskBits)) ? 1 : 0); } return 0; } @@ -603,25 +603,22 @@ extern "C" int ZT_GoTap_ips(ZT_GoTap *tap,void *buf,unsigned int bufSize) unsigned int p = 0; uint8_t *const b = reinterpret_cast(buf); for(auto ip=ips.begin();ip!=ips.end();++ip) { - if ((p + 7) > bufSize) + if ((p + 6) > bufSize) break; const uint8_t *const ipd = reinterpret_cast(ip->rawIpData()); - const unsigned int port = ip->port(); if (ip->isV4()) { b[p++] = AF_INET; b[p++] = ipd[0]; b[p++] = ipd[1]; b[p++] = ipd[2]; b[p++] = ipd[3]; - b[p++] = (uint8_t)((port >> 8) & 0xff); - b[p++] = (uint8_t)(port & 0xff); + b[p++] = (uint8_t)ip->netmaskBits(); } else if (ip->isV6()) { - if ((p + 19) <= bufSize) { + if ((p + 18) <= bufSize) { b[p++] = AF_INET6; for(int j=0;j<16;++j) b[p++] = ipd[j]; - b[p++] = (uint8_t)((port >> 8) & 0xff); - b[p++] = (uint8_t)(port & 0xff); + b[p++] = (uint8_t)ip->netmaskBits(); } } } diff --git a/go/native/GoGlue.h b/go/native/GoGlue.h index 230503d92..7beaf43b8 100644 --- a/go/native/GoGlue.h +++ b/go/native/GoGlue.h @@ -62,14 +62,14 @@ void ZT_GoNode_leave(ZT_GoNode *gn,uint64_t nwid); void ZT_GoTap_setEnabled(ZT_GoTap *tap,int enabled); -int ZT_GoTap_addIp(ZT_GoTap *tap,int af,const void *ip,int port); +int ZT_GoTap_addIp(ZT_GoTap *tap,int af,const void *ip,int netmaskBits); -int ZT_GoTap_removeIp(ZT_GoTap *tap,int af,const void *ip,int port); +int ZT_GoTap_removeIp(ZT_GoTap *tap,int af,const void *ip,int netmaskBits); /* The buf buffer is filled with tuplies of: * uint8_t family * uint8_t ip[4 or 16] - * uint16_t port (big-endian byte order) + * uint8_t netmask bits (up to 32 for ipv4, 128 for ipv6) * * This function returns the number of such tuples in the result. * If the buffer isn't big enough results are incomplete. diff --git a/go/pkg/zerotier/address.go b/go/pkg/zerotier/address.go new file mode 100644 index 000000000..1fada8007 --- /dev/null +++ b/go/pkg/zerotier/address.go @@ -0,0 +1,54 @@ +/* + * Copyright (c)2019 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2023-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + +package zerotier + +import ( + "encoding/json" + "fmt" + "strconv" +) + +// Address represents a 40-bit short ZeroTier address +type Address uint64 + +// NewAddressFromString parses a 10-digit ZeroTier address +func NewAddressFromString(s string) (Address, error) { + if len(s) != 10 { + return Address(0), ErrInvalidZeroTierAddress + } + a, err := strconv.ParseUint(s, 16, 64) + return Address(a & 0xffffffffff), err +} + +// String returns this address's 10-digit hex identifier +func (a Address) String() string { + return fmt.Sprintf("%.10x", uint64(a)) +} + +// MarshalJSON marshals this Address as a string +func (a Address) MarshalJSON() ([]byte, error) { + return []byte(a.String()), nil +} + +// UnmarshalJSON unmarshals this Address from a string +func (a *Address) UnmarshalJSON(j []byte) error { + var s string + err := json.Unmarshal(j, &s) + if err != nil { + return err + } + tmp, err := NewAddressFromString(s) + *a = tmp + return err +} diff --git a/go/pkg/zerotier/errors.go b/go/pkg/zerotier/errors.go index 765c4ee20..79b12f752 100644 --- a/go/pkg/zerotier/errors.go +++ b/go/pkg/zerotier/errors.go @@ -20,6 +20,7 @@ func (e Err) Error() string { return (string)(e) } // Simple ZeroTier Errors const ( - ErrInvalidMACAddress Err = "invalid MAC address" - ErrInvalidParameter Err = "invalid parameter" + ErrInvalidMACAddress Err = "invalid MAC address" + ErrInvalidZeroTierAddress Err = "invalid ZeroTier address" + ErrInvalidParameter Err = "invalid parameter" ) diff --git a/go/pkg/zerotier/mac.go b/go/pkg/zerotier/mac.go index ac40d240c..0f6cd8bef 100644 --- a/go/pkg/zerotier/mac.go +++ b/go/pkg/zerotier/mac.go @@ -43,7 +43,7 @@ func NewMACFromString(s string) (MAC, error) { // String returns this MAC address in canonical human-readable form func (m MAC) String() string { - return fmt.Sprintf("%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", (m>>40)&0xff, (m>>32)&0xff, (m>>24)&0xff, (m>>16)&0xff, (m>>8)&0xff, m&0xff) + return fmt.Sprintf("%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", (uint64(m)>>40)&0xff, (uint64(m)>>32)&0xff, (uint64(m)>>24)&0xff, (uint64(m)>>16)&0xff, (uint64(m)>>8)&0xff, uint64(m)&0xff) } // MarshalJSON marshals this MAC as a string diff --git a/go/pkg/zerotier/nativetap.go b/go/pkg/zerotier/nativetap.go new file mode 100644 index 000000000..d71086331 --- /dev/null +++ b/go/pkg/zerotier/nativetap.go @@ -0,0 +1,137 @@ +/* + * Copyright (c)2019 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2023-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + +package zerotier + +import ( + "fmt" + "net" + "sync/atomic" + "unsafe" +) + +//#cgo CFLAGS: -O3 +//#define ZT_CGO 1 +//#include +//#include +//#include +//#include "../../native/GoGlue.h" +import "C" + +// nativeTap is a Tap implementation that wraps a native C++ interface to a system tun/tap device +type nativeTap struct { + tap unsafe.Pointer + networkStatus uint32 + enabled uint32 +} + +// SetEnabled sets this tap's enabled state +func (t *nativeTap) SetEnabled(enabled bool) { + if enabled && atomic.SwapUint32(&t.enabled, 1) == 0 { + C.ZT_GoTap_setEnabled(t.tap, 1) + } else if !enabled && atomic.SwapUint32(&t.enabled, 0) == 1 { + C.ZT_GoTap_setEnabled(t.tap, 0) + } +} + +// Enabled returns true if this tap is currently processing packets +func (t *nativeTap) Enabled() bool { + return atomic.LoadUint32(&t.enabled) != 0 +} + +// AddIP adds an IP address (with netmask) to this tap +func (t *nativeTap) AddIP(ip net.IPNet) error { + bits, _ := ip.Mask.Size() + if len(ip.IP) == 16 { + if bits > 128 || bits < 0 { + return ErrInvalidParameter + } + C.ZT_GoTap_addIp(t.tap, afInet6, unsafe.Pointer(&ip.IP[0]), C.int(bits)) + } else if len(ip.IP) == 4 { + if bits > 32 || bits < 0 { + return ErrInvalidParameter + } + C.ZT_GoTap_addIp(t.tap, afInet, unsafe.Pointer(&ip.IP[0]), C.int(bits)) + } + return ErrInvalidParameter +} + +// RemoveIP removes this IP address (with netmask) from this tap +func (t *nativeTap) RemoveIP(ip net.IPNet) error { + bits, _ := ip.Mask.Size() + if len(ip.IP) == 16 { + if bits > 128 || bits < 0 { + return ErrInvalidParameter + } + C.ZT_GoTap_removeIp(t.tap, afInet6, unsafe.Pointer(&ip.IP[0]), C.int(bits)) + return nil + } + if len(ip.IP) == 4 { + if bits > 32 || bits < 0 { + return ErrInvalidParameter + } + C.ZT_GoTap_removeIp(t.tap, afInet, unsafe.Pointer(&ip.IP[0]), C.int(bits)) + return nil + } + return ErrInvalidParameter +} + +// IPs returns IPs currently assigned to this tap (including externally or system-assigned IPs) +func (t *nativeTap) IPs() (ips []net.IPNet, err error) { + defer func() { + e := recover() + if e != nil { + err = fmt.Errorf("%v", e) + } + }() + var ipbuf [16384]byte + count := int(C.ZT_GoTap_ips(t.tap, unsafe.Pointer(&ipbuf[0]), 16384)) + ipptr := 0 + for i := 0; i < count; i++ { + af := ipbuf[ipptr] + ipptr++ + switch af { + case afInet: + var ip [4]byte + for j := 0; j < 4; j++ { + ip[j] = ipbuf[ipptr] + ipptr++ + } + bits := ipbuf[ipptr] + ipptr++ + ips = append(ips, net.IPNet{IP: net.IP(ip[:]), Mask: net.CIDRMask(int(bits), 32)}) + case afInet6: + var ip [16]byte + for j := 0; j < 16; j++ { + ip[j] = ipbuf[ipptr] + ipptr++ + } + bits := ipbuf[ipptr] + ipptr++ + ips = append(ips, net.IPNet{IP: net.IP(ip[:]), Mask: net.CIDRMask(int(bits), 128)}) + } + } + return +} + +// DeviceName gets this tap's OS-specific device name +func (t *nativeTap) DeviceName() string { + var dn [256]byte + C.ZT_GoTap_deviceName(t.tap, (*C.char)(unsafe.Pointer(&dn[0]))) + for i, b := range dn { + if b == 0 { + return string(dn[0:i]) + } + } + return "" +} diff --git a/go/pkg/zerotier/network.go b/go/pkg/zerotier/network.go index 28e233c06..3d30c5040 100644 --- a/go/pkg/zerotier/network.go +++ b/go/pkg/zerotier/network.go @@ -14,15 +14,52 @@ package zerotier import ( + "encoding/json" + "fmt" "net" - "sync/atomic" + "strconv" + "sync" "time" ) +// NetworkID is a network's 64-bit unique ID +type NetworkID uint64 + +// NewNetworkIDFromString parses a network ID in string form +func NewNetworkIDFromString(s string) (NetworkID, error) { + if len(s) != 16 { + return NetworkID(0), ErrInvalidZeroTierAddress + } + n, err := strconv.ParseUint(s, 16, 64) + return NetworkID(n), err +} + +// String returns this network ID's 16-digit hex identifier +func (n NetworkID) String() string { + return fmt.Sprintf("%.16x", uint64(n)) +} + +// MarshalJSON marshals this NetworkID as a string +func (n NetworkID) MarshalJSON() ([]byte, error) { + return []byte(n.String()), nil +} + +// UnmarshalJSON unmarshals this NetworkID from a string +func (n *NetworkID) UnmarshalJSON(j []byte) error { + var s string + err := json.Unmarshal(j, &s) + if err != nil { + return err + } + tmp, err := NewNetworkIDFromString(s) + *n = tmp + return err +} + // NetworkConfig represents the network's current state type NetworkConfig struct { // ID is this network's 64-bit globally unique identifier - ID uint64 + ID NetworkID // MAC is the Ethernet MAC address of this device on this network MAC MAC @@ -57,8 +94,8 @@ type NetworkConfig struct { // MulticastSubscriptions are this device's current multicast subscriptions MulticastSubscriptions []MulticastGroup - // PortType is a human-readable description of this port's implementation type or name - PortType string + // PortDeviceType is a human-readable description of this port's implementation type or name + PortDeviceType string // PortDeviceName is the OS-specific device name (e.g. tun0 or feth1856) for this network's virtual port PortDeviceName string @@ -69,6 +106,15 @@ type NetworkConfig struct { // Network is a currently joined network type Network struct { - config atomic.Value - tap atomic.Value + config NetworkConfig + configLock sync.RWMutex + tap *Tap + tapLock sync.Mutex +} + +// Config returns a copy of this network's current configuration +func (n *Network) Config() NetworkConfig { + n.configLock.RLock() + defer n.configLock.RUnlock() + return n.config } diff --git a/go/pkg/zerotier/node.go b/go/pkg/zerotier/node.go index 298df7b23..4ad2bb65c 100644 --- a/go/pkg/zerotier/node.go +++ b/go/pkg/zerotier/node.go @@ -13,6 +13,14 @@ package zerotier +import ( + "net" + "runtime" + "sync" + "sync/atomic" + "unsafe" +) + //#cgo CFLAGS: -O3 //#cgo LDFLAGS: ${SRCDIR}/../../../build/node/libzt_core.a ${SRCDIR}/../../../build/go/native/libzt_go_native.a -lc++ //#define ZT_CGO 1 @@ -27,13 +35,6 @@ package zerotier //#define ZEROTIER_ONE_VERSION_BUILD 255 //#endif import "C" -import ( - "net" - "runtime" - "sync" - "sync/atomic" - "unsafe" -) const ( // CoreVersionMajor is the major version of the ZeroTier core @@ -49,19 +50,13 @@ const ( CoreVersionBuild int = C.ZEROTIER_ONE_VERSION_BUILD ) -// Tap is an instance of an EthernetTap object -type Tap struct { - tap *C.ZT_GoTap - networkStatus uint32 -} - // Node is an instance of a ZeroTier node type Node struct { gn *C.ZT_GoNode zn *C.ZT_Node - taps map[uint64]*Tap - tapsLock sync.RWMutex + networks map[uint64]*Network + networksLock sync.RWMutex online uint32 running uint32 @@ -70,6 +65,7 @@ type Node struct { // NewNode creates and initializes a new instance of the ZeroTier node service func NewNode() *Node { n := new(Node) + n.networks = make(map[uint64]*Network) gnRawAddr := uintptr(unsafe.Pointer(n.gn)) nodesByUserPtrLock.Lock() diff --git a/go/pkg/zerotier/tap.go b/go/pkg/zerotier/tap.go new file mode 100644 index 000000000..c0dbb8c1b --- /dev/null +++ b/go/pkg/zerotier/tap.go @@ -0,0 +1,26 @@ +/* + * Copyright (c)2019 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2023-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + +package zerotier + +import "net" + +// Tap represents an Ethernet tap connecting a virtual network to a device or something else "real" +type Tap interface { + SetEnabled(enabled bool) + Enabled() bool + AddIP(ip net.IPNet) error + RemoveIP(ip net.IPNet) error + IPs() ([]net.IPNet, error) + DeviceName() string +}