Browse Source

Network config stuff in Go

Adam Ierymenko 5 years ago
parent
commit
bcb9df9cdf
4 changed files with 453 additions and 130 deletions
  1. 188 3
      go/pkg/zerotier/misc.go
  2. 140 21
      go/pkg/zerotier/network.go
  3. 111 105
      go/pkg/zerotier/node.go
  4. 14 1
      go/pkg/zerotier/route.go

+ 188 - 3
go/pkg/zerotier/misc.go

@@ -15,6 +15,7 @@ package zerotier
 
 
 import (
 import (
 	"encoding/base32"
 	"encoding/base32"
+	"encoding/binary"
 	"net"
 	"net"
 	"time"
 	"time"
 	"unsafe"
 	"unsafe"
@@ -27,10 +28,194 @@ func TimeMs() int64 { return int64(time.Now().UnixNano()) / int64(1000000) }
 
 
 // ipNetToKey creates a key that can be used in a map[] from a net.IPNet
 // ipNetToKey creates a key that can be used in a map[] from a net.IPNet
 func ipNetToKey(ipn *net.IPNet) (k [3]uint64) {
 func ipNetToKey(ipn *net.IPNet) (k [3]uint64) {
-	if len(ipn.IP) > 0 {
-		copy(((*[16]byte)(unsafe.Pointer(&k[0])))[:], ipn.IP)
-	}
+	copy(((*[16]byte)(unsafe.Pointer(&k[0])))[:], ipn.IP)
 	ones, bits := ipn.Mask.Size()
 	ones, bits := ipn.Mask.Size()
 	k[2] = (uint64(ones) << 32) | uint64(bits)
 	k[2] = (uint64(ones) << 32) | uint64(bits)
 	return
 	return
 }
 }
+
+func allZero(b []byte) bool {
+	for _, bb := range b {
+		if bb != 0 {
+			return false
+		}
+	}
+	return true
+}
+
+// The ipClassify code below is based on and should produce identical results to
+// InetAddress::ipScope() in the C++ code.
+/*
+InetAddress::IpScope InetAddress::ipScope() const
+{
+	switch(ss_family) {
+
+		case AF_INET: {
+			const uint32_t ip = Utils::ntoh((uint32_t)reinterpret_cast<const struct sockaddr_in *>(this)->sin_addr.s_addr);
+			switch(ip >> 24) {
+				case 0x00: return IP_SCOPE_NONE;                                   // 0.0.0.0/8 (reserved, never used)
+				case 0x06: return IP_SCOPE_PSEUDOPRIVATE;                          // 6.0.0.0/8 (US Army)
+				case 0x0a: return IP_SCOPE_PRIVATE;                                // 10.0.0.0/8
+				case 0x0b: return IP_SCOPE_PSEUDOPRIVATE;                          // 11.0.0.0/8 (US DoD)
+				case 0x15: return IP_SCOPE_PSEUDOPRIVATE;                          // 21.0.0.0/8 (US DDN-RVN)
+				case 0x16: return IP_SCOPE_PSEUDOPRIVATE;                          // 22.0.0.0/8 (US DISA)
+				case 0x19: return IP_SCOPE_PSEUDOPRIVATE;                          // 25.0.0.0/8 (UK Ministry of Defense)
+				case 0x1a: return IP_SCOPE_PSEUDOPRIVATE;                          // 26.0.0.0/8 (US DISA)
+				case 0x1c: return IP_SCOPE_PSEUDOPRIVATE;                          // 28.0.0.0/8 (US DSI-North)
+				case 0x1d: return IP_SCOPE_PSEUDOPRIVATE;                          // 29.0.0.0/8 (US DISA)
+				case 0x1e: return IP_SCOPE_PSEUDOPRIVATE;                          // 30.0.0.0/8 (US DISA)
+				case 0x33: return IP_SCOPE_PSEUDOPRIVATE;                          // 51.0.0.0/8 (UK Department of Social Security)
+				case 0x37: return IP_SCOPE_PSEUDOPRIVATE;                          // 55.0.0.0/8 (US DoD)
+				case 0x38: return IP_SCOPE_PSEUDOPRIVATE;                          // 56.0.0.0/8 (US Postal Service)
+				case 0x64:
+					if ((ip & 0xffc00000) == 0x64400000) return IP_SCOPE_PRIVATE;    // 100.64.0.0/10
+					break;
+				case 0x7f: return IP_SCOPE_LOOPBACK;                               // 127.0.0.0/8
+				case 0xa9:
+					if ((ip & 0xffff0000) == 0xa9fe0000) return IP_SCOPE_LINK_LOCAL; // 169.254.0.0/16
+					break;
+				case 0xac:
+					if ((ip & 0xfff00000) == 0xac100000) return IP_SCOPE_PRIVATE;    // 172.16.0.0/12
+					break;
+				case 0xc0:
+					if ((ip & 0xffff0000) == 0xc0a80000) return IP_SCOPE_PRIVATE;    // 192.168.0.0/16
+					break;
+				case 0xff: return IP_SCOPE_NONE;                                   // 255.0.0.0/8 (broadcast, or unused/unusable)
+			}
+			switch(ip >> 28) {
+				case 0xe: return IP_SCOPE_MULTICAST;                               // 224.0.0.0/4
+				case 0xf: return IP_SCOPE_PSEUDOPRIVATE;                           // 240.0.0.0/4 ("reserved," usually unusable)
+			}
+			return IP_SCOPE_GLOBAL;
+		}	break;
+
+		case AF_INET6: {
+			const unsigned char *ip = reinterpret_cast<const unsigned char *>(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr);
+			if ((ip[0] & 0xf0) == 0xf0) {
+				if (ip[0] == 0xff) return IP_SCOPE_MULTICAST;                      // ff00::/8
+				if ((ip[0] == 0xfe)&&((ip[1] & 0xc0) == 0x80)) {
+					unsigned int k = 2;
+					while ((!ip[k])&&(k < 15)) ++k;
+					if ((k == 15)&&(ip[15] == 0x01))
+						return IP_SCOPE_LOOPBACK;                                      // fe80::1/128
+					else return IP_SCOPE_LINK_LOCAL;                                 // fe80::/10
+				}
+				if ((ip[0] & 0xfe) == 0xfc) return IP_SCOPE_PRIVATE;               // fc00::/7
+			}
+			unsigned int k = 0;
+			while ((!ip[k])&&(k < 15)) ++k;
+			if (k == 15) { // all 0's except last byte
+				if (ip[15] == 0x01) return IP_SCOPE_LOOPBACK;                      // ::1/128
+				if (ip[15] == 0x00) return IP_SCOPE_NONE;                          // ::/128
+			}
+			return IP_SCOPE_GLOBAL;
+		}	break;
+
+	}
+
+	return IP_SCOPE_NONE;
+}
+*/
+
+const (
+	ipClassificationNone          = -1
+	ipClassificationLoopback      = 0
+	ipClassificationPseudoprivate = 1
+	ipClassificationPrivate       = 2
+	ipClassificationLinkLocal     = 3
+	ipClassificationMulticast     = 4
+	ipClassificationGlobal        = 5
+)
+
+var ipv4PseudoprivatePrefixes = []byte{
+	0x06, // 6.0.0.0/8 (US Army)
+	0x0b, // 11.0.0.0/8 (US DoD)
+	0x15, // 21.0.0.0/8 (US DDN-RVN)
+	0x16, // 22.0.0.0/8 (US DISA)
+	0x19, // 25.0.0.0/8 (UK Ministry of Defense)
+	0x1a, // 26.0.0.0/8 (US DISA)
+	0x1c, // 28.0.0.0/8 (US DSI-North)
+	0x1d, // 29.0.0.0/8 (US DISA)
+	0x1e, // 30.0.0.0/8 (US DISA)
+	0x33, // 51.0.0.0/8 (UK Department of Social Security)
+	0x37, // 55.0.0.0/8 (US DoD)
+	0x38, // 56.0.0.0/8 (US Postal Service)
+}
+
+// ipClassify determines the official or in a few cases unofficial role of an IP address
+func ipClassify(ip net.IP) int {
+	if len(ip) == 16 {
+		ip4 := ip.To4()
+		if len(ip4) == 4 {
+			ip = ip4
+		}
+	}
+	if len(ip) == 4 {
+		ip4FirstByte := ip[0]
+		for _, b := range ipv4PseudoprivatePrefixes {
+			if ip4FirstByte == b {
+				return ipClassificationPseudoprivate
+			}
+		}
+		ip4 := binary.BigEndian.Uint32(ip)
+		switch ip4FirstByte {
+		case 0x0a: // 10.0.0.0/8
+			return ipClassificationPrivate
+		case 0x64: // 100.64.0.0/10
+			if (ip4 & 0xffc00000) == 0x64400000 {
+				return ipClassificationPrivate
+			}
+		case 0x7f: // 127.0.0.1/8
+			return ipClassificationLoopback
+		case 0xa9: // 169.254.0.0/16
+			if (ip4 & 0xffff0000) == 0xa9fe0000 {
+				return ipClassificationLinkLocal
+			}
+		case 0xac: // 172.16.0.0/12
+			if (ip4 & 0xfff00000) == 0xac100000 {
+				return ipClassificationPrivate
+			}
+		case 0xc0: // 192.168.0.0/16
+			if (ip4 & 0xffff0000) == 0xc0a80000 {
+				return ipClassificationPrivate
+			}
+		}
+		switch ip4 >> 28 {
+		case 0xe: // 224.0.0.0/4
+			return ipClassificationMulticast
+		case 0xf: // 240.0.0.0/4 ("reserved," usually unusable)
+			return ipClassificationNone
+		}
+		return ipClassificationGlobal
+	}
+
+	if len(ip) == 16 {
+		if (ip[0] & 0xf0) == 0xf0 {
+			if ip[0] == 0xff { // ff00::/8
+				return ipClassificationMulticast
+			}
+			if ip[0] == 0xfe && (ip[1]&0xc0) == 0x80 {
+				if allZero(ip[2:15]) {
+					if ip[15] == 0x01 { // fe80::1/128
+						return ipClassificationLoopback
+					}
+					return ipClassificationLinkLocal
+				}
+			}
+			if (ip[0] & 0xfe) == 0xfc { // fc00::/7
+				return ipClassificationPrivate
+			}
+		}
+		if allZero(ip[0:15]) {
+			if ip[15] == 0x01 { // ::1/128
+				return ipClassificationLoopback
+			}
+			if ip[15] == 0x00 { // ::/128
+				return ipClassificationNone
+			}
+		}
+		return ipClassificationGlobal
+	}
+
+	return ipClassificationNone
+}

+ 140 - 21
go/pkg/zerotier/network.go

@@ -40,7 +40,7 @@ func (n NetworkID) String() string {
 
 
 // MarshalJSON marshals this NetworkID as a string
 // MarshalJSON marshals this NetworkID as a string
 func (n NetworkID) MarshalJSON() ([]byte, error) {
 func (n NetworkID) MarshalJSON() ([]byte, error) {
-	return []byte(n.String()), nil
+	return []byte("\"" + n.String() + "\""), nil
 }
 }
 
 
 // UnmarshalJSON unmarshals this NetworkID from a string
 // UnmarshalJSON unmarshals this NetworkID from a string
@@ -75,13 +75,13 @@ type NetworkConfig struct {
 	// MTU is the Ethernet MTU for this network
 	// MTU is the Ethernet MTU for this network
 	MTU int
 	MTU int
 
 
-	// CanBridge is true if this network is allowed to bridge in other devices with different Ethernet addresses
+	// Bridge is true if this network is allowed to bridge in other devices with different Ethernet addresses
 	Bridge bool
 	Bridge bool
 
 
 	// BroadcastEnabled is true if the broadcast (ff:ff:ff:ff:ff:ff) address works (excluding IPv4 ARP which is handled via a special path)
 	// BroadcastEnabled is true if the broadcast (ff:ff:ff:ff:ff:ff) address works (excluding IPv4 ARP which is handled via a special path)
 	BroadcastEnabled bool
 	BroadcastEnabled bool
 
 
-	// Network configuration revision number according to network controller
+	// NetconfRevision is the revision number reported by the controller
 	NetconfRevision uint64
 	NetconfRevision uint64
 
 
 	// AssignedAddresses are static IPs assigned by the network controller to this device
 	// AssignedAddresses are static IPs assigned by the network controller to this device
@@ -91,13 +91,50 @@ type NetworkConfig struct {
 	Routes []Route
 	Routes []Route
 }
 }
 
 
+// NetworkLocalSettings is settings for this network that can be changed locally
+type NetworkLocalSettings struct {
+	// AllowManagedIPs determines whether managed IP assignment is allowed
+	AllowManagedIPs bool
+
+	// AllowGlobalIPs determines if managed IPs that overlap with public Internet addresses are allowed
+	AllowGlobalIPs bool
+
+	// AllowManagedRoutes determines whether managed routes can be set
+	AllowManagedRoutes bool
+
+	// AllowGlobalRoutes determines if managed routes can overlap with public Internet addresses
+	AllowGlobalRoutes bool
+
+	// AllowDefaultRouteOverride determines if the default (0.0.0.0 or ::0) route on the system can be overridden ("full tunnel" mode)
+	AllowDefaultRouteOverride bool
+}
+
 // Network is a currently joined network
 // Network is a currently joined network
 type Network struct {
 type Network struct {
 	id         NetworkID
 	id         NetworkID
-	config     NetworkConfig
 	tap        Tap
 	tap        Tap
+	config     NetworkConfig
+	settings   NetworkLocalSettings // locked by configLock
 	configLock sync.RWMutex
 	configLock sync.RWMutex
-	tapLock    sync.RWMutex
+}
+
+// NewNetwork creates a new network with default settings
+func NewNetwork(id NetworkID, t Tap) (*Network, error) {
+	return &Network{
+		id:  id,
+		tap: t,
+		config: NetworkConfig{
+			ID:     id,
+			Status: NetworkStatusRequestConfiguration,
+		},
+		settings: NetworkLocalSettings{
+			AllowManagedIPs:           true,
+			AllowGlobalIPs:            false,
+			AllowManagedRoutes:        true,
+			AllowGlobalRoutes:         false,
+			AllowDefaultRouteOverride: false,
+		},
+	}, nil
 }
 }
 
 
 // ID gets this network's unique ID
 // ID gets this network's unique ID
@@ -111,36 +148,84 @@ func (n *Network) Config() NetworkConfig {
 }
 }
 
 
 // Tap gets this network's tap device
 // Tap gets this network's tap device
-func (n *Network) Tap() Tap {
-	n.tapLock.RLock()
-	defer n.tapLock.RUnlock()
-	return n.tap
+func (n *Network) Tap() Tap { return n.tap }
+
+// SetLocalSettings modifies this network's local settings
+func (n *Network) SetLocalSettings(ls *NetworkLocalSettings) { n.updateConfig(nil, ls) }
+
+func (n *Network) networkConfigRevision() uint64 {
+	n.configLock.RLock()
+	defer n.configLock.RUnlock()
+	return n.config.NetconfRevision
+}
+
+func networkManagedIPAllowed(ip net.IP, ls *NetworkLocalSettings) bool {
+	if !ls.AllowManagedIPs {
+		return false
+	}
+	switch ipClassify(ip) {
+	case ipClassificationNone, ipClassificationLoopback, ipClassificationLinkLocal, ipClassificationMulticast:
+		return false
+	case ipClassificationGlobal:
+		return ls.AllowGlobalIPs
+	}
+	return true
 }
 }
 
 
-func (n *Network) handleNetworkConfigUpdate(nc *NetworkConfig) {
-	n.tapLock.RLock()
+func networkManagedRouteAllowed(r *Route, ls *NetworkLocalSettings) bool {
+	if !ls.AllowManagedRoutes {
+		return false
+	}
+	bits, _ := r.Target.Mask.Size()
+	if len(r.Target.IP) > 0 && allZero(r.Target.IP) && bits == 0 {
+		return ls.AllowDefaultRouteOverride
+	}
+	switch ipClassify(r.Target.IP) {
+	case ipClassificationNone, ipClassificationLoopback, ipClassificationLinkLocal, ipClassificationMulticast:
+		return false
+	case ipClassificationGlobal:
+		return ls.AllowGlobalRoutes
+	}
+	return true
+}
+
+func (n *Network) updateConfig(nc *NetworkConfig, ls *NetworkLocalSettings) {
 	n.configLock.Lock()
 	n.configLock.Lock()
 	defer n.configLock.Unlock()
 	defer n.configLock.Unlock()
-	defer n.tapLock.RUnlock()
 
 
-	if n.tap == nil { // sanity check
+	if n.tap == nil { // sanity check, should never happen
 		return
 		return
 	}
 	}
 
 
+	if nc == nil {
+		nc = &n.config
+	}
+	if ls == nil {
+		ls = &n.settings
+	}
+
 	// Add IPs to tap that are newly assigned in this config update,
 	// Add IPs to tap that are newly assigned in this config update,
 	// and remove any IPs from the tap that were assigned that are no
 	// and remove any IPs from the tap that were assigned that are no
 	// longer wanted. IPs assigned to the tap externally (e.g. by an
 	// longer wanted. IPs assigned to the tap externally (e.g. by an
 	// "ifconfig" command) are left alone.
 	// "ifconfig" command) are left alone.
 	haveAssignedIPs := make(map[[3]uint64]*net.IPNet)
 	haveAssignedIPs := make(map[[3]uint64]*net.IPNet)
-	for _, ip := range n.config.AssignedAddresses {
-		haveAssignedIPs[ipNetToKey(&ip)] = &ip
-	}
 	wantAssignedIPs := make(map[[3]uint64]bool)
 	wantAssignedIPs := make(map[[3]uint64]bool)
-	for _, ip := range nc.AssignedAddresses {
-		k := ipNetToKey(&ip)
-		wantAssignedIPs[k] = true
-		if _, have := haveAssignedIPs[k]; !have {
-			n.tap.AddIP(&ip)
+	if n.settings.AllowManagedIPs {
+		for _, ip := range n.config.AssignedAddresses {
+			if networkManagedIPAllowed(ip.IP, &n.settings) { // was it allowed?
+				haveAssignedIPs[ipNetToKey(&ip)] = &ip
+			}
+		}
+	}
+	if ls.AllowManagedIPs {
+		for _, ip := range nc.AssignedAddresses {
+			if networkManagedIPAllowed(ip.IP, ls) { // should it be allowed now?
+				k := ipNetToKey(&ip)
+				wantAssignedIPs[k] = true
+				if _, have := haveAssignedIPs[k]; !have {
+					n.tap.AddIP(&ip)
+				}
+			}
 		}
 		}
 	}
 	}
 	for k, ip := range haveAssignedIPs {
 	for k, ip := range haveAssignedIPs {
@@ -148,4 +233,38 @@ func (n *Network) handleNetworkConfigUpdate(nc *NetworkConfig) {
 			n.tap.RemoveIP(ip)
 			n.tap.RemoveIP(ip)
 		}
 		}
 	}
 	}
+
+	// Do the same for managed routes
+	haveManagedRoutes := make(map[[6]uint64]*Route)
+	wantManagedRoutes := make(map[[6]uint64]bool)
+	if n.settings.AllowManagedRoutes {
+		for _, r := range n.config.Routes {
+			if networkManagedRouteAllowed(&r, &n.settings) { // was it allowed?
+				haveManagedRoutes[r.key()] = &r
+			}
+		}
+	}
+	if ls.AllowManagedRoutes {
+		for _, r := range nc.Routes {
+			if networkManagedRouteAllowed(&r, ls) { // should it be allowed now?
+				k := r.key()
+				wantManagedRoutes[k] = true
+				if _, have := haveManagedRoutes[k]; !have {
+					n.tap.AddRoute(&r)
+				}
+			}
+		}
+	}
+	for k, r := range haveManagedRoutes {
+		if _, want := wantManagedRoutes[k]; !want {
+			n.tap.RemoveRoute(r)
+		}
+	}
+
+	if nc != &n.config {
+		n.config = *nc
+	}
+	if ls != &n.settings {
+		n.settings = *ls
+	}
 }
 }

+ 111 - 105
go/pkg/zerotier/node.go

@@ -43,6 +43,9 @@ const (
 	NetworkStatusPortError            int = C.ZT_NETWORK_STATUS_PORT_ERROR
 	NetworkStatusPortError            int = C.ZT_NETWORK_STATUS_PORT_ERROR
 	NetworkStatusClientTooOld         int = C.ZT_NETWORK_STATUS_CLIENT_TOO_OLD
 	NetworkStatusClientTooOld         int = C.ZT_NETWORK_STATUS_CLIENT_TOO_OLD
 
 
+	NetworkTypePrivate int = C.ZT_NETWORK_TYPE_PRIVATE
+	NetworkTypePublic  int = C.ZT_NETWORK_TYPE_PUBLIC
+
 	// CoreVersionMajor is the major version of the ZeroTier core
 	// CoreVersionMajor is the major version of the ZeroTier core
 	CoreVersionMajor int = C.ZEROTIER_ONE_VERSION_MAJOR
 	CoreVersionMajor int = C.ZEROTIER_ONE_VERSION_MAJOR
 
 
@@ -136,13 +139,10 @@ func (n *Node) Join(nwid uint64, tap Tap) (*Network, error) {
 		return nil, ErrTapInitFailed
 		return nil, ErrTapInitFailed
 	}
 	}
 
 
-	nw := &Network{
-		id: NetworkID(nwid),
-		config: NetworkConfig{
-			ID:     NetworkID(nwid),
-			Status: NetworkStatusRequestConfiguration,
-		},
-		tap: &nativeTap{tap: unsafe.Pointer(ntap), enabled: 1},
+	nw, err := NewNetwork(NetworkID(nwid), &nativeTap{tap: unsafe.Pointer(ntap), enabled: 1})
+	if err != nil {
+		C.ZT_GoNode_leave(n.gn, C.uint64_t(nwid))
+		return nil, err
 	}
 	}
 	n.networksLock.Lock()
 	n.networksLock.Lock()
 	n.networks[nwid] = nw
 	n.networks[nwid] = nw
@@ -368,113 +368,82 @@ func sockaddrStorageToIPNet(ss *C.struct_sockaddr_storage) *net.IPNet {
 
 
 //export goVirtualNetworkConfigFunc
 //export goVirtualNetworkConfigFunc
 func goVirtualNetworkConfigFunc(gn, tapP unsafe.Pointer, nwid C.uint64_t, op C.int, conf unsafe.Pointer) {
 func goVirtualNetworkConfigFunc(gn, tapP unsafe.Pointer, nwid C.uint64_t, op C.int, conf unsafe.Pointer) {
-	nodesByUserPtrLock.RLock()
-	node := nodesByUserPtr[uintptr(gn)]
-	nodesByUserPtrLock.RUnlock()
-	if node == nil {
-		return
-	}
-	node.networksLock.RLock()
-	network := node.networks[uint64(nwid)]
-	node.networksLock.RUnlock()
-	if network != nil {
-		ncc := (*C.ZT_VirtualNetworkConfig)(conf)
-		var nc NetworkConfig
-		nc.ID = uint64(ncc.nwid)
-		nc.MAC = MAC(ncc.mac)
-		nc.Name = C.GoString(ncc.name)
-		nc.Status = int(ncc.status)
-		nc.Type = int(ncc._type)
-		nc.MTU = int(ncc.mtu)
-		nc.Bridge = (ncc.bridge != 0)
-		nc.BroadcastEnabled = (ncc.broadcastEnabled != 0)
-		nc.NetconfRevision = uint64(ncc.netconfRevision)
-		for i := 0; i < int(ncc.assignedAddressCount); i++ {
-			a := sockaddrStorageToIPNet(&ncc.assignedAddresses[i])
-			if a != nil {
-				nc.AssignedAddresses = append(nc.AssignedAddresses, *a)
-			}
+	go func() {
+		nodesByUserPtrLock.RLock()
+		node := nodesByUserPtr[uintptr(gn)]
+		nodesByUserPtrLock.RUnlock()
+		if node == nil {
+			return
 		}
 		}
-		for i := 0; i < int(ncc.routeCount); i++ {
-			tgt := sockaddrStorageToIPNet(&ncc.routes[i].target)
-			viaN := sockaddrStorageToIPNet(&ncc.routes[i].via)
-			var via net.IP
-			if viaN != nil {
-				via = viaN.IP
+		node.networksLock.RLock()
+		network := node.networks[uint64(nwid)]
+		node.networksLock.RUnlock()
+		if network != nil {
+			ncc := (*C.ZT_VirtualNetworkConfig)(conf)
+			if network.networkConfigRevision() > uint64(ncc.netconfRevision) {
+				return
+			}
+			var nc NetworkConfig
+			nc.ID = uint64(ncc.nwid)
+			nc.MAC = MAC(ncc.mac)
+			nc.Name = C.GoString(ncc.name)
+			nc.Status = int(ncc.status)
+			nc.Type = int(ncc._type)
+			nc.MTU = int(ncc.mtu)
+			nc.Bridge = (ncc.bridge != 0)
+			nc.BroadcastEnabled = (ncc.broadcastEnabled != 0)
+			nc.NetconfRevision = uint64(ncc.netconfRevision)
+			for i := 0; i < int(ncc.assignedAddressCount); i++ {
+				a := sockaddrStorageToIPNet(&ncc.assignedAddresses[i])
+				if a != nil {
+					nc.AssignedAddresses = append(nc.AssignedAddresses, *a)
+				}
 			}
 			}
-			if tgt != nil {
-				nc.Routes = append(nc.Routes, Route{
-					Target: *tgt,
-					Via:    via,
-					Flags:  uint16(ncc.routes[i].flags),
-					Metric: uint16(ncc.routes[i].metric),
-				})
+			for i := 0; i < int(ncc.routeCount); i++ {
+				tgt := sockaddrStorageToIPNet(&ncc.routes[i].target)
+				viaN := sockaddrStorageToIPNet(&ncc.routes[i].via)
+				var via net.IP
+				if viaN != nil {
+					via = viaN.IP
+				}
+				if tgt != nil {
+					nc.Routes = append(nc.Routes, Route{
+						Target: *tgt,
+						Via:    via,
+						Flags:  uint16(ncc.routes[i].flags),
+						Metric: uint16(ncc.routes[i].metric),
+					})
+				}
 			}
 			}
+			network.updateConfig(&nc, nil)
 		}
 		}
-		network.handleNetworkConfigUpdate(&nc)
-	}
+	}()
 }
 }
 
 
 //export goZtEvent
 //export goZtEvent
 func goZtEvent(gn unsafe.Pointer, eventType C.int, data unsafe.Pointer) {
 func goZtEvent(gn unsafe.Pointer, eventType C.int, data unsafe.Pointer) {
-	nodesByUserPtrLock.RLock()
-	node := nodesByUserPtr[uintptr(gn)]
-	nodesByUserPtrLock.RUnlock()
-	if node == nil {
-		return
-	}
-	switch eventType {
-	case C.ZT_EVENT_OFFLINE:
-		atomic.StoreUint32(&node.online, 0)
-	case C.ZT_EVENT_ONLINE:
-		atomic.StoreUint32(&node.online, 1)
-	case C.ZT_EVENT_TRACE:
-		node.handleTrace(C.GoString((*C.char)(data)))
-	case C.ZT_EVENT_USER_MESSAGE:
-		um := (*C.ZT_UserMessage)(data)
-		node.handleUserMessage(uint64(um.origin), uint64(um.typeId), C.GoBytes(um.data, C.int(um.length)))
-	case C.ZT_EVENT_REMOTE_TRACE:
-		rt := (*C.ZT_RemoteTrace)(data)
-		node.handleRemoteTrace(uint64(rt.origin), C.GoBytes(unsafe.Pointer(rt.data), C.int(rt.len)))
-	}
-}
-
-// These are really part of nativeTap
-
-func handleTapMulticastGroupChange(gn unsafe.Pointer, nwid, mac C.uint64_t, adi C.uint32_t, added bool) {
-	nodesByUserPtrLock.RLock()
-	node := nodesByUserPtr[uintptr(gn)]
-	nodesByUserPtrLock.RUnlock()
-	if node == nil {
-		return
-	}
-
-	node.networksLock.RLock()
-	network := node.networks[uint64(nwid)]
-	node.networksLock.RUnlock()
-
-	network.tapLock.Lock()
-	tap, _ := network.tap.(*nativeTap)
-	network.tapLock.Unlock()
-
-	if tap != nil {
-		mg := &MulticastGroup{MAC: MAC(mac), ADI: uint32(adi)}
-		tap.multicastGroupHandlersLock.Lock()
-		defer tap.multicastGroupHandlersLock.Unlock()
-		for _, h := range tap.multicastGroupHandlers {
-			h(added, mg)
+	go func() {
+		nodesByUserPtrLock.RLock()
+		node := nodesByUserPtr[uintptr(gn)]
+		nodesByUserPtrLock.RUnlock()
+		if node == nil {
+			return
 		}
 		}
-	}
-}
-
-//export goHandleTapAddedMulticastGroup
-func goHandleTapAddedMulticastGroup(gn, tapP unsafe.Pointer, nwid, mac C.uint64_t, adi C.uint32_t) {
-	handleTapMulticastGroupChange(gn, nwid, mac, adi, true)
-}
-
-//export goHandleTapRemovedMulticastGroup
-func goHandleTapRemovedMulticastGroup(gn, tapP unsafe.Pointer, nwid, mac C.uint64_t, adi C.uint32_t) {
-	handleTapMulticastGroupChange(gn, nwid, mac, adi, false)
+		switch eventType {
+		case C.ZT_EVENT_OFFLINE:
+			atomic.StoreUint32(&node.online, 0)
+		case C.ZT_EVENT_ONLINE:
+			atomic.StoreUint32(&node.online, 1)
+		case C.ZT_EVENT_TRACE:
+			node.handleTrace(C.GoString((*C.char)(data)))
+		case C.ZT_EVENT_USER_MESSAGE:
+			um := (*C.ZT_UserMessage)(data)
+			node.handleUserMessage(uint64(um.origin), uint64(um.typeId), C.GoBytes(um.data, C.int(um.length)))
+		case C.ZT_EVENT_REMOTE_TRACE:
+			rt := (*C.ZT_RemoteTrace)(data)
+			node.handleRemoteTrace(uint64(rt.origin), C.GoBytes(unsafe.Pointer(rt.data), C.int(rt.len)))
+		}
+	}()
 }
 }
 
 
 //////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////
@@ -663,3 +632,40 @@ func (t *nativeTap) SyncRoutes() error {
 	C.ZT_GoTap_syncRoutes(t.tap)
 	C.ZT_GoTap_syncRoutes(t.tap)
 	return nil
 	return nil
 }
 }
+
+//////////////////////////////////////////////////////////////////////////////
+
+func handleTapMulticastGroupChange(gn unsafe.Pointer, nwid, mac C.uint64_t, adi C.uint32_t, added bool) {
+	go func() {
+		nodesByUserPtrLock.RLock()
+		node := nodesByUserPtr[uintptr(gn)]
+		nodesByUserPtrLock.RUnlock()
+		if node == nil {
+			return
+		}
+		node.networksLock.RLock()
+		network := node.networks[uint64(nwid)]
+		node.networksLock.RUnlock()
+		if network != nil {
+			tap, _ := network.tap.(*nativeTap)
+			if tap != nil {
+				mg := &MulticastGroup{MAC: MAC(mac), ADI: uint32(adi)}
+				tap.multicastGroupHandlersLock.Lock()
+				defer tap.multicastGroupHandlersLock.Unlock()
+				for _, h := range tap.multicastGroupHandlers {
+					h(added, mg)
+				}
+			}
+		}
+	}()
+}
+
+//export goHandleTapAddedMulticastGroup
+func goHandleTapAddedMulticastGroup(gn, tapP unsafe.Pointer, nwid, mac C.uint64_t, adi C.uint32_t) {
+	handleTapMulticastGroupChange(gn, nwid, mac, adi, true)
+}
+
+//export goHandleTapRemovedMulticastGroup
+func goHandleTapRemovedMulticastGroup(gn, tapP unsafe.Pointer, nwid, mac C.uint64_t, adi C.uint32_t) {
+	handleTapMulticastGroupChange(gn, nwid, mac, adi, false)
+}

+ 14 - 1
go/pkg/zerotier/route.go

@@ -13,7 +13,10 @@
 
 
 package zerotier
 package zerotier
 
 
-import "net"
+import (
+	"net"
+	"unsafe"
+)
 
 
 // Route represents a route in a host's routing table
 // Route represents a route in a host's routing table
 type Route struct {
 type Route struct {
@@ -29,3 +32,13 @@ type Route struct {
 	// Metric is an interface metric that can affect route priority (behavior can be OS-specific)
 	// Metric is an interface metric that can affect route priority (behavior can be OS-specific)
 	Metric uint16
 	Metric uint16
 }
 }
+
+// key generates a key suitable for a map[] from this route
+func (r *Route) key() (k [6]uint64) {
+	copy(((*[16]byte)(unsafe.Pointer(&k[0])))[:], r.Target.IP)
+	ones, bits := r.Target.Mask.Size()
+	k[2] = (uint64(ones) << 32) | uint64(bits)
+	copy(((*[16]byte)(unsafe.Pointer(&k[3])))[:], r.Via)
+	k[5] = (uint64(r.Flags) << 32) | uint64(r.Metric)
+	return
+}