summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-x.gitignore19
-rw-r--r--attic/Filter.cpp408
-rw-r--r--attic/Filter.hpp284
-rw-r--r--attic/SECURITY.md84
-rw-r--r--controller/EmbeddedNetworkController.cpp410
-rw-r--r--controller/EmbeddedNetworkController.hpp21
-rw-r--r--debian/postinst9
-rw-r--r--include/ZeroTierOne.h190
-rw-r--r--java/jni/Android.mk3
-rw-r--r--java/jni/ZT_jniutils.cpp20
-rw-r--r--java/jni/ZT_jniutils.h18
-rw-r--r--java/src/com/zerotier/sdk/PeerRole.java4
-rw-r--r--macui/ZeroTier One.xcodeproj/project.pbxproj382
-rw-r--r--macui/ZeroTier One.xcodeproj/project.xcworkspace/contents.xcworkspacedata7
-rw-r--r--macui/ZeroTier One/AboutViewController.h33
-rw-r--r--macui/ZeroTier One/AboutViewController.m56
-rw-r--r--macui/ZeroTier One/AboutViewController.xib31
-rw-r--r--macui/ZeroTier One/AppDelegate.h61
-rw-r--r--macui/ZeroTier One/AppDelegate.m339
-rw-r--r--macui/ZeroTier One/Assets.xcassets/AppIcon.appiconset/Contents.json59
-rw-r--r--macui/ZeroTier One/Assets.xcassets/AppIcon.appiconset/ZeroTierIcon512x512.pngbin0 -> 51309 bytes
-rw-r--r--macui/ZeroTier One/Assets.xcassets/Contents.json6
-rw-r--r--macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/Contents.json21
-rw-r--r--macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/MenuBar@2x.pngbin0 -> 22973 bytes
-rw-r--r--macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/Menubar.pngbin0 -> 17234 bytes
-rw-r--r--macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/Contents.json21
-rw-r--r--macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/MenubarWhite.pngbin0 -> 17234 bytes
-rw-r--r--macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/MenubarWhite@2x.pngbin0 -> 22973 bytes
-rw-r--r--macui/ZeroTier One/AuthtokenCopy.h26
-rw-r--r--macui/ZeroTier One/AuthtokenCopy.m97
-rw-r--r--macui/ZeroTier One/Base.lproj/MainMenu.xib680
-rw-r--r--macui/ZeroTier One/Info.plist41
-rw-r--r--macui/ZeroTier One/JoinNetworkViewController.h40
-rw-r--r--macui/ZeroTier One/JoinNetworkViewController.m184
-rw-r--r--macui/ZeroTier One/JoinNetworkViewController.xib74
-rw-r--r--macui/ZeroTier One/Network.h62
-rw-r--r--macui/ZeroTier One/Network.m278
-rw-r--r--macui/ZeroTier One/NetworkInfoCell.h50
-rw-r--r--macui/ZeroTier One/NetworkInfoCell.m85
-rw-r--r--macui/ZeroTier One/NetworkMonitor.h45
-rw-r--r--macui/ZeroTier One/NetworkMonitor.m253
-rw-r--r--macui/ZeroTier One/NodeStatus.h35
-rw-r--r--macui/ZeroTier One/NodeStatus.m40
-rw-r--r--macui/ZeroTier One/PreferencesViewController.h31
-rw-r--r--macui/ZeroTier One/PreferencesViewController.m112
-rw-r--r--macui/ZeroTier One/PreferencesViewController.xib33
-rw-r--r--macui/ZeroTier One/ServiceCom.h39
-rw-r--r--macui/ZeroTier One/ServiceCom.m464
-rw-r--r--macui/ZeroTier One/ShowNetworksViewController.h36
-rw-r--r--macui/ZeroTier One/ShowNetworksViewController.m119
-rw-r--r--macui/ZeroTier One/ShowNetworksViewController.xib370
-rw-r--r--macui/ZeroTier One/ZeroTierIcon.icnsbin0 -> 125598 bytes
-rw-r--r--macui/ZeroTier One/about.html65
-rw-r--r--macui/ZeroTier One/main.m23
-rw-r--r--make-linux.mk4
-rw-r--r--node/Capability.hpp36
-rw-r--r--node/Dictionary.hpp42
-rw-r--r--node/IncomingPacket.cpp383
-rw-r--r--node/IncomingPacket.hpp26
-rw-r--r--node/InetAddress.cpp10
-rw-r--r--node/InetAddress.hpp19
-rw-r--r--node/Membership.cpp307
-rw-r--r--node/Membership.hpp270
-rw-r--r--node/Multicaster.cpp3
-rw-r--r--node/Multicaster.hpp2
-rw-r--r--node/Network.cpp568
-rw-r--r--node/Network.hpp234
-rw-r--r--node/NetworkConfig.hpp10
-rw-r--r--node/Node.cpp32
-rw-r--r--node/Node.hpp1
-rw-r--r--node/OutboundMulticast.cpp4
-rw-r--r--node/OutboundMulticast.hpp2
-rw-r--r--node/Packet.cpp3
-rw-r--r--node/Packet.hpp167
-rw-r--r--node/Peer.cpp1
-rw-r--r--node/Peer.hpp17
-rw-r--r--node/Revocation.cpp46
-rw-r--r--node/Revocation.hpp178
-rw-r--r--node/Switch.cpp33
-rw-r--r--node/Tag.cpp2
-rw-r--r--node/Tag.hpp30
-rw-r--r--objects.mk1
-rw-r--r--one.cpp14
-rw-r--r--osdep/LinuxDropPrivileges.cpp164
-rw-r--r--osdep/LinuxDropPrivileges.hpp9
-rw-r--r--osdep/ManagedRoute.cpp4
-rw-r--r--osdep/NeighborDiscovery.cpp264
-rw-r--r--osdep/NeighborDiscovery.hpp76
-rw-r--r--osdep/OSUtils.cpp8
89 files changed, 6492 insertions, 2246 deletions
diff --git a/.gitignore b/.gitignore
index 8bf3c55b..2efd14e7 100755
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
# Main binaries created in *nix builds
/zerotier-one
/zerotier-idtool
@@ -83,3 +84,21 @@ windows/WinUI/obj/
windows/WinUI/bin/
windows/ZeroTierOne/Debug/
/ext/installfiles/windows/chocolatey/zerotier-one/*.nupkg
+
+# Miscellaneous mac/Xcode droppings
+.DS_Store
+.Trashes
+*.swp
+*~.nib
+DerivedData/
+build/
+*.pbxuser
+*.mode1v3
+*.mode2v3
+*.perspectivev3
+!default.pbxuser
+!default.mode1v3
+!default.mode2v3
+!default.perspectivev3
+*.xccheckout
+xcuserdata/
diff --git a/attic/Filter.cpp b/attic/Filter.cpp
deleted file mode 100644
index a701e8b7..00000000
--- a/attic/Filter.cpp
+++ /dev/null
@@ -1,408 +0,0 @@
-/*
- * ZeroTier One - Network Virtualization Everywhere
- * Copyright (C) 2011-2015 ZeroTier, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- * --
- *
- * ZeroTier may be used and distributed under the terms of the GPLv3, which
- * are available at: http://www.gnu.org/licenses/gpl-3.0.html
- *
- * If you would like to embed ZeroTier into a commercial application or
- * redistribute it in a modified binary form, please contact ZeroTier Networks
- * LLC. Start here: http://www.zerotier.com/
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdint.h>
-
-#include <algorithm>
-
-#include "RuntimeEnvironment.hpp"
-#include "Logger.hpp"
-#include "Filter.hpp"
-#include "Utils.hpp"
-
-namespace ZeroTier {
-
-const char *const Filter::UNKNOWN_NAME = "(unknown)";
-const Range<unsigned int> Filter::ANY;
-
-static inline Range<unsigned int> __parseRange(char *r)
- throw(std::invalid_argument)
-{
- char *saveptr = (char *)0;
- unsigned int a = 0;
- unsigned int b = 0;
- unsigned int fn = 0;
- for(char *f=Utils::stok(r,"-",&saveptr);(f);f=Utils::stok((char *)0,"-",&saveptr)) {
- if (*f) {
- switch(fn++) {
- case 0:
- if (*f != '*')
- a = b = (unsigned int)strtoul(f,(char **)0,10);
- break;
- case 1:
- if (*f != '*')
- b = (unsigned int)strtoul(f,(char **)0,10);
- break;
- default:
- throw std::invalid_argument("rule range must be <int>, <int>-<int>, or *");
- }
- }
- }
- return Range<unsigned int>(a,b);
-}
-
-Filter::Rule::Rule(const char *s)
- throw(std::invalid_argument)
-{
- char *saveptr = (char *)0;
- char tmp[256];
- if (!Utils::scopy(tmp,sizeof(tmp),s))
- throw std::invalid_argument("rule string too long");
- unsigned int fn = 0;
- for(char *f=Utils::stok(tmp,";",&saveptr);(f);f=Utils::stok((char *)0,";",&saveptr)) {
- if (*f) {
- switch(fn++) {
- case 0:
- _etherType = __parseRange(f);
- break;
- case 1:
- _protocol = __parseRange(f);
- break;
- case 2:
- _port = __parseRange(f);
- break;
- default:
- throw std::invalid_argument("rule string has unknown extra fields");
- }
- }
- }
- if (fn != 3)
- throw std::invalid_argument("rule string must contain 3 fields");
-}
-
-bool Filter::Rule::operator()(unsigned int etype,const void *data,unsigned int len) const
- throw(std::invalid_argument)
-{
- if ((!_etherType)||(_etherType(etype))) { // ethertype is ANY, or matches
- // Ethertype determines meaning of protocol and port
- switch(etype) {
- case ZT_ETHERTYPE_IPV4:
- if (len > 20) {
- if ((!_protocol)||(_protocol(((const uint8_t *)data)[9]))) { // protocol is ANY or match
- if (!_port) // port is ANY
- return true;
-
- // Don't match on fragments beyond fragment 0. If we've blocked
- // fragment 0, further fragments will fall on deaf ears anyway.
- if ((Utils::ntoh(((const uint16_t *)data)[3]) & 0x1fff))
- return false;
-
- // Internet header length determines where data begins, in multiples of 32 bits
- unsigned int ihl = 4 * (((const uint8_t *)data)[0] & 0x0f);
-
- switch(((const uint8_t *)data)[9]) { // port's meaning depends on IP protocol
- case ZT_IPPROTO_ICMP:
- // For ICMP, port is ICMP type
- return _port(((const uint8_t *)data)[ihl]);
- case ZT_IPPROTO_TCP:
- case ZT_IPPROTO_UDP:
- case ZT_IPPROTO_SCTP:
- case ZT_IPPROTO_UDPLITE:
- // For these, port is destination port. Protocol designers were
- // nice enough to put the field in the same place.
- return _port(((const uint16_t *)data)[(ihl / 2) + 1]);
- default:
- // port has no meaning for other IP types, so ignore it
- return true;
- }
-
- return false; // no match on port
- }
- } else throw std::invalid_argument("undersized IPv4 packet");
- break;
-
- case ZT_ETHERTYPE_IPV6:
- if (len > 40) {
- int nextHeader = ((const uint8_t *)data)[6];
- unsigned int pos = 40;
- while ((pos < len)&&(nextHeader >= 0)&&(nextHeader != 59)) { // 59 == no next header
- fprintf(stderr,"[rule] V6: start header parse, header %.2x pos %d\n",nextHeader,pos);
-
- switch(nextHeader) {
- case 0: // hop-by-hop options
- case 60: // destination options
- case 43: // routing
- case 135: // mobility (mobile IPv6 options)
- if (_protocol((unsigned int)nextHeader))
- return true; // match if our goal was to match any of these
- nextHeader = ((const uint8_t *)data)[pos];
- pos += 8 + (8 * ((const uint8_t *)data)[pos + 1]);
- break;
- case 44: // fragment
- if (_protocol(44))
- return true; // match if our goal was to match fragments
- nextHeader = ((const uint8_t *)data)[pos];
- pos += 8;
- break;
- case ZT_IPPROTO_AH: // AH
- return _protocol(ZT_IPPROTO_AH); // true if AH is matched protocol, otherwise false since packet will be IPsec
- case ZT_IPPROTO_ESP: // ESP
- return _protocol(ZT_IPPROTO_ESP); // true if ESP is matched protocol, otherwise false since packet will be IPsec
- case ZT_IPPROTO_ICMPV6:
- // Only match ICMPv6 if we've selected it specifically
- if (_protocol(ZT_IPPROTO_ICMPV6)) {
- // Port is interpreted as ICMPv6 type
- if ((!_port)||(_port(((const uint8_t *)data)[pos])))
- return true;
- }
- break;
- case ZT_IPPROTO_TCP:
- case ZT_IPPROTO_UDP:
- case ZT_IPPROTO_SCTP:
- case ZT_IPPROTO_UDPLITE:
- // If we encounter any of these, match if protocol matches or is wildcard as
- // we'll consider these the "real payload" if present.
- if ((!_protocol)||(_protocol(nextHeader))) {
- if ((!_port)||(_port(((const uint16_t *)data)[(pos / 2) + 1])))
- return true; // protocol matches or is ANY, port is ANY or matches
- }
- break;
- default: {
- char foo[128];
- Utils::snprintf(foo,sizeof(foo),"unrecognized IPv6 header type %d",(int)nextHeader);
- throw std::invalid_argument(foo);
- }
- }
-
- fprintf(stderr,"[rule] V6: end header parse, next header %.2x, new pos %d\n",nextHeader,pos);
- }
- } else throw std::invalid_argument("undersized IPv6 packet");
- break;
-
- default:
- // For other ethertypes, protocol and port are ignored. What would they mean?
- return true;
- }
- }
-
- return false;
-}
-
-std::string Filter::Rule::toString() const
-{
- char buf[128];
- std::string s;
-
- switch(_etherType.magnitude()) {
- case 0:
- s.push_back('*');
- break;
- case 1:
- Utils::snprintf(buf,sizeof(buf),"%u",_etherType.start);
- s.append(buf);
- break;
- default:
- Utils::snprintf(buf,sizeof(buf),"%u-%u",_etherType.start,_etherType.end);
- s.append(buf);
- break;
- }
- s.push_back(';');
- switch(_protocol.magnitude()) {
- case 0:
- s.push_back('*');
- break;
- case 1:
- Utils::snprintf(buf,sizeof(buf),"%u",_protocol.start);
- s.append(buf);
- break;
- default:
- Utils::snprintf(buf,sizeof(buf),"%u-%u",_protocol.start,_protocol.end);
- s.append(buf);
- break;
- }
- s.push_back(';');
- switch(_port.magnitude()) {
- case 0:
- s.push_back('*');
- break;
- case 1:
- Utils::snprintf(buf,sizeof(buf),"%u",_port.start);
- s.append(buf);
- break;
- default:
- Utils::snprintf(buf,sizeof(buf),"%u-%u",_port.start,_port.end);
- s.append(buf);
- break;
- }
-
- return s;
-}
-
-Filter::Filter(const char *s)
- throw(std::invalid_argument)
-{
- char tmp[16384];
- if (!Utils::scopy(tmp,sizeof(tmp),s))
- throw std::invalid_argument("filter string too long");
- char *saveptr = (char *)0;
- unsigned int fn = 0;
- for(char *f=Utils::stok(tmp,",",&saveptr);(f);f=Utils::stok((char *)0,",",&saveptr)) {
- try {
- _rules.push_back(Rule(f));
- ++fn;
- } catch (std::invalid_argument &exc) {
- char tmp[256];
- Utils::snprintf(tmp,sizeof(tmp),"invalid rule at index %u: %s",fn,exc.what());
- throw std::invalid_argument(tmp);
- }
- }
- std::sort(_rules.begin(),_rules.end());
-}
-
-std::string Filter::toString() const
-{
- std::string s;
-
- for(std::vector<Rule>::const_iterator r(_rules.begin());r!=_rules.end();++r) {
- if (s.length() > 0)
- s.push_back(',');
- s.append(r->toString());
- }
-
- return s;
-}
-
-void Filter::add(const Rule &r)
-{
- for(std::vector<Rule>::iterator rr(_rules.begin());rr!=_rules.end();++rr) {
- if (r == *rr)
- return;
- }
- _rules.push_back(r);
- std::sort(_rules.begin(),_rules.end());
-}
-
-const char *Filter::etherTypeName(const unsigned int etherType)
- throw()
-{
- switch(etherType) {
- case ZT_ETHERTYPE_IPV4: return "ETHERTYPE_IPV4";
- case ZT_ETHERTYPE_ARP: return "ETHERTYPE_ARP";
- case ZT_ETHERTYPE_RARP: return "ETHERTYPE_RARP";
- case ZT_ETHERTYPE_ATALK: return "ETHERTYPE_ATALK";
- case ZT_ETHERTYPE_AARP: return "ETHERTYPE_AARP";
- case ZT_ETHERTYPE_IPX_A: return "ETHERTYPE_IPX_A";
- case ZT_ETHERTYPE_IPX_B: return "ETHERTYPE_IPX_B";
- case ZT_ETHERTYPE_IPV6: return "ETHERTYPE_IPV6";
- }
- return UNKNOWN_NAME;
-}
-
-const char *Filter::ipProtocolName(const unsigned int ipp)
- throw()
-{
- switch(ipp) {
- case ZT_IPPROTO_ICMP: return "IPPROTO_ICMP";
- case ZT_IPPROTO_IGMP: return "IPPROTO_IGMP";
- case ZT_IPPROTO_TCP: return "IPPROTO_TCP";
- case ZT_IPPROTO_UDP: return "IPPROTO_UDP";
- case ZT_IPPROTO_GRE: return "IPPROTO_GRE";
- case ZT_IPPROTO_ESP: return "IPPROTO_ESP";
- case ZT_IPPROTO_AH: return "IPPROTO_AH";
- case ZT_IPPROTO_ICMPV6: return "IPPROTO_ICMPV6";
- case ZT_IPPROTO_OSPF: return "IPPROTO_OSPF";
- case ZT_IPPROTO_IPIP: return "IPPROTO_IPIP";
- case ZT_IPPROTO_IPCOMP: return "IPPROTO_IPCOMP";
- case ZT_IPPROTO_L2TP: return "IPPROTO_L2TP";
- case ZT_IPPROTO_SCTP: return "IPPROTO_SCTP";
- case ZT_IPPROTO_FC: return "IPPROTO_FC";
- case ZT_IPPROTO_UDPLITE: return "IPPROTO_UDPLITE";
- case ZT_IPPROTO_HIP: return "IPPROTO_HIP";
- }
- return UNKNOWN_NAME;
-}
-
-const char *Filter::icmpTypeName(const unsigned int icmpType)
- throw()
-{
- switch(icmpType) {
- case ZT_ICMP_ECHO_REPLY: return "ICMP_ECHO_REPLY";
- case ZT_ICMP_DESTINATION_UNREACHABLE: return "ICMP_DESTINATION_UNREACHABLE";
- case ZT_ICMP_SOURCE_QUENCH: return "ICMP_SOURCE_QUENCH";
- case ZT_ICMP_REDIRECT: return "ICMP_REDIRECT";
- case ZT_ICMP_ALTERNATE_HOST_ADDRESS: return "ICMP_ALTERNATE_HOST_ADDRESS";
- case ZT_ICMP_ECHO_REQUEST: return "ICMP_ECHO_REQUEST";
- case ZT_ICMP_ROUTER_ADVERTISEMENT: return "ICMP_ROUTER_ADVERTISEMENT";
- case ZT_ICMP_ROUTER_SOLICITATION: return "ICMP_ROUTER_SOLICITATION";
- case ZT_ICMP_TIME_EXCEEDED: return "ICMP_TIME_EXCEEDED";
- case ZT_ICMP_BAD_IP_HEADER: return "ICMP_BAD_IP_HEADER";
- case ZT_ICMP_TIMESTAMP: return "ICMP_TIMESTAMP";
- case ZT_ICMP_TIMESTAMP_REPLY: return "ICMP_TIMESTAMP_REPLY";
- case ZT_ICMP_INFORMATION_REQUEST: return "ICMP_INFORMATION_REQUEST";
- case ZT_ICMP_INFORMATION_REPLY: return "ICMP_INFORMATION_REPLY";
- case ZT_ICMP_ADDRESS_MASK_REQUEST: return "ICMP_ADDRESS_MASK_REQUEST";
- case ZT_ICMP_ADDRESS_MASK_REPLY: return "ICMP_ADDRESS_MASK_REPLY";
- case ZT_ICMP_TRACEROUTE: return "ICMP_TRACEROUTE";
- case ZT_ICMP_MOBILE_HOST_REDIRECT: return "ICMP_MOBILE_HOST_REDIRECT";
- case ZT_ICMP_MOBILE_REGISTRATION_REQUEST: return "ICMP_MOBILE_REGISTRATION_REQUEST";
- case ZT_ICMP_MOBILE_REGISTRATION_REPLY: return "ICMP_MOBILE_REGISTRATION_REPLY";
- }
- return UNKNOWN_NAME;
-}
-
-const char *Filter::icmp6TypeName(const unsigned int icmp6Type)
- throw()
-{
- switch(icmp6Type) {
- case ZT_ICMP6_DESTINATION_UNREACHABLE: return "ICMP6_DESTINATION_UNREACHABLE";
- case ZT_ICMP6_PACKET_TOO_BIG: return "ICMP6_PACKET_TOO_BIG";
- case ZT_ICMP6_TIME_EXCEEDED: return "ICMP6_TIME_EXCEEDED";
- case ZT_ICMP6_PARAMETER_PROBLEM: return "ICMP6_PARAMETER_PROBLEM";
- case ZT_ICMP6_ECHO_REQUEST: return "ICMP6_ECHO_REQUEST";
- case ZT_ICMP6_ECHO_REPLY: return "ICMP6_ECHO_REPLY";
- case ZT_ICMP6_MULTICAST_LISTENER_QUERY: return "ICMP6_MULTICAST_LISTENER_QUERY";
- case ZT_ICMP6_MULTICAST_LISTENER_REPORT: return "ICMP6_MULTICAST_LISTENER_REPORT";
- case ZT_ICMP6_MULTICAST_LISTENER_DONE: return "ICMP6_MULTICAST_LISTENER_DONE";
- case ZT_ICMP6_ROUTER_SOLICITATION: return "ICMP6_ROUTER_SOLICITATION";
- case ZT_ICMP6_ROUTER_ADVERTISEMENT: return "ICMP6_ROUTER_ADVERTISEMENT";
- case ZT_ICMP6_NEIGHBOR_SOLICITATION: return "ICMP6_NEIGHBOR_SOLICITATION";
- case ZT_ICMP6_NEIGHBOR_ADVERTISEMENT: return "ICMP6_NEIGHBOR_ADVERTISEMENT";
- case ZT_ICMP6_REDIRECT_MESSAGE: return "ICMP6_REDIRECT_MESSAGE";
- case ZT_ICMP6_ROUTER_RENUMBERING: return "ICMP6_ROUTER_RENUMBERING";
- case ZT_ICMP6_NODE_INFORMATION_QUERY: return "ICMP6_NODE_INFORMATION_QUERY";
- case ZT_ICMP6_NODE_INFORMATION_RESPONSE: return "ICMP6_NODE_INFORMATION_RESPONSE";
- case ZT_ICMP6_INV_NEIGHBOR_SOLICITATION: return "ICMP6_INV_NEIGHBOR_SOLICITATION";
- case ZT_ICMP6_INV_NEIGHBOR_ADVERTISEMENT: return "ICMP6_INV_NEIGHBOR_ADVERTISEMENT";
- case ZT_ICMP6_MLDV2: return "ICMP6_MLDV2";
- case ZT_ICMP6_HOME_AGENT_ADDRESS_DISCOVERY_REQUEST: return "ICMP6_HOME_AGENT_ADDRESS_DISCOVERY_REQUEST";
- case ZT_ICMP6_HOME_AGENT_ADDRESS_DISCOVERY_REPLY: return "ICMP6_HOME_AGENT_ADDRESS_DISCOVERY_REPLY";
- case ZT_ICMP6_MOBILE_PREFIX_SOLICITATION: return "ICMP6_MOBILE_PREFIX_SOLICITATION";
- case ZT_ICMP6_MOBILE_PREFIX_ADVERTISEMENT: return "ICMP6_MOBILE_PREFIX_ADVERTISEMENT";
- case ZT_ICMP6_CERTIFICATION_PATH_SOLICITATION: return "ICMP6_CERTIFICATION_PATH_SOLICITATION";
- case ZT_ICMP6_CERTIFICATION_PATH_ADVERTISEMENT: return "ICMP6_CERTIFICATION_PATH_ADVERTISEMENT";
- case ZT_ICMP6_MULTICAST_ROUTER_ADVERTISEMENT: return "ICMP6_MULTICAST_ROUTER_ADVERTISEMENT";
- case ZT_ICMP6_MULTICAST_ROUTER_SOLICITATION: return "ICMP6_MULTICAST_ROUTER_SOLICITATION";
- case ZT_ICMP6_MULTICAST_ROUTER_TERMINATION: return "ICMP6_MULTICAST_ROUTER_TERMINATION";
- case ZT_ICMP6_RPL_CONTROL_MESSAGE: return "ICMP6_RPL_CONTROL_MESSAGE";
- }
- return UNKNOWN_NAME;
-}
-
-} // namespace ZeroTier
diff --git a/attic/Filter.hpp b/attic/Filter.hpp
deleted file mode 100644
index 4bea3715..00000000
--- a/attic/Filter.hpp
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * ZeroTier One - Network Virtualization Everywhere
- * Copyright (C) 2011-2015 ZeroTier, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- * --
- *
- * ZeroTier may be used and distributed under the terms of the GPLv3, which
- * are available at: http://www.gnu.org/licenses/gpl-3.0.html
- *
- * If you would like to embed ZeroTier into a commercial application or
- * redistribute it in a modified binary form, please contact ZeroTier Networks
- * LLC. Start here: http://www.zerotier.com/
- */
-
-#ifndef _ZT_FILTER_HPP
-#define _ZT_FILTER_HPP
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <string>
-#include <vector>
-#include <utility>
-#include <stdexcept>
-
-#include "Range.hpp"
-
-/* Ethernet frame types that might be relevant to us */
-#define ZT_ETHERTYPE_IPV4 0x0800
-#define ZT_ETHERTYPE_ARP 0x0806
-#define ZT_ETHERTYPE_RARP 0x8035
-#define ZT_ETHERTYPE_ATALK 0x809b
-#define ZT_ETHERTYPE_AARP 0x80f3
-#define ZT_ETHERTYPE_IPX_A 0x8137
-#define ZT_ETHERTYPE_IPX_B 0x8138
-#define ZT_ETHERTYPE_IPV6 0x86dd
-
-/* IP protocols we might care about */
-#define ZT_IPPROTO_ICMP 0x01
-#define ZT_IPPROTO_IGMP 0x02
-#define ZT_IPPROTO_TCP 0x06
-#define ZT_IPPROTO_UDP 0x11
-#define ZT_IPPROTO_GRE 0x2f
-#define ZT_IPPROTO_ESP 0x32
-#define ZT_IPPROTO_AH 0x33
-#define ZT_IPPROTO_ICMPV6 0x3a
-#define ZT_IPPROTO_OSPF 0x59
-#define ZT_IPPROTO_IPIP 0x5e
-#define ZT_IPPROTO_IPCOMP 0x6c
-#define ZT_IPPROTO_L2TP 0x73
-#define ZT_IPPROTO_SCTP 0x84
-#define ZT_IPPROTO_FC 0x85
-#define ZT_IPPROTO_UDPLITE 0x88
-#define ZT_IPPROTO_HIP 0x8b
-
-/* IPv4 ICMP types */
-#define ZT_ICMP_ECHO_REPLY 0
-#define ZT_ICMP_DESTINATION_UNREACHABLE 3
-#define ZT_ICMP_SOURCE_QUENCH 4
-#define ZT_ICMP_REDIRECT 5
-#define ZT_ICMP_ALTERNATE_HOST_ADDRESS 6
-#define ZT_ICMP_ECHO_REQUEST 8
-#define ZT_ICMP_ROUTER_ADVERTISEMENT 9
-#define ZT_ICMP_ROUTER_SOLICITATION 10
-#define ZT_ICMP_TIME_EXCEEDED 11
-#define ZT_ICMP_BAD_IP_HEADER 12
-#define ZT_ICMP_TIMESTAMP 13
-#define ZT_ICMP_TIMESTAMP_REPLY 14
-#define ZT_ICMP_INFORMATION_REQUEST 15
-#define ZT_ICMP_INFORMATION_REPLY 16
-#define ZT_ICMP_ADDRESS_MASK_REQUEST 17
-#define ZT_ICMP_ADDRESS_MASK_REPLY 18
-#define ZT_ICMP_TRACEROUTE 30
-#define ZT_ICMP_MOBILE_HOST_REDIRECT 32
-#define ZT_ICMP_MOBILE_REGISTRATION_REQUEST 35
-#define ZT_ICMP_MOBILE_REGISTRATION_REPLY 36
-
-/* IPv6 ICMP types */
-#define ZT_ICMP6_DESTINATION_UNREACHABLE 1
-#define ZT_ICMP6_PACKET_TOO_BIG 2
-#define ZT_ICMP6_TIME_EXCEEDED 3
-#define ZT_ICMP6_PARAMETER_PROBLEM 4
-#define ZT_ICMP6_ECHO_REQUEST 128
-#define ZT_ICMP6_ECHO_REPLY 129
-#define ZT_ICMP6_MULTICAST_LISTENER_QUERY 130
-#define ZT_ICMP6_MULTICAST_LISTENER_REPORT 131
-#define ZT_ICMP6_MULTICAST_LISTENER_DONE 132
-#define ZT_ICMP6_ROUTER_SOLICITATION 133
-#define ZT_ICMP6_ROUTER_ADVERTISEMENT 134
-#define ZT_ICMP6_NEIGHBOR_SOLICITATION 135
-#define ZT_ICMP6_NEIGHBOR_ADVERTISEMENT 136
-#define ZT_ICMP6_REDIRECT_MESSAGE 137
-#define ZT_ICMP6_ROUTER_RENUMBERING 138
-#define ZT_ICMP6_NODE_INFORMATION_QUERY 139
-#define ZT_ICMP6_NODE_INFORMATION_RESPONSE 140
-#define ZT_ICMP6_INV_NEIGHBOR_SOLICITATION 141
-#define ZT_ICMP6_INV_NEIGHBOR_ADVERTISEMENT 142
-#define ZT_ICMP6_MLDV2 143
-#define ZT_ICMP6_HOME_AGENT_ADDRESS_DISCOVERY_REQUEST 144
-#define ZT_ICMP6_HOME_AGENT_ADDRESS_DISCOVERY_REPLY 145
-#define ZT_ICMP6_MOBILE_PREFIX_SOLICITATION 146
-#define ZT_ICMP6_MOBILE_PREFIX_ADVERTISEMENT 147
-#define ZT_ICMP6_CERTIFICATION_PATH_SOLICITATION 148
-#define ZT_ICMP6_CERTIFICATION_PATH_ADVERTISEMENT 149
-#define ZT_ICMP6_MULTICAST_ROUTER_ADVERTISEMENT 151
-#define ZT_ICMP6_MULTICAST_ROUTER_SOLICITATION 152
-#define ZT_ICMP6_MULTICAST_ROUTER_TERMINATION 153
-#define ZT_ICMP6_RPL_CONTROL_MESSAGE 155
-
-namespace ZeroTier {
-
-class RuntimeEnvironment;
-
-/**
- * A simple Ethernet frame level filter
- *
- * This doesn't specify actions, since it's used as a deny filter. The rule
- * in ZT1 is "that which is not explicitly prohibited is allowed." (Except for
- * ethertypes, which are handled by a whitelist.)
- */
-class Filter
-{
-public:
- /**
- * Value returned by etherTypeName, etc. on unknown
- *
- * These static methods return precisely this, so a pointer equality
- * check will work.
- */
- static const char *const UNKNOWN_NAME;
-
- /**
- * An empty range as a more idiomatic way of specifying a wildcard match
- */
- static const Range<unsigned int> ANY;
-
- /**
- * A filter rule
- */
- class Rule
- {
- public:
- Rule()
- throw() :
- _etherType(),
- _protocol(),
- _port()
- {
- }
-
- /**
- * Construct a rule from a string-serialized value
- *
- * @param s String formatted rule, such as returned by toString()
- * @throws std::invalid_argument String formatted rule is not valid
- */
- Rule(const char *s)
- throw(std::invalid_argument);
-
- /**
- * Construct a new rule
- *
- * @param etype Ethernet type or empty range for ANY
- * @param prot Protocol or empty range for ANY (meaning depends on ethertype, e.g. IP protocol numbers)
- * @param prt Port or empty range for ANY (only applies to some protocols)
- */
- Rule(const Range<unsigned int> &etype,const Range<unsigned int> &prot,const Range<unsigned int> &prt)
- throw() :
- _etherType(etype),
- _protocol(prot),
- _port(prt)
- {
- }
-
- inline const Range<unsigned int> &etherType() const throw() { return _etherType; }
- inline const Range<unsigned int> &protocol() const throw() { return _protocol; }
- inline const Range<unsigned int> &port() const throw() { return _port; }
-
- /**
- * Test this rule against a frame
- *
- * @param etype Type of ethernet frame
- * @param data Ethernet frame data
- * @param len Length of ethernet frame
- * @return True if rule matches
- * @throws std::invalid_argument Frame invalid or not parseable
- */
- bool operator()(unsigned int etype,const void *data,unsigned int len) const
- throw(std::invalid_argument);
-
- /**
- * Serialize rule as string
- *
- * @return Human readable representation of rule
- */
- std::string toString() const;
-
- inline bool operator==(const Rule &r) const throw() { return ((_etherType == r._etherType)&&(_protocol == r._protocol)&&(_port == r._port)); }
- inline bool operator!=(const Rule &r) const throw() { return !(*this == r); }
- inline bool operator<(const Rule &r) const
- throw()
- {
- if (_etherType < r._etherType)
- return true;
- else if (_etherType == r._etherType) {
- if (_protocol < r._protocol)
- return true;
- else if (_protocol == r._protocol) {
- if (_port < r._port)
- return true;
- }
- }
- return false;
- }
- inline bool operator>(const Rule &r) const throw() { return (r < *this); }
- inline bool operator<=(const Rule &r) const throw() { return !(r < *this); }
- inline bool operator>=(const Rule &r) const throw() { return !(*this < r); }
-
- private:
- Range<unsigned int> _etherType;
- Range<unsigned int> _protocol;
- Range<unsigned int> _port;
- };
-
- Filter() {}
-
- /**
- * @param s String-serialized filter representation
- */
- Filter(const char *s)
- throw(std::invalid_argument);
-
- /**
- * @return Comma-delimited list of string-format rules
- */
- std::string toString() const;
-
- /**
- * Add a rule to this filter
- *
- * @param r Rule to add to filter
- */
- void add(const Rule &r);
-
- inline bool operator()(unsigned int etype,const void *data,unsigned int len) const
- throw(std::invalid_argument)
- {
- for(std::vector<Rule>::const_iterator r(_rules.begin());r!=_rules.end();++r) {
- if ((*r)(etype,data,len))
- return true;
- }
- return false;
- }
-
- static const char *etherTypeName(const unsigned int etherType)
- throw();
- static const char *ipProtocolName(const unsigned int ipp)
- throw();
- static const char *icmpTypeName(const unsigned int icmpType)
- throw();
- static const char *icmp6TypeName(const unsigned int icmp6Type)
- throw();
-
-private:
- std::vector<Rule> _rules;
-};
-
-} // namespace ZeroTier
-
-#endif
diff --git a/attic/SECURITY.md b/attic/SECURITY.md
deleted file mode 100644
index 5ca125e9..00000000
--- a/attic/SECURITY.md
+++ /dev/null
@@ -1,84 +0,0 @@
-ZeroTier Security
-======
-
-## Summary
-
-
-## Using ZeroTier Securely
-
-### Overall Recommendations
-
-*TL;DR: same as anything else: defense in depth defense in depth defense in depth.*
-
-We encourage our users to treat private ZeroTier networks as being rougly equivalent in security to WPA2-enterprise securied WiFi or on-premise wired Ethernet. (Public networks on the other hand are open by design.) That means they're networks with perimeters, but like all networks the compromise of any participating device or network controller allows an attacker to breach this perimeter.
-
-**Never trust the network.** Many modern security professionals discourage reliance on network perimeters as major components in any security strategy, and we strongly agree regardless of whether your network is physical or virtual.
-
-As part of a defense in depth approach **we specifically encourage the use of other secure protocols and authentication systems over ZeroTier networks**. While the use of secure encrypted protocols like SSH and SSL over ZeroTier adds a bit more overhead, it greatly reduces the chance of total compromise.
-
-Imagine that the per-day probability of a major "0-day" security flaw in ZeroTier and OpenSSH are both roughly 0.001 or one per thousand days. Using both at the same time gives you a cumulative 0-day risk of roughly 0.000001 or one per one million days.
-
-Those are made-up numbers. In reality these probabilities can't be known ahead of time. History shows that a 0-day could be found in anything tomorrow, next week, or never. But layers of security give you an overall posture that is the product -- more than the sum -- of its parts. That's how defense in depth works.
-
-### ZeroTier Specifics
-
-#### Protect Your Identity
-
-Each ZeroTier device has an identity. The secret portion of this identity is stored in a file called "identity.secret." *Protect this file.* If it's stolen your device's identity (as represented by its 10-digit ZeroTier address) can easily be stolen or impersonated and your traffic can be decrypted or man-in-the-middle'd.
-
-#### Protect Your Controller
-
-The second major component of ZeroTier network security is the network controller. It's responsible for issuing certificates and configuration information to all network members. That makes it a certificate authority. Compromise of the controller allows an attacker to join or disrupt any network the controller controls. It does *not*, however, allow an attacker to decrypt peer to peer unicast traffic.
-
-If you are using our controller-as-a-service at [my.zerotier.com](https://my.zerotier.com), you are delegating this responsibility to us.
-
-## Security Priorities
-
-These are our security "must-haves." If the system fails in any of these objectives it is broken.
-
-* ZeroTier must be secure against remote vulnerabilities. This includes things like unauthorized remote control, remote penetration of the device using ZeroTier as a vector, or remote injection of malware.
-
-* The content (but not meta-data) of communication must be secure against eavesdropping on the wire by any known means. (We can't warrant against secret vulnerabilities against ciphers, etc., or anything else we don't know about.)
-
-* Communication must be secure against man-in-the-middle attacks and remote device impersonation.
-
-## Security Non-Priorities
-
-There are a few aspects of security we knowingly do not address, since doing so would be beyond scope or would conflict too greatly with other priorities.
-
-* ZeroTier makes no effort to conceal communication meta-data such as source and destination addresses and the amount of information transferred between peers. To do this more or less requires onion routing or other "heavy" approaches to anonymity, and this is beyond scope.
-
-* ZeroTier does not implement complex certificate chains, X.509, or other feature-rich (some would say feature-laden) cryptographic stuff. We only implement the crypto we need to get the job done.
-
-* We don't take extraordinary measures to preserve security under conditions in which an endpoint device has been penetrated by other means (e.g. "rooted" by third party malware) or physicall compromised. If someone steals your keys they've stolen your keys, and if they've "pwned" your device they can easily eavesdrop on everything directly.
-
-## Insecurities and Areas for Improvement
-
-The only perfectly secure system is one that is off. All real world systems have potential security weaknesses. If possible, we like to know what these are and acknowledge their existence.
-
-In some cases we plan to improve these. In other cases we have deliberately decided to "punt" on them in favor of some other priority (see philosophy). We may or may not revisit this decision in the future.
-
-* We don't implement forward secrecy / ephemeral keys. A [discussion of this can be found at the closed GitHub issue for this feature](https://github.com/zerotier/ZeroTierOne/issues/204). In short: we've decided to "punt" on this feature because it introduces complexity and state negotiation. One of the design goals of ZeroTier is "reliability convergence" -- the reliability of ZeroTier virtual networks should rapidly converge with that of the underlying physical wire. Any state that must be negotiated prior to communication multiplies the probability of delay or failure due to packet loss. We *may* revisit this decision at a later date.
-
-## Secure Coding Practices
-
-The first line of defense employed against remote vulnerabilities and other major security flaws is the use of secure coding practices. These are, in no particular order:
-
-* All parsing of remote messages is performed via higher level safe bounds-checked data structures and interfaces. See node/Buffer.hpp for one of the core elements of this.
-
-* C++ exceptions are used to ensure that any unhandled failure or error condition (such as a bounds checking violation) results in the safe and complete termination of message processing. Invalid messages are dropped and ignored.
-
-* Minimalism is a secure coding practice. There is an exponential relationship between complexity and the probability of bugs, and complex designs are much harder to audit and reason about.
-
-* Our build scripts try to enable any OS and compiler level security features such as ASLR and "stack canaries" on non-debug builds.
-
-## Cryptographic Security Practices
-
-* We use [boring crypto](https://cr.yp.to/talks/2015.10.05/slides-djb-20151005-a4.pdf). A single symmetric algorithm (Salsa20/12), a single asymmetric algorithm (Curve25519 ECDH-256), and a single MAC (Poly1305). The way these algorithms are used is identical to how they're used in the NaCl reference implementation. The protocol supports selection of alternative algorithms but only for "future proofing" in the case that a serious flaw is discovered in any of these. Avoding algorithm bloat and cryptographic state negotiation helps guard against down-grade, "oracle," and other protocol level attacks.
-
-* Authenticated encryption is employed with authentication being performed prior to any other operations on received messages. See also: [the cryptographic doom principle](https://moxie.org/blog/the-cryptographic-doom-principle/).
-
-* "Never branch on anything secret" -- deterministic-time comparisons and other operations are used in cryptographic operations. See Utils::secureEq() in node/Utils.hpp.
-
-* OS-derived crypographic random numbers (/dev/urandom or Windows CryptGenRandom) are further randomized using encryption by a secondary key with a secondary source of entropy to guard against CSPRNG bugs. Such OS-level CSPRNG bugs have been found in the past. See Utils::getSecureRandom() in node/Utils.hpp.
-
diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp
index 53b345b4..d9ec76de 100644
--- a/controller/EmbeddedNetworkController.cpp
+++ b/controller/EmbeddedNetworkController.cpp
@@ -56,7 +56,7 @@ using json = nlohmann::json;
#define ZT_NETCONF_MIN_REQUEST_PERIOD 1000
// Nodes are considered active if they've queried in less than this long
-#define ZT_NETCONF_NODE_ACTIVE_THRESHOLD ((ZT_NETWORK_AUTOCONF_DELAY * 2) + 5000)
+#define ZT_NETCONF_NODE_ACTIVE_THRESHOLD (ZT_NETWORK_AUTOCONF_DELAY * 2)
namespace ZeroTier {
@@ -127,7 +127,9 @@ static json _renderRule(ZT_VirtualNetworkRule &rule)
{
char tmp[128];
json r = json::object();
- switch((rule.t) & 0x7f) {
+ const ZT_VirtualNetworkRuleType rt = (ZT_VirtualNetworkRuleType)(rule.t & 0x3f);
+
+ switch(rt) {
case ZT_NETWORK_RULE_ACTION_DROP:
r["type"] = "ACTION_DROP";
break;
@@ -140,6 +142,12 @@ static json _renderRule(ZT_VirtualNetworkRule &rule)
r["flags"] = (unsigned int)rule.v.fwd.flags;
r["length"] = (unsigned int)rule.v.fwd.length;
break;
+ case ZT_NETWORK_RULE_ACTION_WATCH:
+ r["type"] = "ACTION_WATCH";
+ r["address"] = Address(rule.v.fwd.address).toString();
+ r["flags"] = (unsigned int)rule.v.fwd.flags;
+ r["length"] = (unsigned int)rule.v.fwd.length;
+ break;
case ZT_NETWORK_RULE_ACTION_REDIRECT:
r["type"] = "ACTION_REDIRECT";
r["address"] = Address(rule.v.fwd.address).toString();
@@ -148,137 +156,136 @@ static json _renderRule(ZT_VirtualNetworkRule &rule)
case ZT_NETWORK_RULE_ACTION_DEBUG_LOG:
r["type"] = "ACTION_DEBUG_LOG";
break;
- case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS:
- r["type"] = "MATCH_SOURCE_ZEROTIER_ADDRESS";
- r["not"] = ((rule.t & 0x80) != 0);
- r["zt"] = Address(rule.v.zt).toString();
- break;
- case ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS:
- r["type"] = "MATCH_DEST_ZEROTIER_ADDRESS";
- r["not"] = ((rule.t & 0x80) != 0);
- r["zt"] = Address(rule.v.zt).toString();
- break;
- case ZT_NETWORK_RULE_MATCH_VLAN_ID:
- r["type"] = "MATCH_VLAN_ID";
- r["not"] = ((rule.t & 0x80) != 0);
- r["vlanId"] = (unsigned int)rule.v.vlanId;
- break;
- case ZT_NETWORK_RULE_MATCH_VLAN_PCP:
- r["type"] = "MATCH_VLAN_PCP";
- r["not"] = ((rule.t & 0x80) != 0);
- r["vlanPcp"] = (unsigned int)rule.v.vlanPcp;
- break;
- case ZT_NETWORK_RULE_MATCH_VLAN_DEI:
- r["type"] = "MATCH_VLAN_DEI";
- r["not"] = ((rule.t & 0x80) != 0);
- r["vlanDei"] = (unsigned int)rule.v.vlanDei;
- break;
- case ZT_NETWORK_RULE_MATCH_ETHERTYPE:
- r["type"] = "MATCH_ETHERTYPE";
- r["not"] = ((rule.t & 0x80) != 0);
- r["etherType"] = (unsigned int)rule.v.etherType;
- break;
- case ZT_NETWORK_RULE_MATCH_MAC_SOURCE:
- r["type"] = "MATCH_MAC_SOURCE";
- r["not"] = ((rule.t & 0x80) != 0);
- Utils::snprintf(tmp,sizeof(tmp),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(unsigned int)rule.v.mac[0],(unsigned int)rule.v.mac[1],(unsigned int)rule.v.mac[2],(unsigned int)rule.v.mac[3],(unsigned int)rule.v.mac[4],(unsigned int)rule.v.mac[5]);
- r["mac"] = tmp;
- break;
- case ZT_NETWORK_RULE_MATCH_MAC_DEST:
- r["type"] = "MATCH_MAC_DEST";
- r["not"] = ((rule.t & 0x80) != 0);
- Utils::snprintf(tmp,sizeof(tmp),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(unsigned int)rule.v.mac[0],(unsigned int)rule.v.mac[1],(unsigned int)rule.v.mac[2],(unsigned int)rule.v.mac[3],(unsigned int)rule.v.mac[4],(unsigned int)rule.v.mac[5]);
- r["mac"] = tmp;
- break;
- case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE:
- r["type"] = "MATCH_IPV4_SOURCE";
- r["not"] = ((rule.t & 0x80) != 0);
- r["ip"] = InetAddress(&(rule.v.ipv4.ip),4,(unsigned int)rule.v.ipv4.mask).toString();
- break;
- case ZT_NETWORK_RULE_MATCH_IPV4_DEST:
- r["type"] = "MATCH_IPV4_DEST";
- r["not"] = ((rule.t & 0x80) != 0);
- r["ip"] = InetAddress(&(rule.v.ipv4.ip),4,(unsigned int)rule.v.ipv4.mask).toString();
- break;
- case ZT_NETWORK_RULE_MATCH_IPV6_SOURCE:
- r["type"] = "MATCH_IPV6_SOURCE";
- r["not"] = ((rule.t & 0x80) != 0);
- r["ip"] = InetAddress(rule.v.ipv6.ip,16,(unsigned int)rule.v.ipv6.mask).toString();
- break;
- case ZT_NETWORK_RULE_MATCH_IPV6_DEST:
- r["type"] = "MATCH_IPV6_DEST";
- r["not"] = ((rule.t & 0x80) != 0);
- r["ip"] = InetAddress(rule.v.ipv6.ip,16,(unsigned int)rule.v.ipv6.mask).toString();
- break;
- case ZT_NETWORK_RULE_MATCH_IP_TOS:
- r["type"] = "MATCH_IP_TOS";
- r["not"] = ((rule.t & 0x80) != 0);
- r["ipTos"] = (unsigned int)rule.v.ipTos;
- break;
- case ZT_NETWORK_RULE_MATCH_IP_PROTOCOL:
- r["type"] = "MATCH_IP_PROTOCOL";
- r["not"] = ((rule.t & 0x80) != 0);
- r["ipProtocol"] = (unsigned int)rule.v.ipProtocol;
- break;
- case ZT_NETWORK_RULE_MATCH_ICMP:
- r["type"] = "MATCH_ICMP";
- r["not"] = ((rule.t & 0x80) != 0);
- r["type"] = (unsigned int)rule.v.icmp.type;
- if ((rule.v.icmp.flags & 0x01) != 0)
- r["code"] = (unsigned int)rule.v.icmp.code;
- else r["code"] = json();
- break;
- case ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE:
- r["type"] = "MATCH_IP_SOURCE_PORT_RANGE";
- r["not"] = ((rule.t & 0x80) != 0);
- r["start"] = (unsigned int)rule.v.port[0];
- r["end"] = (unsigned int)rule.v.port[1];
- break;
- case ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE:
- r["type"] = "MATCH_IP_DEST_PORT_RANGE";
- r["not"] = ((rule.t & 0x80) != 0);
- r["start"] = (unsigned int)rule.v.port[0];
- r["end"] = (unsigned int)rule.v.port[1];
- break;
- case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS:
- r["type"] = "MATCH_CHARACTERISTICS";
- r["not"] = ((rule.t & 0x80) != 0);
- Utils::snprintf(tmp,sizeof(tmp),"%.16llx",rule.v.characteristics[0]);
- r["mask"] = tmp;
- Utils::snprintf(tmp,sizeof(tmp),"%.16llx",rule.v.characteristics[1]);
- r["value"] = tmp;
- break;
- case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE:
- r["type"] = "MATCH_FRAME_SIZE_RANGE";
- r["not"] = ((rule.t & 0x80) != 0);
- r["start"] = (unsigned int)rule.v.frameSize[0];
- r["end"] = (unsigned int)rule.v.frameSize[1];
- break;
- case ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE:
- r["type"] = "MATCH_TAGS_DIFFERENCE";
- r["not"] = ((rule.t & 0x80) != 0);
- r["id"] = rule.v.tag.id;
- r["value"] = rule.v.tag.value;
- break;
- case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND:
- r["type"] = "MATCH_TAGS_BITWISE_AND";
- r["not"] = ((rule.t & 0x80) != 0);
- r["id"] = rule.v.tag.id;
- r["value"] = rule.v.tag.value;
- break;
- case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR:
- r["type"] = "MATCH_TAGS_BITWISE_OR";
- r["not"] = ((rule.t & 0x80) != 0);
- r["id"] = rule.v.tag.id;
- r["value"] = rule.v.tag.value;
+ default:
break;
- case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR:
- r["type"] = "MATCH_TAGS_BITWISE_XOR";
+ }
+
+ if (r.size() == 0) {
+ switch(rt) {
+ case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS:
+ r["type"] = "MATCH_SOURCE_ZEROTIER_ADDRESS";
+ r["zt"] = Address(rule.v.zt).toString();
+ break;
+ case ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS:
+ r["type"] = "MATCH_DEST_ZEROTIER_ADDRESS";
+ r["zt"] = Address(rule.v.zt).toString();
+ break;
+ case ZT_NETWORK_RULE_MATCH_VLAN_ID:
+ r["type"] = "MATCH_VLAN_ID";
+ r["vlanId"] = (unsigned int)rule.v.vlanId;
+ break;
+ case ZT_NETWORK_RULE_MATCH_VLAN_PCP:
+ r["type"] = "MATCH_VLAN_PCP";
+ r["vlanPcp"] = (unsigned int)rule.v.vlanPcp;
+ break;
+ case ZT_NETWORK_RULE_MATCH_VLAN_DEI:
+ r["type"] = "MATCH_VLAN_DEI";
+ r["vlanDei"] = (unsigned int)rule.v.vlanDei;
+ break;
+ case ZT_NETWORK_RULE_MATCH_MAC_SOURCE:
+ r["type"] = "MATCH_MAC_SOURCE";
+ Utils::snprintf(tmp,sizeof(tmp),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(unsigned int)rule.v.mac[0],(unsigned int)rule.v.mac[1],(unsigned int)rule.v.mac[2],(unsigned int)rule.v.mac[3],(unsigned int)rule.v.mac[4],(unsigned int)rule.v.mac[5]);
+ r["mac"] = tmp;
+ break;
+ case ZT_NETWORK_RULE_MATCH_MAC_DEST:
+ r["type"] = "MATCH_MAC_DEST";
+ Utils::snprintf(tmp,sizeof(tmp),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(unsigned int)rule.v.mac[0],(unsigned int)rule.v.mac[1],(unsigned int)rule.v.mac[2],(unsigned int)rule.v.mac[3],(unsigned int)rule.v.mac[4],(unsigned int)rule.v.mac[5]);
+ r["mac"] = tmp;
+ break;
+ case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE:
+ r["type"] = "MATCH_IPV4_SOURCE";
+ r["ip"] = InetAddress(&(rule.v.ipv4.ip),4,(unsigned int)rule.v.ipv4.mask).toString();
+ break;
+ case ZT_NETWORK_RULE_MATCH_IPV4_DEST:
+ r["type"] = "MATCH_IPV4_DEST";
+ r["ip"] = InetAddress(&(rule.v.ipv4.ip),4,(unsigned int)rule.v.ipv4.mask).toString();
+ break;
+ case ZT_NETWORK_RULE_MATCH_IPV6_SOURCE:
+ r["type"] = "MATCH_IPV6_SOURCE";
+ r["ip"] = InetAddress(rule.v.ipv6.ip,16,(unsigned int)rule.v.ipv6.mask).toString();
+ break;
+ case ZT_NETWORK_RULE_MATCH_IPV6_DEST:
+ r["type"] = "MATCH_IPV6_DEST";
+ r["ip"] = InetAddress(rule.v.ipv6.ip,16,(unsigned int)rule.v.ipv6.mask).toString();
+ break;
+ case ZT_NETWORK_RULE_MATCH_IP_TOS:
+ r["type"] = "MATCH_IP_TOS";
+ r["ipTos"] = (unsigned int)rule.v.ipTos;
+ break;
+ case ZT_NETWORK_RULE_MATCH_IP_PROTOCOL:
+ r["type"] = "MATCH_IP_PROTOCOL";
+ r["ipProtocol"] = (unsigned int)rule.v.ipProtocol;
+ break;
+ case ZT_NETWORK_RULE_MATCH_ETHERTYPE:
+ r["type"] = "MATCH_ETHERTYPE";
+ r["etherType"] = (unsigned int)rule.v.etherType;
+ break;
+ case ZT_NETWORK_RULE_MATCH_ICMP:
+ r["type"] = "MATCH_ICMP";
+ r["icmpType"] = (unsigned int)rule.v.icmp.type;
+ if ((rule.v.icmp.flags & 0x01) != 0)
+ r["icmpCode"] = (unsigned int)rule.v.icmp.code;
+ else r["icmpCode"] = json();
+ break;
+ case ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE:
+ r["type"] = "MATCH_IP_SOURCE_PORT_RANGE";
+ r["start"] = (unsigned int)rule.v.port[0];
+ r["end"] = (unsigned int)rule.v.port[1];
+ break;
+ case ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE:
+ r["type"] = "MATCH_IP_DEST_PORT_RANGE";
+ r["start"] = (unsigned int)rule.v.port[0];
+ r["end"] = (unsigned int)rule.v.port[1];
+ break;
+ case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS:
+ r["type"] = "MATCH_CHARACTERISTICS";
+ Utils::snprintf(tmp,sizeof(tmp),"%.16llx",rule.v.characteristics);
+ r["mask"] = tmp;
+ break;
+ case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE:
+ r["type"] = "MATCH_FRAME_SIZE_RANGE";
+ r["start"] = (unsigned int)rule.v.frameSize[0];
+ r["end"] = (unsigned int)rule.v.frameSize[1];
+ break;
+ case ZT_NETWORK_RULE_MATCH_RANDOM:
+ r["type"] = "MATCH_RANDOM";
+ r["probability"] = (unsigned long)rule.v.randomProbability;
+ break;
+ case ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE:
+ r["type"] = "MATCH_TAGS_DIFFERENCE";
+ r["id"] = rule.v.tag.id;
+ r["value"] = rule.v.tag.value;
+ break;
+ case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND:
+ r["type"] = "MATCH_TAGS_BITWISE_AND";
+ r["id"] = rule.v.tag.id;
+ r["value"] = rule.v.tag.value;
+ break;
+ case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR:
+ r["type"] = "MATCH_TAGS_BITWISE_OR";
+ r["id"] = rule.v.tag.id;
+ r["value"] = rule.v.tag.value;
+ break;
+ case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR:
+ r["type"] = "MATCH_TAGS_BITWISE_XOR";
+ r["id"] = rule.v.tag.id;
+ r["value"] = rule.v.tag.value;
+ break;
+ case ZT_NETWORK_RULE_MATCH_TAGS_EQUAL:
+ r["type"] = "MATCH_TAGS_EQUAL";
+ r["id"] = rule.v.tag.id;
+ r["value"] = rule.v.tag.value;
+ break;
+ default:
+ break;
+ }
+
+ if (r.size() > 0) {
r["not"] = ((rule.t & 0x80) != 0);
- r["id"] = rule.v.tag.id;
- r["value"] = rule.v.tag.value;
- break;
+ r["or"] = ((rule.t & 0x40) != 0);
+ }
}
+
return r;
}
@@ -286,11 +293,16 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule)
{
if (!r.is_object())
return false;
+
const std::string t(_jS(r["type"],""));
memset(&rule,0,sizeof(ZT_VirtualNetworkRule));
+
if (_jB(r["not"],false))
rule.t = 0x80;
else rule.t = 0x00;
+ if (_jB(r["or"],false))
+ rule.t |= 0x40;
+
if (t == "ACTION_DROP") {
rule.t |= ZT_NETWORK_RULE_ACTION_DROP;
return true;
@@ -303,6 +315,12 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule)
rule.v.fwd.flags = (uint32_t)(_jI(r["flags"],0ULL) & 0xffffffffULL);
rule.v.fwd.length = (uint16_t)(_jI(r["length"],0ULL) & 0xffffULL);
return true;
+ } else if (t == "ACTION_WATCH") {
+ rule.t |= ZT_NETWORK_RULE_ACTION_WATCH;
+ rule.v.fwd.address = Utils::hexStrToU64(_jS(r["address"],"0").c_str()) & 0xffffffffffULL;
+ rule.v.fwd.flags = (uint32_t)(_jI(r["flags"],0ULL) & 0xffffffffULL);
+ rule.v.fwd.length = (uint16_t)(_jI(r["length"],0ULL) & 0xffffULL);
+ return true;
} else if (t == "ACTION_REDIRECT") {
rule.t |= ZT_NETWORK_RULE_ACTION_REDIRECT;
rule.v.fwd.address = Utils::hexStrToU64(_jS(r["zt"],"0").c_str()) & 0xffffffffffULL;
@@ -331,10 +349,6 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule)
rule.t |= ZT_NETWORK_RULE_MATCH_VLAN_DEI;
rule.v.vlanDei = (uint8_t)(_jI(r["vlanDei"],0ULL) & 0xffULL);
return true;
- } else if (t == "MATCH_ETHERTYPE") {
- rule.t |= ZT_NETWORK_RULE_MATCH_ETHERTYPE;
- rule.v.etherType = (uint16_t)(_jI(r["etherType"],0ULL) & 0xffffULL);
- return true;
} else if (t == "MATCH_MAC_SOURCE") {
rule.t |= ZT_NETWORK_RULE_MATCH_MAC_SOURCE;
const std::string mac(_jS(r["mac"],"0"));
@@ -381,10 +395,14 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule)
rule.t |= ZT_NETWORK_RULE_MATCH_IP_PROTOCOL;
rule.v.ipProtocol = (uint8_t)(_jI(r["ipProtocol"],0ULL) & 0xffULL);
return true;
+ } else if (t == "MATCH_ETHERTYPE") {
+ rule.t |= ZT_NETWORK_RULE_MATCH_ETHERTYPE;
+ rule.v.etherType = (uint16_t)(_jI(r["etherType"],0ULL) & 0xffffULL);
+ return true;
} else if (t == "MATCH_ICMP") {
rule.t |= ZT_NETWORK_RULE_MATCH_ICMP;
- rule.v.icmp.type = (uint8_t)(_jI(r["type"],0ULL) & 0xffULL);
- json &code = r["code"];
+ rule.v.icmp.type = (uint8_t)(_jI(r["icmpType"],0ULL) & 0xffULL);
+ json &code = r["icmpCode"];
if (code.is_null()) {
rule.v.icmp.code = 0;
rule.v.icmp.flags = 0x00;
@@ -406,21 +424,12 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule)
} else if (t == "MATCH_CHARACTERISTICS") {
rule.t |= ZT_NETWORK_RULE_MATCH_CHARACTERISTICS;
if (r.count("mask")) {
- auto v = r["mask"];
+ json &v = r["mask"];
if (v.is_number()) {
- rule.v.characteristics[0] = v;
+ rule.v.characteristics = v;
} else {
std::string tmp = v;
- rule.v.characteristics[0] = Utils::hexStrToU64(tmp.c_str());
- }
- }
- if (r.count("value")) {
- auto v = r["value"];
- if (v.is_number()) {
- rule.v.characteristics[1] = v;
- } else {
- std::string tmp = v;
- rule.v.characteristics[1] = Utils::hexStrToU64(tmp.c_str());
+ rule.v.characteristics = Utils::hexStrToU64(tmp.c_str());
}
}
return true;
@@ -429,6 +438,9 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule)
rule.v.frameSize[0] = (uint16_t)(_jI(r["start"],0ULL) & 0xffffULL);
rule.v.frameSize[1] = (uint16_t)(_jI(r["end"],(uint64_t)rule.v.frameSize[0]) & 0xffffULL);
return true;
+ } else if (t == "MATCH_RANDOM") {
+ rule.t |= ZT_NETWORK_RULE_MATCH_RANDOM;
+ rule.v.randomProbability = (uint32_t)(_jI(r["probability"],0ULL) & 0xffffffffULL);
} else if (t == "MATCH_TAGS_DIFFERENCE") {
rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE;
rule.v.tag.id = (uint32_t)(_jI(r["id"],0ULL) & 0xffffffffULL);
@@ -449,7 +461,13 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule)
rule.v.tag.id = (uint32_t)(_jI(r["id"],0ULL) & 0xffffffffULL);
rule.v.tag.value = (uint32_t)(_jI(r["value"],0ULL) & 0xffffffffULL);
return true;
+ } else if (t == "MATCH_TAGS_EQUAL") {
+ rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_EQUAL;
+ rule.v.tag.id = (uint32_t)(_jI(r["id"],0ULL) & 0xffffffffULL);
+ rule.v.tag.value = (uint32_t)(_jI(r["value"],0ULL) & 0xffffffffULL);
+ return true;
}
+
return false;
}
@@ -499,19 +517,6 @@ void EmbeddedNetworkController::threadMain()
lastUpdatedNetworkMemberCache = OSUtils::now();
}
- { // Every 25ms we push up to 50 network refreshes, which amounts to a max of about 300-500kb/sec
- unsigned int count = 0;
- Mutex::Lock _l(_refreshQueue_m);
- while (_refreshQueue.size() > 0) {
- _Refresh &r = _refreshQueue.front();
- if (_node)
- _node->pushNetworkRefresh(r.dest,r.nwid,r.blacklistAddresses,r.blacklistThresholds,r.numBlacklistEntries);
- _refreshQueue.pop_front();
- if (++count >= 50)
- break;
- }
- }
-
Thread::sleep(25);
}
}
@@ -992,12 +997,7 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpGET(
if (!member.size())
return 404;
- char addrs[24];
- Utils::snprintf(addrs,sizeof(addrs),"%.10llx",address);
-
- // Add non-persisted fields
- member["clock"] = OSUtils::now();
-
+ _addMemberNonPersistedFields(member,OSUtils::now());
responseBody = member.dump(2);
responseContentType = "application/json";
@@ -1023,32 +1023,6 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpGET(
return 200;
}
- } else if ((path[2] == "active")&&(path.size() == 3)) {
-
- responseBody = "{";
- std::vector<std::string> members(OSUtils::listSubdirectories((_networkBP(nwid,false) + ZT_PATH_SEPARATOR_S + "member").c_str()));
- const uint64_t threshold = OSUtils::now() - ZT_NETCONF_NODE_ACTIVE_THRESHOLD;
- for(std::vector<std::string>::iterator i(members.begin());i!=members.end();++i) {
- if (i->length() == ZT_ADDRESS_LENGTH_HEX) {
- json member(_readJson(_memberJP(nwid,Address(Utils::hexStrToU64(i->c_str())),false)));
- if (member.size()) {
- auto recentLog = member["recentLog"];
- if ((recentLog.is_array())&&(recentLog.size() > 0)) {
- auto mostRecentLog = recentLog[0];
- if ((mostRecentLog.is_object())&&(_jI(mostRecentLog["ts"],0ULL) >= threshold)) {
- responseBody.append((responseBody.length() == 1) ? "\"" : ",\"");
- responseBody.append(*i);
- responseBody.append("\":");
- responseBody.append(mostRecentLog.dump());
- }
- }
- }
- }
- }
- responseBody.push_back('}');
- responseContentType = "application/json";
- return 200;
-
} else if ((path[2] == "test")&&(path.size() >= 4)) {
Mutex::Lock _l(_circuitTests_m);
@@ -1243,15 +1217,6 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
_networkMemberCache[nwid][Address(address)] = member;
}
- {
- Mutex::Lock _l(_refreshQueue_m);
- _refreshQueue.push_back(_Refresh());
- _Refresh &r = _refreshQueue.back();
- r.dest = Address(address);
- r.nwid = nwid;
- r.numBlacklistEntries = 0;
- }
-
// Add non-persisted fields
member["clock"] = now;
@@ -1343,28 +1308,28 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
if (b.count("multicastLimit")) network["multicastLimit"] = _jI(b["multicastLimit"],32ULL);
if (b.count("v4AssignMode")) {
- json &nv4m = network["v4AssignMode"];
- if (!nv4m.is_object()) nv4m = json::object();
- if (b["v4AssignMode"].is_string()) { // backward compatibility
- nv4m["zt"] = (_jS(b["v4AssignMode"],"") == "zt");
- } else if (b["v4AssignMode"].is_object()) {
- json &v4m = b["v4AssignMode"];
- if (v4m.count("zt")) nv4m["zt"] = _jB(v4m["zt"],false);
- }
- if (!nv4m.count("zt")) nv4m["zt"] = false;
+ json nv4m;
+ json &v4m = b["v4AssignMode"];
+ if (v4m.is_string()) { // backward compatibility
+ nv4m["zt"] = (_jS(v4m,"") == "zt");
+ } else if (v4m.is_object()) {
+ nv4m["zt"] = _jB(v4m["zt"],false);
+ } else nv4m["zt"] = false;
+ network["v4AssignMode"] = nv4m;
}
if (b.count("v6AssignMode")) {
- json &nv6m = network["v6AssignMode"];
+ json nv6m;
+ json &v6m = b["v6AssignMode"];
if (!nv6m.is_object()) nv6m = json::object();
- if (b["v6AssignMode"].is_string()) { // backward compatibility
- std::vector<std::string> v6m(Utils::split(_jS(b["v6AssignMode"],"").c_str(),",","",""));
- std::sort(v6m.begin(),v6m.end());
- v6m.erase(std::unique(v6m.begin(),v6m.end()),v6m.end());
+ if (v6m.is_string()) { // backward compatibility
+ std::vector<std::string> v6ms(Utils::split(_jS(v6m,"").c_str(),",","",""));
+ std::sort(v6ms.begin(),v6ms.end());
+ v6ms.erase(std::unique(v6ms.begin(),v6ms.end()),v6ms.end());
nv6m["rfc4193"] = false;
nv6m["zt"] = false;
nv6m["6plane"] = false;
- for(std::vector<std::string>::iterator i(v6m.begin());i!=v6m.end();++i) {
+ for(std::vector<std::string>::iterator i(v6ms.begin());i!=v6ms.end();++i) {
if (*i == "rfc4193")
nv6m["rfc4193"] = true;
else if (*i == "zt")
@@ -1372,15 +1337,16 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
else if (*i == "6plane")
nv6m["6plane"] = true;
}
- } else if (b["v6AssignMode"].is_object()) {
- json &v6m = b["v6AssignMode"];
+ } else if (v6m.is_object()) {
if (v6m.count("rfc4193")) nv6m["rfc4193"] = _jB(v6m["rfc4193"],false);
if (v6m.count("zt")) nv6m["zt"] = _jB(v6m["zt"],false);
if (v6m.count("6plane")) nv6m["6plane"] = _jB(v6m["6plane"],false);
+ } else {
+ nv6m["rfc4193"] = false;
+ nv6m["zt"] = false;
+ nv6m["6plane"] = false;
}
- if (!nv6m.count("rfc4193")) nv6m["rfc4193"] = false;
- if (!nv6m.count("zt")) nv6m["zt"] = false;
- if (!nv6m.count("6plane")) nv6m["6plane"] = false;
+ network["v6AssignMode"] = nv6m;
}
if (b.count("routes")) {
diff --git a/controller/EmbeddedNetworkController.hpp b/controller/EmbeddedNetworkController.hpp
index 1bfd9577..99e1836d 100644
--- a/controller/EmbeddedNetworkController.hpp
+++ b/controller/EmbeddedNetworkController.hpp
@@ -165,12 +165,13 @@ private:
if (!network.count("v6AssignMode")) network["v6AssignMode"] = {{"rfc4193",false},{"zt",false},{"6plane",false}};
if (!network.count("authTokens")) network["authTokens"] = nlohmann::json::array();
if (!network.count("capabilities")) network["capabilities"] = nlohmann::json::array();
+ if (!network.count("ipAssignmentPools")) network["ipAssignmentPools"] = nlohmann::json::array();
if (!network.count("rules")) {
// If unspecified, rules are set to allow anything and behave like a flat L2 segment
- network["rules"] = {
+ network["rules"] = {{
{ "not",false },
{ "type","ACTION_ACCEPT" }
- };
+ }};
}
network["objtype"] = "network";
}
@@ -181,6 +182,10 @@ private:
network["activeMemberCount"] = nmi.activeMemberCount;
network["totalMemberCount"] = nmi.totalMemberCount;
}
+ inline void _addMemberNonPersistedFields(nlohmann::json &member,uint64_t now)
+ {
+ member["clock"] = now;
+ }
// These are const after construction
Node *const _node;
@@ -199,18 +204,6 @@ private:
std::map< std::pair<uint64_t,uint64_t>,uint64_t > _lastRequestTime;
Mutex _lastRequestTime_m;
- // Queue of network member refreshes to be pushed
- struct _Refresh
- {
- Address dest;
- uint64_t nwid;
- uint64_t blacklistAddresses[64];
- uint64_t blacklistThresholds[64];
- unsigned int numBlacklistEntries;
- };
- std::list< _Refresh > _refreshQueue;
- Mutex _refreshQueue_m;
-
Thread _daemon;
volatile bool _daemonRun;
};
diff --git a/debian/postinst b/debian/postinst
new file mode 100644
index 00000000..ecd148a4
--- /dev/null
+++ b/debian/postinst
@@ -0,0 +1,9 @@
+#!/bin/sh -e
+
+case "$1" in
+ configure)
+ adduser --system --group --home /var/lib/zerotier-one --no-create-home zerotier-one
+ ;;
+esac
+
+#DEBHELPER#
diff --git a/include/ZeroTierOne.h b/include/ZeroTierOne.h
index e0f6ca28..17112e90 100644
--- a/include/ZeroTierOne.h
+++ b/include/ZeroTierOne.h
@@ -491,15 +491,15 @@ enum ZT_VirtualNetworkType
/**
* The type of a virtual network rules table entry
*
- * These must range from 0 to 127 (0x7f) because the most significant bit
- * is reserved as a NOT flag.
+ * These must be from 0 to 63 since the most significant two bits of each
+ * rule type are NOT (MSB) and AND/OR.
*
* Each rule is composed of zero or more MATCHes followed by an ACTION.
* An ACTION with no MATCHes is always taken.
*/
enum ZT_VirtualNetworkRuleType
{
- // 0 to 31 reserved for actions
+ // 0 to 15 reserved for actions
/**
* Drop frame
@@ -517,141 +517,57 @@ enum ZT_VirtualNetworkRuleType
ZT_NETWORK_RULE_ACTION_TEE = 2,
/**
- * Drop and redirect this frame to another node (by ZT address)
- */
- ZT_NETWORK_RULE_ACTION_REDIRECT = 3,
-
- /**
- * Log if match and if rule debugging is enabled in the build, otherwise does nothing (for developers)
+ * Exactly like TEE but mandates ACKs from observer
*/
- ZT_NETWORK_RULE_ACTION_DEBUG_LOG = 4,
+ ZT_NETWORK_RULE_ACTION_WATCH = 3,
/**
- * Maximum ID for an ACTION, anything higher is a MATCH
- */
- ZT_NETWORK_RULE_ACTION__MAX_ID = 31,
-
- // 32 to 127 reserved for match criteria
-
- /**
- * Source ZeroTier address -- analogous to an Ethernet port ID on a switch
- */
- ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS = 32,
-
- /**
- * Destination ZeroTier address -- analogous to an Ethernet port ID on a switch
- */
- ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS = 33,
-
- /**
- * Ethernet VLAN ID
- */
- ZT_NETWORK_RULE_MATCH_VLAN_ID = 34,
-
- /**
- * Ethernet VLAN PCP
+ * Drop and redirect this frame to another node (by ZT address)
*/
- ZT_NETWORK_RULE_MATCH_VLAN_PCP = 35,
+ ZT_NETWORK_RULE_ACTION_REDIRECT = 4,
/**
- * Ethernet VLAN DEI
+ * Log if match and if rule debugging is enabled in the build, otherwise does nothing (for developers)
*/
- ZT_NETWORK_RULE_MATCH_VLAN_DEI = 36,
+ ZT_NETWORK_RULE_ACTION_DEBUG_LOG = 5,
/**
- * Ethernet frame type
+ * Maximum ID for an ACTION, anything higher is a MATCH
*/
+ ZT_NETWORK_RULE_ACTION__MAX_ID = 15,
+
+ // 16 to 63 reserved for match criteria
+
+ ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS = 24,
+ ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS = 25,
+ ZT_NETWORK_RULE_MATCH_VLAN_ID = 26,
+ ZT_NETWORK_RULE_MATCH_VLAN_PCP = 27,
+ ZT_NETWORK_RULE_MATCH_VLAN_DEI = 28,
+ ZT_NETWORK_RULE_MATCH_MAC_SOURCE = 29,
+ ZT_NETWORK_RULE_MATCH_MAC_DEST = 30,
+ ZT_NETWORK_RULE_MATCH_IPV4_SOURCE = 31,
+ ZT_NETWORK_RULE_MATCH_IPV4_DEST = 32,
+ ZT_NETWORK_RULE_MATCH_IPV6_SOURCE = 33,
+ ZT_NETWORK_RULE_MATCH_IPV6_DEST = 34,
+ ZT_NETWORK_RULE_MATCH_IP_TOS = 35,
+ ZT_NETWORK_RULE_MATCH_IP_PROTOCOL = 36,
ZT_NETWORK_RULE_MATCH_ETHERTYPE = 37,
-
- /**
- * Source Ethernet MAC address
- */
- ZT_NETWORK_RULE_MATCH_MAC_SOURCE = 38,
-
- /**
- * Destination Ethernet MAC address
- */
- ZT_NETWORK_RULE_MATCH_MAC_DEST = 39,
-
- /**
- * Source IPv4 address
- */
- ZT_NETWORK_RULE_MATCH_IPV4_SOURCE = 40,
-
- /**
- * Destination IPv4 address
- */
- ZT_NETWORK_RULE_MATCH_IPV4_DEST = 41,
-
- /**
- * Source IPv6 address
- */
- ZT_NETWORK_RULE_MATCH_IPV6_SOURCE = 42,
-
- /**
- * Destination IPv6 address
- */
- ZT_NETWORK_RULE_MATCH_IPV6_DEST = 43,
-
- /**
- * IP TOS (type of service)
- */
- ZT_NETWORK_RULE_MATCH_IP_TOS = 44,
-
- /**
- * IP protocol
- */
- ZT_NETWORK_RULE_MATCH_IP_PROTOCOL = 45,
-
- /**
- * ICMP type and possibly code (does not match if not ICMP)
- */
- ZT_NETWORK_RULE_MATCH_ICMP = 46,
-
- /**
- * IP source port range (start-end, inclusive)
- */
- ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE = 47,
-
- /**
- * IP destination port range (start-end, inclusive)
- */
- ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE = 48,
-
- /**
- * Packet characteristics (set of flags)
- */
- ZT_NETWORK_RULE_MATCH_CHARACTERISTICS = 49,
-
- /**
- * Frame size range (start-end, inclusive)
- */
- ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE = 50,
-
- /**
- * Match if local and remote tags differ by no more than value, use 0 to check for equality
- */
- ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE = 51,
-
- /**
- * Match if local and remote tags ANDed together equal value.
- */
- ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND = 52,
-
- /**
- * Match if local and remote tags ANDed together equal value.
- */
- ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR = 53,
-
- /**
- * Match if local and remote tags XORed together equal value.
- */
- ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR = 54,
+ ZT_NETWORK_RULE_MATCH_ICMP = 38,
+ ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE = 39,
+ ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE = 40,
+ ZT_NETWORK_RULE_MATCH_CHARACTERISTICS = 41,
+ ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE = 42,
+ ZT_NETWORK_RULE_MATCH_RANDOM = 43,
+ ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE = 44,
+ ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND = 45,
+ ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR = 46,
+ ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR = 47,
+ ZT_NETWORK_RULE_MATCH_TAGS_EQUAL = 48,
/**
* Maximum ID allowed for a MATCH entry in the rules table
*/
- ZT_NETWORK_RULE_MATCH__MAX_ID = 127
+ ZT_NETWORK_RULE_MATCH__MAX_ID = 63
};
/**
@@ -703,7 +619,7 @@ typedef struct
/**
* Packet characteristic flags being matched
*/
- uint64_t characteristics[2];
+ uint64_t characteristics;
/**
* IP port range -- start-end inclusive -- host byte order
@@ -716,6 +632,11 @@ typedef struct
uint64_t zt;
/**
+ * 0 = never, UINT32_MAX = always
+ */
+ uint32_t randomProbability;
+
+ /**
* 48-bit Ethernet MAC address in big-endian order
*/
uint8_t mac[6];
@@ -1924,27 +1845,6 @@ enum ZT_ResultCode ZT_Node_circuitTestBegin(ZT_Node *node,ZT_CircuitTest *test,v
void ZT_Node_circuitTestEnd(ZT_Node *node,ZT_CircuitTest *test);
/**
- * Push a network refresh
- *
- * This is used by network controller implementations to send a
- * NETWORK_CONFIG_REFRESH message to tell a node to refresh its
- * config and to optionally push one or more credential timestamp
- * blacklist thresholds for members of the network.
- *
- * Code outside a controller implementation will have no use for
- * this as these messages are ignored if they do not come from a
- * controller.
- *
- * @param node Node instance
- * @param dest ZeroTier address of destination to which to send NETWORK_CONFIG_REFRESH
- * @param nwid Network ID
- * @param blacklistAddresses Array of ZeroTier addresses of network members to set timestamp blacklists for
- * @param blacklistBeforeTimestamps Timestamps before which to blacklist credentials for each corresponding address in blacklistAddresses[]
- * @param blacklistCount Size of blacklistAddresses[] and blacklistBeforeTimestamps[]
- */
-void ZT_Node_pushNetworkRefresh(ZT_Node *node,uint64_t dest,uint64_t nwid,const uint64_t *blacklistAddresses,const uint64_t *blacklistBeforeTimestamps,unsigned int blacklistCount);
-
-/**
* Initialize cluster operation
*
* This initializes the internal structures and state for cluster operation.
diff --git a/java/jni/Android.mk b/java/jni/Android.mk
index 6b5d4a4b..73eb2c46 100644
--- a/java/jni/Android.mk
+++ b/java/jni/Android.mk
@@ -5,7 +5,7 @@ include $(CLEAR_VARS)
LOCAL_MODULE := ZeroTierOneJNI
LOCAL_C_INCLUDES := $(ZT1)/include
LOCAL_C_INCLUDES += $(ZT1)/node
-LOCAL_LDLIBS := -llog
+LOCAL_LDLIBS := -llog -latomic
# LOCAL_CFLAGS := -g
# ZeroTierOne SDK source files
@@ -28,6 +28,7 @@ LOCAL_SRC_FILES := \
$(ZT1)/node/Path.cpp \
$(ZT1)/node/Peer.cpp \
$(ZT1)/node/Poly1305.cpp \
+ $(ZT1)/node/Revocation.cpp \
$(ZT1)/node/Salsa20.cpp \
$(ZT1)/node/SelfAwareness.cpp \
$(ZT1)/node/SHA512.cpp \
diff --git a/java/jni/ZT_jniutils.cpp b/java/jni/ZT_jniutils.cpp
index 6faa91a4..27ca7374 100644
--- a/java/jni/ZT_jniutils.cpp
+++ b/java/jni/ZT_jniutils.cpp
@@ -1,3 +1,21 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
#include "ZT_jniutils.h"
#include "ZT_jnilookup.h"
#include <string>
@@ -163,7 +181,7 @@ jobject createPeerRole(JNIEnv *env, ZT_PeerRole role)
fieldName = "PEER_ROLE_LEAF";
break;
case ZT_PEER_ROLE_UPSTREAM:
- fieldName = "PEER_ROLE_RELAY";
+ fieldName = "PEER_ROLE_UPSTREAM";
break;
case ZT_PEER_ROLE_ROOT:
fieldName = "PEER_ROLE_ROOTS";
diff --git a/java/jni/ZT_jniutils.h b/java/jni/ZT_jniutils.h
index 4dec7201..e35d4f42 100644
--- a/java/jni/ZT_jniutils.h
+++ b/java/jni/ZT_jniutils.h
@@ -1,3 +1,21 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
#ifndef ZT_jniutils_h_
#define ZT_jniutils_h_
#include <stdio.h>
diff --git a/java/src/com/zerotier/sdk/PeerRole.java b/java/src/com/zerotier/sdk/PeerRole.java
index d7d55f05..f281c1b6 100644
--- a/java/src/com/zerotier/sdk/PeerRole.java
+++ b/java/src/com/zerotier/sdk/PeerRole.java
@@ -34,9 +34,9 @@ public enum PeerRole {
PEER_ROLE_LEAF,
/**
- * relay node
+ * upstream node
*/
- PEER_ROLE_RELAY,
+ PEER_ROLE_UPSTREAM,
/**
* root server
diff --git a/macui/ZeroTier One.xcodeproj/project.pbxproj b/macui/ZeroTier One.xcodeproj/project.pbxproj
new file mode 100644
index 00000000..900daacb
--- /dev/null
+++ b/macui/ZeroTier One.xcodeproj/project.pbxproj
@@ -0,0 +1,382 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 932D472F1D1CD499004BCFE2 /* ZeroTierIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 932D472E1D1CD499004BCFE2 /* ZeroTierIcon.icns */; };
+ 932D47331D1CD861004BCFE2 /* PreferencesViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 932D47311D1CD861004BCFE2 /* PreferencesViewController.xib */; };
+ 932D47371D1CDC9B004BCFE2 /* AboutViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 932D47351D1CDC9B004BCFE2 /* AboutViewController.xib */; };
+ 93326BDE1CE7C816005CA2AC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 93326BDD1CE7C816005CA2AC /* Assets.xcassets */; };
+ 93326BE11CE7C816005CA2AC /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 93326BDF1CE7C816005CA2AC /* MainMenu.xib */; };
+ 93326BEB1CE7D9B9005CA2AC /* JoinNetworkViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 93326BE91CE7D9B9005CA2AC /* JoinNetworkViewController.xib */; };
+ 93326BEF1CE7DA30005CA2AC /* ShowNetworksViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 93326BED1CE7DA30005CA2AC /* ShowNetworksViewController.xib */; };
+ 93D1675F1D54191C00330C99 /* NodeStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D1675E1D54191C00330C99 /* NodeStatus.m */; };
+ 93D167621D541BC200330C99 /* ServiceCom.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D167611D541BC200330C99 /* ServiceCom.m */; };
+ 93D167661D54308200330C99 /* Network.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D167651D54308200330C99 /* Network.m */; };
+ 93D167691D57E7EA00330C99 /* AboutViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D167681D57E7EA00330C99 /* AboutViewController.m */; };
+ 93D1676D1D57EB8400330C99 /* PreferencesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D1676C1D57EB8400330C99 /* PreferencesViewController.m */; };
+ 93D167701D57FD3800330C99 /* NetworkMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D1676F1D57FD3800330C99 /* NetworkMonitor.m */; };
+ 93D167731D58093C00330C99 /* NetworkInfoCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D167721D58093C00330C99 /* NetworkInfoCell.m */; };
+ 93D167761D580C3500330C99 /* ShowNetworksViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D167751D580C3500330C99 /* ShowNetworksViewController.m */; };
+ 93D167791D5815E600330C99 /* JoinNetworkViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D167781D5815E600330C99 /* JoinNetworkViewController.m */; };
+ 93D1677C1D58228A00330C99 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D1677B1D58228A00330C99 /* AppDelegate.m */; };
+ 93D1679B1D58300F00330C99 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D1679A1D58300F00330C99 /* main.m */; };
+ 93D1679D1D595F0000330C99 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93D1679C1D595F0000330C99 /* WebKit.framework */; };
+ 93DAFB271D3F0BEE004D5417 /* about.html in Resources */ = {isa = PBXBuildFile; fileRef = 93DAFB261D3F0BEE004D5417 /* about.html */; };
+ 93DAFE4B1CFE53CA00547CC4 /* AuthtokenCopy.m in Sources */ = {isa = PBXBuildFile; fileRef = 93DAFE4A1CFE53CA00547CC4 /* AuthtokenCopy.m */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 932D472E1D1CD499004BCFE2 /* ZeroTierIcon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = ZeroTierIcon.icns; sourceTree = "<group>"; };
+ 932D47311D1CD861004BCFE2 /* PreferencesViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PreferencesViewController.xib; sourceTree = "<group>"; };
+ 932D47351D1CDC9B004BCFE2 /* AboutViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AboutViewController.xib; sourceTree = "<group>"; };
+ 93326BD81CE7C816005CA2AC /* ZeroTier One.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ZeroTier One.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 93326BDD1CE7C816005CA2AC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+ 93326BE01CE7C816005CA2AC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
+ 93326BE21CE7C816005CA2AC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+ 93326BE91CE7D9B9005CA2AC /* JoinNetworkViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = JoinNetworkViewController.xib; sourceTree = "<group>"; };
+ 93326BED1CE7DA30005CA2AC /* ShowNetworksViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ShowNetworksViewController.xib; sourceTree = "<group>"; };
+ 93D1675D1D54191C00330C99 /* NodeStatus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NodeStatus.h; sourceTree = "<group>"; };
+ 93D1675E1D54191C00330C99 /* NodeStatus.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NodeStatus.m; sourceTree = "<group>"; };
+ 93D167601D541BC200330C99 /* ServiceCom.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ServiceCom.h; sourceTree = "<group>"; };
+ 93D167611D541BC200330C99 /* ServiceCom.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ServiceCom.m; sourceTree = "<group>"; };
+ 93D167641D54308200330C99 /* Network.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Network.h; sourceTree = "<group>"; };
+ 93D167651D54308200330C99 /* Network.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Network.m; sourceTree = "<group>"; };
+ 93D167671D57E7EA00330C99 /* AboutViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AboutViewController.h; sourceTree = "<group>"; };
+ 93D167681D57E7EA00330C99 /* AboutViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AboutViewController.m; sourceTree = "<group>"; };
+ 93D1676B1D57EB8400330C99 /* PreferencesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PreferencesViewController.h; sourceTree = "<group>"; };
+ 93D1676C1D57EB8400330C99 /* PreferencesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PreferencesViewController.m; sourceTree = "<group>"; };
+ 93D1676E1D57FD3800330C99 /* NetworkMonitor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NetworkMonitor.h; sourceTree = "<group>"; };
+ 93D1676F1D57FD3800330C99 /* NetworkMonitor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NetworkMonitor.m; sourceTree = "<group>"; };
+ 93D167711D58093C00330C99 /* NetworkInfoCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NetworkInfoCell.h; sourceTree = "<group>"; };
+ 93D167721D58093C00330C99 /* NetworkInfoCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NetworkInfoCell.m; sourceTree = "<group>"; };
+ 93D167741D580C3500330C99 /* ShowNetworksViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShowNetworksViewController.h; sourceTree = "<group>"; };
+ 93D167751D580C3500330C99 /* ShowNetworksViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShowNetworksViewController.m; sourceTree = "<group>"; };
+ 93D167771D5815E600330C99 /* JoinNetworkViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JoinNetworkViewController.h; sourceTree = "<group>"; };
+ 93D167781D5815E600330C99 /* JoinNetworkViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JoinNetworkViewController.m; sourceTree = "<group>"; };
+ 93D1677A1D58228A00330C99 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
+ 93D1677B1D58228A00330C99 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
+ 93D1679A1D58300F00330C99 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+ 93D1679C1D595F0000330C99 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; };
+ 93DAFB261D3F0BEE004D5417 /* about.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = about.html; sourceTree = "<group>"; };
+ 93DAFE4A1CFE53CA00547CC4 /* AuthtokenCopy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AuthtokenCopy.m; sourceTree = "<group>"; };
+ 93DAFE4C1CFE53DA00547CC4 /* AuthtokenCopy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AuthtokenCopy.h; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 93326BD51CE7C816005CA2AC /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 93D1679D1D595F0000330C99 /* WebKit.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 93326BCF1CE7C816005CA2AC = {
+ isa = PBXGroup;
+ children = (
+ 93D1679C1D595F0000330C99 /* WebKit.framework */,
+ 93326BDA1CE7C816005CA2AC /* ZeroTier One */,
+ 93326BD91CE7C816005CA2AC /* Products */,
+ );
+ sourceTree = "<group>";
+ };
+ 93326BD91CE7C816005CA2AC /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 93326BD81CE7C816005CA2AC /* ZeroTier One.app */,
+ );
+ name = Products;
+ sourceTree = "<group>";
+ };
+ 93326BDA1CE7C816005CA2AC /* ZeroTier One */ = {
+ isa = PBXGroup;
+ children = (
+ 932D472E1D1CD499004BCFE2 /* ZeroTierIcon.icns */,
+ 93326BDD1CE7C816005CA2AC /* Assets.xcassets */,
+ 93326BDF1CE7C816005CA2AC /* MainMenu.xib */,
+ 93326BE21CE7C816005CA2AC /* Info.plist */,
+ 93DAFE4A1CFE53CA00547CC4 /* AuthtokenCopy.m */,
+ 93DAFE4C1CFE53DA00547CC4 /* AuthtokenCopy.h */,
+ 93D1676E1D57FD3800330C99 /* NetworkMonitor.h */,
+ 93D1676F1D57FD3800330C99 /* NetworkMonitor.m */,
+ 93DAFB261D3F0BEE004D5417 /* about.html */,
+ 93D1675D1D54191C00330C99 /* NodeStatus.h */,
+ 93D1675E1D54191C00330C99 /* NodeStatus.m */,
+ 93D167601D541BC200330C99 /* ServiceCom.h */,
+ 93D167611D541BC200330C99 /* ServiceCom.m */,
+ 93D167641D54308200330C99 /* Network.h */,
+ 93D167651D54308200330C99 /* Network.m */,
+ 93D167671D57E7EA00330C99 /* AboutViewController.h */,
+ 93D167681D57E7EA00330C99 /* AboutViewController.m */,
+ 932D47351D1CDC9B004BCFE2 /* AboutViewController.xib */,
+ 93D1676B1D57EB8400330C99 /* PreferencesViewController.h */,
+ 93D1676C1D57EB8400330C99 /* PreferencesViewController.m */,
+ 932D47311D1CD861004BCFE2 /* PreferencesViewController.xib */,
+ 93D167711D58093C00330C99 /* NetworkInfoCell.h */,
+ 93D167721D58093C00330C99 /* NetworkInfoCell.m */,
+ 93D167741D580C3500330C99 /* ShowNetworksViewController.h */,
+ 93D167751D580C3500330C99 /* ShowNetworksViewController.m */,
+ 93326BED1CE7DA30005CA2AC /* ShowNetworksViewController.xib */,
+ 93D167771D5815E600330C99 /* JoinNetworkViewController.h */,
+ 93D167781D5815E600330C99 /* JoinNetworkViewController.m */,
+ 93326BE91CE7D9B9005CA2AC /* JoinNetworkViewController.xib */,
+ 93D1677A1D58228A00330C99 /* AppDelegate.h */,
+ 93D1677B1D58228A00330C99 /* AppDelegate.m */,
+ 93D1679A1D58300F00330C99 /* main.m */,
+ );
+ path = "ZeroTier One";
+ sourceTree = "<group>";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 93326BD71CE7C816005CA2AC /* ZeroTier One */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 93326BE51CE7C816005CA2AC /* Build configuration list for PBXNativeTarget "ZeroTier One" */;
+ buildPhases = (
+ 93326BD41CE7C816005CA2AC /* Sources */,
+ 93326BD51CE7C816005CA2AC /* Frameworks */,
+ 93326BD61CE7C816005CA2AC /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = "ZeroTier One";
+ productName = "ZeroTier One";
+ productReference = 93326BD81CE7C816005CA2AC /* ZeroTier One.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 93326BD01CE7C816005CA2AC /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastSwiftUpdateCheck = 0730;
+ LastUpgradeCheck = 0800;
+ ORGANIZATIONNAME = "ZeroTier, Inc";
+ TargetAttributes = {
+ 93326BD71CE7C816005CA2AC = {
+ CreatedOnToolsVersion = 7.3;
+ };
+ };
+ };
+ buildConfigurationList = 93326BD31CE7C816005CA2AC /* Build configuration list for PBXProject "ZeroTier One" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 93326BCF1CE7C816005CA2AC;
+ productRefGroup = 93326BD91CE7C816005CA2AC /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 93326BD71CE7C816005CA2AC /* ZeroTier One */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 93326BD61CE7C816005CA2AC /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 93DAFB271D3F0BEE004D5417 /* about.html in Resources */,
+ 93326BEF1CE7DA30005CA2AC /* ShowNetworksViewController.xib in Resources */,
+ 932D47371D1CDC9B004BCFE2 /* AboutViewController.xib in Resources */,
+ 93326BEB1CE7D9B9005CA2AC /* JoinNetworkViewController.xib in Resources */,
+ 93326BDE1CE7C816005CA2AC /* Assets.xcassets in Resources */,
+ 93326BE11CE7C816005CA2AC /* MainMenu.xib in Resources */,
+ 932D472F1D1CD499004BCFE2 /* ZeroTierIcon.icns in Resources */,
+ 932D47331D1CD861004BCFE2 /* PreferencesViewController.xib in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 93326BD41CE7C816005CA2AC /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 93D1679B1D58300F00330C99 /* main.m in Sources */,
+ 93D167621D541BC200330C99 /* ServiceCom.m in Sources */,
+ 93D167761D580C3500330C99 /* ShowNetworksViewController.m in Sources */,
+ 93DAFE4B1CFE53CA00547CC4 /* AuthtokenCopy.m in Sources */,
+ 93D167701D57FD3800330C99 /* NetworkMonitor.m in Sources */,
+ 93D1675F1D54191C00330C99 /* NodeStatus.m in Sources */,
+ 93D167691D57E7EA00330C99 /* AboutViewController.m in Sources */,
+ 93D1676D1D57EB8400330C99 /* PreferencesViewController.m in Sources */,
+ 93D1677C1D58228A00330C99 /* AppDelegate.m in Sources */,
+ 93D167731D58093C00330C99 /* NetworkInfoCell.m in Sources */,
+ 93D167661D54308200330C99 /* Network.m in Sources */,
+ 93D167791D5815E600330C99 /* JoinNetworkViewController.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+ 93326BDF1CE7C816005CA2AC /* MainMenu.xib */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 93326BE01CE7C816005CA2AC /* Base */,
+ );
+ name = MainMenu.xib;
+ sourceTree = "<group>";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 93326BE31CE7C816005CA2AC /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "-";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MACOSX_DEPLOYMENT_TARGET = 10.11;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = macosx;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ 93326BE41CE7C816005CA2AC /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "-";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MACOSX_DEPLOYMENT_TARGET = 10.11;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = macosx;
+ };
+ name = Release;
+ };
+ 93326BE61CE7C816005CA2AC /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
+ COMBINE_HIDPI_IMAGES = YES;
+ INFOPLIST_FILE = "ZeroTier One/Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
+ MACOSX_DEPLOYMENT_TARGET = 10.7;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.zerotier.ZeroTier-One";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "ZeroTier One/ZeroTier One-Bridging-Header.h";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ 93326BE71CE7C816005CA2AC /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
+ COMBINE_HIDPI_IMAGES = YES;
+ INFOPLIST_FILE = "ZeroTier One/Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
+ MACOSX_DEPLOYMENT_TARGET = 10.7;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.zerotier.ZeroTier-One";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "ZeroTier One/ZeroTier One-Bridging-Header.h";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 93326BD31CE7C816005CA2AC /* Build configuration list for PBXProject "ZeroTier One" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 93326BE31CE7C816005CA2AC /* Debug */,
+ 93326BE41CE7C816005CA2AC /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 93326BE51CE7C816005CA2AC /* Build configuration list for PBXNativeTarget "ZeroTier One" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 93326BE61CE7C816005CA2AC /* Debug */,
+ 93326BE71CE7C816005CA2AC /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 93326BD01CE7C816005CA2AC /* Project object */;
+}
diff --git a/macui/ZeroTier One.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/macui/ZeroTier One.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 00000000..fd60338e
--- /dev/null
+++ b/macui/ZeroTier One.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+ version = "1.0">
+ <FileRef
+ location = "self:ZeroTier One.xcodeproj">
+ </FileRef>
+</Workspace>
diff --git a/macui/ZeroTier One/AboutViewController.h b/macui/ZeroTier One/AboutViewController.h
new file mode 100644
index 00000000..d3d5bc14
--- /dev/null
+++ b/macui/ZeroTier One/AboutViewController.h
@@ -0,0 +1,33 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import <Cocoa/Cocoa.h>
+#import <WebKit/WebKit.h>
+
+@interface AboutViewController : NSViewController <WebPolicyDelegate>
+
+@property (nonatomic, weak) IBOutlet WebView *webView;
+
+- (void)viewDidLoad;
+
+- (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation
+ request:(NSURLRequest *)request
+ frame:(WebFrame *)frame
+decisionListener:(id<WebPolicyDecisionListener>)listener;
+
+@end
diff --git a/macui/ZeroTier One/AboutViewController.m b/macui/ZeroTier One/AboutViewController.m
new file mode 100644
index 00000000..21dceae1
--- /dev/null
+++ b/macui/ZeroTier One/AboutViewController.m
@@ -0,0 +1,56 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import "AboutViewController.h"
+
+@interface AboutViewController ()
+
+@end
+
+@implementation AboutViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+
+ [self.webView setWantsLayer:YES];
+ self.webView.layer.borderWidth = 1.0f;
+ [self.webView.layer setCornerRadius:1.0f];
+ self.webView.layer.masksToBounds = YES;
+ [self.webView.layer setBorderColor:[[NSColor darkGrayColor] CGColor]];
+
+ NSBundle *bundle = [NSBundle mainBundle];
+ NSURL *path = [bundle URLForResource:@"about" withExtension:@"html"];
+ if(path) {
+ [self.webView.mainFrame loadRequest:[NSURLRequest requestWithURL:path]];
+ }
+}
+
+- (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation
+ request:(NSURLRequest *)request
+ frame:(WebFrame *)frame
+decisionListener:(id<WebPolicyDecisionListener>)listener
+{
+ if(request.URL != nil && request.URL.host != nil) {
+ [[NSWorkspace sharedWorkspace] openURL:request.URL];
+ }
+ else {
+ [listener use];
+ }
+}
+
+@end
diff --git a/macui/ZeroTier One/AboutViewController.xib b/macui/ZeroTier One/AboutViewController.xib
new file mode 100644
index 00000000..a0df0fcf
--- /dev/null
+++ b/macui/ZeroTier One/AboutViewController.xib
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10116" systemVersion="15G31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+ <dependencies>
+ <deployment identifier="macosx"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10116"/>
+ <plugIn identifier="com.apple.WebKitIBPlugin" version="10116"/>
+ </dependencies>
+ <objects>
+ <customObject id="-2" userLabel="File's Owner" customClass="AboutViewController" customModule="ZeroTier_One" customModuleProvider="target">
+ <connections>
+ <outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
+ <outlet property="webView" destination="3BS-QW-rZO" id="ucY-A9-7p7"/>
+ </connections>
+ </customObject>
+ <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+ <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+ <customView id="Hz6-mo-xeY">
+ <rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <subviews>
+ <webView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3BS-QW-rZO">
+ <rect key="frame" x="20" y="20" width="440" height="440"/>
+ <webPreferences key="preferences" defaultFontSize="16" defaultFixedFontSize="13" minimumFontSize="0">
+ <nil key="identifier"/>
+ </webPreferences>
+ </webView>
+ </subviews>
+ <point key="canvasLocation" x="463" y="570"/>
+ </customView>
+ </objects>
+</document>
diff --git a/macui/ZeroTier One/AppDelegate.h b/macui/ZeroTier One/AppDelegate.h
new file mode 100644
index 00000000..a00cfba9
--- /dev/null
+++ b/macui/ZeroTier One/AppDelegate.h
@@ -0,0 +1,61 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+@class NetworkMonitor;
+@class Network;
+@class NodeStatus;
+
+@interface AppDelegate : NSObject <NSApplicationDelegate>
+
+@property (weak, nonatomic) IBOutlet NSWindow *window;
+
+@property (nonatomic) NSStatusItem *statusItem;
+
+@property (nonatomic) NSPopover *networkListPopover;
+@property (nonatomic) NSPopover *joinNetworkPopover;
+@property (nonatomic) NSPopover *preferencesPopover;
+@property (nonatomic) NSPopover *aboutPopover;
+
+@property (nonatomic) id transientMonitor;
+
+@property (nonatomic) NetworkMonitor *monitor;
+
+@property (nonatomic) NSMutableArray<Network*> *networks;
+
+@property (nonatomic) NodeStatus *status;
+
+- (void)buildMenu;
+
+- (void)onNetworkListUpdated:(NSNotification*)note;
+- (void)onNodeStatusUpdated:(NSNotification*)note;
+
+- (void)showNetworks;
+- (void)joinNetwork;
+- (void)showPreferences;
+- (void)showAbout;
+- (void)quit;
+- (void)toggleNetwork:(NSMenuItem*)sender;
+- (void)copyNodeID;
+
+- (void)closeJoinNetworkPopover;
+
+- (void)darkModeChanged:(NSNotification*)note;
+
+@end
diff --git a/macui/ZeroTier One/AppDelegate.m b/macui/ZeroTier One/AppDelegate.m
new file mode 100644
index 00000000..5da6b354
--- /dev/null
+++ b/macui/ZeroTier One/AppDelegate.m
@@ -0,0 +1,339 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import "AppDelegate.h"
+#import "NetworkMonitor.h"
+#import "Network.h"
+#import "NodeStatus.h"
+#import "JoinNetworkViewController.h"
+#import "ShowNetworksViewController.h"
+#import "PreferencesViewController.h"
+#import "AboutViewController.h"
+#import "ServiceCom.h"
+
+@implementation AppDelegate
+
+- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
+ self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:-2.0f];
+ self.networkListPopover = [[NSPopover alloc] init];
+ self.joinNetworkPopover = [[NSPopover alloc] init];
+ self.preferencesPopover = [[NSPopover alloc] init];
+ self.aboutPopover = [[NSPopover alloc] init];
+ self.transientMonitor = nil;
+ self.monitor = [[NetworkMonitor alloc] init];
+ self.networks = [NSMutableArray<Network*> array];
+ self.status = nil;
+
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+ NSDictionary *defaultsDict = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:@"firstRun"];
+ [defaults registerDefaults:defaultsDict];
+
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+
+ [nc addObserver:self
+ selector:@selector(onNetworkListUpdated:)
+ name:NetworkUpdateKey
+ object:nil];
+ [nc addObserver:self
+ selector:@selector(onNodeStatusUpdated:)
+ name:StatusUpdateKey
+ object:nil];
+
+ NSString *osxMode = [defaults stringForKey:@"AppleInterfaceStyle"];
+
+ if(osxMode != nil && [osxMode isEqualToString:@"Dark"]) {
+ self.statusItem.image = [NSImage imageNamed:@"MenuBarIconMacWhite"];
+ }
+ else {
+ self.statusItem.image = [NSImage imageNamed:@"MenuBarIconMac"];
+ }
+
+ [[NSDistributedNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(darkModeChanged:)
+ name:@"AppleInterfaceThemeChangedNotification"
+ object:nil];
+
+ [self buildMenu];
+ JoinNetworkViewController *jnvc = [[JoinNetworkViewController alloc] initWithNibName:@"JoinNetworkViewController" bundle:nil];
+ jnvc.appDelegate = self;
+ self.joinNetworkPopover.contentViewController = jnvc;
+ self.joinNetworkPopover.behavior = NSPopoverBehaviorTransient;
+
+ ShowNetworksViewController *showNetworksView = [[ShowNetworksViewController alloc] initWithNibName:@"ShowNetworksViewController" bundle:nil];
+ showNetworksView.netMonitor = self.monitor;
+ self.networkListPopover.contentViewController = showNetworksView;
+ self.networkListPopover.behavior = NSPopoverBehaviorTransient;
+
+ PreferencesViewController *prefsView = [[PreferencesViewController alloc] initWithNibName:@"PreferencesViewController" bundle:nil];
+ self.preferencesPopover.contentViewController = prefsView;
+ self.preferencesPopover.behavior = NSPopoverBehaviorTransient;
+
+ self.aboutPopover.contentViewController = [[AboutViewController alloc] initWithNibName:@"AboutViewController" bundle:nil];
+ self.aboutPopover.behavior = NSPopoverBehaviorTransient;
+
+ BOOL firstRun = [defaults boolForKey:@"firstRun"];
+
+ if(firstRun) {
+ [defaults setBool:NO forKey:@"firstRun"];
+ [defaults synchronize];
+
+ [prefsView setLaunchAtLoginEnabled:YES];
+
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ sleep(2);
+ [[NSOperationQueue mainQueue] addOperationWithBlock:^{
+ [self showAbout];
+ }];
+ });
+ }
+
+ [self.monitor updateNetworkInfo];
+ [self.monitor start];
+}
+
+- (void)applicationWillTerminate:(NSNotification *)aNotification {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [[NSDistributedNotificationCenter defaultCenter] removeObserver:self
+ name:@"AppleInterfaceThemeChangedNotification"
+ object:nil];
+}
+
+- (void)showNetworks {
+ if(self.statusItem.button != nil) {
+ NSStatusBarButton *button = self.statusItem.button;
+ [self.networkListPopover showRelativeToRect:button.bounds
+ ofView:button
+ preferredEdge:NSMinYEdge];
+
+ if(self.transientMonitor == nil) {
+ self.transientMonitor =
+ [NSEvent addGlobalMonitorForEventsMatchingMask:(NSLeftMouseDown|NSRightMouseDown|NSOtherMouseDown)
+ handler:^(NSEvent * _Nonnull e) {
+ [NSEvent removeMonitor:self.transientMonitor];
+ self.transientMonitor = nil;
+ [self.networkListPopover close];
+ }];
+ }
+ }
+}
+
+- (void)joinNetwork {
+ if(self.statusItem.button != nil) {
+ NSStatusBarButton *button = self.statusItem.button;
+ [self.joinNetworkPopover showRelativeToRect:button.bounds
+ ofView:button
+ preferredEdge:NSMinYEdge];
+ if(self.transientMonitor == nil) {
+ self.transientMonitor =
+ [NSEvent addGlobalMonitorForEventsMatchingMask:(NSLeftMouseDown|NSRightMouseDown|NSOtherMouseDown)
+ handler:^(NSEvent * _Nonnull e) {
+ [NSEvent removeMonitor:self.transientMonitor];
+ self.transientMonitor = nil;
+ [self.joinNetworkPopover close];
+ }];
+ }
+ }
+}
+
+- (void)showPreferences {
+ if(self.statusItem.button != nil) {
+ NSStatusBarButton *button = self.statusItem.button;
+ [self.preferencesPopover showRelativeToRect:button.bounds
+ ofView:button
+ preferredEdge:NSMinYEdge];
+ if(self.transientMonitor == nil) {
+ [NSEvent addGlobalMonitorForEventsMatchingMask:(NSLeftMouseDown|NSRightMouseDown|NSOtherMouseDown)
+ handler:^(NSEvent * _Nonnull e) {
+ [NSEvent removeMonitor:self.transientMonitor];
+ self.transientMonitor = nil;
+ [self.preferencesPopover close];
+ }];
+ }
+ }
+}
+
+- (void)showAbout {
+ if(self.statusItem.button != nil) {
+ NSStatusBarButton *button = self.statusItem.button;
+ [self.aboutPopover showRelativeToRect:button.bounds
+ ofView:button
+ preferredEdge:NSMinYEdge];
+ if(self.transientMonitor == nil) {
+ [NSEvent addGlobalMonitorForEventsMatchingMask:(NSLeftMouseDown|NSRightMouseDown|NSOtherMouseDown)
+ handler:^(NSEvent * _Nonnull e) {
+ [NSEvent removeMonitor:self.transientMonitor];
+ self.transientMonitor = nil;
+ [self.aboutPopover close];
+ }];
+ }
+ }
+
+}
+
+- (void)quit {
+ [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0];
+}
+
+- (void)onNetworkListUpdated:(NSNotification*)note {
+ NSArray<Network*> *netList = [note.userInfo objectForKey:@"networks"];
+ [(ShowNetworksViewController*)self.networkListPopover.contentViewController setNetworks:netList];
+ self.networks = [netList mutableCopy];
+
+ [self buildMenu];
+}
+
+- (void)onNodeStatusUpdated:(NSNotification*)note {
+ NodeStatus *status = [note.userInfo objectForKey:@"status"];
+ self.status = status;
+
+ [self buildMenu];
+}
+
+- (void)buildMenu {
+ NSMenu *menu = [[NSMenu alloc] init];
+
+ if(self.status != nil) {
+ NSString *nodeId = @"Node ID: ";
+ nodeId = [nodeId stringByAppendingString:self.status.address];
+ [menu addItem:[[NSMenuItem alloc] initWithTitle:nodeId
+ action:@selector(copyNodeID)
+ keyEquivalent:@""]];
+ [menu addItem:[NSMenuItem separatorItem]];
+ }
+
+ [menu addItem:[[NSMenuItem alloc] initWithTitle:@"Network Details..."
+ action:@selector(showNetworks)
+ keyEquivalent:@"n"]];
+ [menu addItem:[[NSMenuItem alloc] initWithTitle:@"Join Network..."
+ action:@selector(joinNetwork)
+ keyEquivalent:@"j"]];
+
+ [menu addItem:[NSMenuItem separatorItem]];
+
+ if([self.networks count] > 0) {
+ for(Network *net in self.networks) {
+ NSString *nwid = [NSString stringWithFormat:@"%10llx", net.nwid];
+ NSString *networkName = @"";
+ if([net.name lengthOfBytesUsingEncoding:NSUTF8StringEncoding] == 0) {
+ networkName = nwid;
+ }
+ else {
+ networkName = [NSString stringWithFormat:@"%@ (%@)", nwid, net.name];
+ }
+
+ if(net.allowDefault && net.connected) {
+ networkName = [networkName stringByAppendingString:@" [default]"];
+ }
+
+ NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:networkName
+ action:@selector(toggleNetwork:)
+ keyEquivalent:@""];
+ if(net.connected) {
+ item.state = NSOnState;
+ }
+ else {
+ item.state = NSOffState;
+ }
+
+ item.representedObject = net;
+
+ [menu addItem:item];
+ }
+
+ [menu addItem:[NSMenuItem separatorItem]];
+ }
+
+ [menu addItem:[[NSMenuItem alloc] initWithTitle:@"About ZeroTier One..."
+ action:@selector(showAbout)
+ keyEquivalent:@""]];
+ [menu addItem:[[NSMenuItem alloc] initWithTitle:@"Preferences..."
+ action:@selector(showPreferences)
+ keyEquivalent:@""]];
+
+ [menu addItem:[NSMenuItem separatorItem]];
+
+ [menu addItem:[[NSMenuItem alloc] initWithTitle:@"Quit"
+ action:@selector(quit)
+ keyEquivalent:@"q"]];
+
+ self.statusItem.menu = menu;
+}
+
+- (void)toggleNetwork:(NSMenuItem*)sender {
+ Network *network = sender.representedObject;
+ NSString *nwid = [NSString stringWithFormat:@"%10llx", network.nwid];
+
+ if(network.connected) {
+ NSError *error = nil;
+
+ [[ServiceCom sharedInstance] leaveNetwork:nwid error:&error];
+
+ if (error) {
+ NSAlert *alert = [NSAlert alertWithError:error];
+ alert.alertStyle = NSCriticalAlertStyle;
+ [alert addButtonWithTitle:@"Ok"];
+
+ [alert runModal];
+ }
+ }
+ else {
+ NSError *error = nil;
+ [[ServiceCom sharedInstance] joinNetwork:nwid
+ allowManaged:network.allowManaged
+ allowGlobal:network.allowGlobal
+ allowDefault:(network.allowDefault && ![Network defaultRouteExists:self.networks])
+ error:&error];
+
+ if (error) {
+ NSAlert *alert = [NSAlert alertWithError:error];
+ alert.alertStyle = NSCriticalAlertStyle;
+ [alert addButtonWithTitle:@"Ok"];
+
+ [alert runModal];
+ }
+ }
+}
+
+- (void)copyNodeID {
+ if(self.status != nil) {
+ NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
+ [pasteboard declareTypes:[NSArray arrayWithObject:NSPasteboardTypeString] owner:nil];
+ [pasteboard setString:self.status.address forType:NSPasteboardTypeString];
+ }
+}
+
+- (void)darkModeChanged:(NSNotification*)note {
+ NSString *osxMode = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"];
+
+ if(osxMode != nil && [osxMode isEqualToString:@"Dark"]) {
+ self.statusItem.image = [NSImage imageNamed:@"MenuBarIconMacWhite"];
+ }
+ else {
+ self.statusItem.image = [NSImage imageNamed:@"MenuBarIconMac"];
+ }
+}
+
+- (void)closeJoinNetworkPopover {
+ if (self.transientMonitor) {
+ [NSEvent removeMonitor:self.transientMonitor];
+ self.transientMonitor = nil;
+ }
+ [self.joinNetworkPopover close];
+}
+
+@end
diff --git a/macui/ZeroTier One/Assets.xcassets/AppIcon.appiconset/Contents.json b/macui/ZeroTier One/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 00000000..24c81d35
--- /dev/null
+++ b/macui/ZeroTier One/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,59 @@
+{
+ "images" : [
+ {
+ "idiom" : "mac",
+ "size" : "16x16",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "16x16",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "32x32",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "32x32",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "128x128",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "128x128",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "256x256",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "256x256",
+ "scale" : "2x"
+ },
+ {
+ "size" : "512x512",
+ "idiom" : "mac",
+ "filename" : "ZeroTierIcon512x512.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "512x512",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/macui/ZeroTier One/Assets.xcassets/AppIcon.appiconset/ZeroTierIcon512x512.png b/macui/ZeroTier One/Assets.xcassets/AppIcon.appiconset/ZeroTierIcon512x512.png
new file mode 100644
index 00000000..d225c2e3
--- /dev/null
+++ b/macui/ZeroTier One/Assets.xcassets/AppIcon.appiconset/ZeroTierIcon512x512.png
Binary files differ
diff --git a/macui/ZeroTier One/Assets.xcassets/Contents.json b/macui/ZeroTier One/Assets.xcassets/Contents.json
new file mode 100644
index 00000000..da4a164c
--- /dev/null
+++ b/macui/ZeroTier One/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/Contents.json b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/Contents.json
new file mode 100644
index 00000000..a680b58b
--- /dev/null
+++ b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "mac",
+ "filename" : "Menubar.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "mac",
+ "filename" : "MenuBar@2x.png",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+} \ No newline at end of file
diff --git a/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/MenuBar@2x.png b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/MenuBar@2x.png
new file mode 100644
index 00000000..9fd3d3de
--- /dev/null
+++ b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/MenuBar@2x.png
Binary files differ
diff --git a/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/Menubar.png b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/Menubar.png
new file mode 100644
index 00000000..ee0d7e3f
--- /dev/null
+++ b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/Menubar.png
Binary files differ
diff --git a/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/Contents.json b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/Contents.json
new file mode 100644
index 00000000..61737760
--- /dev/null
+++ b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "mac",
+ "filename" : "MenubarWhite.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "mac",
+ "filename" : "MenubarWhite@2x.png",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+} \ No newline at end of file
diff --git a/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/MenubarWhite.png b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/MenubarWhite.png
new file mode 100644
index 00000000..7049ae55
--- /dev/null
+++ b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/MenubarWhite.png
Binary files differ
diff --git a/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/MenubarWhite@2x.png b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/MenubarWhite@2x.png
new file mode 100644
index 00000000..8c20e36f
--- /dev/null
+++ b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/MenubarWhite@2x.png
Binary files differ
diff --git a/macui/ZeroTier One/AuthtokenCopy.h b/macui/ZeroTier One/AuthtokenCopy.h
new file mode 100644
index 00000000..f0497cc6
--- /dev/null
+++ b/macui/ZeroTier One/AuthtokenCopy.h
@@ -0,0 +1,26 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef AuthtokenCopy_h
+#define AuthtokenCopy_h
+
+#import <Foundation/Foundation.h>
+
+NSString* getAdminAuthToken(AuthorizationRef authRef);
+
+#endif /* AuthtokenCopy_h */
diff --git a/macui/ZeroTier One/AuthtokenCopy.m b/macui/ZeroTier One/AuthtokenCopy.m
new file mode 100644
index 00000000..a10350f7
--- /dev/null
+++ b/macui/ZeroTier One/AuthtokenCopy.m
@@ -0,0 +1,97 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import <Foundation/Foundation.h>
+
+#import "AuthtokenCopy.h"
+
+
+NSString* getAdminAuthToken(AuthorizationRef authRef) {
+ char *tool = "/bin/cat";
+ char *args[] = { "/Library/Application Support/ZeroTier/One/authtoken.secret", NULL};
+ FILE *pipe = nil;
+ char token[25];
+ memset(token, 0, sizeof(char)*25);
+
+
+ OSStatus status = AuthorizationExecuteWithPrivileges(authRef, tool, kAuthorizationFlagDefaults, args, &pipe);
+
+ if (status != errAuthorizationSuccess) {
+ NSLog(@"Reading authtoken failed!");
+
+
+ switch(status) {
+ case errAuthorizationDenied:
+ NSLog(@"Autorization Denied");
+ break;
+ case errAuthorizationCanceled:
+ NSLog(@"Authorization Canceled");
+ break;
+ case errAuthorizationInternal:
+ NSLog(@"Authorization Internal");
+ break;
+ case errAuthorizationBadAddress:
+ NSLog(@"Bad Address");
+ break;
+ case errAuthorizationInvalidRef:
+ NSLog(@"Invalid Ref");
+ break;
+ case errAuthorizationInvalidSet:
+ NSLog(@"Invalid Set");
+ break;
+ case errAuthorizationInvalidTag:
+ NSLog(@"Invalid Tag");
+ break;
+ case errAuthorizationInvalidFlags:
+ NSLog(@"Invalid Flags");
+ break;
+ case errAuthorizationInvalidPointer:
+ NSLog(@"Invalid Pointer");
+ break;
+ case errAuthorizationToolExecuteFailure:
+ NSLog(@"Tool Execute Failure");
+ break;
+ case errAuthorizationToolEnvironmentError:
+ NSLog(@"Tool Environment Failure");
+ break;
+ case errAuthorizationExternalizeNotAllowed:
+ NSLog(@"Externalize Not Allowed");
+ break;
+ case errAuthorizationInteractionNotAllowed:
+ NSLog(@"Interaction Not Allowed");
+ break;
+ case errAuthorizationInternalizeNotAllowed:
+ NSLog(@"Internalize Not Allowed");
+ break;
+ default:
+ NSLog(@"Unknown Error");
+ break;
+ }
+
+ return @"";
+ }
+
+ if(pipe != nil) {
+ fread(&token, sizeof(char), 24, pipe);
+ fclose(pipe);
+
+ return [NSString stringWithUTF8String:token];
+ }
+
+ return @"";
+}
diff --git a/macui/ZeroTier One/Base.lproj/MainMenu.xib b/macui/ZeroTier One/Base.lproj/MainMenu.xib
new file mode 100644
index 00000000..6b6da2a6
--- /dev/null
+++ b/macui/ZeroTier One/Base.lproj/MainMenu.xib
@@ -0,0 +1,680 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10116" systemVersion="15G31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+ <dependencies>
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10116"/>
+ </dependencies>
+ <objects>
+ <customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
+ <connections>
+ <outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
+ </connections>
+ </customObject>
+ <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+ <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+ <customObject id="Voe-Tx-rLC" customClass="AppDelegate">
+ <connections>
+ <outlet property="window" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
+ </connections>
+ </customObject>
+ <customObject id="YLy-65-1bz" customClass="NSFontManager"/>
+ <menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
+ <items>
+ <menuItem title="ZeroTier One" id="1Xt-HY-uBw">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="ZeroTier One" systemMenu="apple" id="uQy-DD-JDr">
+ <items>
+ <menuItem title="About ZeroTier One" id="5kV-Vb-QxS">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
+ <menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
+ <menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
+ <menuItem title="Services" id="NMo-om-nkz">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
+ <menuItem title="Hide ZeroTier One" keyEquivalent="h" id="Olw-nP-bQN">
+ <connections>
+ <action selector="hide:" target="-1" id="PnN-Uc-m68"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ <connections>
+ <action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Show All" id="Kd2-mp-pUS">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
+ <menuItem title="Quit ZeroTier One" keyEquivalent="q" id="4sb-4s-VLi">
+ <connections>
+ <action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="File" id="dMs-cI-mzQ">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="File" id="bib-Uj-vzu">
+ <items>
+ <menuItem title="New" keyEquivalent="n" id="Was-JA-tGl">
+ <connections>
+ <action selector="newDocument:" target="-1" id="4Si-XN-c54"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Open…" keyEquivalent="o" id="IAo-SY-fd9">
+ <connections>
+ <action selector="openDocument:" target="-1" id="bVn-NM-KNZ"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Open Recent" id="tXI-mr-wws">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Open Recent" systemMenu="recentDocuments" id="oas-Oc-fiZ">
+ <items>
+ <menuItem title="Clear Menu" id="vNY-rz-j42">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="clearRecentDocuments:" target="-1" id="Daa-9d-B3U"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="m54-Is-iLE"/>
+ <menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG">
+ <connections>
+ <action selector="performClose:" target="-1" id="HmO-Ls-i7Q"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Save…" keyEquivalent="s" id="pxx-59-PXV">
+ <connections>
+ <action selector="saveDocument:" target="-1" id="teZ-XB-qJY"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Save As…" keyEquivalent="S" id="Bw7-FT-i3A">
+ <connections>
+ <action selector="saveDocumentAs:" target="-1" id="mDf-zr-I0C"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Revert to Saved" id="KaW-ft-85H">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="revertDocumentToSaved:" target="-1" id="iJ3-Pv-kwq"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="aJh-i4-bef"/>
+ <menuItem title="Page Setup…" keyEquivalent="P" id="qIS-W8-SiK">
+ <modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
+ <connections>
+ <action selector="runPageLayout:" target="-1" id="Din-rz-gC5"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Print…" keyEquivalent="p" id="aTl-1u-JFS">
+ <connections>
+ <action selector="print:" target="-1" id="qaZ-4w-aoO"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Edit" id="5QF-Oa-p0T">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Edit" id="W48-6f-4Dl">
+ <items>
+ <menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
+ <connections>
+ <action selector="undo:" target="-1" id="M6e-cu-g7V"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
+ <connections>
+ <action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
+ <menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
+ <connections>
+ <action selector="cut:" target="-1" id="YJe-68-I9s"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
+ <connections>
+ <action selector="copy:" target="-1" id="G1f-GL-Joy"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
+ <connections>
+ <action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ <connections>
+ <action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Delete" id="pa3-QI-u2k">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
+ <connections>
+ <action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
+ <menuItem title="Find" id="4EN-yA-p0u">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Find" id="1b7-l0-nxx">
+ <items>
+ <menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
+ <connections>
+ <action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ <connections>
+ <action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
+ <connections>
+ <action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
+ <connections>
+ <action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
+ <connections>
+ <action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
+ <connections>
+ <action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
+ <items>
+ <menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
+ <connections>
+ <action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
+ <connections>
+ <action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
+ <menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Substitutions" id="9ic-FL-obx">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
+ <items>
+ <menuItem title="Show Substitutions" id="z6F-FW-3nz">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
+ <menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Smart Quotes" id="hQb-2v-fYv">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Smart Dashes" id="rgM-f4-ycn">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Smart Links" id="cwL-P1-jid">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Data Detectors" id="tRr-pd-1PS">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Text Replacement" id="HFQ-gK-NFA">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Transformations" id="2oI-Rn-ZJC">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Transformations" id="c8a-y6-VQd">
+ <items>
+ <menuItem title="Make Upper Case" id="vmV-6d-7jI">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Make Lower Case" id="d9M-CD-aMd">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Capitalize" id="UEZ-Bs-lqG">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Speech" id="xrE-MZ-jX0">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Speech" id="3rS-ZA-NoH">
+ <items>
+ <menuItem title="Start Speaking" id="Ynk-f8-cLZ">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Stop Speaking" id="Oyz-dy-DGm">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Format" id="jxT-CU-nIS">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Format" id="GEO-Iw-cKr">
+ <items>
+ <menuItem title="Font" id="Gi5-1S-RQB">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Font" systemMenu="font" id="aXa-aM-Jaq">
+ <items>
+ <menuItem title="Show Fonts" keyEquivalent="t" id="Q5e-8K-NDq">
+ <connections>
+ <action selector="orderFrontFontPanel:" target="YLy-65-1bz" id="WHr-nq-2xA"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Bold" tag="2" keyEquivalent="b" id="GB9-OM-e27">
+ <connections>
+ <action selector="addFontTrait:" target="YLy-65-1bz" id="hqk-hr-sYV"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Italic" tag="1" keyEquivalent="i" id="Vjx-xi-njq">
+ <connections>
+ <action selector="addFontTrait:" target="YLy-65-1bz" id="IHV-OB-c03"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Underline" keyEquivalent="u" id="WRG-CD-K1S">
+ <connections>
+ <action selector="underline:" target="-1" id="FYS-2b-JAY"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="5gT-KC-WSO"/>
+ <menuItem title="Bigger" tag="3" keyEquivalent="+" id="Ptp-SP-VEL">
+ <connections>
+ <action selector="modifyFont:" target="YLy-65-1bz" id="Uc7-di-UnL"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Smaller" tag="4" keyEquivalent="-" id="i1d-Er-qST">
+ <connections>
+ <action selector="modifyFont:" target="YLy-65-1bz" id="HcX-Lf-eNd"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="kx3-Dk-x3B"/>
+ <menuItem title="Kern" id="jBQ-r6-VK2">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Kern" id="tlD-Oa-oAM">
+ <items>
+ <menuItem title="Use Default" id="GUa-eO-cwY">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="useStandardKerning:" target="-1" id="6dk-9l-Ckg"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Use None" id="cDB-IK-hbR">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="turnOffKerning:" target="-1" id="U8a-gz-Maa"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Tighten" id="46P-cB-AYj">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="tightenKerning:" target="-1" id="hr7-Nz-8ro"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Loosen" id="ogc-rX-tC1">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="loosenKerning:" target="-1" id="8i4-f9-FKE"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Ligatures" id="o6e-r0-MWq">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Ligatures" id="w0m-vy-SC9">
+ <items>
+ <menuItem title="Use Default" id="agt-UL-0e3">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="useStandardLigatures:" target="-1" id="7uR-wd-Dx6"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Use None" id="J7y-lM-qPV">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="turnOffLigatures:" target="-1" id="iX2-gA-Ilz"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Use All" id="xQD-1f-W4t">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="useAllLigatures:" target="-1" id="KcB-kA-TuK"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Baseline" id="OaQ-X3-Vso">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Baseline" id="ijk-EB-dga">
+ <items>
+ <menuItem title="Use Default" id="3Om-Ey-2VK">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="unscript:" target="-1" id="0vZ-95-Ywn"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Superscript" id="Rqc-34-cIF">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="superscript:" target="-1" id="3qV-fo-wpU"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Subscript" id="I0S-gh-46l">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="subscript:" target="-1" id="Q6W-4W-IGz"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Raise" id="2h7-ER-AoG">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="raiseBaseline:" target="-1" id="4sk-31-7Q9"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Lower" id="1tx-W0-xDw">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="lowerBaseline:" target="-1" id="OF1-bc-KW4"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="Ndw-q3-faq"/>
+ <menuItem title="Show Colors" keyEquivalent="C" id="bgn-CT-cEk">
+ <connections>
+ <action selector="orderFrontColorPanel:" target="-1" id="mSX-Xz-DV3"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="iMs-zA-UFJ"/>
+ <menuItem title="Copy Style" keyEquivalent="c" id="5Vv-lz-BsD">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ <connections>
+ <action selector="copyFont:" target="-1" id="GJO-xA-L4q"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Paste Style" keyEquivalent="v" id="vKC-jM-MkH">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ <connections>
+ <action selector="pasteFont:" target="-1" id="JfD-CL-leO"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Text" id="Fal-I4-PZk">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Text" id="d9c-me-L2H">
+ <items>
+ <menuItem title="Align Left" keyEquivalent="{" id="ZM1-6Q-yy1">
+ <connections>
+ <action selector="alignLeft:" target="-1" id="zUv-R1-uAa"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Center" keyEquivalent="|" id="VIY-Ag-zcb">
+ <connections>
+ <action selector="alignCenter:" target="-1" id="spX-mk-kcS"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Justify" id="J5U-5w-g23">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="alignJustified:" target="-1" id="ljL-7U-jND"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Align Right" keyEquivalent="}" id="wb2-vD-lq4">
+ <connections>
+ <action selector="alignRight:" target="-1" id="r48-bG-YeY"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="4s2-GY-VfK"/>
+ <menuItem title="Writing Direction" id="H1b-Si-o9J">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Writing Direction" id="8mr-sm-Yjd">
+ <items>
+ <menuItem title="Paragraph" enabled="NO" id="ZvO-Gk-QUH">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem id="YGs-j5-SAR">
+ <string key="title"> Default</string>
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="makeBaseWritingDirectionNatural:" target="-1" id="qtV-5e-UBP"/>
+ </connections>
+ </menuItem>
+ <menuItem id="Lbh-J2-qVU">
+ <string key="title"> Left to Right</string>
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="makeBaseWritingDirectionLeftToRight:" target="-1" id="S0X-9S-QSf"/>
+ </connections>
+ </menuItem>
+ <menuItem id="jFq-tB-4Kx">
+ <string key="title"> Right to Left</string>
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="makeBaseWritingDirectionRightToLeft:" target="-1" id="5fk-qB-AqJ"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="swp-gr-a21"/>
+ <menuItem title="Selection" enabled="NO" id="cqv-fj-IhA">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem id="Nop-cj-93Q">
+ <string key="title"> Default</string>
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="makeTextWritingDirectionNatural:" target="-1" id="lPI-Se-ZHp"/>
+ </connections>
+ </menuItem>
+ <menuItem id="BgM-ve-c93">
+ <string key="title"> Left to Right</string>
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="makeTextWritingDirectionLeftToRight:" target="-1" id="caW-Bv-w94"/>
+ </connections>
+ </menuItem>
+ <menuItem id="RB4-Sm-HuC">
+ <string key="title"> Right to Left</string>
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="makeTextWritingDirectionRightToLeft:" target="-1" id="EXD-6r-ZUu"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="fKy-g9-1gm"/>
+ <menuItem title="Show Ruler" id="vLm-3I-IUL">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleRuler:" target="-1" id="FOx-HJ-KwY"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Copy Ruler" keyEquivalent="c" id="MkV-Pr-PK5">
+ <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
+ <connections>
+ <action selector="copyRuler:" target="-1" id="71i-fW-3W2"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Paste Ruler" keyEquivalent="v" id="LVM-kO-fVI">
+ <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
+ <connections>
+ <action selector="pasteRuler:" target="-1" id="cSh-wd-qM2"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="View" id="H8h-7b-M4v">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="View" id="HyV-fh-RgO">
+ <items>
+ <menuItem title="Show Toolbar" keyEquivalent="t" id="snW-S8-Cw5">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ <connections>
+ <action selector="toggleToolbarShown:" target="-1" id="BXY-wc-z0C"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Customize Toolbar…" id="1UK-8n-QPP">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="runToolbarCustomizationPalette:" target="-1" id="pQI-g3-MTW"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Window" id="aUF-d1-5bR">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
+ <items>
+ <menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
+ <connections>
+ <action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Zoom" id="R4o-n2-Eq4">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
+ <menuItem title="Bring All to Front" id="LE2-aR-0XJ">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Help" id="wpr-3q-Mcd">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
+ <items>
+ <menuItem title="ZeroTier One Help" keyEquivalent="?" id="FKE-Sm-Kum">
+ <connections>
+ <action selector="showHelp:" target="-1" id="y7X-2Q-9no"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ </items>
+ </menu>
+ <window title="ZeroTier One" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g">
+ <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
+ <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
+ <rect key="contentRect" x="335" y="390" width="480" height="360"/>
+ <rect key="screenRect" x="0.0" y="0.0" width="1716" height="1024"/>
+ <view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
+ <rect key="frame" x="0.0" y="0.0" width="480" height="360"/>
+ <autoresizingMask key="autoresizingMask"/>
+ </view>
+ </window>
+ </objects>
+</document>
diff --git a/macui/ZeroTier One/Info.plist b/macui/ZeroTier One/Info.plist
new file mode 100644
index 00000000..e04b5f28
--- /dev/null
+++ b/macui/ZeroTier One/Info.plist
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>$(EXECUTABLE_NAME)</string>
+ <key>CFBundleIconFile</key>
+ <string>ZeroTierIcon.icns</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>$(PRODUCT_NAME)</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>15</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>$(MACOSX_DEPLOYMENT_TARGET)</string>
+ <key>LSUIElement</key>
+ <true/>
+ <key>NSAppTransportSecurity</key>
+ <dict>
+ <key>NSAllowsArbitraryLoads</key>
+ <true/>
+ </dict>
+ <key>NSHumanReadableCopyright</key>
+ <string>Copyright © 2016 ZeroTier, Inc. All rights reserved.</string>
+ <key>NSMainNibFile</key>
+ <string>MainMenu</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+</dict>
+</plist>
diff --git a/macui/ZeroTier One/JoinNetworkViewController.h b/macui/ZeroTier One/JoinNetworkViewController.h
new file mode 100644
index 00000000..428959fb
--- /dev/null
+++ b/macui/ZeroTier One/JoinNetworkViewController.h
@@ -0,0 +1,40 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+
+extern NSString * const JoinedNetworksKey;
+
+@class AppDelegate;
+
+@interface JoinNetworkViewController : NSViewController <NSComboBoxDelegate, NSComboBoxDataSource>
+
+@property (nonatomic, weak) IBOutlet NSComboBox *network;
+@property (nonatomic, weak) IBOutlet NSButton *joinButton;
+@property (nonatomic, weak) IBOutlet NSButton *allowManagedCheckBox;
+@property (nonatomic, weak) IBOutlet NSButton *allowGlobalCheckBox;
+@property (nonatomic, weak) IBOutlet NSButton *allowDefaultCheckBox;
+@property (nonatomic, weak) IBOutlet AppDelegate *appDelegate;
+
+@property (nonatomic) NSMutableArray<NSString*> *values;
+
+- (IBAction)onJoinClicked:(id)sender;
+
+
+@end
diff --git a/macui/ZeroTier One/JoinNetworkViewController.m b/macui/ZeroTier One/JoinNetworkViewController.m
new file mode 100644
index 00000000..cae26541
--- /dev/null
+++ b/macui/ZeroTier One/JoinNetworkViewController.m
@@ -0,0 +1,184 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import "JoinNetworkViewController.h"
+#import "ServiceCom.h"
+#import "AppDelegate.h"
+
+
+NSString * const JoinedNetworksKey = @"com.zerotier.one.joined-networks";
+
+@interface NSString (extra)
+
+- (BOOL)contains:(NSString*)find;
+
+@end
+
+@implementation NSString (extra)
+
+- (BOOL)contains:(NSString*)find {
+ NSRange range = [self rangeOfString:find];
+ return range.location != NSNotFound;
+}
+
+@end
+
+
+@implementation JoinNetworkViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ // Do view setup here.
+ [self.network setDelegate:self];
+ [self.network setDataSource:self];
+}
+
+- (void)viewWillAppear {
+ [super viewWillAppear];
+
+ self.allowManagedCheckBox.state = NSOnState;
+ self.allowGlobalCheckBox.state = NSOffState;
+ self.allowDefaultCheckBox.state = NSOffState;
+
+ self.network.stringValue = @"";
+
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+
+ NSMutableArray<NSString*> *vals = [[defaults stringArrayForKey:JoinedNetworksKey] mutableCopy];
+
+ if(vals) {
+ self.values = vals;
+ }
+}
+
+- (void)viewWillDisappear {
+ [super viewWillDisappear];
+
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+
+ [defaults setObject:self.values forKey:JoinedNetworksKey];
+}
+
+- (IBAction)onJoinClicked:(id)sender {
+ NSString *networkId = self.network.stringValue;
+
+ NSError *error = nil;
+ [[ServiceCom sharedInstance] joinNetwork:networkId
+ allowManaged:(self.allowManagedCheckBox.state == NSOnState)
+ allowGlobal:(self.allowGlobalCheckBox.state == NSOnState)
+ allowDefault:(self.allowDefaultCheckBox.state == NSOnState)
+ error:&error];
+
+ if(error) {
+ NSAlert *alert = [NSAlert alertWithError:error];
+ alert.alertStyle = NSCriticalAlertStyle;
+ [alert addButtonWithTitle:@"Ok"];
+
+ [alert runModal];
+ return;
+ }
+
+ self.network.stringValue = @"";
+
+ if(![self.values containsObject:networkId]) {
+ [self.values insertObject:networkId atIndex:0];
+
+ while([self.values count] > 20) {
+ [self.values removeLastObject];
+ }
+ }
+
+ [self.appDelegate closeJoinNetworkPopover];
+}
+
+// NSComboBoxDelegate methods
+
+- (void)controlTextDidChange:(NSNotification *)obj {
+ NSComboBox *cb = (NSComboBox*)obj.object;
+ NSString *value = cb.stringValue;
+
+ NSString *allowedCharacters = @"abcdefABCDEF0123456789";
+
+ NSString *outValue = @"";
+
+ for(int i = 0; i < [value length]; ++i) {
+ if(![allowedCharacters contains:[NSString stringWithFormat:@"%C", [value characterAtIndex:i]]]) {
+ NSBeep();
+ }
+ else {
+ outValue = [outValue stringByAppendingString:[NSString stringWithFormat:@"%C", [value characterAtIndex:i]]];
+ }
+ }
+
+ if([outValue lengthOfBytesUsingEncoding:NSUTF8StringEncoding] == 16) {
+ self.joinButton.enabled = YES;
+ }
+ else {
+ if([outValue lengthOfBytesUsingEncoding:NSUTF8StringEncoding] > 16) {
+ NSRange range = {0, 16};
+ range = [outValue rangeOfComposedCharacterSequencesForRange:range];
+ outValue = [outValue substringWithRange:range];
+ NSBeep();
+ self.joinButton.enabled = YES;
+ }
+ else {
+ self.joinButton.enabled = NO;
+ }
+ }
+
+ cb.stringValue = outValue;
+}
+
+// end NSComboBoxDelegate methods
+
+// NSComboBoxDataSource methods
+
+- (NSInteger)numberOfItemsInComboBox:(NSComboBox *)aComboBox {
+ return [self.values count];
+}
+
+- (id)comboBox:(NSComboBox *)aComboBox objectValueForItemAtIndex:(NSInteger)index {
+ return [self.values objectAtIndex:index];
+}
+
+- (NSUInteger)comboBox:(NSComboBox *)aComboBox indexOfItemWithStringValue:(NSString *)string {
+ NSUInteger counter = 0;
+
+ for(NSString *val in self.values) {
+ if([val isEqualToString:string]) {
+ return counter;
+ }
+
+ counter += 1;
+ }
+
+ return NSNotFound;
+}
+
+- (NSString*)comboBox:(NSComboBox *)aComboBox completedString:(NSString *)string {
+ for(NSString *val in self.values) {
+ if([val hasPrefix:string]) {
+ return val;
+ }
+ }
+ return nil;
+}
+
+// end NSComboBoxDataSource methods
+
+@end
diff --git a/macui/ZeroTier One/JoinNetworkViewController.xib b/macui/ZeroTier One/JoinNetworkViewController.xib
new file mode 100644
index 00000000..2ef43442
--- /dev/null
+++ b/macui/ZeroTier One/JoinNetworkViewController.xib
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10116" systemVersion="15G31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+ <dependencies>
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10116"/>
+ </dependencies>
+ <objects>
+ <customObject id="-2" userLabel="File's Owner" customClass="JoinNetworkViewController" customModule="ZeroTier_One" customModuleProvider="target">
+ <connections>
+ <outlet property="allowDefaultCheckBox" destination="rz3-0a-oNA" id="mYu-Wq-MHW"/>
+ <outlet property="allowGlobalCheckBox" destination="BH2-2O-Qeu" id="alx-Je-q9I"/>
+ <outlet property="allowManagedCheckBox" destination="OQS-QZ-zcY" id="7pz-vO-3IC"/>
+ <outlet property="joinButton" destination="BGy-vd-NQX" id="LGE-2G-7ND"/>
+ <outlet property="network" destination="BQy-d9-BNg" id="Yf7-BG-c84"/>
+ <outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
+ </connections>
+ </customObject>
+ <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+ <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+ <customView id="Hz6-mo-xeY">
+ <rect key="frame" x="0.0" y="0.0" width="397" height="123"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <subviews>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="puT-Yk-CWC">
+ <rect key="frame" x="18" y="83" width="107" height="17"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Enter Network ID" id="oYH-gS-BX5">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ </textField>
+ <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="BGy-vd-NQX">
+ <rect key="frame" x="302" y="13" width="81" height="32"/>
+ <buttonCell key="cell" type="push" title="Join" bezelStyle="rounded" alignment="center" enabled="NO" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="6Rp-TA-XLl">
+ <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="system"/>
+ </buttonCell>
+ <connections>
+ <action selector="onJoinClicked:" target="-2" id="kzC-GT-JKb"/>
+ </connections>
+ </button>
+ <comboBox verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="BQy-d9-BNg">
+ <rect key="frame" x="131" y="79" width="249" height="26"/>
+ <comboBoxCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" drawsBackground="YES" usesDataSource="YES" numberOfVisibleItems="5" id="n71-4S-AaI">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
+ </comboBoxCell>
+ </comboBox>
+ <button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="OQS-QZ-zcY">
+ <rect key="frame" x="18" y="59" width="115" height="18"/>
+ <buttonCell key="cell" type="check" title="Allow Managed" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="QEN-MJ-xaj">
+ <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+ <font key="font" metaFont="system"/>
+ </buttonCell>
+ </button>
+ <button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="BH2-2O-Qeu">
+ <rect key="frame" x="137" y="59" width="97" height="18"/>
+ <buttonCell key="cell" type="check" title="Allow Global" bezelStyle="regularSquare" imagePosition="left" inset="2" id="epO-Uh-aHN">
+ <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+ <font key="font" metaFont="system"/>
+ </buttonCell>
+ </button>
+ <button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="rz3-0a-oNA">
+ <rect key="frame" x="238" y="59" width="141" height="18"/>
+ <buttonCell key="cell" type="check" title="Allow Default Route" bezelStyle="regularSquare" imagePosition="left" inset="2" id="Lkd-XI-Kcu">
+ <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+ <font key="font" metaFont="system"/>
+ </buttonCell>
+ </button>
+ </subviews>
+ <point key="canvasLocation" x="263.5" y="372.5"/>
+ </customView>
+ </objects>
+</document>
diff --git a/macui/ZeroTier One/Network.h b/macui/ZeroTier One/Network.h
new file mode 100644
index 00000000..0f3c4964
--- /dev/null
+++ b/macui/ZeroTier One/Network.h
@@ -0,0 +1,62 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import <Foundation/Foundation.h>
+
+enum NetworkStatus {
+ REQUESTING_CONFIGURATION,
+ OK,
+ ACCESS_DENIED,
+ NOT_FOUND,
+ PORT_ERROR,
+ CLIENT_TOO_OLD,
+};
+
+enum NetworkType {
+ PUBLIC,
+ PRIVATE,
+};
+
+@interface Network : NSObject <NSCoding>
+
+@property (readonly) NSArray<NSString*> *assignedAddresses;
+@property (readonly) BOOL bridge;
+@property (readonly) BOOL broadcastEnabled;
+@property (readonly) BOOL dhcp;
+@property (readonly) NSString *mac;
+@property (readonly) int mtu;
+@property (readonly) int netconfRevision;
+@property (readonly) NSString *name;
+@property (readonly) UInt64 nwid;
+@property (readonly) NSString *portDeviceName;
+@property (readonly) int portError;
+@property (readonly) enum NetworkStatus status;
+@property (readonly) enum NetworkType type;
+@property (readonly) BOOL allowManaged;
+@property (readonly) BOOL allowGlobal;
+@property (readonly) BOOL allowDefault;
+@property (readonly) BOOL connected; // not persisted. set to YES if loaded via json
+
+- (id)initWithJsonData:(NSDictionary*)jsonData;
+- (id)initWithCoder:(NSCoder *)aDecoder;
+- (void)encodeWithCoder:(NSCoder *)aCoder;
++ (BOOL)defaultRouteExists:(NSArray<Network *>*)netList;
+- (NSString*)statusString;
+- (NSString*)typeString;
+
+@end
diff --git a/macui/ZeroTier One/Network.m b/macui/ZeroTier One/Network.m
new file mode 100644
index 00000000..16efc6e3
--- /dev/null
+++ b/macui/ZeroTier One/Network.m
@@ -0,0 +1,278 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import "Network.h"
+
+NSString *NetworkAddressesKey = @"addresses";
+NSString *NetworkBridgeKey = @"bridge";
+NSString *NetworkBroadcastKey = @"broadcast";
+NSString *NetworkDhcpKey = @"dhcp";
+NSString *NetworkMacKey = @"mac";
+NSString *NetworkMtuKey = @"mtu";
+NSString *NetworkMulticastKey = @"multicast";
+NSString *NetworkNameKey = @"name";
+NSString *NetworkNetconfKey = @"netconf";
+NSString *NetworkNwidKey = @"nwid";
+NSString *NetworkPortNameKey = @"port";
+NSString *NetworkPortErrorKey = @"portError";
+NSString *NetworkStatusKey = @"status";
+NSString *NetworkTypeKey = @"type";
+NSString *NetworkAllowManagedKey = @"allowManaged";
+NSString *NetworkAllowGlobalKey = @"allowGlobal";
+NSString *NetworkAllowDefaultKey = @"allowDefault";
+
+@implementation Network
+
+- (id)initWithJsonData:(NSDictionary*)jsonData
+{
+ self = [super init];
+
+ if(self) {
+ if([jsonData objectForKey:@"assignedAddresses"]) {
+ _assignedAddresses = (NSArray<NSString*>*)[jsonData objectForKey:@"assignedAddresses"];
+ }
+
+ if([jsonData objectForKey:@"bridge"]) {
+ _bridge = [(NSNumber*)[jsonData objectForKey:@"bridge"] boolValue];
+ }
+
+ if([jsonData objectForKey:@"broadcastEnabled"]) {
+ _broadcastEnabled = [(NSNumber*)[jsonData objectForKey:@"broadcastEnabled"] boolValue];
+ }
+
+ if([jsonData objectForKey:@"dhcp"]) {
+ _dhcp = [(NSNumber*)[jsonData objectForKey:@"dhcp"] boolValue];
+ }
+
+ if([jsonData objectForKey:@"mac"]) {
+ _mac = (NSString*)[jsonData objectForKey:@"mac"];
+ }
+
+ if([jsonData objectForKey:@"mtu"]) {
+ _mtu = [(NSNumber*)[jsonData objectForKey:@"mtu"] intValue];
+ }
+
+ if([jsonData objectForKey:@"name"]) {
+ _name = (NSString*)[jsonData objectForKey:@"name"];
+ }
+
+ if([jsonData objectForKey:@"netconfRevision"]) {
+ _netconfRevision = [(NSNumber*)[jsonData objectForKey:@"netconfRevision"] intValue];
+ }
+
+ if([jsonData objectForKey:@"nwid"]) {
+ NSString *networkid = (NSString*)[jsonData objectForKey:@"nwid"];
+
+ NSScanner *scanner = [NSScanner scannerWithString:networkid];
+ [scanner scanHexLongLong:&_nwid];
+ }
+
+ if([jsonData objectForKey:@"portDeviceName"]) {
+ _portDeviceName = (NSString*)[jsonData objectForKey:@"portDeviceName"];
+ }
+
+ if([jsonData objectForKey:@"portError"]) {
+ _portError = [(NSNumber*)[jsonData objectForKey:@"portError"] intValue];
+ }
+
+ if([jsonData objectForKey:@"allowManaged"]) {
+ _allowManaged = [(NSNumber*)[jsonData objectForKey:@"allowManaged"] boolValue];
+ }
+
+ if([jsonData objectForKey:@"allowGlobal"]) {
+ _allowGlobal = [(NSNumber*)[jsonData objectForKey:@"allowGlobal"] boolValue];
+ }
+
+ if([jsonData objectForKey:@"allowDefault"]) {
+ _allowDefault = [(NSNumber*)[jsonData objectForKey:@"allowDefault"] boolValue];
+ }
+
+ if([jsonData objectForKey:@"status"]) {
+ NSString *statusStr = (NSString*)[jsonData objectForKey:@"status"];
+ if([statusStr isEqualToString:@"REQUESTING_CONFIGURATION"]) {
+ _status = REQUESTING_CONFIGURATION;
+ }
+ else if([statusStr isEqualToString:@"OK"]) {
+ _status = OK;
+ }
+ else if([statusStr isEqualToString:@"ACCESS_DENIED"]) {
+ _status = ACCESS_DENIED;
+ }
+ else if([statusStr isEqualToString:@"NOT_FOUND"]) {
+ _status = NOT_FOUND;
+ }
+ else if([statusStr isEqualToString:@"PORT_ERROR"]) {
+ _status = PORT_ERROR;
+ }
+ else if([statusStr isEqualToString:@"CLIENT_TOO_OLD"]) {
+ _status = CLIENT_TOO_OLD;
+ }
+ }
+
+ if([jsonData objectForKey:@"type"]) {
+ NSString *typeStr = (NSString*)[jsonData objectForKey:@"type"];
+ if([typeStr isEqualToString:@"PRIVATE"]) {
+ _type = PRIVATE;
+ }
+ else if([typeStr isEqualToString:@"PUBLIC"]) {
+ _type = PUBLIC;
+ }
+ }
+
+ _connected = YES;
+ }
+
+ return self;
+}
+- (id)initWithCoder:(NSCoder *)aDecoder
+{
+ self = [super init];
+
+ if(self) {
+ if([aDecoder containsValueForKey:NetworkAddressesKey]) {
+ _assignedAddresses = (NSArray<NSString*>*)[aDecoder decodeObjectForKey:NetworkAddressesKey];
+ }
+
+ if([aDecoder containsValueForKey:NetworkBridgeKey]) {
+ _bridge = [aDecoder decodeBoolForKey:NetworkBridgeKey];
+ }
+
+ if([aDecoder containsValueForKey:NetworkBroadcastKey]) {
+ _broadcastEnabled = [aDecoder decodeBoolForKey:NetworkBroadcastKey];
+ }
+
+ if([aDecoder containsValueForKey:NetworkDhcpKey]) {
+ _dhcp = [aDecoder decodeBoolForKey:NetworkDhcpKey];
+ }
+
+ if([aDecoder containsValueForKey:NetworkMacKey]) {
+ _mac = (NSString*)[aDecoder decodeObjectForKey:NetworkMacKey];
+ }
+
+ if([aDecoder containsValueForKey:NetworkMtuKey]) {
+ _mtu = (int)[aDecoder decodeIntegerForKey:NetworkMtuKey];
+ }
+
+ if([aDecoder containsValueForKey:NetworkNameKey]) {
+ _name = (NSString*)[aDecoder decodeObjectForKey:NetworkNameKey];
+ }
+
+ if([aDecoder containsValueForKey:NetworkNetconfKey]) {
+ _netconfRevision = (int)[aDecoder decodeIntegerForKey:NetworkNetconfKey];
+ }
+
+ if([aDecoder containsValueForKey:NetworkNwidKey]) {
+ _nwid = [(NSNumber*)[aDecoder decodeObjectForKey:NetworkNwidKey] unsignedLongLongValue];
+ }
+
+ if([aDecoder containsValueForKey:NetworkPortNameKey]) {
+ _portDeviceName = (NSString*)[aDecoder decodeObjectForKey:NetworkPortNameKey];
+ }
+
+ if([aDecoder containsValueForKey:NetworkPortErrorKey]) {
+ _portError = (int)[aDecoder decodeIntegerForKey:NetworkPortErrorKey];
+ }
+
+ if([aDecoder containsValueForKey:NetworkStatusKey]) {
+ _status = (enum NetworkStatus)[aDecoder decodeIntegerForKey:NetworkStatusKey];
+ }
+
+ if([aDecoder containsValueForKey:NetworkTypeKey]) {
+ _type = (enum NetworkType)[aDecoder decodeIntegerForKey:NetworkTypeKey];
+ }
+
+ if([aDecoder containsValueForKey:NetworkAllowManagedKey]) {
+ _allowManaged = [aDecoder decodeBoolForKey:NetworkAllowManagedKey];
+ }
+
+ if([aDecoder containsValueForKey:NetworkAllowGlobalKey]) {
+ _allowGlobal = [aDecoder decodeBoolForKey:NetworkAllowGlobalKey];
+ }
+
+ if([aDecoder containsValueForKey:NetworkAllowDefaultKey]) {
+ _allowDefault = [aDecoder decodeBoolForKey:NetworkAllowDefaultKey];
+ }
+
+ _connected = NO;
+ }
+
+ return self;
+}
+
+- (void)encodeWithCoder:(NSCoder *)aCoder
+{
+ [aCoder encodeObject:_assignedAddresses forKey:NetworkAddressesKey];
+ [aCoder encodeBool:_bridge forKey:NetworkBridgeKey];
+ [aCoder encodeBool:_broadcastEnabled forKey:NetworkBroadcastKey];
+ [aCoder encodeBool:_dhcp forKey:NetworkDhcpKey];
+ [aCoder encodeObject:_mac forKey:NetworkMacKey];
+ [aCoder encodeInteger:_mtu forKey:NetworkMtuKey];
+ [aCoder encodeObject:_name forKey:NetworkNameKey];
+ [aCoder encodeInteger:_netconfRevision forKey:NetworkNetconfKey];
+ [aCoder encodeObject:[NSNumber numberWithUnsignedLongLong:_nwid]
+ forKey:NetworkNwidKey];
+ [aCoder encodeObject:_portDeviceName forKey:NetworkPortNameKey];
+ [aCoder encodeInteger:_portError forKey:NetworkPortErrorKey];
+ [aCoder encodeInteger:_status forKey:NetworkStatusKey];
+ [aCoder encodeInteger:_type forKey:NetworkTypeKey];
+ [aCoder encodeBool:_allowManaged forKey:NetworkAllowManagedKey];
+ [aCoder encodeBool:_allowGlobal forKey:NetworkAllowGlobalKey];
+ [aCoder encodeBool:_allowDefault forKey:NetworkAllowDefaultKey];
+}
+
++ (BOOL)defaultRouteExists:(NSArray<Network *>*)netList
+{
+ for(Network *net in netList) {
+ if (net.allowDefault && net.connected) {
+ return YES;
+ }
+ }
+ return NO;
+}
+
+- (NSString*)statusString {
+ switch(_status) {
+ case REQUESTING_CONFIGURATION:
+ return @"REQUESTING_CONFIGURATION";
+ case OK:
+ return @"OK";
+ case ACCESS_DENIED:
+ return @"ACCESS_DENIED";
+ case NOT_FOUND:
+ return @"NOT_FOUND";
+ case PORT_ERROR:
+ return @"PORT_ERROR";
+ case CLIENT_TOO_OLD:
+ return @"CLIENT_TOO_OLD";
+ default:
+ return @"";
+ }
+}
+
+- (NSString*)typeString {
+ switch(_type) {
+ case PUBLIC:
+ return @"PUBLIC";
+ case PRIVATE:
+ return @"PRIVATE";
+ default:
+ return @"";
+ }
+}
+
+@end
diff --git a/macui/ZeroTier One/NetworkInfoCell.h b/macui/ZeroTier One/NetworkInfoCell.h
new file mode 100644
index 00000000..be9345d7
--- /dev/null
+++ b/macui/ZeroTier One/NetworkInfoCell.h
@@ -0,0 +1,50 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+@class ShowNetworksViewController;
+
+@interface NetworkInfoCell : NSTableCellView
+
+@property (weak, nonatomic) ShowNetworksViewController *parent;
+
+@property (weak, nonatomic) IBOutlet NSTextField *networkIdField;
+@property (weak, nonatomic) IBOutlet NSTextField *networkNameField;
+@property (weak, nonatomic) IBOutlet NSTextField *statusField;
+@property (weak, nonatomic) IBOutlet NSTextField *typeField;
+@property (weak, nonatomic) IBOutlet NSTextField *macField;
+@property (weak, nonatomic) IBOutlet NSTextField *mtuField;
+@property (weak, nonatomic) IBOutlet NSTextField *broadcastField;
+@property (weak, nonatomic) IBOutlet NSTextField *bridgingField;
+@property (weak, nonatomic) IBOutlet NSTextField *deviceField;
+@property (weak, nonatomic) IBOutlet NSTextField *addressesField;
+@property (weak, nonatomic) IBOutlet NSButton *allowManaged;
+@property (weak, nonatomic) IBOutlet NSButton *allowGlobal;
+@property (weak, nonatomic) IBOutlet NSButton *allowDefault;
+@property (weak, nonatomic) IBOutlet NSButton *connectedCheckbox;
+@property (weak, nonatomic) IBOutlet NSButton *deleteButton;
+
+- (IBAction)onConnectCheckStateChanged:(NSButton*)sender;
+- (IBAction)deleteNetwork:(NSButton*)sender;
+- (IBAction)onAllowStatusChanged:(NSButton*)sender;
+
+- (void)joinNetwork:(NSString*)nwid;
+- (void)leaveNetwork:(NSString*)nwid;
+
+@end
diff --git a/macui/ZeroTier One/NetworkInfoCell.m b/macui/ZeroTier One/NetworkInfoCell.m
new file mode 100644
index 00000000..dc75cab3
--- /dev/null
+++ b/macui/ZeroTier One/NetworkInfoCell.m
@@ -0,0 +1,85 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import "NetworkInfoCell.h"
+#import "ServiceCom.h"
+#import "ShowNetworksViewController.h"
+#import "Network.h"
+
+@implementation NetworkInfoCell
+
+- (void)drawRect:(NSRect)dirtyRect {
+ [super drawRect:dirtyRect];
+
+ // Drawing code here.
+}
+
+- (IBAction)onConnectCheckStateChanged:(NSButton*)sender
+{
+ if(sender.state == NSOnState) {
+ [self joinNetwork:self.networkIdField.stringValue];
+ }
+ else {
+ [self leaveNetwork:self.networkIdField.stringValue];
+ }
+}
+
+- (IBAction)deleteNetwork:(NSButton*)sender;
+{
+ [self leaveNetwork:self.networkIdField.stringValue];
+ [self.parent deleteNetworkFromList:self.networkIdField.stringValue];
+}
+
+- (IBAction)onAllowStatusChanged:(NSButton*)sender
+{
+ [self joinNetwork:self.networkIdField.stringValue];
+}
+
+- (void)joinNetwork:(NSString*)nwid
+{
+ NSError *error = nil;
+ [[ServiceCom sharedInstance] joinNetwork:nwid
+ allowManaged:(self.allowManaged.state == NSOnState)
+ allowGlobal:(self.allowGlobal.state == NSOnState)
+ allowDefault:![Network defaultRouteExists:_parent.networkList] && (self.allowDefault.state == NSOnState)
+ error:&error];
+
+ if (error) {
+ NSAlert *alert = [NSAlert alertWithError:error];
+ alert.alertStyle = NSCriticalAlertStyle;
+ [alert addButtonWithTitle:@"Ok"];
+
+ [alert runModal];
+ }
+}
+
+- (void)leaveNetwork:(NSString*)nwid
+{
+ NSError *error = nil;
+ [[ServiceCom sharedInstance] leaveNetwork:nwid error:&error];
+
+ if (error) {
+ NSAlert *alert = [NSAlert alertWithError:error];
+ alert.alertStyle = NSCriticalAlertStyle;
+ [alert addButtonWithTitle:@"Ok"];
+
+ [alert runModal];
+ }
+}
+
+@end
diff --git a/macui/ZeroTier One/NetworkMonitor.h b/macui/ZeroTier One/NetworkMonitor.h
new file mode 100644
index 00000000..8cdec4ed
--- /dev/null
+++ b/macui/ZeroTier One/NetworkMonitor.h
@@ -0,0 +1,45 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import <Foundation/Foundation.h>
+
+extern NSString * const NetworkUpdateKey;
+extern NSString * const StatusUpdateKey;
+
+@class Network;
+
+@interface NetworkMonitor : NSObject
+{
+ NSMutableArray<Network*> *_savedNetworks;
+ NSArray<Network*> *_receivedNetworks;
+ NSMutableArray<Network*> *_allNetworks;
+
+ NSTimer *_timer;
+}
+
+- (id)init;
+- (void)dealloc;
+
+- (void)start;
+- (void)stop;
+
+- (void)updateNetworkInfo;
+
+- (void)deleteSavedNetwork:(NSString*)networkId;
+
+@end
diff --git a/macui/ZeroTier One/NetworkMonitor.m b/macui/ZeroTier One/NetworkMonitor.m
new file mode 100644
index 00000000..7ed22c4a
--- /dev/null
+++ b/macui/ZeroTier One/NetworkMonitor.m
@@ -0,0 +1,253 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import "NetworkMonitor.h"
+#import "Network.h"
+#import "ServiceCom.h"
+#import "NodeStatus.h"
+
+@import AppKit;
+
+
+NSString * const NetworkUpdateKey = @"com.zerotier.one.network-list";
+NSString * const StatusUpdateKey = @"com.zerotier.one.status";
+
+@interface NetworkMonitor (private)
+
+- (NSString*)dataFile;
+- (void)internal_updateNetworkInfo;
+- (NSInteger)findNetworkWithID:(UInt64)networkId;
+- (NSInteger)findSavedNetworkWithID:(UInt64)networkId;
+- (void)saveNetworks;
+
+@end
+
+@implementation NetworkMonitor
+
+- (id)init
+{
+ self = [super init];
+ if(self)
+ {
+ _savedNetworks = [NSMutableArray<Network*> array];
+ _receivedNetworks = [NSArray<Network*> array];
+ _allNetworks = [NSMutableArray<Network*> array];
+ _timer = nil;
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [_timer invalidate];
+}
+
+- (void)start
+{
+ NSLog(@"ZeroTier monitor started");
+ _timer = [NSTimer scheduledTimerWithTimeInterval:1.0f
+ target:self
+ selector:@selector(updateNetworkInfo)
+ userInfo:nil
+ repeats:YES];
+}
+
+- (void)stop
+{
+ NSLog(@"ZeroTier monitor stopped");
+ [_timer invalidate];
+ _timer = nil;
+}
+
+- (void)updateNetworkInfo
+{
+ NSString *filePath = [self dataFile];
+
+ if([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
+ NSArray<Network*> *networks = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
+
+ if(networks != nil) {
+ _savedNetworks = [networks mutableCopy];
+ }
+ }
+
+ NSError *error = nil;
+
+ [[ServiceCom sharedInstance] getNetworklist:^(NSArray<Network *> *networkList) {
+ _receivedNetworks = networkList;
+
+ [[NSOperationQueue mainQueue] addOperationWithBlock:^{
+ [self internal_updateNetworkInfo];
+ } ];
+ } error:&error];
+
+ if(error) {
+ [self stop];
+
+ NSAlert *alert = [NSAlert alertWithError:error];
+ alert.alertStyle = NSCriticalAlertStyle;
+ [alert addButtonWithTitle:@"Quit"];
+ [alert addButtonWithTitle:@"Retry"];
+
+ NSModalResponse res = [alert runModal];
+
+ if(res == NSAlertFirstButtonReturn) {
+ [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0];
+ }
+ else if(res == NSAlertSecondButtonReturn) {
+ [self start];
+ return;
+ }
+ }
+
+ [[ServiceCom sharedInstance] getNodeStatus:^(NodeStatus *status) {
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:status forKey:@"status"];
+
+ [[NSOperationQueue mainQueue] addOperationWithBlock:^{
+ [[NSNotificationCenter defaultCenter] postNotificationName:StatusUpdateKey
+ object:nil
+ userInfo:userInfo];
+ }];
+ } error:&error];
+
+ if (error) {
+ [self stop];
+
+ NSAlert *alert = [NSAlert alertWithError:error];
+ alert.alertStyle = NSCriticalAlertStyle;
+ [alert addButtonWithTitle:@"Quit"];
+ [alert addButtonWithTitle:@"Retry"];
+
+ NSModalResponse res = [alert runModal];
+
+ if(res == NSAlertFirstButtonReturn) {
+ [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0];
+ }
+ else if(res == NSAlertSecondButtonReturn) {
+ [self start];
+ return;
+ }
+ }
+}
+
+- (void)deleteSavedNetwork:(NSString*)networkId
+{
+ UInt64 nwid = 0;
+ NSScanner *scanner = [NSScanner scannerWithString:networkId];
+ [scanner scanHexLongLong:&nwid];
+
+ NSInteger index = [self findNetworkWithID:nwid];
+
+ if(index != NSNotFound) {
+ [_allNetworks removeObjectAtIndex:index];
+ }
+
+ index = [self findSavedNetworkWithID:nwid];
+
+ if(index != NSNotFound) {
+ [_savedNetworks removeObjectAtIndex:index];
+ }
+
+ [self saveNetworks];
+}
+
+@end
+
+@implementation NetworkMonitor (private)
+- (NSString*)dataFile
+{
+ NSURL *appSupport = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
+ inDomains:NSUserDomainMask] objectAtIndex:0];
+
+ appSupport = [[[appSupport URLByAppendingPathComponent:@"ZeroTier"] URLByAppendingPathComponent:@"One"] URLByAppendingPathComponent:@"networkinfo.dat"];
+ return appSupport.path;
+}
+
+- (void)internal_updateNetworkInfo
+{
+ NSMutableArray<Network*> *networks = [_savedNetworks mutableCopy];
+
+ for(Network *nw in _receivedNetworks) {
+ NSInteger index = [self findSavedNetworkWithID:nw.nwid];
+
+ if(index != NSNotFound) {
+ [networks setObject:nw atIndexedSubscript:index];
+ }
+ else {
+ [networks addObject:nw];
+ }
+ }
+
+ [networks sortUsingComparator:^NSComparisonResult(Network *obj1, Network *obj2) {
+ if(obj1.nwid > obj2.nwid) {
+ return true;
+ }
+ return false;
+ }];
+
+ @synchronized(_allNetworks) {
+ _allNetworks = networks;
+ }
+
+ [self saveNetworks];
+
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:networks forKey:@"networks"];
+
+ [[NSNotificationCenter defaultCenter] postNotificationName:NetworkUpdateKey
+ object:nil
+ userInfo:userInfo];
+}
+
+- (NSInteger)findNetworkWithID:(UInt64)networkId
+{
+ for(int i = 0; i < [_allNetworks count]; ++i) {
+ Network *nw = [_allNetworks objectAtIndex:i];
+
+ if(nw.nwid == networkId) {
+ return i;
+ }
+ }
+
+ return NSNotFound;
+}
+
+
+- (NSInteger)findSavedNetworkWithID:(UInt64)networkId
+{
+ for(int i = 0; i < [_savedNetworks count]; ++i) {
+ Network *nw = [_savedNetworks objectAtIndex:i];
+
+ if(nw.nwid == networkId) {
+ return i;
+ }
+ }
+
+ return NSNotFound;
+}
+
+- (void)saveNetworks
+{
+ NSString *filePath = [self dataFile];
+
+ @synchronized(_allNetworks) {
+ [NSKeyedArchiver archiveRootObject:_allNetworks toFile:filePath];
+ }
+}
+
+@end
diff --git a/macui/ZeroTier One/NodeStatus.h b/macui/ZeroTier One/NodeStatus.h
new file mode 100644
index 00000000..eab5bfe4
--- /dev/null
+++ b/macui/ZeroTier One/NodeStatus.h
@@ -0,0 +1,35 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import <Foundation/Foundation.h>
+
+@interface NodeStatus : NSObject
+
+@property (readonly) NSString *address;
+@property (readonly) NSString *publicIdentity;
+@property (readonly) BOOL online;
+@property (readonly) BOOL tcpFallbackActive;
+@property (readonly) int versionMajor;
+@property (readonly) int versionMinor;
+@property (readonly) int versionRev;
+@property (readonly) NSString *version;
+@property (readonly) UInt64 clock;
+
+- (id)initWithJsonData:(NSDictionary*)jsonData;
+
+@end
diff --git a/macui/ZeroTier One/NodeStatus.m b/macui/ZeroTier One/NodeStatus.m
new file mode 100644
index 00000000..3bae3c7d
--- /dev/null
+++ b/macui/ZeroTier One/NodeStatus.m
@@ -0,0 +1,40 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#import "NodeStatus.h"
+
+@implementation NodeStatus
+
+- (id)initWithJsonData:(NSDictionary*)jsonData
+{
+ self = [super init];
+
+ if(self) {
+ _address = (NSString*)[jsonData objectForKey:@"address"];
+ _publicIdentity = (NSString*)[jsonData objectForKey:@"publicIdentity"];
+ _online = [(NSNumber*)[jsonData objectForKey:@"online"] boolValue];
+ _tcpFallbackActive = [(NSNumber*)[jsonData objectForKey:@"tcpFallbackActive"] boolValue];
+ _versionMajor = [(NSNumber*)[jsonData objectForKey:@"versionMajor"] intValue];
+ _versionMinor = [(NSNumber*)[jsonData objectForKey:@"versionMinor"] intValue];
+ _versionRev = [(NSNumber*)[jsonData objectForKey:@"versionRev"] intValue];
+ _version = (NSString*)[jsonData objectForKey:@"version"];
+ _clock = [(NSNumber*)[jsonData objectForKey:@"clock"] unsignedLongLongValue];
+ }
+
+ return self;
+}
+@end
diff --git a/macui/ZeroTier One/PreferencesViewController.h b/macui/ZeroTier One/PreferencesViewController.h
new file mode 100644
index 00000000..56d0fdb8
--- /dev/null
+++ b/macui/ZeroTier One/PreferencesViewController.h
@@ -0,0 +1,31 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+@interface PreferencesViewController : NSViewController
+
+@property (nonatomic, weak) IBOutlet NSButton *startupCheckBox;
+
+- (IBAction)onStartupCheckBoxChanged:(NSButton*)sender;
+
+- (BOOL)isLaunchAtStartup;
+- (LSSharedFileListItemRef)itemRefInLoginItems;
+- (void)setLaunchAtLoginEnabled:(BOOL)enabled;
+
+@end
diff --git a/macui/ZeroTier One/PreferencesViewController.m b/macui/ZeroTier One/PreferencesViewController.m
new file mode 100644
index 00000000..13927fba
--- /dev/null
+++ b/macui/ZeroTier One/PreferencesViewController.m
@@ -0,0 +1,112 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import "PreferencesViewController.h"
+
+@interface PreferencesViewController ()
+
+@end
+
+@implementation PreferencesViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+
+ if([self isLaunchAtStartup]) {
+ self.startupCheckBox.state = NSOnState;
+ }
+ else {
+ self.startupCheckBox.state = NSOffState;
+ }
+}
+
+- (IBAction)onStartupCheckBoxChanged:(NSButton *)sender
+{
+ if(sender.state == NSOnState) {
+ [self setLaunchAtLoginEnabled:YES];
+ }
+ else {
+ [self setLaunchAtLoginEnabled:NO];
+ }
+
+}
+
+- (void)setLaunchAtLoginEnabled:(BOOL)enabled
+{
+ LSSharedFileListRef loginItemsRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
+
+ if (enabled) {
+ // Add the app to the LoginItems list.
+ CFURLRef appUrl = (__bridge CFURLRef)[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
+ LSSharedFileListItemRef itemRef = LSSharedFileListInsertItemURL(loginItemsRef, kLSSharedFileListItemLast, NULL, NULL, appUrl, NULL, NULL);
+ if (itemRef) CFRelease(itemRef);
+ }
+ else {
+ // Remove the app from the LoginItems list.
+ LSSharedFileListItemRef itemRef = [self itemRefInLoginItems];
+ LSSharedFileListItemRemove(loginItemsRef,itemRef);
+ if (itemRef != nil) CFRelease(itemRef);
+ }
+}
+
+
+- (BOOL)isLaunchAtStartup {
+ // See if the app is currently in LoginItems.
+ LSSharedFileListItemRef itemRef = [self itemRefInLoginItems];
+ // Store away that boolean.
+ BOOL isInList = itemRef != nil;
+ // Release the reference if it exists.
+ if (itemRef != nil) CFRelease(itemRef);
+
+ return isInList;
+}
+
+- (LSSharedFileListItemRef)itemRefInLoginItems {
+ LSSharedFileListItemRef itemRef = nil;
+
+ NSString * appPath = [[NSBundle mainBundle] bundlePath];
+
+ // This will retrieve the path for the application
+ // For example, /Applications/test.app
+ CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:appPath];
+
+ // Create a reference to the shared file list.
+ LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
+
+ if (loginItems) {
+ UInt32 seedValue;
+ //Retrieve the list of Login Items and cast them to
+ // a NSArray so that it will be easier to iterate.
+ NSArray *loginItemsArray = (__bridge NSArray *)LSSharedFileListCopySnapshot(loginItems, &seedValue);
+ for(int i = 0; i< [loginItemsArray count]; i++){
+ LSSharedFileListItemRef currentItemRef = (__bridge LSSharedFileListItemRef)[loginItemsArray
+ objectAtIndex:i];
+ //Resolve the item with URL
+ if (LSSharedFileListItemResolve(currentItemRef, 0, (CFURLRef*) &url, NULL) == noErr) {
+ NSString * urlPath = [(__bridge NSURL*)url path];
+ if ([urlPath compare:appPath] == NSOrderedSame){
+ itemRef = currentItemRef;
+ }
+ }
+ }
+ }
+ CFRelease(loginItems);
+ return itemRef;
+}
+
+@end
diff --git a/macui/ZeroTier One/PreferencesViewController.xib b/macui/ZeroTier One/PreferencesViewController.xib
new file mode 100644
index 00000000..62aef4c0
--- /dev/null
+++ b/macui/ZeroTier One/PreferencesViewController.xib
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10116" systemVersion="15F34" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+ <dependencies>
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10116"/>
+ </dependencies>
+ <objects>
+ <customObject id="-2" userLabel="File's Owner" customClass="PreferencesViewController" customModule="ZeroTier_One" customModuleProvider="target">
+ <connections>
+ <outlet property="startupCheckBox" destination="XSk-jN-ner" id="nvL-b1-gza"/>
+ <outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
+ </connections>
+ </customObject>
+ <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+ <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+ <customView id="Hz6-mo-xeY">
+ <rect key="frame" x="0.0" y="0.0" width="284" height="54"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <subviews>
+ <button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="XSk-jN-ner">
+ <rect key="frame" x="18" y="18" width="248" height="18"/>
+ <buttonCell key="cell" type="check" title="Start ZeroTier One on system startup" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="VkJ-h4-tHf">
+ <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+ <font key="font" metaFont="system"/>
+ </buttonCell>
+ <connections>
+ <action selector="onStartupCheckBoxChanged:" target="-2" id="zAQ-DJ-c3w"/>
+ </connections>
+ </button>
+ </subviews>
+ <point key="canvasLocation" x="365" y="208"/>
+ </customView>
+ </objects>
+</document>
diff --git a/macui/ZeroTier One/ServiceCom.h b/macui/ZeroTier One/ServiceCom.h
new file mode 100644
index 00000000..74ab2b35
--- /dev/null
+++ b/macui/ZeroTier One/ServiceCom.h
@@ -0,0 +1,39 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import <Foundation/Foundation.h>
+
+@class NodeStatus;
+@class Network;
+
+@interface ServiceCom : NSObject
+{
+ NSString *baseURL;
+ NSURLSession *session;
+ BOOL _isQuitting;
+}
++ (ServiceCom*)sharedInstance;
+
+- (id)init;
+
+- (void)getNetworklist:(void (^)(NSArray<Network*>*))completionHandler error:(NSError* __autoreleasing *)error;
+- (void)getNodeStatus:(void (^)(NodeStatus*))completionHandler error:(NSError*__autoreleasing*)error;
+- (void)joinNetwork:(NSString*)networkId allowManaged:(BOOL)allowManaged allowGlobal:(BOOL)allowGlobal allowDefault:(BOOL)allowDefault error:(NSError*__autoreleasing*)error;
+- (void)leaveNetwork:(NSString*)networkId error:(NSError*__autoreleasing*)error;
+
+@end
diff --git a/macui/ZeroTier One/ServiceCom.m b/macui/ZeroTier One/ServiceCom.m
new file mode 100644
index 00000000..4982d40e
--- /dev/null
+++ b/macui/ZeroTier One/ServiceCom.m
@@ -0,0 +1,464 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import "ServiceCom.h"
+#import "AuthtokenCopy.h"
+#import "Network.h"
+#import "NodeStatus.h"
+@import AppKit;
+
+@interface ServiceCom (Private)
+
+- (NSString*)key;
+
+@end
+
+@implementation ServiceCom
+
++ (ServiceCom*)sharedInstance {
+ static ServiceCom *sc = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ sc = [[ServiceCom alloc] init];
+ });
+ return sc;
+}
+
+- (id)init
+{
+ self = [super init];
+ if(self) {
+ baseURL = @"http://localhost:9993";
+ session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]];
+ _isQuitting = NO;
+ }
+
+ return self;
+}
+
+- (NSString*)key:(NSError* __autoreleasing *)err
+{
+ static NSString *k = nil;
+
+ if (k == nil) {
+ NSError *error = nil;
+ NSURL *appSupportDir = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:false error:&error];
+
+ if (error) {
+ NSLog(@"Error: %@", error);
+ return @"";
+ }
+
+ appSupportDir = [[appSupportDir URLByAppendingPathComponent:@"ZeroTier"] URLByAppendingPathComponent:@"One"];
+ NSURL *authtokenURL = [appSupportDir URLByAppendingPathComponent:@"authtoken.secret"];
+
+ if ([[NSFileManager defaultManager] fileExistsAtPath:[authtokenURL path]]) {
+ k = [NSString stringWithContentsOfURL:authtokenURL
+ encoding:NSUTF8StringEncoding
+ error:&error];
+
+ if (error) {
+ NSLog(@"Error: %@", error);
+ k = nil;
+ *err = error;
+ return @"";
+ }
+ }
+ else {
+ NSURL *sysAppSupportDir = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSSystemDomainMask appropriateForURL:nil create:false error:nil];
+
+ sysAppSupportDir = [[sysAppSupportDir URLByAppendingPathComponent:@"ZeroTier"] URLByAppendingPathComponent:@"One"];
+ NSURL *sysAuthtokenURL = [sysAppSupportDir URLByAppendingPathComponent:@"authtoken.secret"];
+
+ if(![[NSFileManager defaultManager] fileExistsAtPath:[sysAuthtokenURL path]]) {
+
+ }
+
+ [[NSFileManager defaultManager] createDirectoryAtURL:appSupportDir
+ withIntermediateDirectories:YES
+ attributes:nil
+ error:&error];
+
+ if (error) {
+ NSLog(@"Error: %@", error);
+ *err = error;
+ k = nil;
+ return @"";
+ }
+
+ AuthorizationRef authRef;
+ OSStatus status = AuthorizationCreate(nil, nil, kAuthorizationFlagDefaults, &authRef);
+
+ if (status != errAuthorizationSuccess) {
+ NSLog(@"Authorization Failed! %d", status);
+
+ NSDictionary *userInfo = @{
+ NSLocalizedDescriptionKey: NSLocalizedString(@"Couldn't create AuthorizationRef", nil),
+ };
+ *err = [NSError errorWithDomain:@"com.zerotier.one" code:-1 userInfo:userInfo];
+
+ return @"";
+ }
+
+ AuthorizationItem authItem;
+ authItem.name = kAuthorizationRightExecute;
+ authItem.valueLength = 0;
+ authItem.flags = 0;
+
+ AuthorizationRights authRights;
+ authRights.count = 1;
+ authRights.items = &authItem;
+
+ AuthorizationFlags authFlags = kAuthorizationFlagDefaults |
+ kAuthorizationFlagInteractionAllowed |
+ kAuthorizationFlagPreAuthorize |
+ kAuthorizationFlagExtendRights;
+
+ status = AuthorizationCopyRights(authRef, &authRights, nil, authFlags, nil);
+
+ if (status != errAuthorizationSuccess) {
+ NSLog(@"Authorization Failed! %d", status);
+ NSDictionary *userInfo = @{
+ NSLocalizedDescriptionKey: NSLocalizedString(@"Couldn't copy authorization rights", nil),
+ };
+ *err = [NSError errorWithDomain:@"com.zerotier.one" code:-1 userInfo:userInfo];
+ return @"";
+ }
+
+ NSString *localKey = getAdminAuthToken(authRef);
+ AuthorizationFree(authRef, kAuthorizationFlagDestroyRights);
+
+ if (localKey != nil && [localKey lengthOfBytesUsingEncoding:NSUTF8StringEncoding] > 0) {
+ k = localKey;
+
+ [localKey writeToURL:authtokenURL
+ atomically:YES
+ encoding:NSUTF8StringEncoding
+ error:&error];
+
+ if (error) {
+ NSLog(@"Error writing token to disk: %@", error);
+ *err = error;
+ }
+ }
+ }
+ }
+
+ if (k == nil) {
+ NSDictionary *userInfo = @{
+ NSLocalizedDescriptionKey: NSLocalizedString(@"Unknown error finding authorization key", nil),
+ };
+ *err = [NSError errorWithDomain:@"com.zerotier.one" code:-1 userInfo:userInfo];
+
+ return @"";
+ }
+
+ return k;
+}
+
+- (void)getNetworklist:(void (^)(NSArray<Network *> *))completionHandler error:(NSError *__autoreleasing*)error
+{
+ NSString* key = [self key:error];
+ if(*error) {
+ return;
+ }
+
+ NSString *urlString = [[baseURL stringByAppendingString:@"/network?auth="] stringByAppendingString:key];
+
+ NSURL *url = [NSURL URLWithString:urlString];
+ NSURLSessionDataTask *task =
+ [session dataTaskWithURL:url
+ completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable err) {
+
+ if (err) {
+ [[NSOperationQueue mainQueue] addOperationWithBlock:^{
+ NSAlert *alert = [NSAlert alertWithError:err];
+ alert.alertStyle = NSCriticalAlertStyle;
+ [alert addButtonWithTitle:@"Quit"];
+ [alert addButtonWithTitle:@"Retry"];
+
+ NSModalResponse res;
+ if (!_isQuitting) {
+ res = [alert runModal];
+ }
+ else {
+ return;
+ }
+
+ if(res == NSAlertFirstButtonReturn) {
+ [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0];
+ _isQuitting = YES;
+ }
+ }];
+ return;
+ }
+
+ NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
+ NSInteger status = [httpResponse statusCode];
+
+ NSError *err2;
+
+ if (status == 200) {
+ NSArray *json = [NSJSONSerialization JSONObjectWithData:data
+ options:0
+ error:&err2];
+ if (err) {
+ NSLog(@"Error fetching network list: %@", err2);
+
+ [[NSOperationQueue mainQueue] addOperationWithBlock:^{
+ NSAlert *alert = [NSAlert alertWithError:err2];
+ alert.alertStyle = NSCriticalAlertStyle;
+ [alert addButtonWithTitle:@"Quit"];
+ [alert addButtonWithTitle:@"Retry"];
+
+ NSModalResponse res;
+ if (!_isQuitting) {
+ res = [alert runModal];
+ }
+ else {
+ return;
+ }
+
+ if(res == NSAlertFirstButtonReturn) {
+ _isQuitting = YES;
+ [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0];
+ }
+ }];
+ return;
+ }
+
+ NSMutableArray<Network*> *networks = [[NSMutableArray<Network*> alloc] init];
+ for(NSDictionary *dict in json) {
+ [networks addObject:[[Network alloc] initWithJsonData:dict]];
+ }
+
+ completionHandler(networks);
+ }
+ }];
+ [task resume];
+}
+
+- (void)getNodeStatus:(void (^)(NodeStatus*))completionHandler error:(NSError*__autoreleasing*)error
+{
+ NSString *key = [self key:error];
+ if(*error) {
+ return;
+ }
+
+ NSString *urlString = [[baseURL stringByAppendingString:@"/status?auth="] stringByAppendingString:key];
+
+ NSURL *url = [NSURL URLWithString:urlString];
+ NSURLSessionDataTask *task =
+ [session dataTaskWithURL:url
+ completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable err) {
+
+ if(err) {
+ [[NSOperationQueue mainQueue] addOperationWithBlock:^{
+ NSAlert *alert = [NSAlert alertWithError:err];
+ alert.alertStyle = NSCriticalAlertStyle;
+ [alert addButtonWithTitle:@"Quit"];
+ [alert addButtonWithTitle:@"Retry"];
+
+ NSModalResponse res;
+ if (!_isQuitting) {
+ res = [alert runModal];
+ }
+ else {
+ return;
+ }
+
+ if(res == NSAlertFirstButtonReturn) {
+ [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0];
+ _isQuitting = YES;
+ }
+ }];
+ return;
+ }
+
+ NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
+ NSInteger status = [httpResponse statusCode];
+
+ NSError *err2;
+ if(status == 200) {
+ NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data
+ options:0
+ error:&err2];
+
+ if(err2) {
+ NSLog(@"Error fetching node status: %@", err2);
+ [[NSOperationQueue mainQueue] addOperationWithBlock:^{
+ NSAlert *alert = [NSAlert alertWithError:err2];
+ alert.alertStyle = NSCriticalAlertStyle;
+ [alert addButtonWithTitle:@"Quit"];
+ [alert addButtonWithTitle:@"Retry"];
+
+ NSModalResponse res;
+ if (!_isQuitting) {
+ res = [alert runModal];
+ }
+ else {
+ return;
+ }
+
+ if(res == NSAlertFirstButtonReturn) {
+ [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0];
+ _isQuitting = YES;
+ }
+ }];
+ return;
+ }
+
+ NodeStatus *status = [[NodeStatus alloc] initWithJsonData:json];
+
+ completionHandler(status);
+ }
+ }];
+ [task resume];
+}
+
+- (void)joinNetwork:(NSString*)networkId allowManaged:(BOOL)allowManaged allowGlobal:(BOOL)allowGlobal allowDefault:(BOOL)allowDefault error:(NSError *__autoreleasing*)error
+{
+ NSString *key = [self key:error];
+ if(*error) {
+ return;
+ }
+
+ NSString *urlString = [[[[baseURL stringByAppendingString:@"/network/"]
+ stringByAppendingString:networkId]
+ stringByAppendingString:@"?auth="]
+ stringByAppendingString:key];
+
+ NSURL *url = [NSURL URLWithString:urlString];
+
+ NSMutableDictionary *jsonDict = [NSMutableDictionary dictionary];
+ [jsonDict setObject:[NSNumber numberWithBool:allowManaged] forKey:@"allowManaged"];
+ [jsonDict setObject:[NSNumber numberWithBool:allowGlobal] forKey:@"allowGlobal"];
+ [jsonDict setObject:[NSNumber numberWithBool:allowDefault] forKey:@"allowDefault"];
+
+ NSError *err = nil;
+
+ NSData *json = [NSJSONSerialization dataWithJSONObject:jsonDict
+ options:0
+ error:&err];
+
+ if(err) {
+ NSLog(@"Error creating json data: %@", err);
+ *error = err;
+ return;
+ }
+
+ NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
+ request.HTTPMethod = @"POST";
+ request.HTTPBody = json;
+ [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
+
+ NSURLSessionDataTask *task =
+ [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable err) {
+ if(err) {
+ NSLog(@"Error posting join request: %@", err);
+ [[NSOperationQueue mainQueue] addOperationWithBlock:^{
+ NSAlert *alert = [NSAlert alertWithError:err];
+ alert.alertStyle = NSCriticalAlertStyle;
+ [alert addButtonWithTitle:@"Quit"];
+ [alert addButtonWithTitle:@"Retry"];
+
+ NSModalResponse res;
+ if (!_isQuitting) {
+ res = [alert runModal];
+ }
+ else {
+ return;
+ }
+
+ if(res == NSAlertFirstButtonReturn) {
+ [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0];
+ _isQuitting = YES;
+ }
+ }];
+ }
+
+ NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
+ NSInteger status = [httpResponse statusCode];
+
+ if(status == 200) {
+ NSLog(@"join ok");
+ }
+ else {
+ NSLog(@"join error: %ld", (long)status);
+ }
+ }];
+ [task resume];
+}
+
+- (void)leaveNetwork:(NSString*)networkId error:(NSError*__autoreleasing*)error
+{
+ NSString *key = [self key:error];
+ if(*error) {
+ return;
+ }
+
+ NSString *urlString = [[[[baseURL stringByAppendingString:@"/network/"]
+ stringByAppendingString:networkId]
+ stringByAppendingString:@"?auth="]
+ stringByAppendingString:key];
+
+ NSURL *url = [NSURL URLWithString:urlString];
+
+ NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
+ request.HTTPMethod = @"DELETE";
+
+ NSURLSessionDataTask *task =
+ [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable err) {
+ if(err) {
+ NSLog(@"Error posting delete request: %@", err);
+ [[NSOperationQueue mainQueue] addOperationWithBlock:^{
+ NSAlert *alert = [NSAlert alertWithError:err];
+ alert.alertStyle = NSCriticalAlertStyle;
+ [alert addButtonWithTitle:@"Quit"];
+ [alert addButtonWithTitle:@"Retry"];
+
+ NSModalResponse res;
+ if (!_isQuitting) {
+ res = [alert runModal];
+ }
+ else {
+ return;
+ }
+
+ if(res == NSAlertFirstButtonReturn) {
+ [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0];
+ _isQuitting = YES;
+ }
+ }];
+ return;
+ }
+
+ NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
+ NSInteger status = httpResponse.statusCode;
+
+ if(status == 200) {
+ NSLog(@"leave ok");
+ }
+ else {
+ NSLog(@"leave error: %ld", status);
+ }
+ }];
+ [task resume];
+}
+
+@end
diff --git a/macui/ZeroTier One/ShowNetworksViewController.h b/macui/ZeroTier One/ShowNetworksViewController.h
new file mode 100644
index 00000000..5c251674
--- /dev/null
+++ b/macui/ZeroTier One/ShowNetworksViewController.h
@@ -0,0 +1,36 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+@class NetworkMonitor;
+@class Network;
+
+@interface ShowNetworksViewController : NSViewController<NSTableViewDelegate, NSTableViewDataSource>
+
+@property (nonatomic) NSArray<Network*> *networkList;
+@property (nonatomic) NetworkMonitor *netMonitor;
+@property (nonatomic) BOOL visible;
+
+@property (weak, nonatomic) IBOutlet NSTableView *tableView;
+
+- (void)deleteNetworkFromList:(NSString*)nwid;
+- (void)setNetworks:(NSArray<Network*>*)list;
+
+
+@end
diff --git a/macui/ZeroTier One/ShowNetworksViewController.m b/macui/ZeroTier One/ShowNetworksViewController.m
new file mode 100644
index 00000000..e3a1e52c
--- /dev/null
+++ b/macui/ZeroTier One/ShowNetworksViewController.m
@@ -0,0 +1,119 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import "ShowNetworksViewController.h"
+#import "NetworkMonitor.h"
+#import "NetworkInfoCell.h"
+#import "Network.h"
+
+@interface ShowNetworksViewController ()
+
+@end
+
+@implementation ShowNetworksViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+
+ [self.tableView setDelegate:self];
+ [self.tableView setDataSource:self];
+ [self.tableView setBackgroundColor:[NSColor clearColor]];
+}
+
+- (void)viewWillAppear {
+ [super viewWillAppear];
+ self.visible = YES;
+}
+
+- (void)viewWillDisappear {
+ [super viewWillDisappear];
+ self.visible = NO;
+}
+
+- (void)deleteNetworkFromList:(NSString *)nwid {
+ [self.netMonitor deleteSavedNetwork:nwid];
+}
+
+- (void)setNetworks:(NSArray<Network *> *)list {
+ _networkList = list;
+ if(_visible) {
+ [_tableView reloadData];
+ }
+}
+
+- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
+ return [_networkList count];
+}
+
+- (NSView*)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
+{
+ NetworkInfoCell *cell = (NetworkInfoCell*)[tableView makeViewWithIdentifier:@"NetworkInfoCell"
+ owner:nil];
+ Network *network = [_networkList objectAtIndex:row];
+ cell.parent = self;
+ cell.networkIdField.stringValue = [NSString stringWithFormat:@"%10llx", network.nwid];
+ cell.networkNameField.stringValue = network.name;
+ cell.statusField.stringValue = [network statusString];
+ cell.typeField.stringValue = [network typeString];
+ cell.mtuField.stringValue = [NSString stringWithFormat:@"%d", network.mtu];
+ cell.macField.stringValue = network.mac;
+ cell.broadcastField.stringValue = network.broadcastEnabled ? @"ENABLED" : @"DISABLED";
+ cell.bridgingField.stringValue = network.bridge ? @"ENABLED" : @"DISABLED";
+ cell.deviceField.stringValue = network.portDeviceName;
+
+ if(network.connected) {
+ cell.connectedCheckbox.state = NSOnState;
+
+ if(network.allowDefault) {
+ cell.allowDefault.enabled = YES;
+ cell.allowDefault.state = NSOnState;
+ }
+ else {
+ cell.allowDefault.state = NSOffState;
+
+ if([Network defaultRouteExists:_networkList]) {
+ cell.allowDefault.enabled = NO;
+ }
+ else {
+ cell.allowDefault.enabled = YES;
+ }
+ }
+
+ cell.allowGlobal.enabled = YES;
+ cell.allowManaged.enabled = YES;
+ }
+ else {
+ cell.connectedCheckbox.state = NSOffState;
+ cell.allowDefault.enabled = NO;
+ cell.allowGlobal.enabled = NO;
+ cell.allowManaged.enabled = NO;
+ }
+
+ cell.allowGlobal.state = network.allowGlobal ? NSOnState : NSOffState;
+ cell.allowManaged.state = network.allowManaged ? NSOnState : NSOffState;
+
+ cell.addressesField.stringValue = @"";
+
+ for(NSString *addr in network.assignedAddresses) {
+ cell.addressesField.stringValue = [[cell.addressesField.stringValue stringByAppendingString:addr] stringByAppendingString:@"\n"];
+ }
+
+ return cell;
+}
+
+@end
diff --git a/macui/ZeroTier One/ShowNetworksViewController.xib b/macui/ZeroTier One/ShowNetworksViewController.xib
new file mode 100644
index 00000000..f26cb446
--- /dev/null
+++ b/macui/ZeroTier One/ShowNetworksViewController.xib
@@ -0,0 +1,370 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10116" systemVersion="15G31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+ <dependencies>
+ <deployment identifier="macosx"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10116"/>
+ </dependencies>
+ <objects>
+ <customObject id="-2" userLabel="File's Owner" customClass="ShowNetworksViewController" customModule="ZeroTier_One" customModuleProvider="target">
+ <connections>
+ <outlet property="tableView" destination="w5O-vn-cYB" id="Ud6-Bs-UFB"/>
+ <outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
+ </connections>
+ </customObject>
+ <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+ <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+ <customView id="Hz6-mo-xeY">
+ <rect key="frame" x="0.0" y="0.0" width="570" height="521"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <subviews>
+ <scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="388" horizontalPageScroll="10" verticalLineScroll="388" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="miH-DQ-i4L">
+ <rect key="frame" x="20" y="20" width="530" height="481"/>
+ <clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="KXW-dX-mx6">
+ <rect key="frame" x="0.0" y="0.0" width="530" height="481"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" selectionHighlightStyle="none" columnReordering="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="386" rowSizeStyle="automatic" viewBased="YES" id="w5O-vn-cYB">
+ <rect key="frame" x="0.0" y="0.0" width="530" height="0.0"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <size key="intercellSpacing" width="3" height="2"/>
+ <color key="backgroundColor" red="1" green="1" blue="1" alpha="0.59999999999999998" colorSpace="calibratedRGB"/>
+ <tableViewGridLines key="gridStyleMask" horizontal="YES"/>
+ <color key="gridColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
+ <tableColumns>
+ <tableColumn width="527" minWidth="40" maxWidth="1000" id="ztK-JB-A6Q">
+ <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
+ <font key="font" metaFont="smallSystem"/>
+ <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
+ </tableHeaderCell>
+ <textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="WAX-7a-M9n">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
+ <prototypeCellViews>
+ <tableCellView identifier="NetworkInfoCell" focusRingType="none" id="rmb-il-W5I" customClass="NetworkInfoCell">
+ <rect key="frame" x="1" y="1" width="527" height="386"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="EUT-1A-lgY">
+ <rect key="frame" x="480" y="364" width="44" height="19"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Label" id="bf8-gi-tpp">
+ <font key="font" size="13" name="AndaleMono"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ </textField>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="UAC-7B-K8Y">
+ <rect key="frame" x="55" y="339" width="43" height="17"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Status" id="KgQ-AO-0Us">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ </textField>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="FeX-UV-AFG">
+ <rect key="frame" x="64" y="314" width="34" height="17"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Type" id="8OM-iG-575">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ </textField>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LIA-GW-caM">
+ <rect key="frame" x="65" y="289" width="33" height="17"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="MAC" id="UK0-f8-L0U">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ </textField>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="EWq-81-Y6j">
+ <rect key="frame" x="64" y="264" width="34" height="17"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="MTU" id="5Wi-02-yNW">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ </textField>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Mi3-aV-yWg">
+ <rect key="frame" x="32" y="239" width="66" height="17"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Broadcast" id="b7V-FV-HH7">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ </textField>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="EKG-la-snQ">
+ <rect key="frame" x="43" y="214" width="55" height="17"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Bridging" id="bkm-pp-3tu">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ </textField>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="syI-eg-Zka">
+ <rect key="frame" x="52" y="114" width="46" height="17"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Device" id="3tY-hy-qbu">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ </textField>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="6Af-7O-CaP">
+ <rect key="frame" x="16" y="89" width="82" height="17"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Managed IPs" id="wHt-u2-7XG">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ </textField>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="wl2-R8-KQO">
+ <rect key="frame" x="480" y="338" width="44" height="19"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Label" id="fkd-z8-2sv">
+ <font key="font" size="13" name="AndaleMono"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ </textField>
+ <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="AiO-ZR-9A3">
+ <rect key="frame" x="-3" y="13" width="134" height="32"/>
+ <buttonCell key="cell" type="push" title="Delete Network" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="7Dg-VG-hjC">
+ <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="system"/>
+ </buttonCell>
+ <connections>
+ <action selector="deleteNetwork:" target="rmb-il-W5I" id="gBI-fk-OoF"/>
+ </connections>
+ </button>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="6rE-7R-shM">
+ <rect key="frame" x="1" y="189" width="97" height="17"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Allow Managed" id="AOv-4I-rQj">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ </textField>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dke-gy-mG0">
+ <rect key="frame" x="14" y="139" width="84" height="17"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Allow Default" id="SIi-Uu-Uzw">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ </textField>
+ <button translatesAutoresizingMaskIntoConstraints="NO" id="ulU-Mk-uV9">
+ <rect key="frame" x="504" y="188" width="22" height="18"/>
+ <buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="Zhe-BT-cXO">
+ <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+ <font key="font" metaFont="system"/>
+ </buttonCell>
+ <connections>
+ <action selector="onAllowStatusChanged:" target="rmb-il-W5I" id="FW8-5N-vt1"/>
+ </connections>
+ </button>
+ <button translatesAutoresizingMaskIntoConstraints="NO" id="8CS-6g-NFI">
+ <rect key="frame" x="504" y="139" width="22" height="18"/>
+ <buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" inset="2" id="S2d-lU-JhT">
+ <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+ <font key="font" metaFont="system"/>
+ </buttonCell>
+ <connections>
+ <action selector="onAllowStatusChanged:" target="rmb-il-W5I" id="ahh-N8-nhW"/>
+ </connections>
+ </button>
+ <button translatesAutoresizingMaskIntoConstraints="NO" id="URn-qw-7jG">
+ <rect key="frame" x="504" y="163" width="22" height="18"/>
+ <buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" inset="2" id="yBQ-2g-3t5">
+ <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+ <font key="font" metaFont="system"/>
+ </buttonCell>
+ <connections>
+ <action selector="onAllowStatusChanged:" target="rmb-il-W5I" id="XGy-pE-Dzf"/>
+ </connections>
+ </button>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="jdz-W3-UwS">
+ <rect key="frame" x="19" y="164" width="79" height="17"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Allow Global" id="PLj-4F-ROh">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ </textField>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dou-E5-8PF">
+ <rect key="frame" x="480" y="313" width="44" height="19"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Label" id="3Dq-d7-Gt3">
+ <font key="font" size="13" name="AndaleMono"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ </textField>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oD0-Qh-7L4">
+ <rect key="frame" x="480" y="288" width="44" height="19"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Label" id="35o-HJ-KYh">
+ <font key="font" size="13" name="AndaleMono"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ </textField>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="98C-J0-Y03">
+ <rect key="frame" x="480" y="263" width="44" height="19"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Label" id="63c-hy-MHZ">
+ <font key="font" size="13" name="AndaleMono"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ </textField>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="la4-uE-ffj">
+ <rect key="frame" x="480" y="238" width="44" height="19"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Label" id="rtd-6E-MsN">
+ <font key="font" size="13" name="AndaleMono"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ </textField>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="qHh-oP-Tdb">
+ <rect key="frame" x="480" y="213" width="44" height="19"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Label" id="Hex-iJ-2by">
+ <font key="font" size="13" name="AndaleMono"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ </textField>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="C0q-lJ-5dX">
+ <rect key="frame" x="480" y="113" width="44" height="19"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Label" id="5Ms-FS-k0W">
+ <font key="font" size="13" name="AndaleMono"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ </textField>
+ <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="GEJ-6D-gWU">
+ <rect key="frame" x="102" y="86" width="424" height="19"/>
+ <textFieldCell key="cell" sendsActionOnEndEditing="YES" alignment="right" title="Multiline Label" id="A3M-ZZ-6h7">
+ <font key="font" size="13" name="AndaleMono"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ </textField>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lO9-Jg-9f8">
+ <rect key="frame" x="1" y="364" width="44" height="19"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Label" id="p7O-rs-RvR">
+ <font key="font" size="13" name="AndaleMono"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ </textField>
+ <button translatesAutoresizingMaskIntoConstraints="NO" id="KRe-B1-51k">
+ <rect key="frame" x="437" y="22" width="89" height="18"/>
+ <buttonCell key="cell" type="check" title="Connected" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="DWJ-ZT-UIa">
+ <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+ <font key="font" metaFont="system"/>
+ </buttonCell>
+ <connections>
+ <action selector="onConnectCheckStateChanged:" target="rmb-il-W5I" id="Ov2-bq-67i"/>
+ </connections>
+ </button>
+ </subviews>
+ <constraints>
+ <constraint firstItem="LIA-GW-caM" firstAttribute="trailing" secondItem="EWq-81-Y6j" secondAttribute="trailing" id="0vU-gw-esB"/>
+ <constraint firstItem="FeX-UV-AFG" firstAttribute="trailing" secondItem="LIA-GW-caM" secondAttribute="trailing" id="1xy-N5-B6U"/>
+ <constraint firstItem="FeX-UV-AFG" firstAttribute="top" secondItem="UAC-7B-K8Y" secondAttribute="bottom" constant="8" id="2Vh-D6-T45"/>
+ <constraint firstItem="qHh-oP-Tdb" firstAttribute="centerY" secondItem="EKG-la-snQ" secondAttribute="centerY" id="4xy-tk-eA7"/>
+ <constraint firstItem="LIA-GW-caM" firstAttribute="top" secondItem="FeX-UV-AFG" secondAttribute="bottom" constant="8" id="5Vd-2M-2yv"/>
+ <constraint firstItem="dou-E5-8PF" firstAttribute="centerY" secondItem="FeX-UV-AFG" secondAttribute="centerY" id="7iQ-Vx-9AJ"/>
+ <constraint firstAttribute="trailing" secondItem="wl2-R8-KQO" secondAttribute="trailing" constant="5" id="CaI-uw-8uL"/>
+ <constraint firstItem="Mi3-aV-yWg" firstAttribute="top" secondItem="EWq-81-Y6j" secondAttribute="bottom" constant="8" id="Cen-A6-Hdr"/>
+ <constraint firstItem="URn-qw-7jG" firstAttribute="centerY" secondItem="jdz-W3-UwS" secondAttribute="centerY" id="DEW-3F-MXB"/>
+ <constraint firstAttribute="trailing" secondItem="GEJ-6D-gWU" secondAttribute="trailing" constant="3" id="FC0-J7-2QF"/>
+ <constraint firstItem="EWq-81-Y6j" firstAttribute="trailing" secondItem="Mi3-aV-yWg" secondAttribute="trailing" id="FDe-x8-ERi"/>
+ <constraint firstAttribute="trailing" secondItem="qHh-oP-Tdb" secondAttribute="trailing" constant="5" id="Fcy-KL-LsD"/>
+ <constraint firstAttribute="trailing" secondItem="C0q-lJ-5dX" secondAttribute="trailing" constant="5" id="I3g-Oy-geM"/>
+ <constraint firstItem="EUT-1A-lgY" firstAttribute="top" secondItem="rmb-il-W5I" secondAttribute="top" constant="3" id="II4-Ta-EeQ"/>
+ <constraint firstItem="98C-J0-Y03" firstAttribute="centerY" secondItem="EWq-81-Y6j" secondAttribute="centerY" id="Ibd-3u-9fm"/>
+ <constraint firstAttribute="trailing" secondItem="8CS-6g-NFI" secondAttribute="trailing" constant="3" id="Jqv-Kb-lOT"/>
+ <constraint firstAttribute="trailing" secondItem="dou-E5-8PF" secondAttribute="trailing" constant="5" id="LPR-aD-BAP"/>
+ <constraint firstItem="jdz-W3-UwS" firstAttribute="top" secondItem="6rE-7R-shM" secondAttribute="bottom" constant="8" id="Lit-Zb-6pq"/>
+ <constraint firstItem="UAC-7B-K8Y" firstAttribute="top" secondItem="lO9-Jg-9f8" secondAttribute="bottom" constant="8" id="Mn0-LO-gSb"/>
+ <constraint firstItem="la4-uE-ffj" firstAttribute="centerY" secondItem="Mi3-aV-yWg" secondAttribute="centerY" id="Nhp-8t-OoR"/>
+ <constraint firstItem="lO9-Jg-9f8" firstAttribute="leading" secondItem="rmb-il-W5I" secondAttribute="leading" constant="3" id="Rzw-sT-s3c"/>
+ <constraint firstAttribute="trailing" secondItem="la4-uE-ffj" secondAttribute="trailing" constant="5" id="SQo-oO-lbs"/>
+ <constraint firstItem="AiO-ZR-9A3" firstAttribute="top" secondItem="6Af-7O-CaP" secondAttribute="bottom" constant="48" id="SbW-ma-xhO"/>
+ <constraint firstItem="wl2-R8-KQO" firstAttribute="centerY" secondItem="UAC-7B-K8Y" secondAttribute="centerY" id="TIS-fV-H3y"/>
+ <constraint firstItem="C0q-lJ-5dX" firstAttribute="centerY" secondItem="syI-eg-Zka" secondAttribute="centerY" id="TXe-Dr-AUr"/>
+ <constraint firstItem="lO9-Jg-9f8" firstAttribute="top" secondItem="rmb-il-W5I" secondAttribute="top" constant="3" id="TqS-9j-AZ2"/>
+ <constraint firstAttribute="trailing" secondItem="oD0-Qh-7L4" secondAttribute="trailing" constant="5" id="U1G-04-Zjb"/>
+ <constraint firstItem="jdz-W3-UwS" firstAttribute="trailing" secondItem="6rE-7R-shM" secondAttribute="trailing" id="UWD-yZ-0gu"/>
+ <constraint firstItem="EWq-81-Y6j" firstAttribute="top" secondItem="LIA-GW-caM" secondAttribute="bottom" constant="8" id="WS4-Xr-Uvt"/>
+ <constraint firstItem="GEJ-6D-gWU" firstAttribute="top" secondItem="C0q-lJ-5dX" secondAttribute="bottom" constant="8" id="WhD-XW-q7s"/>
+ <constraint firstAttribute="trailing" secondItem="KRe-B1-51k" secondAttribute="trailing" constant="3" id="WsQ-kS-Luf"/>
+ <constraint firstItem="8CS-6g-NFI" firstAttribute="centerY" secondItem="dke-gy-mG0" secondAttribute="centerY" id="X1u-9J-gLq"/>
+ <constraint firstItem="EKG-la-snQ" firstAttribute="top" secondItem="Mi3-aV-yWg" secondAttribute="bottom" constant="8" id="XEZ-4d-Yl9"/>
+ <constraint firstItem="6Af-7O-CaP" firstAttribute="top" secondItem="syI-eg-Zka" secondAttribute="bottom" constant="8" id="Xi3-aV-ZPe"/>
+ <constraint firstAttribute="trailing" secondItem="EUT-1A-lgY" secondAttribute="trailing" constant="5" id="cDU-PD-iMu"/>
+ <constraint firstItem="6rE-7R-shM" firstAttribute="leading" secondItem="rmb-il-W5I" secondAttribute="leading" constant="3" id="cIK-gU-COR"/>
+ <constraint firstItem="6Af-7O-CaP" firstAttribute="trailing" secondItem="syI-eg-Zka" secondAttribute="trailing" id="gP2-re-FqN"/>
+ <constraint firstItem="syI-eg-Zka" firstAttribute="trailing" secondItem="dke-gy-mG0" secondAttribute="trailing" id="gdf-bR-0Qk"/>
+ <constraint firstAttribute="trailing" secondItem="98C-J0-Y03" secondAttribute="trailing" constant="5" id="ghX-kA-tEa"/>
+ <constraint firstItem="UAC-7B-K8Y" firstAttribute="trailing" secondItem="FeX-UV-AFG" secondAttribute="trailing" id="hOJ-rF-Xn8"/>
+ <constraint firstItem="GEJ-6D-gWU" firstAttribute="leading" secondItem="6Af-7O-CaP" secondAttribute="trailing" constant="8" id="iBi-dY-iZi"/>
+ <constraint firstItem="KRe-B1-51k" firstAttribute="baseline" secondItem="AiO-ZR-9A3" secondAttribute="baseline" id="jBe-rC-QAF"/>
+ <constraint firstItem="ulU-Mk-uV9" firstAttribute="centerY" secondItem="6rE-7R-shM" secondAttribute="centerY" id="l5g-Dr-Bht"/>
+ <constraint firstItem="6rE-7R-shM" firstAttribute="top" secondItem="EKG-la-snQ" secondAttribute="bottom" constant="8" id="lvp-Dn-1m0"/>
+ <constraint firstItem="syI-eg-Zka" firstAttribute="top" secondItem="dke-gy-mG0" secondAttribute="bottom" constant="8" id="pSb-5E-l16"/>
+ <constraint firstAttribute="trailing" secondItem="KRe-B1-51k" secondAttribute="trailing" constant="3" id="pZ7-4k-n3s"/>
+ <constraint firstItem="AiO-ZR-9A3" firstAttribute="leading" secondItem="rmb-il-W5I" secondAttribute="leading" constant="3" id="pau-hs-aaF"/>
+ <constraint firstItem="dke-gy-mG0" firstAttribute="trailing" secondItem="jdz-W3-UwS" secondAttribute="trailing" id="qd7-UW-evp"/>
+ <constraint firstItem="dke-gy-mG0" firstAttribute="top" secondItem="jdz-W3-UwS" secondAttribute="bottom" constant="8" id="sHm-Jz-ko8"/>
+ <constraint firstItem="EKG-la-snQ" firstAttribute="trailing" secondItem="6rE-7R-shM" secondAttribute="trailing" id="tNY-Wb-Aig"/>
+ <constraint firstAttribute="trailing" secondItem="URn-qw-7jG" secondAttribute="trailing" constant="3" id="tb8-cb-vhf"/>
+ <constraint firstAttribute="trailing" secondItem="ulU-Mk-uV9" secondAttribute="trailing" constant="3" id="vCN-jZ-bH7"/>
+ <constraint firstItem="oD0-Qh-7L4" firstAttribute="centerY" secondItem="LIA-GW-caM" secondAttribute="centerY" id="vol-qx-psh"/>
+ <constraint firstItem="Mi3-aV-yWg" firstAttribute="trailing" secondItem="EKG-la-snQ" secondAttribute="trailing" id="yWV-G4-bDS"/>
+ </constraints>
+ <connections>
+ <outlet property="addressesField" destination="GEJ-6D-gWU" id="MDn-cx-TNx"/>
+ <outlet property="allowDefault" destination="8CS-6g-NFI" id="YLt-Gp-HNa"/>
+ <outlet property="allowGlobal" destination="URn-qw-7jG" id="VUy-Mh-nK4"/>
+ <outlet property="allowManaged" destination="ulU-Mk-uV9" id="poZ-5E-m0m"/>
+ <outlet property="bridgingField" destination="qHh-oP-Tdb" id="eSw-Dw-QoN"/>
+ <outlet property="broadcastField" destination="la4-uE-ffj" id="A89-yC-MhR"/>
+ <outlet property="connectedCheckbox" destination="KRe-B1-51k" id="Sz5-Fq-jWL"/>
+ <outlet property="deleteButton" destination="AiO-ZR-9A3" id="rYp-Vt-gHl"/>
+ <outlet property="deviceField" destination="C0q-lJ-5dX" id="bc1-Fh-zT8"/>
+ <outlet property="macField" destination="oD0-Qh-7L4" id="4TS-1J-vRK"/>
+ <outlet property="mtuField" destination="98C-J0-Y03" id="KTM-C4-bEQ"/>
+ <outlet property="networkIdField" destination="lO9-Jg-9f8" id="UlQ-yf-ZYk"/>
+ <outlet property="networkNameField" destination="EUT-1A-lgY" id="Kwa-OJ-LTB"/>
+ <outlet property="statusField" destination="wl2-R8-KQO" id="fH2-Wl-DtW"/>
+ <outlet property="typeField" destination="dou-E5-8PF" id="v6U-sw-kzs"/>
+ </connections>
+ </tableCellView>
+ </prototypeCellViews>
+ </tableColumn>
+ </tableColumns>
+ </tableView>
+ </subviews>
+ <color key="backgroundColor" red="1" green="1" blue="1" alpha="0.59999999999999998" colorSpace="calibratedRGB"/>
+ </clipView>
+ <scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="ySn-XX-fde">
+ <rect key="frame" x="1" y="256" width="478" height="15"/>
+ <autoresizingMask key="autoresizingMask"/>
+ </scroller>
+ <scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="be6-Ft-7fo">
+ <rect key="frame" x="224" y="17" width="15" height="102"/>
+ <autoresizingMask key="autoresizingMask"/>
+ </scroller>
+ </scrollView>
+ </subviews>
+ <constraints>
+ <constraint firstAttribute="trailing" secondItem="miH-DQ-i4L" secondAttribute="trailing" constant="20" id="453-vf-nVL"/>
+ <constraint firstItem="miH-DQ-i4L" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="20" id="IS6-3U-KHq"/>
+ <constraint firstAttribute="bottom" secondItem="miH-DQ-i4L" secondAttribute="bottom" constant="20" id="YIB-OP-keG"/>
+ <constraint firstItem="miH-DQ-i4L" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="20" id="ceZ-7I-uoc"/>
+ </constraints>
+ <point key="canvasLocation" x="220" y="323.5"/>
+ </customView>
+ </objects>
+</document>
diff --git a/macui/ZeroTier One/ZeroTierIcon.icns b/macui/ZeroTier One/ZeroTierIcon.icns
new file mode 100644
index 00000000..17e60d58
--- /dev/null
+++ b/macui/ZeroTier One/ZeroTierIcon.icns
Binary files differ
diff --git a/macui/ZeroTier One/about.html b/macui/ZeroTier One/about.html
new file mode 100644
index 00000000..4fa41d7b
--- /dev/null
+++ b/macui/ZeroTier One/about.html
@@ -0,0 +1,65 @@
+<html>
+ <head>
+ <style type="text/css">
+ html,body {
+ background: #ffffff;
+ margin: 0;
+ padding: 0;
+ font-family: "Helvetica";
+ font-size: 12pt;
+ height: 100%;
+ width: 100%;
+ }
+ div.icon {
+ background: #ffb354;
+ color: #000000;
+ font-size: 100pt;
+ border-radius: 2.5rem;
+ display: inline-block;
+ width: 1.3em;
+ height: 1.3em;
+ padding: 0;
+ margin: 0;
+ line-height: 1.4em;
+ vertical-align: middle;
+ text-align: center;
+ }
+ div.icon_container {
+ font-weight: bold;
+ }
+ a,p,h1,h2,h3,h4,span,div,strong,center,lead,nav,ol,ul,li,img,button,input,textarea,form {
+ font-family: "Clear Sans Light","Helvetica Neue","Helvetica",sans-serif !important;
+ -webkit-font-smoothing: antialiased;
+ }
+ .code {
+ font-family: "Menlo","Consolas","Lucida Console","Bitstream Vera Sans Mono","Courier",monospace !important;
+ }
+ a:link {
+ text-decoration: none;
+ }
+ div.text {
+ padding: 5px;
+ }
+ </style>
+
+ </head>
+ <body>
+ <center>
+ <div class="icon_container">
+ <div class="icon">&#x23c1;</div>
+ </div>
+ </center>
+
+ <div class="text">
+ <h2>Getting Started</h2>
+
+ <p>Getting started is simple. Simply click <font class="code">Join Network</font> from the ZeroTier status bar menu. To join the public network "Earth", enter <font class="code">8056c2e21c000001</font> and click the Join button. Once connected, you'll be able to navigate to <a href="http://earth.zerotier.net">earth.zerotier.net</a>.</p>
+
+ <h3>Create a Network</h3>
+ <p>Visit <a href="http://my.zerotier.com">my.zerotier.com</a> to create and manage your own virtual networks.</p>
+
+ <p>For more information, visit <a href="http://www.zerotier.com">zerotier.com</a>.</p>
+
+ </div>
+ </body>
+</html> \ No newline at end of file
diff --git a/macui/ZeroTier One/main.m b/macui/ZeroTier One/main.m
new file mode 100644
index 00000000..108a6bd1
--- /dev/null
+++ b/macui/ZeroTier One/main.m
@@ -0,0 +1,23 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+int main(int argc, const char * argv[]) {
+ return NSApplicationMain(argc, argv);
+}
diff --git a/make-linux.mk b/make-linux.mk
index 016f7b7c..9dfd39bf 100644
--- a/make-linux.mk
+++ b/make-linux.mk
@@ -111,8 +111,8 @@ endif
all: one manpages
-one: $(OBJS) service/OneService.o one.o osdep/LinuxEthernetTap.o
- $(CXX) $(CXXFLAGS) $(LDFLAGS) -o zerotier-one $(OBJS) service/OneService.o one.o osdep/LinuxEthernetTap.o $(LDLIBS)
+one: $(OBJS) service/OneService.o one.o osdep/LinuxEthernetTap.o osdep/LinuxDropPrivileges.o
+ $(CXX) $(CXXFLAGS) $(LDFLAGS) -o zerotier-one $(OBJS) service/OneService.o one.o osdep/LinuxEthernetTap.o osdep/LinuxDropPrivileges.o $(LDLIBS)
$(STRIP) zerotier-one
ln -sf zerotier-one zerotier-idtool
ln -sf zerotier-one zerotier-cli
diff --git a/node/Capability.hpp b/node/Capability.hpp
index e23d7943..2c829ee5 100644
--- a/node/Capability.hpp
+++ b/node/Capability.hpp
@@ -166,7 +166,7 @@ public:
// field followed by field data. The inclusion of the size will allow non-supported
// rules to be ignored but still parsed.
b.append((uint8_t)rules[i].t);
- switch((ZT_VirtualNetworkRuleType)(rules[i].t & 0x7f)) {
+ switch((ZT_VirtualNetworkRuleType)(rules[i].t & 0x3f)) {
//case ZT_NETWORK_RULE_ACTION_DROP:
//case ZT_NETWORK_RULE_ACTION_ACCEPT:
//case ZT_NETWORK_RULE_ACTION_DEBUG_LOG:
@@ -174,6 +174,7 @@ public:
b.append((uint8_t)0);
break;
case ZT_NETWORK_RULE_ACTION_TEE:
+ case ZT_NETWORK_RULE_ACTION_WATCH:
case ZT_NETWORK_RULE_ACTION_REDIRECT:
b.append((uint8_t)14);
b.append((uint64_t)rules[i].v.fwd.address);
@@ -197,10 +198,6 @@ public:
b.append((uint8_t)1);
b.append((uint8_t)rules[i].v.vlanDei);
break;
- case ZT_NETWORK_RULE_MATCH_ETHERTYPE:
- b.append((uint8_t)2);
- b.append((uint16_t)rules[i].v.etherType);
- break;
case ZT_NETWORK_RULE_MATCH_MAC_SOURCE:
case ZT_NETWORK_RULE_MATCH_MAC_DEST:
b.append((uint8_t)6);
@@ -226,6 +223,10 @@ public:
b.append((uint8_t)1);
b.append((uint8_t)rules[i].v.ipProtocol);
break;
+ case ZT_NETWORK_RULE_MATCH_ETHERTYPE:
+ b.append((uint8_t)2);
+ b.append((uint16_t)rules[i].v.etherType);
+ break;
case ZT_NETWORK_RULE_MATCH_ICMP:
b.append((uint8_t)3);
b.append((uint8_t)rules[i].v.icmp.type);
@@ -239,15 +240,18 @@ public:
b.append((uint16_t)rules[i].v.port[1]);
break;
case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS:
- b.append((uint8_t)16);
- b.append((uint64_t)rules[i].v.characteristics[0]);
- b.append((uint64_t)rules[i].v.characteristics[1]);
+ b.append((uint8_t)8);
+ b.append((uint64_t)rules[i].v.characteristics);
break;
case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE:
b.append((uint8_t)4);
b.append((uint16_t)rules[i].v.frameSize[0]);
b.append((uint16_t)rules[i].v.frameSize[1]);
break;
+ case ZT_NETWORK_RULE_MATCH_RANDOM:
+ b.append((uint8_t)4);
+ b.append((uint32_t)rules[i].v.randomProbability);
+ break;
case ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE:
case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND:
case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR:
@@ -266,10 +270,11 @@ public:
while ((ruleCount < maxRuleCount)&&(p < b.size())) {
rules[ruleCount].t = (uint8_t)b[p++];
const unsigned int fieldLen = (unsigned int)b[p++];
- switch((ZT_VirtualNetworkRuleType)(rules[ruleCount].t & 0x7f)) {
+ switch((ZT_VirtualNetworkRuleType)(rules[ruleCount].t & 0x3f)) {
default:
break;
case ZT_NETWORK_RULE_ACTION_TEE:
+ case ZT_NETWORK_RULE_ACTION_WATCH:
case ZT_NETWORK_RULE_ACTION_REDIRECT:
rules[ruleCount].v.fwd.address = b.template at<uint64_t>(p);
rules[ruleCount].v.fwd.flags = b.template at<uint32_t>(p + 8);
@@ -288,9 +293,6 @@ public:
case ZT_NETWORK_RULE_MATCH_VLAN_DEI:
rules[ruleCount].v.vlanDei = (uint8_t)b[p];
break;
- case ZT_NETWORK_RULE_MATCH_ETHERTYPE:
- rules[ruleCount].v.etherType = b.template at<uint16_t>(p);
- break;
case ZT_NETWORK_RULE_MATCH_MAC_SOURCE:
case ZT_NETWORK_RULE_MATCH_MAC_DEST:
memcpy(rules[ruleCount].v.mac,b.field(p,6),6);
@@ -311,6 +313,9 @@ public:
case ZT_NETWORK_RULE_MATCH_IP_PROTOCOL:
rules[ruleCount].v.ipProtocol = (uint8_t)b[p];
break;
+ case ZT_NETWORK_RULE_MATCH_ETHERTYPE:
+ rules[ruleCount].v.etherType = b.template at<uint16_t>(p);
+ break;
case ZT_NETWORK_RULE_MATCH_ICMP:
rules[ruleCount].v.icmp.type = (uint8_t)b[p];
rules[ruleCount].v.icmp.code = (uint8_t)b[p+1];
@@ -322,17 +327,20 @@ public:
rules[ruleCount].v.port[1] = b.template at<uint16_t>(p + 2);
break;
case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS:
- rules[ruleCount].v.characteristics[0] = b.template at<uint64_t>(p);
- rules[ruleCount].v.characteristics[1] = b.template at<uint64_t>(p + 8);
+ rules[ruleCount].v.characteristics = b.template at<uint64_t>(p);
break;
case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE:
rules[ruleCount].v.frameSize[0] = b.template at<uint16_t>(p);
rules[ruleCount].v.frameSize[1] = b.template at<uint16_t>(p + 2);
break;
+ case ZT_NETWORK_RULE_MATCH_RANDOM:
+ rules[ruleCount].v.randomProbability = b.template at<uint32_t>(p);
+ break;
case ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE:
case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND:
case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR:
case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR:
+ case ZT_NETWORK_RULE_MATCH_TAGS_EQUAL:
rules[ruleCount].v.tag.id = b.template at<uint32_t>(p);
rules[ruleCount].v.tag.value = b.template at<uint32_t>(p + 4);
break;
diff --git a/node/Dictionary.hpp b/node/Dictionary.hpp
index eab2b162..15ab9ce3 100644
--- a/node/Dictionary.hpp
+++ b/node/Dictionary.hpp
@@ -23,7 +23,6 @@
#include "Utils.hpp"
#include "Buffer.hpp"
#include "Address.hpp"
-#include "C25519.hpp"
#include <stdint.h>
@@ -445,48 +444,13 @@ public:
}
/**
- * Sign this Dictionary, replacing any previous signature
- *
- * @param sigKey Key to use for signature in dictionary
- * @param kp Key pair to sign with
- */
- inline void wrapWithSignature(const char *sigKey,const C25519::Pair &kp)
- {
- this->erase(sigKey);
- C25519::Signature sig(C25519::sign(kp,this->data(),this->sizeBytes()));
- this->add(sigKey,reinterpret_cast<const char *>(sig.data),ZT_C25519_SIGNATURE_LEN);
- }
-
- /**
- * Verify signature (and erase signature key)
- *
- * This erases this Dictionary's signature key (if present) and verifies
- * the signature. The key is erased to render the Dictionary into the
- * original unsigned form it was signed in for verification purposes.
- *
- * @param sigKey Key to use for signature in dictionary
- * @param pk Public key to check against
- * @return True if signature was present and valid
- */
- inline bool unwrapAndVerify(const char *sigKey,const C25519::Public &pk)
- {
- char sig[ZT_C25519_SIGNATURE_LEN+1];
- if (this->get(sigKey,sig,sizeof(sig)) != ZT_C25519_SIGNATURE_LEN)
- return false;
- this->erase(sigKey);
- return C25519::verify(pk,this->data(),this->sizeBytes(),sig);
- }
-
- /**
- * @return Dictionary data as a 0-terminated C-string
- */
- inline const char *data() const { return _d; }
-
- /**
* @return Value of C template parameter
*/
inline unsigned int capacity() const { return C; }
+ inline const char *data() const { return _d; }
+ inline char *unsafeData() { return _d; }
+
private:
char _d[C];
};
diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp
index b3925773..5afacd0e 100644
--- a/node/IncomingPacket.cpp
+++ b/node/IncomingPacket.cpp
@@ -39,6 +39,7 @@
#include "CertificateOfMembership.hpp"
#include "Capability.hpp"
#include "Tag.hpp"
+#include "Revocation.hpp"
namespace ZeroTier {
@@ -66,7 +67,7 @@ bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR)
return _doHELLO(RR,false);
}
- SharedPtr<Peer> peer(RR->topology->getPeer(sourceAddress));
+ const SharedPtr<Peer> peer(RR->topology->getPeer(sourceAddress));
if (peer) {
if (!trusted) {
if (!dearmor(peer->key())) {
@@ -99,13 +100,12 @@ bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR)
case Packet::VERB_MULTICAST_LIKE: return _doMULTICAST_LIKE(RR,peer);
case Packet::VERB_NETWORK_CREDENTIALS: return _doNETWORK_CREDENTIALS(RR,peer);
case Packet::VERB_NETWORK_CONFIG_REQUEST: return _doNETWORK_CONFIG_REQUEST(RR,peer);
- case Packet::VERB_NETWORK_CONFIG_REFRESH: return _doNETWORK_CONFIG_REFRESH(RR,peer);
+ case Packet::VERB_NETWORK_CONFIG: return _doNETWORK_CONFIG(RR,peer);
case Packet::VERB_MULTICAST_GATHER: return _doMULTICAST_GATHER(RR,peer);
case Packet::VERB_MULTICAST_FRAME: return _doMULTICAST_FRAME(RR,peer);
case Packet::VERB_PUSH_DIRECT_PATHS: return _doPUSH_DIRECT_PATHS(RR,peer);
case Packet::VERB_CIRCUIT_TEST: return _doCIRCUIT_TEST(RR,peer);
case Packet::VERB_CIRCUIT_TEST_REPORT: return _doCIRCUIT_TEST_REPORT(RR,peer);
- case Packet::VERB_REQUEST_PROOF_OF_WORK: return _doREQUEST_PROOF_OF_WORK(RR,peer);
case Packet::VERB_USER_MESSAGE:
return true;
}
@@ -130,12 +130,18 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,const SharedPtr<Peer>
//TRACE("ERROR %s from %s(%s) in-re %s",Packet::errorString(errorCode),peer->address().toString().c_str(),_path->address().toString().c_str(),Packet::verbString(inReVerb));
+ /* Security note: we do not gate doERROR() with expectingReplyTo() to
+ * avoid having to log every outgoing packet ID. Instead we put the
+ * logic to determine whether we should consider an ERROR in each
+ * error handler. In most cases these are only trusted in specific
+ * circumstances. */
+
switch(errorCode) {
case Packet::ERROR_OBJ_NOT_FOUND:
// Object not found, currently only meaningful from network controllers.
if (inReVerb == Packet::VERB_NETWORK_CONFIG_REQUEST) {
- SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
+ const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
if ((network)&&(network->controller() == peer->address()))
network->setNotFound();
}
@@ -146,7 +152,7 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,const SharedPtr<Peer>
// consider it meaningful from network controllers. This would indicate
// that the queried node does not support acting as a controller.
if (inReVerb == Packet::VERB_NETWORK_CONFIG_REQUEST) {
- SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
+ const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
if ((network)&&(network->controller() == peer->address()))
network->setNotFound();
}
@@ -160,20 +166,15 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,const SharedPtr<Peer>
case Packet::ERROR_NEED_MEMBERSHIP_CERTIFICATE: {
// Peers can send this in response to frames if they do not have a recent enough COM from us
- SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
+ const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
const uint64_t now = RR->node->now();
- if ( (network) && (network->config().com) && (peer->rateGateComRequest(now)) ) {
- Packet outp(peer->address(),RR->identity.address(),Packet::VERB_NETWORK_CREDENTIALS);
- network->config().com.serialize(outp);
- outp.append((uint8_t)0);
- outp.armor(peer->key(),true);
- _path->send(RR,outp.data(),outp.size(),now);
- }
+ if ( (network) && (network->config().com) && (peer->rateGateIncomingComRequest(now)) )
+ network->pushCredentialsNow(peer->address(),now);
} break;
case Packet::ERROR_NETWORK_ACCESS_DENIED_: {
// Network controller: network access denied.
- SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
+ const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
if ((network)&&(network->controller() == peer->address()))
network->setAccessDenied();
} break;
@@ -181,9 +182,9 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,const SharedPtr<Peer>
case Packet::ERROR_UNWANTED_MULTICAST: {
// Members of networks can use this error to indicate that they no longer
// want to receive multicasts on a given channel.
- SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
- if ((network)&&(network->gate(peer,verb(),packetId()))) {
- MulticastGroup mg(MAC(field(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 8,6),6),at<uint32_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 14));
+ const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
+ if ((network)&&(network->gate(peer))) {
+ const MulticastGroup mg(MAC(field(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 8,6),6),at<uint32_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 14));
TRACE("%.16llx: peer %s unsubscrubed from multicast group %s",network->id(),peer->address().toString().c_str(),mg.toString().c_str());
RR->mc->remove(network->id(),mg,peer->address());
}
@@ -373,9 +374,7 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,const SharedPtr<Peer> &p
try {
const Packet::Verb inReVerb = (Packet::Verb)(*this)[ZT_PROTO_VERB_OK_IDX_IN_RE_VERB];
const uint64_t inRePacketId = at<uint64_t>(ZT_PROTO_VERB_OK_IDX_IN_RE_PACKET_ID);
- bool trustEstablished = false;
- // Don't parse OK packets that are not in response to a packet ID we sent
if (!RR->node->expectingReplyTo(inRePacketId)) {
TRACE("%s(%s): OK(%s) DROPPED: not expecting reply to %.16llx",peer->address().toString().c_str(),_path->address().toString().c_str(),Packet::verbString(inReVerb),packetId());
return true;
@@ -424,39 +423,23 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,const SharedPtr<Peer> &p
RR->sa->iam(peer->address(),_path->localAddress(),_path->address(),externalSurfaceAddress,RR->topology->isUpstream(peer->identity()),RR->node->now());
} break;
- case Packet::VERB_WHOIS: {
+ case Packet::VERB_WHOIS:
if (RR->topology->isUpstream(peer->identity())) {
const Identity id(*this,ZT_PROTO_VERB_WHOIS__OK__IDX_IDENTITY);
RR->sw->doAnythingWaitingForPeer(RR->topology->addPeer(SharedPtr<Peer>(new Peer(RR,RR->identity,id))));
}
- } break;
+ break;
case Packet::VERB_NETWORK_CONFIG_REQUEST: {
- const uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_NETWORK_ID);
- const SharedPtr<Network> network(RR->node->network(nwid));
- if ((network)&&(network->controller() == peer->address())) {
- trustEstablished = true;
- const unsigned int chunkLen = at<uint16_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT_LEN);
- const void *chunkData = field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT,chunkLen);
- unsigned int chunkIndex = 0;
- unsigned int totalSize = chunkLen;
- if ((ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT + chunkLen) < size()) {
- totalSize = at<uint32_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT + chunkLen);
- chunkIndex = at<uint32_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT + chunkLen + 4);
- }
- TRACE("%s(%s): OK(NETWORK_CONFIG_REQUEST) chunkLen==%u chunkIndex==%u totalSize==%u",source().toString().c_str(),_path->address().toString().c_str(),chunkLen,chunkIndex,totalSize);
- network->handleInboundConfigChunk(inRePacketId,chunkData,chunkLen,chunkIndex,totalSize);
- }
+ const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_OK_IDX_PAYLOAD)));
+ if (network)
+ network->handleConfigChunk(*this,ZT_PROTO_VERB_OK_IDX_PAYLOAD);
} break;
- //case Packet::VERB_ECHO: {
- //} break;
-
case Packet::VERB_MULTICAST_GATHER: {
const uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_NETWORK_ID);
- SharedPtr<Network> network(RR->node->network(nwid));
- if ((network)&&(network->gateMulticastGatherReply(peer,verb(),packetId()))) {
- trustEstablished = true;
+ const SharedPtr<Network> network(RR->node->network(nwid));
+ if (network) {
const MulticastGroup mg(MAC(field(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_MAC,6),6),at<uint32_t>(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_ADI));
//TRACE("%s(%s): OK(MULTICAST_GATHER) %.16llx/%s length %u",source().toString().c_str(),_path->address().toString().c_str(),nwid,mg.toString().c_str(),size());
const unsigned int count = at<uint16_t>(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_GATHER_RESULTS + 4);
@@ -471,7 +454,7 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,const SharedPtr<Peer> &p
//TRACE("%s(%s): OK(MULTICAST_FRAME) %.16llx/%s flags %.2x",peer->address().toString().c_str(),_path->address().toString().c_str(),nwid,mg.toString().c_str(),flags);
- SharedPtr<Network> network(RR->node->network(nwid));
+ const SharedPtr<Network> network(RR->node->network(nwid));
if (network) {
unsigned int offset = 0;
@@ -482,15 +465,12 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,const SharedPtr<Peer> &p
network->addCredential(com);
}
- if (network->gateMulticastGatherReply(peer,verb(),packetId())) {
- trustEstablished = true;
- if ((flags & 0x02) != 0) {
- // OK(MULTICAST_FRAME) includes implicit gather results
- offset += ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_COM_AND_GATHER_RESULTS;
- unsigned int totalKnown = at<uint32_t>(offset); offset += 4;
- unsigned int count = at<uint16_t>(offset); offset += 2;
- RR->mc->addMultiple(RR->node->now(),nwid,mg,field(offset,count * 5),count,totalKnown);
- }
+ if ((flags & 0x02) != 0) {
+ // OK(MULTICAST_FRAME) includes implicit gather results
+ offset += ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_COM_AND_GATHER_RESULTS;
+ unsigned int totalKnown = at<uint32_t>(offset); offset += 4;
+ unsigned int count = at<uint16_t>(offset); offset += 2;
+ RR->mc->addMultiple(RR->node->now(),nwid,mg,field(offset,count * 5),count,totalKnown);
}
}
} break;
@@ -498,7 +478,7 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,const SharedPtr<Peer> &p
default: break;
}
- peer->received(_path,hops(),packetId(),Packet::VERB_OK,inRePacketId,inReVerb,trustEstablished);
+ peer->received(_path,hops(),packetId(),Packet::VERB_OK,inRePacketId,inReVerb,false);
} catch ( ... ) {
TRACE("dropped OK from %s(%s): unexpected exception",source().toString().c_str(),_path->address().toString().c_str());
}
@@ -595,9 +575,7 @@ bool IncomingPacket::_doFRAME(const RuntimeEnvironment *RR,const SharedPtr<Peer>
const SharedPtr<Network> network(RR->node->network(nwid));
bool trustEstablished = false;
if (network) {
- if (!network->gate(peer,verb(),packetId())) {
- TRACE("dropped FRAME from %s(%s): not a member of private network %.16llx",peer->address().toString().c_str(),_path->address().toString().c_str(),(unsigned long long)network->id());
- } else {
+ if (network->gate(peer)) {
trustEstablished = true;
if (size() > ZT_PROTO_VERB_FRAME_IDX_PAYLOAD) {
const unsigned int etherType = at<uint16_t>(ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE);
@@ -607,9 +585,13 @@ bool IncomingPacket::_doFRAME(const RuntimeEnvironment *RR,const SharedPtr<Peer>
if (network->filterIncomingPacket(peer,RR->identity.address(),sourceMac,network->mac(),frameData,frameLen,etherType,0) > 0)
RR->node->putFrame(nwid,network->userPtr(),sourceMac,network->mac(),etherType,0,(const void *)frameData,frameLen);
}
+ } else {
+ TRACE("dropped FRAME from %s(%s): not a member of private network %.16llx",peer->address().toString().c_str(),_path->address().toString().c_str(),(unsigned long long)network->id());
+ _sendErrorNeedCredentials(RR,peer,nwid);
}
} else {
TRACE("dropped FRAME from %s(%s): we are not a member of network %.16llx",source().toString().c_str(),_path->address().toString().c_str(),at<uint64_t>(ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID));
+ _sendErrorNeedCredentials(RR,peer,nwid);
}
peer->received(_path,hops(),packetId(),Packet::VERB_FRAME,0,Packet::VERB_NOP,trustEstablished);
} catch ( ... ) {
@@ -634,8 +616,9 @@ bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,const SharedPtr<P
network->addCredential(com);
}
- if (!network->gate(peer,verb(),packetId())) {
+ if (!network->gate(peer)) {
TRACE("dropped EXT_FRAME from %s(%s): not a member of private network %.16llx",peer->address().toString().c_str(),_path->address().toString().c_str(),network->id());
+ _sendErrorNeedCredentials(RR,peer,nwid);
peer->received(_path,hops(),packetId(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,false);
return true;
}
@@ -681,11 +664,21 @@ bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,const SharedPtr<P
RR->node->putFrame(nwid,network->userPtr(),from,to,etherType,0,(const void *)frameData,frameLen);
break;
}
+ }
- peer->received(_path,hops(),packetId(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,true);
+ if ((flags & 0x10) != 0) {
+ Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK);
+ outp.append((uint8_t)Packet::VERB_EXT_FRAME);
+ outp.append((uint64_t)packetId());
+ outp.append((uint64_t)nwid);
+ outp.armor(peer->key(),true);
+ _path->send(RR,outp.data(),outp.size(),RR->node->now());
}
+
+ peer->received(_path,hops(),packetId(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,true);
} else {
TRACE("dropped EXT_FRAME from %s(%s): we are not connected to network %.16llx",source().toString().c_str(),_path->address().toString().c_str(),at<uint64_t>(ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID));
+ _sendErrorNeedCredentials(RR,peer,nwid);
peer->received(_path,hops(),packetId(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,false);
}
} catch ( ... ) {
@@ -723,7 +716,7 @@ bool IncomingPacket::_doMULTICAST_LIKE(const RuntimeEnvironment *RR,const Shared
try {
const uint64_t now = RR->node->now();
- uint64_t authOnNetwork[256];
+ uint64_t authOnNetwork[256]; // cache for approved network IDs
unsigned int authOnNetworkCount = 0;
SharedPtr<Network> network;
bool trustEstablished = false;
@@ -742,7 +735,9 @@ bool IncomingPacket::_doMULTICAST_LIKE(const RuntimeEnvironment *RR,const Shared
if (!auth) {
if ((!network)||(network->id() != nwid))
network = RR->node->network(nwid);
- const bool authOnNet = ((network)&&(network->gate(peer,verb(),packetId())));
+ const bool authOnNet = ((network)&&(network->gate(peer)));
+ if (!authOnNet)
+ _sendErrorNeedCredentials(RR,peer,nwid);
trustEstablished |= authOnNet;
if (authOnNet||RR->mc->cacheAuthorized(peer->address(),nwid,now)) {
auth = true;
@@ -775,32 +770,45 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const S
CertificateOfMembership com;
Capability cap;
Tag tag;
+ Revocation revocation;
bool trustEstablished = false;
unsigned int p = ZT_PACKET_IDX_PAYLOAD;
while ((p < size())&&((*this)[p])) {
p += com.deserialize(*this,p);
if (com) {
- SharedPtr<Network> network(RR->node->network(com.networkId()));
+ const SharedPtr<Network> network(RR->node->network(com.networkId()));
if (network) {
switch (network->addCredential(com)) {
- case 0: trustEstablished = true; break;
- case 1: return false; // wait for WHOIS
+ case Membership::ADD_REJECTED:
+ break;
+ case Membership::ADD_ACCEPTED_NEW:
+ case Membership::ADD_ACCEPTED_REDUNDANT:
+ trustEstablished = true;
+ break;
+ case Membership::ADD_DEFERRED_FOR_WHOIS:
+ return false;
}
} else RR->mc->addCredential(com,false);
}
}
++p; // skip trailing 0 after COMs if present
- if (p < size()) { // check if new capabilities and tags fields are present
+ if (p < size()) { // older ZeroTier versions do not send capabilities, tags, or revocations
const unsigned int numCapabilities = at<uint16_t>(p); p += 2;
for(unsigned int i=0;i<numCapabilities;++i) {
p += cap.deserialize(*this,p);
- SharedPtr<Network> network(RR->node->network(cap.networkId()));
+ const SharedPtr<Network> network(RR->node->network(cap.networkId()));
if (network) {
switch (network->addCredential(cap)) {
- case 0: trustEstablished = true; break;
- case 1: return false; // wait for WHOIS
+ case Membership::ADD_REJECTED:
+ break;
+ case Membership::ADD_ACCEPTED_NEW:
+ case Membership::ADD_ACCEPTED_REDUNDANT:
+ trustEstablished = true;
+ break;
+ case Membership::ADD_DEFERRED_FOR_WHOIS:
+ return false;
}
}
}
@@ -808,17 +816,43 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const S
const unsigned int numTags = at<uint16_t>(p); p += 2;
for(unsigned int i=0;i<numTags;++i) {
p += tag.deserialize(*this,p);
- SharedPtr<Network> network(RR->node->network(tag.networkId()));
+ const SharedPtr<Network> network(RR->node->network(tag.networkId()));
if (network) {
switch (network->addCredential(tag)) {
- case 0: trustEstablished = true; break;
- case 1: return false; // wait for WHOIS
+ case Membership::ADD_REJECTED:
+ break;
+ case Membership::ADD_ACCEPTED_NEW:
+ case Membership::ADD_ACCEPTED_REDUNDANT:
+ trustEstablished = true;
+ break;
+ case Membership::ADD_DEFERRED_FOR_WHOIS:
+ return false;
+ }
+ }
+ }
+
+ const unsigned int numRevocations = at<uint16_t>(p); p += 2;
+ for(unsigned int i=0;i<numRevocations;++i) {
+ p += revocation.deserialize(*this,p);
+ const SharedPtr<Network> network(RR->node->network(revocation.networkId()));
+ if (network) {
+ switch(network->addCredential(peer->address(),revocation)) {
+ case Membership::ADD_REJECTED:
+ break;
+ case Membership::ADD_ACCEPTED_NEW:
+ case Membership::ADD_ACCEPTED_REDUNDANT:
+ trustEstablished = true;
+ break;
+ case Membership::ADD_DEFERRED_FOR_WHOIS:
+ return false;
}
}
}
}
peer->received(_path,hops(),packetId(),Packet::VERB_NETWORK_CREDENTIALS,0,Packet::VERB_NOP,trustEstablished);
+ } catch (std::exception &exc) {
+ TRACE("dropped NETWORK_CREDENTIALS from %s(%s): %s",source().toString().c_str(),_path->address().toString().c_str(),exc.what());
} catch ( ... ) {
TRACE("dropped NETWORK_CREDENTIALS from %s(%s): unexpected exception",source().toString().c_str(),_path->address().toString().c_str());
}
@@ -847,19 +881,31 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons
Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> *dconf = new Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>();
try {
if (netconf->toDictionary(*dconf,metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION,0) < 6)) {
- dconf->wrapWithSignature(ZT_NETWORKCONFIG_DICT_KEY_SIGNATURE,RR->identity.privateKeyPair());
+ uint64_t configUpdateId = RR->node->prng();
+ if (!configUpdateId) ++configUpdateId;
const unsigned int totalSize = dconf->sizeBytes();
unsigned int chunkIndex = 0;
while (chunkIndex < totalSize) {
- const unsigned int chunkLen = std::min(totalSize - chunkIndex,(unsigned int)(ZT_PROTO_MAX_PACKET_LENGTH - (ZT_PACKET_IDX_PAYLOAD + 32)));
+ const unsigned int chunkLen = std::min(totalSize - chunkIndex,(unsigned int)(ZT_UDP_DEFAULT_PAYLOAD_MTU - (ZT_PACKET_IDX_PAYLOAD + 256)));
Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK);
outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST);
outp.append(requestPacketId);
+
+ const unsigned int sigStart = outp.size();
outp.append(nwid);
outp.append((uint16_t)chunkLen);
outp.append((const void *)(dconf->data() + chunkIndex),chunkLen);
+
+ outp.append((uint8_t)0); // no flags
+ outp.append((uint64_t)configUpdateId);
outp.append((uint32_t)totalSize);
outp.append((uint32_t)chunkIndex);
+
+ C25519::Signature sig(RR->identity.sign(reinterpret_cast<const uint8_t *>(outp.data()) + sigStart,outp.size() - sigStart));
+ outp.append((uint8_t)1);
+ outp.append((uint16_t)ZT_C25519_SIGNATURE_LEN);
+ outp.append(sig.data,ZT_C25519_SIGNATURE_LEN);
+
outp.compress();
RR->sw->send(outp,true);
chunkIndex += chunkLen;
@@ -926,32 +972,23 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons
return true;
}
-bool IncomingPacket::_doNETWORK_CONFIG_REFRESH(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer)
+bool IncomingPacket::_doNETWORK_CONFIG(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer)
{
try {
- const uint64_t nwid = at<uint64_t>(ZT_PACKET_IDX_PAYLOAD);
- bool trustEstablished = false;
-
- if (Network::controllerFor(nwid) == peer->address()) {
- SharedPtr<Network> network(RR->node->network(nwid));
- if (network) {
- network->requestConfiguration();
- trustEstablished = true;
- } else {
- TRACE("dropped NETWORK_CONFIG_REFRESH from %s(%s): not a member of %.16llx",source().toString().c_str(),_path->address().toString().c_str(),nwid);
- peer->received(_path,hops(),packetId(),Packet::VERB_NETWORK_CONFIG_REFRESH,0,Packet::VERB_NOP,false);
- return true;
- }
-
- const unsigned int blacklistCount = at<uint16_t>(ZT_PACKET_IDX_PAYLOAD + 8);
- unsigned int ptr = ZT_PACKET_IDX_PAYLOAD + 10;
- for(unsigned int i=0;i<blacklistCount;++i) {
- network->blacklistBefore(Address(field(ptr,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH),at<uint64_t>(ptr + 5));
- ptr += 13;
+ const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PACKET_IDX_PAYLOAD)));
+ if (network) {
+ const uint64_t configUpdateId = network->handleConfigChunk(*this,ZT_PACKET_IDX_PAYLOAD);
+ if (configUpdateId) {
+ Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK);
+ outp.append((uint8_t)Packet::VERB_ECHO);
+ outp.append((uint64_t)packetId());
+ outp.append((uint64_t)network->id());
+ outp.append((uint64_t)configUpdateId);
+ outp.armor(peer->key(),true);
+ _path->send(RR,outp.data(),outp.size(),RR->node->now());
}
}
-
- peer->received(_path,hops(),packetId(),Packet::VERB_NETWORK_CONFIG_REFRESH,0,Packet::VERB_NOP,trustEstablished);
+ peer->received(_path,hops(),packetId(),Packet::VERB_NETWORK_CONFIG,0,Packet::VERB_NOP,false);
} catch ( ... ) {
TRACE("dropped NETWORK_CONFIG_REFRESH from %s(%s): unexpected exception",source().toString().c_str(),_path->address().toString().c_str());
}
@@ -984,7 +1021,9 @@ bool IncomingPacket::_doMULTICAST_GATHER(const RuntimeEnvironment *RR,const Shar
}
}
- const bool trustEstablished = ((network)&&(network->gate(peer,verb(),packetId())));
+ const bool trustEstablished = ((network)&&(network->gate(peer)));
+ if (!trustEstablished)
+ _sendErrorNeedCredentials(RR,peer,nwid);
if ( ( trustEstablished || RR->mc->cacheAuthorized(peer->address(),nwid,RR->node->now()) ) && (gatherLimit > 0) ) {
Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK);
outp.append((unsigned char)Packet::VERB_MULTICAST_GATHER);
@@ -1031,8 +1070,9 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,const Share
network->addCredential(com);
}
- if (!network->gate(peer,verb(),packetId())) {
+ if (!network->gate(peer)) {
TRACE("dropped MULTICAST_FRAME from %s(%s): not a member of private network %.16llx",peer->address().toString().c_str(),_path->address().toString().c_str(),(unsigned long long)network->id());
+ _sendErrorNeedCredentials(RR,peer,nwid);
peer->received(_path,hops(),packetId(),Packet::VERB_MULTICAST_FRAME,0,Packet::VERB_NOP,false);
return true;
}
@@ -1107,6 +1147,7 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,const Share
peer->received(_path,hops(),packetId(),Packet::VERB_MULTICAST_FRAME,0,Packet::VERB_NOP,true);
} else {
+ _sendErrorNeedCredentials(RR,peer,nwid);
peer->received(_path,hops(),packetId(),Packet::VERB_MULTICAST_FRAME,0,Packet::VERB_NOP,false);
}
} catch ( ... ) {
@@ -1252,7 +1293,7 @@ bool IncomingPacket::_doCIRCUIT_TEST(const RuntimeEnvironment *RR,const SharedPt
peer->received(_path,hops(),packetId(),Packet::VERB_CIRCUIT_TEST,0,Packet::VERB_NOP,false);
return true;
}
- if (network->gate(peer,verb(),packetId()))
+ if (network->gate(peer))
reportFlags |= ZT_CIRCUIT_TEST_REPORT_FLAGS_UPSTREAM_AUTHORIZED_IN_PATH;
} else {
TRACE("dropped CIRCUIT_TEST from %s(%s): originator %s did not specify a credential or credential type",source().toString().c_str(),_path->address().toString().c_str(),originatorAddress.toString().c_str());
@@ -1379,146 +1420,18 @@ bool IncomingPacket::_doCIRCUIT_TEST_REPORT(const RuntimeEnvironment *RR,const S
return true;
}
-bool IncomingPacket::_doREQUEST_PROOF_OF_WORK(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer)
+void IncomingPacket::_sendErrorNeedCredentials(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer,const uint64_t nwid)
{
- try {
- // If this were allowed from anyone, it would itself be a DOS vector. Right
- // now we only allow it from roots and controllers of networks you have joined.
- bool allowed = RR->topology->isUpstream(peer->identity());
- if (!allowed) {
- std::vector< SharedPtr<Network> > allNetworks(RR->node->allNetworks());
- for(std::vector< SharedPtr<Network> >::const_iterator n(allNetworks.begin());n!=allNetworks.end();++n) {
- if (peer->address() == (*n)->controller()) {
- allowed = true;
- break;
- }
- }
- }
-
- if (allowed) {
- const uint64_t pid = packetId();
- const unsigned int difficulty = (*this)[ZT_PACKET_IDX_PAYLOAD + 1];
- const unsigned int challengeLength = at<uint16_t>(ZT_PACKET_IDX_PAYLOAD + 2);
- if (challengeLength > ZT_PROTO_MAX_PACKET_LENGTH)
- return true; // sanity check, drop invalid size
- const unsigned char *challenge = field(ZT_PACKET_IDX_PAYLOAD + 4,challengeLength);
-
- switch((*this)[ZT_PACKET_IDX_PAYLOAD]) {
-
- // Salsa20/12+SHA512 hashcash
- case 0x01: {
- if (difficulty <= 14) {
- unsigned char result[16];
- computeSalsa2012Sha512ProofOfWork(difficulty,challenge,challengeLength,result);
- TRACE("PROOF_OF_WORK computed for %s: difficulty==%u, challengeLength==%u, result: %.16llx%.16llx",peer->address().toString().c_str(),difficulty,challengeLength,Utils::ntoh(*(reinterpret_cast<const uint64_t *>(result))),Utils::ntoh(*(reinterpret_cast<const uint64_t *>(result + 8))));
- Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK);
- outp.append((unsigned char)Packet::VERB_REQUEST_PROOF_OF_WORK);
- outp.append(pid);
- outp.append((uint16_t)sizeof(result));
- outp.append(result,sizeof(result));
- outp.armor(peer->key(),true);
- _path->send(RR,outp.data(),outp.size(),RR->node->now());
- } else {
- Packet outp(peer->address(),RR->identity.address(),Packet::VERB_ERROR);
- outp.append((unsigned char)Packet::VERB_REQUEST_PROOF_OF_WORK);
- outp.append(pid);
- outp.append((unsigned char)Packet::ERROR_INVALID_REQUEST);
- outp.armor(peer->key(),true);
- _path->send(RR,outp.data(),outp.size(),RR->node->now());
- }
- } break;
-
- default:
- TRACE("dropped REQUEST_PROOF_OF_WORK from %s(%s): unrecognized proof of work type",peer->address().toString().c_str(),_path->address().toString().c_str());
- break;
- }
-
- peer->received(_path,hops(),pid,Packet::VERB_REQUEST_PROOF_OF_WORK,0,Packet::VERB_NOP,false);
- } else {
- TRACE("dropped REQUEST_PROOF_OF_WORK from %s(%s): not trusted enough",peer->address().toString().c_str(),_path->address().toString().c_str());
- }
- } catch ( ... ) {
- TRACE("dropped REQUEST_PROOF_OF_WORK from %s(%s): unexpected exception",peer->address().toString().c_str(),_path->address().toString().c_str());
- }
- return true;
-}
-
-void IncomingPacket::computeSalsa2012Sha512ProofOfWork(unsigned int difficulty,const void *challenge,unsigned int challengeLength,unsigned char result[16])
-{
- unsigned char salsabuf[131072]; // 131072 == protocol constant, size of memory buffer for this proof of work function
- char candidatebuf[ZT_PROTO_MAX_PACKET_LENGTH + 256];
- unsigned char shabuf[ZT_SHA512_DIGEST_LEN];
- const uint64_t s20iv = 0; // zero IV for Salsa20
- char *const candidate = (char *)(( ((uintptr_t)&(candidatebuf[0])) | 0xf ) + 1); // align to 16-byte boundary to ensure that uint64_t type punning of initial nonce is okay
- Salsa20 s20;
- unsigned int d;
- unsigned char *p;
-
- Utils::getSecureRandom(candidate,16);
- memcpy(candidate + 16,challenge,challengeLength);
-
- if (difficulty > 512)
- difficulty = 512; // sanity check
-
-try_salsa2012sha512_again:
- ++*(reinterpret_cast<volatile uint64_t *>(candidate));
-
- SHA512::hash(shabuf,candidate,16 + challengeLength);
- s20.init(shabuf,256,&s20iv);
- memset(salsabuf,0,sizeof(salsabuf));
- s20.encrypt12(salsabuf,salsabuf,sizeof(salsabuf));
- SHA512::hash(shabuf,salsabuf,sizeof(salsabuf));
-
- d = difficulty;
- p = shabuf;
- while (d >= 8) {
- if (*(p++))
- goto try_salsa2012sha512_again;
- d -= 8;
- }
- if (d > 0) {
- if ( ((((unsigned int)*p) << d) & 0xff00) != 0 )
- goto try_salsa2012sha512_again;
- }
-
- memcpy(result,candidate,16);
-}
-
-bool IncomingPacket::testSalsa2012Sha512ProofOfWorkResult(unsigned int difficulty,const void *challenge,unsigned int challengeLength,const unsigned char proposedResult[16])
-{
- unsigned char salsabuf[131072]; // 131072 == protocol constant, size of memory buffer for this proof of work function
- char candidate[ZT_PROTO_MAX_PACKET_LENGTH + 256];
- unsigned char shabuf[ZT_SHA512_DIGEST_LEN];
- const uint64_t s20iv = 0; // zero IV for Salsa20
- Salsa20 s20;
- unsigned int d;
- unsigned char *p;
-
- if (difficulty > 512)
- difficulty = 512; // sanity check
-
- memcpy(candidate,proposedResult,16);
- memcpy(candidate + 16,challenge,challengeLength);
-
- SHA512::hash(shabuf,candidate,16 + challengeLength);
- s20.init(shabuf,256,&s20iv);
- memset(salsabuf,0,sizeof(salsabuf));
- s20.encrypt12(salsabuf,salsabuf,sizeof(salsabuf));
- SHA512::hash(shabuf,salsabuf,sizeof(salsabuf));
-
- d = difficulty;
- p = shabuf;
- while (d >= 8) {
- if (*(p++))
- return false;
- d -= 8;
- }
- if (d > 0) {
- if ( ((((unsigned int)*p) << d) & 0xff00) != 0 )
- return false;
+ const uint64_t now = RR->node->now();
+ if (peer->rateGateOutgoingComRequest(now)) {
+ Packet outp(source(),RR->identity.address(),Packet::VERB_ERROR);
+ outp.append((uint8_t)verb());
+ outp.append(packetId());
+ outp.append((uint8_t)Packet::ERROR_NEED_MEMBERSHIP_CERTIFICATE);
+ outp.append(nwid);
+ outp.armor(peer->key(),true);
+ _path->send(RR,outp.data(),outp.size(),now);
}
-
- return true;
}
} // namespace ZeroTier
diff --git a/node/IncomingPacket.hpp b/node/IncomingPacket.hpp
index dbaf67b8..80244ea4 100644
--- a/node/IncomingPacket.hpp
+++ b/node/IncomingPacket.hpp
@@ -111,27 +111,6 @@ public:
*/
inline uint64_t receiveTime() const throw() { return _receiveTime; }
- /**
- * Compute the Salsa20/12+SHA512 proof of work function
- *
- * @param difficulty Difficulty in bits (max: 64)
- * @param challenge Challenge string
- * @param challengeLength Length of challenge in bytes (max allowed: ZT_PROTO_MAX_PACKET_LENGTH)
- * @param result Buffer to fill with 16-byte result
- */
- static void computeSalsa2012Sha512ProofOfWork(unsigned int difficulty,const void *challenge,unsigned int challengeLength,unsigned char result[16]);
-
- /**
- * Verify the result of Salsa20/12+SHA512 proof of work
- *
- * @param difficulty Difficulty in bits (max: 64)
- * @param challenge Challenge bytes
- * @param challengeLength Length of challenge in bytes (max allowed: ZT_PROTO_MAX_PACKET_LENGTH)
- * @param proposedResult Result supplied by client
- * @return True if result is valid
- */
- static bool testSalsa2012Sha512ProofOfWorkResult(unsigned int difficulty,const void *challenge,unsigned int challengeLength,const unsigned char proposedResult[16]);
-
private:
// These are called internally to handle packet contents once it has
// been authenticated, decrypted, decompressed, and classified.
@@ -146,13 +125,14 @@ private:
bool _doMULTICAST_LIKE(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);
bool _doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);
bool _doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);
- bool _doNETWORK_CONFIG_REFRESH(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);
+ bool _doNETWORK_CONFIG(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);
bool _doMULTICAST_GATHER(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);
bool _doMULTICAST_FRAME(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);
bool _doPUSH_DIRECT_PATHS(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);
bool _doCIRCUIT_TEST(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);
bool _doCIRCUIT_TEST_REPORT(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);
- bool _doREQUEST_PROOF_OF_WORK(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);
+
+ void _sendErrorNeedCredentials(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer,const uint64_t nwid);
uint64_t _receiveTime;
SharedPtr<Path> _path;
diff --git a/node/InetAddress.cpp b/node/InetAddress.cpp
index 12446909..7d22eeae 100644
--- a/node/InetAddress.cpp
+++ b/node/InetAddress.cpp
@@ -236,8 +236,14 @@ InetAddress InetAddress::netmask() const
case AF_INET6: {
uint64_t nm[2];
const unsigned int bits = netmaskBits();
- nm[0] = Utils::hton((uint64_t)((bits >= 64) ? 0xffffffffffffffffULL : (0xffffffffffffffffULL << (64 - bits))));
- nm[1] = Utils::hton((uint64_t)((bits <= 64) ? 0ULL : (0xffffffffffffffffULL << (128 - bits))));
+ if(bits) {
+ nm[0] = Utils::hton((uint64_t)((bits >= 64) ? 0xffffffffffffffffULL : (0xffffffffffffffffULL << (64 - bits))));
+ nm[1] = Utils::hton((uint64_t)((bits <= 64) ? 0ULL : (0xffffffffffffffffULL << (128 - bits))));
+ }
+ else {
+ nm[0] = 0;
+ nm[1] = 0;
+ }
memcpy(reinterpret_cast<struct sockaddr_in6 *>(&r)->sin6_addr.s6_addr,nm,16);
} break;
}
diff --git a/node/InetAddress.hpp b/node/InetAddress.hpp
index 2cd4dbd3..6f070fbf 100644
--- a/node/InetAddress.hpp
+++ b/node/InetAddress.hpp
@@ -414,6 +414,25 @@ struct InetAddress : public sockaddr_storage
return false;
}
+ inline unsigned long hashCode() const
+ {
+ if (ss_family == AF_INET) {
+ return ((unsigned long)reinterpret_cast<const struct sockaddr_in *>(this)->sin_addr.s_addr + (unsigned long)reinterpret_cast<const struct sockaddr_in *>(this)->sin_port);
+ } else if (ss_family == AF_INET6) {
+ unsigned long tmp = reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_port;
+ const uint8_t *a = reinterpret_cast<const uint8_t *>(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr);
+ for(long i=0;i<16;++i)
+ reinterpret_cast<uint8_t *>(&tmp)[i % sizeof(tmp)] ^= a[i];
+ return tmp;
+ } else {
+ unsigned long tmp = reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_port;
+ const uint8_t *a = reinterpret_cast<const uint8_t *>(this);
+ for(long i=0;i<sizeof(InetAddress);++i)
+ reinterpret_cast<uint8_t *>(&tmp)[i % sizeof(tmp)] ^= a[i];
+ return tmp;
+ }
+ }
+
/**
* Set to null/zero
*/
diff --git a/node/Membership.cpp b/node/Membership.cpp
index 8c2ba673..c8fb8e4e 100644
--- a/node/Membership.cpp
+++ b/node/Membership.cpp
@@ -16,6 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <algorithm>
+
#include "Membership.hpp"
#include "RuntimeEnvironment.hpp"
#include "Peer.hpp"
@@ -28,28 +30,43 @@
namespace ZeroTier {
-void Membership::sendCredentialsIfNeeded(const RuntimeEnvironment *RR,const uint64_t now,const Address &peerAddress,const NetworkConfig &nconf,const Capability *cap)
+Membership::Membership() :
+ _lastUpdatedMulticast(0),
+ _lastPushAttempt(0),
+ _lastPushedCom(0),
+ _comRevocationThreshold(0)
+{
+ for(unsigned int i=0;i<ZT_MAX_NETWORK_TAGS;++i) _remoteTags[i] = &(_tagMem[i]);
+ for(unsigned int i=0;i<ZT_MAX_NETWORK_CAPABILITIES;++i) _remoteCaps[i] = &(_capMem[i]);
+}
+
+void Membership::pushCredentials(const RuntimeEnvironment *RR,const uint64_t now,const Address &peerAddress,const NetworkConfig &nconf,int localCapabilityIndex,const bool force)
{
- if ((now - _lastPushAttempt) < 2000ULL)
+ // This limits how often we go through this logic, which prevents us from
+ // doing all this for every single packet or other event.
+ if ( ((now - _lastPushAttempt) < 1000ULL) && (!force) )
return;
_lastPushAttempt = now;
try {
- bool unfinished;
+ unsigned int localTagPtr = 0;
+ bool needCom = ( (nconf.com) && ( ((now - _lastPushedCom) >= ZT_CREDENTIAL_PUSH_EVERY) || (force) ) );
do {
- unfinished = false;
Buffer<ZT_PROTO_MAX_PACKET_LENGTH> capsAndTags;
unsigned int appendedCaps = 0;
- if (cap) {
+ if (localCapabilityIndex >= 0) {
capsAndTags.addSize(2);
- std::map<uint32_t,CState>::iterator cs(_caps.find(cap->id()));
- if ((cs != _caps.end())&&((now - cs->second.lastPushed) >= ZT_CREDENTIAL_PUSH_EVERY)) {
- cap->serialize(capsAndTags);
- cs->second.lastPushed = now;
+
+ if ( (_localCaps[localCapabilityIndex].id != nconf.capabilities[localCapabilityIndex].id()) || ((now - _localCaps[localCapabilityIndex].lastPushed) >= ZT_CREDENTIAL_PUSH_EVERY) || (force) ) {
+ _localCaps[localCapabilityIndex].lastPushed = now;
+ _localCaps[localCapabilityIndex].id = nconf.capabilities[localCapabilityIndex].id();
+ nconf.capabilities[localCapabilityIndex].serialize(capsAndTags);
++appendedCaps;
}
+
capsAndTags.setAt<uint16_t>(0,(uint16_t)appendedCaps);
+ localCapabilityIndex = -1; // don't send this cap again on subsequent loops if force is true
} else {
capsAndTags.append((uint16_t)0);
}
@@ -57,22 +74,17 @@ void Membership::sendCredentialsIfNeeded(const RuntimeEnvironment *RR,const uint
unsigned int appendedTags = 0;
const unsigned int tagCountPos = capsAndTags.size();
capsAndTags.addSize(2);
- for(unsigned int i=0;i<nconf.tagCount;++i) {
- TState *const ts = _tags.get(nconf.tags[i].id());
- if ((now - ts->lastPushed) >= ZT_CREDENTIAL_PUSH_EVERY) {
- if ((capsAndTags.size() + sizeof(Tag)) >= (ZT_PROTO_MAX_PACKET_LENGTH - sizeof(CertificateOfMembership))) {
- unfinished = true;
+ for(;localTagPtr<nconf.tagCount;++localTagPtr) {
+ if ( (_localTags[localTagPtr].id != nconf.tags[localTagPtr].id()) || ((now - _localTags[localTagPtr].lastPushed) >= ZT_CREDENTIAL_PUSH_EVERY) || (force) ) {
+ if ((capsAndTags.size() + sizeof(Tag)) >= (ZT_PROTO_MAX_PACKET_LENGTH - sizeof(CertificateOfMembership)))
break;
- }
- nconf.tags[i].serialize(capsAndTags);
- ts->lastPushed = now;
+ nconf.tags[localTagPtr].serialize(capsAndTags);
++appendedTags;
}
}
capsAndTags.setAt<uint16_t>(tagCountPos,(uint16_t)appendedTags);
- const bool needCom = ((nconf.com)&&((now - _lastPushedCom) >= ZT_CREDENTIAL_PUSH_EVERY));
- if ( (needCom) || (appendedCaps) || (appendedTags) ) {
+ if (needCom||appendedCaps||appendedTags) {
Packet outp(peerAddress,RR->identity.address(),Packet::VERB_NETWORK_CREDENTIALS);
if (needCom) {
nconf.com.serialize(outp);
@@ -80,110 +92,219 @@ void Membership::sendCredentialsIfNeeded(const RuntimeEnvironment *RR,const uint
}
outp.append((uint8_t)0x00);
outp.append(capsAndTags.data(),capsAndTags.size());
+ outp.append((uint16_t)0); // no revocations, these propagate differently
outp.compress();
RR->sw->send(outp,true);
+ needCom = false; // don't send COM again on subsequent loops if force is true
}
- } while (unfinished); // if there are many tags, etc., we can send more than one
+ } while (localTagPtr < nconf.tagCount);
} catch ( ... ) {
TRACE("unable to send credentials due to unexpected exception");
}
}
-int Membership::addCredential(const RuntimeEnvironment *RR,const CertificateOfMembership &com)
+const Capability *Membership::getCapability(const NetworkConfig &nconf,const uint32_t id) const
{
- if (_com == com) {
- TRACE("addCredential(CertificateOfMembership) for %s on %.16llx ACCEPTED (redundant)",com.issuedTo().toString().c_str(),com.networkId());
- return 0;
+ const _RemoteCapability *const *c = std::lower_bound(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),(uint64_t)id,_RemoteCredentialSorter<_RemoteCapability>());
+ return ( ((c != &(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*c)->id == (uint64_t)id)) ? ((((*c)->lastReceived)&&(_isCredentialTimestampValid(nconf,(*c)->cap,**c))) ? &((*c)->cap) : (const Capability *)0) : (const Capability *)0);
+}
+
+const Tag *Membership::getTag(const NetworkConfig &nconf,const uint32_t id) const
+{
+ const _RemoteTag *const *t = std::lower_bound(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),(uint64_t)id,_RemoteCredentialSorter<_RemoteTag>());
+ return ( ((t != &(_remoteTags[ZT_MAX_NETWORK_CAPABILITIES]))&&((*t)->id == (uint64_t)id)) ? ((((*t)->lastReceived)&&(_isCredentialTimestampValid(nconf,(*t)->tag,**t))) ? &((*t)->tag) : (const Tag *)0) : (const Tag *)0);
+}
+
+Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,const NetworkConfig &nconf,const CertificateOfMembership &com)
+{
+ const uint64_t newts = com.timestamp().first;
+ if (newts <= _comRevocationThreshold) {
+ TRACE("addCredential(CertificateOfMembership) for %s on %.16llx REJECTED (revoked)",com.issuedTo().toString().c_str(),com.networkId());
+ return ADD_REJECTED;
}
- const int vr = com.verify(RR);
+ const uint64_t oldts = _com.timestamp().first;
+ if (newts < oldts) {
+ TRACE("addCredential(CertificateOfMembership) for %s on %.16llx REJECTED (older than current)",com.issuedTo().toString().c_str(),com.networkId());
+ return ADD_REJECTED;
+ }
+ if ((newts == oldts)&&(_com == com)) {
+ TRACE("addCredential(CertificateOfMembership) for %s on %.16llx ACCEPTED (redundant)",com.issuedTo().toString().c_str(),com.networkId());
+ return ADD_ACCEPTED_REDUNDANT;
+ }
- if (vr == 0) {
- if (com.timestamp().first >= _com.timestamp().first) {
+ switch(com.verify(RR)) {
+ default:
+ TRACE("addCredential(CertificateOfMembership) for %s on %.16llx REJECTED (invalid signature or object)",com.issuedTo().toString().c_str(),com.networkId());
+ return ADD_REJECTED;
+ case 0:
TRACE("addCredential(CertificateOfMembership) for %s on %.16llx ACCEPTED (new)",com.issuedTo().toString().c_str(),com.networkId());
_com = com;
- } else {
- TRACE("addCredential(CertificateOfMembership) for %s on %.16llx ACCEPTED but not used (OK but older than current)",com.issuedTo().toString().c_str(),com.networkId());
+ return ADD_ACCEPTED_NEW;
+ case 1:
+ return ADD_DEFERRED_FOR_WHOIS;
+ }
+}
+
+Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,const NetworkConfig &nconf,const Tag &tag)
+{
+ _RemoteTag *const *htmp = std::lower_bound(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),(uint64_t)tag.id(),_RemoteCredentialSorter<_RemoteTag>());
+ _RemoteTag *have = ((htmp != &(_remoteTags[ZT_MAX_NETWORK_TAGS]))&&((*htmp)->id == (uint64_t)tag.id())) ? *htmp : (_RemoteTag *)0;
+ if (have) {
+ if ( (!_isCredentialTimestampValid(nconf,tag,*have)) || (have->tag.timestamp() > tag.timestamp()) ) {
+ TRACE("addCredential(Tag) for %s on %.16llx REJECTED (revoked or too old)",tag.issuedTo().toString().c_str(),tag.networkId());
+ return ADD_REJECTED;
+ }
+ if (have->tag == tag) {
+ TRACE("addCredential(Tag) for %s on %.16llx ACCEPTED (redundant)",tag.issuedTo().toString().c_str(),tag.networkId());
+ return ADD_ACCEPTED_REDUNDANT;
}
- } else {
- TRACE("addCredential(CertificateOfMembership) for %s on %.16llx REJECTED (%d)",com.issuedTo().toString().c_str(),com.networkId(),vr);
}
- return vr;
+ switch(tag.verify(RR)) {
+ default:
+ TRACE("addCredential(Tag) for %s on %.16llx REJECTED (invalid)",tag.issuedTo().toString().c_str(),tag.networkId());
+ return ADD_REJECTED;
+ case 0:
+ TRACE("addCredential(Tag) for %s on %.16llx ACCEPTED (new)",tag.issuedTo().toString().c_str(),tag.networkId());
+ if (!have) have = _newTag(tag.id());
+ have->lastReceived = RR->node->now();
+ have->tag = tag;
+ return ADD_ACCEPTED_NEW;
+ case 1:
+ return ADD_DEFERRED_FOR_WHOIS;
+ }
}
-int Membership::addCredential(const RuntimeEnvironment *RR,const Tag &tag)
+Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,const NetworkConfig &nconf,const Capability &cap)
{
- TState *t = _tags.get(tag.id());
- if ((t)&&(t->lastReceived != 0)&&(t->tag == tag)) {
- TRACE("addCredential(Tag) for %s on %.16llx ACCEPTED (redundant)",tag.issuedTo().toString().c_str(),tag.networkId());
- return 0;
+ _RemoteCapability *const *htmp = std::lower_bound(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),(uint64_t)cap.id(),_RemoteCredentialSorter<_RemoteCapability>());
+ _RemoteCapability *have = ((htmp != &(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*htmp)->id == (uint64_t)cap.id())) ? *htmp : (_RemoteCapability *)0;
+ if (have) {
+ if ( (!_isCredentialTimestampValid(nconf,cap,*have)) || (have->cap.timestamp() > cap.timestamp()) ) {
+ TRACE("addCredential(Tag) for %s on %.16llx REJECTED (revoked or too old)",cap.issuedTo().toString().c_str(),cap.networkId());
+ return ADD_REJECTED;
+ }
+ if (have->cap == cap) {
+ TRACE("addCredential(Tag) for %s on %.16llx ACCEPTED (redundant)",cap.issuedTo().toString().c_str(),cap.networkId());
+ return ADD_ACCEPTED_REDUNDANT;
+ }
}
- const int vr = tag.verify(RR);
- if (vr == 0) {
- TRACE("addCredential(Tag) for %s on %.16llx ACCEPTED (new)",tag.issuedTo().toString().c_str(),tag.networkId());
- if (!t) {
- while (_tags.size() >= ZT_MAX_NETWORK_TAGS) {
- uint32_t oldest = 0;
- uint64_t oldestLastReceived = 0xffffffffffffffffULL;
- uint32_t *i = (uint32_t *)0;
- TState *ts = (TState *)0;
- Hashtable<uint32_t,TState>::Iterator tsi(_tags);
- while (tsi.next(i,ts)) {
- if (ts->lastReceived < oldestLastReceived) {
- oldestLastReceived = ts->lastReceived;
- oldest = *i;
- }
- }
- if (oldestLastReceived != 0xffffffffffffffffULL)
- _tags.erase(oldest);
+
+ switch(cap.verify(RR)) {
+ default:
+ TRACE("addCredential(Tag) for %s on %.16llx REJECTED (invalid)",cap.issuedTo().toString().c_str(),cap.networkId());
+ return ADD_REJECTED;
+ case 0:
+ TRACE("addCredential(Tag) for %s on %.16llx ACCEPTED (new)",cap.issuedTo().toString().c_str(),cap.networkId());
+ if (!have) have = _newCapability(cap.id());
+ have->lastReceived = RR->node->now();
+ have->cap = cap;
+ return ADD_ACCEPTED_NEW;
+ case 1:
+ return ADD_DEFERRED_FOR_WHOIS;
+ }
+}
+
+Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,const NetworkConfig &nconf,const Revocation &rev)
+{
+ switch(rev.verify(RR)) {
+ default:
+ return ADD_REJECTED;
+ case 0: {
+ const uint64_t now = RR->node->now();
+ switch(rev.type()) {
+ default:
+ //case Revocation::CREDENTIAL_TYPE_ALL:
+ return ( (_revokeCom(rev)||_revokeCap(rev,now)||_revokeTag(rev,now)) ? ADD_ACCEPTED_NEW : ADD_ACCEPTED_REDUNDANT );
+ case Revocation::CREDENTIAL_TYPE_COM:
+ return (_revokeCom(rev) ? ADD_ACCEPTED_NEW : ADD_ACCEPTED_REDUNDANT);
+ case Revocation::CREDENTIAL_TYPE_CAPABILITY:
+ return (_revokeCap(rev,now) ? ADD_ACCEPTED_NEW : ADD_ACCEPTED_REDUNDANT);
+ case Revocation::CREDENTIAL_TYPE_TAG:
+ return (_revokeTag(rev,now) ? ADD_ACCEPTED_NEW : ADD_ACCEPTED_REDUNDANT);
}
- t = &(_tags[tag.id()]);
- }
- if (t->tag.timestamp() <= tag.timestamp()) {
- t->lastReceived = RR->node->now();
- t->tag = tag;
}
- } else {
- TRACE("addCredential(Tag) for %s on %.16llx REJECTED (%d)",tag.issuedTo().toString().c_str(),tag.networkId(),vr);
+ case 1:
+ return ADD_DEFERRED_FOR_WHOIS;
}
- return vr;
}
-int Membership::addCredential(const RuntimeEnvironment *RR,const Capability &cap)
+Membership::_RemoteTag *Membership::_newTag(const uint64_t id)
{
- std::map<uint32_t,CState>::iterator c(_caps.find(cap.id()));
- if ((c != _caps.end())&&(c->second.lastReceived != 0)&&(c->second.cap == cap)) {
- TRACE("addCredential(Capability) for %s on %.16llx ACCEPTED (redundant)",cap.issuedTo().toString().c_str(),cap.networkId());
- return 0;
+ _RemoteTag *t;
+ uint64_t minlr = 0xffffffffffffffffULL;
+ for(unsigned int i=0;i<ZT_MAX_NETWORK_TAGS;++i) {
+ if (_remoteTags[i]->id == ZT_MEMBERSHIP_CRED_ID_UNUSED) {
+ t = _remoteTags[i];
+ break;
+ } else if (_remoteTags[i]->lastReceived <= minlr) {
+ t = _remoteTags[i];
+ minlr = _remoteTags[i]->lastReceived;
+ }
}
- const int vr = cap.verify(RR);
- if (vr == 0) {
- TRACE("addCredential(Capability) for %s on %.16llx ACCEPTED (new)",cap.issuedTo().toString().c_str(),cap.networkId());
- if (c == _caps.end()) {
- while (_caps.size() >= ZT_MAX_NETWORK_CAPABILITIES) {
- std::map<uint32_t,CState>::iterator oldest;
- uint64_t oldestLastReceived = 0xffffffffffffffffULL;
- for(std::map<uint32_t,CState>::iterator i(_caps.begin());i!=_caps.end();++i) {
- if (i->second.lastReceived < oldestLastReceived) {
- oldestLastReceived = i->second.lastReceived;
- oldest = i;
- }
- }
- if (oldestLastReceived != 0xffffffffffffffffULL)
- _caps.erase(oldest);
- }
- CState &c2 = _caps[cap.id()];
- c2.lastReceived = RR->node->now();
- c2.cap = cap;
- } else if (c->second.cap.timestamp() <= cap.timestamp()) {
- c->second.lastReceived = RR->node->now();
- c->second.cap = cap;
+ t->id = id;
+ t->lastReceived = 0;
+ t->revocationThreshold = 0;
+ t->tag = Tag();
+ std::sort(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),_RemoteCredentialSorter<_RemoteTag>());
+ return t;
+}
+
+Membership::_RemoteCapability *Membership::_newCapability(const uint64_t id)
+{
+ _RemoteCapability *c;
+ uint64_t minlr = 0xffffffffffffffffULL;
+ for(unsigned int i=0;i<ZT_MAX_NETWORK_CAPABILITIES;++i) {
+ if (_remoteCaps[i]->id == ZT_MEMBERSHIP_CRED_ID_UNUSED) {
+ c = _remoteCaps[i];
+ break;
+ } else if (_remoteCaps[i]->lastReceived <= minlr) {
+ c = _remoteCaps[i];
+ minlr = _remoteCaps[i]->lastReceived;
}
- } else {
- TRACE("addCredential(Capability) for %s on %.16llx REJECTED (%d)",cap.issuedTo().toString().c_str(),cap.networkId(),vr);
}
- return vr;
+ c->id = id;
+ c->lastReceived = 0;
+ c->revocationThreshold = 0;
+ c->cap = Capability();
+ std::sort(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),_RemoteCredentialSorter<_RemoteCapability>());
+ return c;
+}
+
+bool Membership::_revokeCom(const Revocation &rev)
+{
+ if (rev.threshold() > _comRevocationThreshold) {
+ _comRevocationThreshold = rev.threshold();
+ return true;
+ }
+ return false;
+}
+
+bool Membership::_revokeCap(const Revocation &rev,const uint64_t now)
+{
+ _RemoteCapability *const *htmp = std::lower_bound(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),(uint64_t)rev.credentialId(),_RemoteCredentialSorter<_RemoteCapability>());
+ _RemoteCapability *have = ((htmp != &(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*htmp)->id == (uint64_t)rev.credentialId())) ? *htmp : (_RemoteCapability *)0;
+ if (!have) have = _newCapability(rev.credentialId());
+ if (rev.threshold() > have->revocationThreshold) {
+ have->lastReceived = now;
+ have->revocationThreshold = rev.threshold();
+ return true;
+ }
+ return false;
+}
+
+bool Membership::_revokeTag(const Revocation &rev,const uint64_t now)
+{
+ _RemoteTag *const *htmp = std::lower_bound(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),(uint64_t)rev.credentialId(),_RemoteCredentialSorter<_RemoteTag>());
+ _RemoteTag *have = ((htmp != &(_remoteTags[ZT_MAX_NETWORK_TAGS]))&&((*htmp)->id == (uint64_t)rev.credentialId())) ? *htmp : (_RemoteTag *)0;
+ if (!have) have = _newTag(rev.credentialId());
+ if (rev.threshold() > have->revocationThreshold) {
+ have->lastReceived = now;
+ have->revocationThreshold = rev.threshold();
+ return true;
+ }
+ return false;
}
} // namespace ZeroTier
diff --git a/node/Membership.hpp b/node/Membership.hpp
index 5eb68d34..c54aec9b 100644
--- a/node/Membership.hpp
+++ b/node/Membership.hpp
@@ -21,16 +21,16 @@
#include <stdint.h>
-#include <map>
-
#include "Constants.hpp"
#include "../include/ZeroTierOne.h"
#include "CertificateOfMembership.hpp"
#include "Capability.hpp"
#include "Tag.hpp"
-#include "Hashtable.hpp"
+#include "Revocation.hpp"
#include "NetworkConfig.hpp"
+#define ZT_MEMBERSHIP_CRED_ID_UNUSED 0xffffffffffffffffULL
+
namespace ZeroTier {
class RuntimeEnvironment;
@@ -40,77 +40,135 @@ class Network;
* A container for certificates of membership and other network credentials
*
* This is kind of analogous to a join table between Peer and Network. It is
- * presently held by the Network object for each participating Peer.
+ * held by the Network object for each participating Peer.
*
- * This is not thread safe. It must be locked externally.
+ * This class is not thread safe. It must be locked externally.
*/
class Membership
{
private:
// Tags and related state
- struct TState
+ struct _RemoteTag
{
- TState() : lastPushed(0),lastReceived(0) {}
- // Last time we pushed OUR tag to this peer (with this ID)
- uint64_t lastPushed;
+ _RemoteTag() : id(ZT_MEMBERSHIP_CRED_ID_UNUSED),lastReceived(0),revocationThreshold(0) {}
+ // Tag ID (last 32 bits, first 32 bits are set in unused entries to sort them to end)
+ uint64_t id;
// Last time we received THEIR tag (with this ID)
uint64_t lastReceived;
+ // Revocation blacklist threshold or 0 if none
+ uint64_t revocationThreshold;
// THEIR tag
Tag tag;
};
// Credentials and related state
- struct CState
+ struct _RemoteCapability
{
- CState() : lastPushed(0),lastReceived(0) {}
- // Last time we pushed OUR capability to this peer (with this ID)
- uint64_t lastPushed;
+ _RemoteCapability() : id(ZT_MEMBERSHIP_CRED_ID_UNUSED),lastReceived(0),revocationThreshold(0) {}
+ // Capability ID (last 32 bits, first 32 bits are set in unused entries to sort them to end)
+ uint64_t id;
// Last time we received THEIR capability (with this ID)
uint64_t lastReceived;
+ // Revocation blacklist threshold or 0 if none
+ uint64_t revocationThreshold;
// THEIR capability
Capability cap;
};
+ // Comparison operator for remote credential entries
+ template<typename T>
+ struct _RemoteCredentialSorter
+ {
+ inline bool operator()(const T *a,const T *b) const { return (a->id < b->id); }
+ inline bool operator()(const uint64_t a,const T *b) const { return (a < b->id); }
+ inline bool operator()(const T *a,const uint64_t b) const { return (a->id < b); }
+ inline bool operator()(const uint64_t a,const uint64_t b) const { return (a < b); }
+ };
+
+ // Used to track push state for network config tags[] and capabilities[] entries
+ struct _LocalCredentialPushState
+ {
+ _LocalCredentialPushState() : lastPushed(0),id(0) {}
+ uint64_t lastPushed;
+ uint32_t id;
+ };
+
public:
+ enum AddCredentialResult
+ {
+ ADD_REJECTED,
+ ADD_ACCEPTED_NEW,
+ ADD_ACCEPTED_REDUNDANT,
+ ADD_DEFERRED_FOR_WHOIS
+ };
+
/**
- * A wrapper to iterate through member capabilities in ascending order of capability ID and return only valid ones
+ * Iterator to scan forward through capabilities in ascending order of ID
*/
class CapabilityIterator
{
public:
- CapabilityIterator(const Membership &m) :
- _m(m),
- _i(m._caps.begin()),
- _e(m._caps.end())
- {
- }
+ CapabilityIterator(const Membership &m,const NetworkConfig &nconf) :
+ _m(&m),
+ _c(&nconf),
+ _i(&(m._remoteCaps[0])) {}
- inline const Capability *next(const NetworkConfig &nconf)
+ inline const Capability *next()
{
- while (_i != _e) {
- if ((_i->second.lastReceived)&&(_m.isCredentialTimestampValid(nconf,_i->second.cap)))
- return &((_i++)->second.cap);
- else ++_i;
+ for(;;) {
+ if ((_i != &(_m->_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*_i)->id != ZT_MEMBERSHIP_CRED_ID_UNUSED)) {
+ const Capability *tmp = &((*_i)->cap);
+ if (_m->_isCredentialTimestampValid(*_c,*tmp,**_i)) {
+ ++_i;
+ return tmp;
+ } else ++_i;
+ } else {
+ return (const Capability *)0;
+ }
}
- return (const Capability *)0;
}
private:
- const Membership &_m;
- std::map<uint32_t,CState>::const_iterator _i,_e;
+ const Membership *_m;
+ const NetworkConfig *_c;
+ const _RemoteCapability *const *_i;
};
friend class CapabilityIterator;
- Membership() :
- _lastUpdatedMulticast(0),
- _lastPushAttempt(0),
- _lastPushedCom(0),
- _blacklistBefore(0),
- _com(),
- _caps(),
- _tags(8)
+ /**
+ * Iterator to scan forward through tags in ascending order of ID
+ */
+ class TagIterator
{
- }
+ public:
+ TagIterator(const Membership &m,const NetworkConfig &nconf) :
+ _m(&m),
+ _c(&nconf),
+ _i(&(m._remoteTags[0])) {}
+
+ inline const Tag *next()
+ {
+ for(;;) {
+ if ((_i != &(_m->_remoteTags[ZT_MAX_NETWORK_TAGS]))&&((*_i)->id != ZT_MEMBERSHIP_CRED_ID_UNUSED)) {
+ const Tag *tmp = &((*_i)->tag);
+ if (_m->_isCredentialTimestampValid(*_c,*tmp,**_i)) {
+ ++_i;
+ return tmp;
+ } else ++_i;
+ } else {
+ return (const Tag *)0;
+ }
+ }
+ }
+
+ private:
+ const Membership *_m;
+ const NetworkConfig *_c;
+ const _RemoteTag *const *_i;
+ };
+ friend class TagIterator;
+
+ Membership();
/**
* Send COM and other credentials to this peer if needed
@@ -122,9 +180,10 @@ public:
* @param now Current time
* @param peerAddress Address of member peer (the one that this Membership describes)
* @param nconf My network config
- * @param cap Capability to send or 0 if none
+ * @param localCapabilityIndex Index of local capability to include (in nconf.capabilities[]) or -1 if none
+ * @param force If true, send objects regardless of last push time
*/
- void sendCredentialsIfNeeded(const RuntimeEnvironment *RR,const uint64_t now,const Address &peerAddress,const NetworkConfig &nconf,const Capability *cap);
+ void pushCredentials(const RuntimeEnvironment *RR,const uint64_t now,const Address &peerAddress,const NetworkConfig &nconf,int localCapabilityIndex,const bool force);
/**
* Check whether we should push MULTICAST_LIKEs to this peer
@@ -142,6 +201,8 @@ public:
inline void likingMulticasts(const uint64_t now) { _lastUpdatedMulticast = now; }
/**
+ * Check whether the peer represented by this Membership should be allowed on this network at all
+ *
* @param nconf Our network config
* @return True if this peer is allowed on this network at all
*/
@@ -149,126 +210,59 @@ public:
{
if (nconf.isPublic())
return true;
- if ((_blacklistBefore)&&(_com.timestamp().first <= _blacklistBefore))
+ if ((_comRevocationThreshold)&&(_com.timestamp().first <= _comRevocationThreshold))
return false;
return nconf.com.agreesWith(_com);
}
/**
- * Check whether a capability or tag is within its max delta from the timestamp of our network config and newer than any blacklist cutoff time
- *
- * @param cred Credential to check -- must have timestamp() accessor method
- * @return True if credential is NOT expired
+ * @param nconf Network configuration
+ * @param id Capablity ID
+ * @return Pointer to capability or NULL if not found
*/
- template<typename C>
- inline bool isCredentialTimestampValid(const NetworkConfig &nconf,const C &cred) const
- {
- const uint64_t ts = cred.timestamp();
- const uint64_t delta = (ts >= nconf.timestamp) ? (ts - nconf.timestamp) : (nconf.timestamp - ts);
- return ((delta <= nconf.credentialTimeMaxDelta)&&(ts > _blacklistBefore));
- }
+ const Capability *getCapability(const NetworkConfig &nconf,const uint32_t id) const;
/**
* @param nconf Network configuration
* @param id Tag ID
* @return Pointer to tag or NULL if not found
*/
- inline const Tag *getTag(const NetworkConfig &nconf,const uint32_t id) const
- {
- const TState *t = _tags.get(id);
- return ((t) ? (((t->lastReceived != 0)&&(isCredentialTimestampValid(nconf,t->tag))) ? &(t->tag) : (const Tag *)0) : (const Tag *)0);
- }
-
- /**
- * @param nconf Network configuration
- * @param ids Array to store IDs into
- * @param values Array to store values into
- * @param maxTags Capacity of ids[] and values[]
- * @return Number of tags added to arrays
- */
- inline unsigned int getAllTags(const NetworkConfig &nconf,uint32_t *ids,uint32_t *values,unsigned int maxTags) const
- {
- unsigned int n = 0;
- uint32_t *id = (uint32_t *)0;
- TState *ts = (TState *)0;
- Hashtable<uint32_t,TState>::Iterator i(const_cast<Membership *>(this)->_tags);
- while (i.next(id,ts)) {
- if ((ts->lastReceived)&&(isCredentialTimestampValid(nconf,ts->tag))) {
- if (n >= maxTags)
- return n;
- ids[n] = *id;
- values[n] = ts->tag.value();
- }
- }
- return n;
- }
-
- /**
- * @param nconf Network configuration
- * @param id Capablity ID
- * @return Pointer to capability or NULL if not found
- */
- inline const Capability *getCapability(const NetworkConfig &nconf,const uint32_t id) const
- {
- std::map<uint32_t,CState>::const_iterator c(_caps.find(id));
- return ((c != _caps.end()) ? (((c->second.lastReceived != 0)&&(isCredentialTimestampValid(nconf,c->second.cap))) ? &(c->second.cap) : (const Capability *)0) : (const Capability *)0);
- }
+ const Tag *getTag(const NetworkConfig &nconf,const uint32_t id) const;
/**
* Validate and add a credential if signature is okay and it's otherwise good
- *
- * @param RR Runtime environment
- * @param com Certificate of membership
- * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or credential
*/
- int addCredential(const RuntimeEnvironment *RR,const CertificateOfMembership &com);
+ AddCredentialResult addCredential(const RuntimeEnvironment *RR,const NetworkConfig &nconf,const CertificateOfMembership &com);
/**
* Validate and add a credential if signature is okay and it's otherwise good
- *
- * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or credential
*/
- int addCredential(const RuntimeEnvironment *RR,const Tag &tag);
+ AddCredentialResult addCredential(const RuntimeEnvironment *RR,const NetworkConfig &nconf,const Tag &tag);
/**
* Validate and add a credential if signature is okay and it's otherwise good
- *
- * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or credential
*/
- int addCredential(const RuntimeEnvironment *RR,const Capability &cap);
+ AddCredentialResult addCredential(const RuntimeEnvironment *RR,const NetworkConfig &nconf,const Capability &cap);
/**
- * Blacklist COM, tags, and capabilities before this time
- *
- * @param ts Blacklist cutoff
+ * Validate and add a credential if signature is okay and it's otherwise good
*/
- inline void blacklistBefore(const uint64_t ts) { _blacklistBefore = ts; }
+ AddCredentialResult addCredential(const RuntimeEnvironment *RR,const NetworkConfig &nconf,const Revocation &rev);
- /**
- * Clean up old or stale entries
- *
- * @param nconf Network config
- */
- inline void clean(const NetworkConfig &nconf)
+private:
+ _RemoteTag *_newTag(const uint64_t id);
+ _RemoteCapability *_newCapability(const uint64_t id);
+ bool _revokeCom(const Revocation &rev);
+ bool _revokeCap(const Revocation &rev,const uint64_t now);
+ bool _revokeTag(const Revocation &rev,const uint64_t now);
+
+ template<typename C,typename CS>
+ inline bool _isCredentialTimestampValid(const NetworkConfig &nconf,const C &cred,const CS &state) const
{
- for(std::map<uint32_t,CState>::iterator i(_caps.begin());i!=_caps.end();) {
- if (!isCredentialTimestampValid(nconf,i->second.cap)) {
- _caps.erase(i++);
- } else {
- ++i;
- }
- }
-
- uint32_t *i = (uint32_t *)0;
- TState *ts = (TState *)0;
- Hashtable<uint32_t,TState>::Iterator tsi(_tags);
- while (tsi.next(i,ts)) {
- if (!isCredentialTimestampValid(nconf,ts->tag))
- _tags.erase(*i);
- }
+ const uint64_t ts = cred.timestamp();
+ return ( (((ts >= nconf.timestamp) ? (ts - nconf.timestamp) : (nconf.timestamp - ts)) <= nconf.credentialTimeMaxDelta) && (ts > state.revocationThreshold) );
}
-private:
// Last time we pushed MULTICAST_LIKE(s)
uint64_t _lastUpdatedMulticast;
@@ -278,17 +272,23 @@ private:
// Last time we pushed our COM to this peer
uint64_t _lastPushedCom;
- // Time before which to blacklist credentials from this peer
- uint64_t _blacklistBefore;
+ // Revocation threshold for COM or 0 if none
+ uint64_t _comRevocationThreshold;
- // COM from this peer
+ // Remote member's latest network COM
CertificateOfMembership _com;
- // Capability-related state (we need an ordered container here, hence std::map)
- std::map<uint32_t,CState> _caps;
+ // Sorted (in ascending order of ID) arrays of pointers to remote tags and capabilities
+ _RemoteTag *_remoteTags[ZT_MAX_NETWORK_TAGS];
+ _RemoteCapability *_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES];
+
+ // This is the RAM allocated for remote tags and capabilities from which the sorted arrays are populated
+ _RemoteTag _tagMem[ZT_MAX_NETWORK_TAGS];
+ _RemoteCapability _capMem[ZT_MAX_NETWORK_CAPABILITIES];
- // Tag-related state
- Hashtable<uint32_t,TState> _tags;
+ // Local credential push state tracking
+ _LocalCredentialPushState _localTags[ZT_MAX_NETWORK_TAGS];
+ _LocalCredentialPushState _localCaps[ZT_MAX_NETWORK_CAPABILITIES];
};
} // namespace ZeroTier
diff --git a/node/Multicaster.cpp b/node/Multicaster.cpp
index fc8fa1bd..8743e8f8 100644
--- a/node/Multicaster.cpp
+++ b/node/Multicaster.cpp
@@ -155,6 +155,7 @@ void Multicaster::send(
unsigned int limit,
uint64_t now,
uint64_t nwid,
+ bool disableCompression,
const std::vector<Address> &alwaysSendTo,
const MulticastGroup &mg,
const MAC &src,
@@ -193,6 +194,7 @@ void Multicaster::send(
RR,
now,
nwid,
+ disableCompression,
limit,
1, // we'll still gather a little from peers to keep multicast list fresh
src,
@@ -265,6 +267,7 @@ void Multicaster::send(
RR,
now,
nwid,
+ disableCompression,
limit,
gatherLimit,
src,
diff --git a/node/Multicaster.hpp b/node/Multicaster.hpp
index 8be3b736..5c94cd3a 100644
--- a/node/Multicaster.hpp
+++ b/node/Multicaster.hpp
@@ -153,6 +153,7 @@ public:
* @param limit Multicast limit
* @param now Current time
* @param nwid Network ID
+ * @param disableCompression Disable packet payload compression?
* @param alwaysSendTo Send to these peers first and even if not included in subscriber list
* @param mg Multicast group
* @param src Source Ethernet MAC address or NULL to skip in packet and compute from ZT address (non-bridged mode)
@@ -164,6 +165,7 @@ public:
unsigned int limit,
uint64_t now,
uint64_t nwid,
+ bool disableCompression,
const std::vector<Address> &alwaysSendTo,
const MulticastGroup &mg,
const MAC &src,
diff --git a/node/Network.cpp b/node/Network.cpp
index 455e185e..c0e4b105 100644
--- a/node/Network.cpp
+++ b/node/Network.cpp
@@ -50,6 +50,7 @@ static const char *_rtn(const ZT_VirtualNetworkRuleType rt)
case ZT_NETWORK_RULE_ACTION_DROP: return "ACTION_DROP";
case ZT_NETWORK_RULE_ACTION_ACCEPT: return "ACTION_ACCEPT";
case ZT_NETWORK_RULE_ACTION_TEE: return "ACTION_TEE";
+ case ZT_NETWORK_RULE_ACTION_WATCH: return "ACTION_WATCH";
case ZT_NETWORK_RULE_ACTION_REDIRECT: return "ACTION_REDIRECT";
case ZT_NETWORK_RULE_ACTION_DEBUG_LOG: return "ACTION_DEBUG_LOG";
case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS: return "MATCH_SOURCE_ZEROTIER_ADDRESS";
@@ -57,7 +58,6 @@ static const char *_rtn(const ZT_VirtualNetworkRuleType rt)
case ZT_NETWORK_RULE_MATCH_VLAN_ID: return "MATCH_VLAN_ID";
case ZT_NETWORK_RULE_MATCH_VLAN_PCP: return "MATCH_VLAN_PCP";
case ZT_NETWORK_RULE_MATCH_VLAN_DEI: return "MATCH_VLAN_DEI";
- case ZT_NETWORK_RULE_MATCH_ETHERTYPE: return "MATCH_ETHERTYPE";
case ZT_NETWORK_RULE_MATCH_MAC_SOURCE: return "MATCH_MAC_SOURCE";
case ZT_NETWORK_RULE_MATCH_MAC_DEST: return "MATCH_MAC_DEST";
case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE: return "MATCH_IPV4_SOURCE";
@@ -66,6 +66,7 @@ static const char *_rtn(const ZT_VirtualNetworkRuleType rt)
case ZT_NETWORK_RULE_MATCH_IPV6_DEST: return "MATCH_IPV6_DEST";
case ZT_NETWORK_RULE_MATCH_IP_TOS: return "MATCH_IP_TOS";
case ZT_NETWORK_RULE_MATCH_IP_PROTOCOL: return "MATCH_IP_PROTOCOL";
+ case ZT_NETWORK_RULE_MATCH_ETHERTYPE: return "MATCH_ETHERTYPE";
case ZT_NETWORK_RULE_MATCH_ICMP: return "MATCH_ICMP";
case ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE: return "MATCH_IP_SOURCE_PORT_RANGE";
case ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE: return "MATCH_IP_DEST_PORT_RANGE";
@@ -155,24 +156,21 @@ enum _doZtFilterResult
static _doZtFilterResult _doZtFilter(
const RuntimeEnvironment *RR,
const NetworkConfig &nconf,
+ const Membership *membership, // can be NULL
const bool inbound,
const Address &ztSource,
- Address &ztDest, // MUTABLE
+ Address &ztDest, // MUTABLE -- is changed on REDIRECT actions
const MAC &macSource,
const MAC &macDest,
const uint8_t *const frameData,
const unsigned int frameLen,
const unsigned int etherType,
const unsigned int vlanId,
- const ZT_VirtualNetworkRule *rules,
+ const ZT_VirtualNetworkRule *rules, // cannot be NULL
const unsigned int ruleCount,
- const Tag *localTags,
- const unsigned int localTagCount,
- const uint32_t *const remoteTagIds,
- const uint32_t *const remoteTagValues,
- const unsigned int remoteTagCount,
- Address &cc, // MUTABLE
- unsigned int &ccLength) // MUTABLE
+ Address &cc, // MUTABLE -- set to TEE destination if TEE action is taken or left alone otherwise
+ unsigned int &ccLength, // MUTABLE -- set to length of packet payload to TEE
+ bool &ccWatch) // MUTABLE -- set to true for WATCH target as opposed to normal TEE
{
#ifdef ZT_RULES_ENGINE_DEBUGGING
char dpbuf[1024]; // used by FILTER_TRACE macro
@@ -184,7 +182,7 @@ static _doZtFilterResult _doZtFilter(
uint8_t thisSetMatches = 1;
for(unsigned int rn=0;rn<ruleCount;++rn) {
- const ZT_VirtualNetworkRuleType rt = (ZT_VirtualNetworkRuleType)(rules[rn].t & 0x7f);
+ const ZT_VirtualNetworkRuleType rt = (ZT_VirtualNetworkRuleType)(rules[rn].t & 0x3f);
// First check if this is an ACTION
if ((unsigned int)rt <= (unsigned int)ZT_NETWORK_RULE_ACTION__MAX_ID) {
@@ -204,6 +202,7 @@ static _doZtFilterResult _doZtFilter(
// These are initially handled together since preliminary logic is common
case ZT_NETWORK_RULE_ACTION_TEE:
+ case ZT_NETWORK_RULE_ACTION_WATCH:
case ZT_NETWORK_RULE_ACTION_REDIRECT: {
const Address fwdAddr(rules[rn].v.fwd.address);
if (fwdAddr == ztSource) {
@@ -242,6 +241,7 @@ static _doZtFilterResult _doZtFilter(
#endif // ZT_RULES_ENGINE_DEBUGGING
cc = fwdAddr;
ccLength = (rules[rn].v.fwd.length != 0) ? ((frameLen < (unsigned int)rules[rn].v.fwd.length) ? frameLen : (unsigned int)rules[rn].v.fwd.length) : frameLen;
+ ccWatch = (rt == ZT_NETWORK_RULE_ACTION_WATCH);
}
}
} continue;
@@ -272,8 +272,9 @@ static _doZtFilterResult _doZtFilter(
}
}
- // Circuit breaker: skip further MATCH entries up to next ACTION if match state is false
- if (!thisSetMatches)
+ // Circuit breaker: no need to evaluate an AND if the set's match state
+ // is currently false since anything AND false is false.
+ if ((!thisSetMatches)&&(!(rules[rn].t & 0x40)))
continue;
// If this was not an ACTION evaluate next MATCH and update thisSetMatches with (AND [result])
@@ -301,10 +302,6 @@ static _doZtFilterResult _doZtFilter(
thisRuleMatches = (uint8_t)(rules[rn].v.vlanDei == 0);
FILTER_TRACE("%u %s %c %u==%u -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.vlanDei,0,(unsigned int)thisRuleMatches);
break;
- case ZT_NETWORK_RULE_MATCH_ETHERTYPE:
- thisRuleMatches = (uint8_t)(rules[rn].v.etherType == (uint16_t)etherType);
- FILTER_TRACE("%u %s %c %u==%u -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.etherType,etherType,(unsigned int)thisRuleMatches);
- break;
case ZT_NETWORK_RULE_MATCH_MAC_SOURCE:
thisRuleMatches = (uint8_t)(MAC(rules[rn].v.mac,6) == macSource);
FILTER_TRACE("%u %s %c %.12llx=%.12llx -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),rules[rn].v.mac,macSource.toInt(),(unsigned int)thisRuleMatches);
@@ -380,10 +377,14 @@ static _doZtFilterResult _doZtFilter(
FILTER_TRACE("%u %s %c [frame not IP] -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='));
}
break;
+ case ZT_NETWORK_RULE_MATCH_ETHERTYPE:
+ thisRuleMatches = (uint8_t)(rules[rn].v.etherType == (uint16_t)etherType);
+ FILTER_TRACE("%u %s %c %u==%u -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.etherType,etherType,(unsigned int)thisRuleMatches);
+ break;
case ZT_NETWORK_RULE_MATCH_ICMP:
if ((etherType == ZT_ETHERTYPE_IPV4)&&(frameLen >= 20)) {
- if (frameData[9] == 0x01) {
- const unsigned int ihl = (frameData[0] & 0xf) * 32;
+ if (frameData[9] == 0x01) { // IP protocol == ICMP
+ const unsigned int ihl = (frameData[0] & 0xf) * 4;
if (frameLen >= (ihl + 2)) {
if (rules[rn].v.icmp.type == frameData[ihl]) {
if ((rules[rn].v.icmp.flags & 0x01) != 0) {
@@ -416,7 +417,7 @@ static _doZtFilterResult _doZtFilter(
} else {
thisRuleMatches = 0;
}
- FILTER_TRACE("%u %s %c (IPv4) icmp-type:%d==%d icmp-code:%d==%d -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(int)frameData[pos],(int)rules[rn].v.icmp.type,(int)frameData[pos+1],(((rules[rn].v.icmp.flags & 0x01) != 0) ? (int)rules[rn].v.icmp.code : -1),(unsigned int)thisRuleMatches);
+ FILTER_TRACE("%u %s %c (IPv6) icmp-type:%d==%d icmp-code:%d==%d -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(int)frameData[pos],(int)rules[rn].v.icmp.type,(int)frameData[pos+1],(((rules[rn].v.icmp.flags & 0x01) != 0) ? (int)rules[rn].v.icmp.code : -1),(unsigned int)thisRuleMatches);
} else {
thisRuleMatches = 0;
FILTER_TRACE("%u %s %c [frame not ICMPv6] -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='));
@@ -497,61 +498,59 @@ static _doZtFilterResult _doZtFilter(
}
}
}
- thisRuleMatches = (uint8_t)((cf & rules[rn].v.characteristics[0]) == rules[rn].v.characteristics[1]);
- FILTER_TRACE("%u %s %c (%.16llx & %.16llx)==%.16llx -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),cf,rules[rn].v.characteristics[0],rules[rn].v.characteristics[1],(unsigned int)thisRuleMatches);
+ thisRuleMatches = (uint8_t)((cf | rules[rn].v.characteristics) != 0);
+ FILTER_TRACE("%u %s %c (%.16llx | %.16llx)!=0 -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),cf,rules[rn].v.characteristics,(unsigned int)thisRuleMatches);
} break;
case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE:
thisRuleMatches = (uint8_t)((frameLen >= (unsigned int)rules[rn].v.frameSize[0])&&(frameLen <= (unsigned int)rules[rn].v.frameSize[1]));
FILTER_TRACE("%u %s %c %u in %u-%u -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),frameLen,(unsigned int)rules[rn].v.frameSize[0],(unsigned int)rules[rn].v.frameSize[1],(unsigned int)thisRuleMatches);
break;
+ case ZT_NETWORK_RULE_MATCH_RANDOM:
+ thisRuleMatches = (uint8_t)((uint32_t)(RR->node->prng() & 0xffffffffULL) <= rules[rn].v.randomProbability);
+ FILTER_TRACE("%u %s %c -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)thisRuleMatches);
+ break;
case ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE:
case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND:
case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR:
- case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR: {
- const Tag *lt = (const Tag *)0;
- for(unsigned int i=0;i<localTagCount;++i) {
- if (rules[rn].v.tag.id == localTags[i].id()) {
- lt = &(localTags[i]);
- break;
- }
- }
- if (!lt) {
- thisRuleMatches = 0;
- FILTER_TRACE("%u %s %c local tag %u not found -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id);
- } else {
- const uint32_t *rtv = (const uint32_t *)0;
- for(unsigned int i=0;i<remoteTagCount;++i) {
- if (rules[rn].v.tag.id == remoteTagIds[i]) {
- rtv = &(remoteTagValues[i]);
- break;
- }
- }
- if (!rtv) {
- if (inbound) {
- thisRuleMatches = 0;
- FILTER_TRACE("%u %s %c remote tag %u not found -> 0 (inbound side is strict)",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id);
- } else {
- thisRuleMatches = 1;
- FILTER_TRACE("%u %s %c remote tag %u not found -> 1 (outbound side is not strict)",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id);
- }
- } else {
+ case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR:
+ case ZT_NETWORK_RULE_MATCH_TAGS_EQUAL: {
+ const Tag *const localTag = std::lower_bound(&(nconf.tags[0]),&(nconf.tags[nconf.tagCount]),rules[rn].v.tag.id,Tag::IdComparePredicate());
+ if ((localTag != &(nconf.tags[nconf.tagCount]))&&(localTag->id() == rules[rn].v.tag.id)) {
+ const Tag *const remoteTag = ((membership) ? membership->getTag(nconf,rules[rn].v.tag.id) : (const Tag *)0);
+ if (remoteTag) {
+ const uint32_t ltv = localTag->value();
+ const uint32_t rtv = remoteTag->value();
if (rt == ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE) {
- const uint32_t diff = (lt->value() > *rtv) ? (lt->value() - *rtv) : (*rtv - lt->value());
+ const uint32_t diff = (ltv > rtv) ? (ltv - rtv) : (rtv - ltv);
thisRuleMatches = (uint8_t)(diff <= rules[rn].v.tag.value);
- FILTER_TRACE("%u %s %c TAG %u local:%u remote:%u difference:%u<=%u -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,lt->value(),*rtv,diff,(unsigned int)rules[rn].v.tag.value,thisRuleMatches);
+ FILTER_TRACE("%u %s %c TAG %u local:%u remote:%u difference:%u<=%u -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,ltv,rtv,diff,(unsigned int)rules[rn].v.tag.value,thisRuleMatches);
} else if (rt == ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND) {
- thisRuleMatches = (uint8_t)((lt->value() & *rtv) == rules[rn].v.tag.value);
- FILTER_TRACE("%u %s %c TAG %u local:%.8x & remote:%.8x == %.8x -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,lt->value(),*rtv,(unsigned int)rules[rn].v.tag.value,(unsigned int)thisRuleMatches);
+ thisRuleMatches = (uint8_t)((ltv & rtv) == rules[rn].v.tag.value);
+ FILTER_TRACE("%u %s %c TAG %u local:%.8x & remote:%.8x == %.8x -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,ltv,rtv,(unsigned int)rules[rn].v.tag.value,(unsigned int)thisRuleMatches);
} else if (rt == ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR) {
- thisRuleMatches = (uint8_t)((lt->value() | *rtv) == rules[rn].v.tag.value);
- FILTER_TRACE("%u %s %c TAG %u local:%.8x | remote:%.8x == %.8x -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,lt->value(),*rtv,(unsigned int)rules[rn].v.tag.value,(unsigned int)thisRuleMatches);
+ thisRuleMatches = (uint8_t)((ltv | rtv) == rules[rn].v.tag.value);
+ FILTER_TRACE("%u %s %c TAG %u local:%.8x | remote:%.8x == %.8x -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,ltv,rtv,(unsigned int)rules[rn].v.tag.value,(unsigned int)thisRuleMatches);
} else if (rt == ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR) {
- thisRuleMatches = (uint8_t)((lt->value() ^ *rtv) == rules[rn].v.tag.value);
- FILTER_TRACE("%u %s %c TAG %u local:%.8x ^ remote:%.8x == %.8x -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,lt->value(),*rtv,(unsigned int)rules[rn].v.tag.value,(unsigned int)thisRuleMatches);
+ thisRuleMatches = (uint8_t)((ltv ^ rtv) == rules[rn].v.tag.value);
+ FILTER_TRACE("%u %s %c TAG %u local:%.8x ^ remote:%.8x == %.8x -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,ltv,rtv,(unsigned int)rules[rn].v.tag.value,(unsigned int)thisRuleMatches);
+ } else if (rt == ZT_NETWORK_RULE_MATCH_TAGS_EQUAL) {
+ thisRuleMatches = (uint8_t)((ltv == rules[rn].v.tag.value)&&(rtv == rules[rn].v.tag.value));
+ FILTER_TRACE("%u %s %c TAG %u local:%.8x and remote:%.8x == %.8x -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,ltv,rtv,(unsigned int)rules[rn].v.tag.value,(unsigned int)thisRuleMatches);
} else { // sanity check, can't really happen
thisRuleMatches = 0;
}
+ } else {
+ if (inbound) {
+ thisRuleMatches = 0;
+ FILTER_TRACE("%u %s %c remote tag %u not found -> 0 (inbound side is strict)",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id);
+ } else {
+ thisRuleMatches = 1;
+ FILTER_TRACE("%u %s %c remote tag %u not found -> 1 (outbound side is not strict)",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id);
+ }
}
+ } else {
+ thisRuleMatches = 0;
+ FILTER_TRACE("%u %s %c local tag %u not found -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id);
}
} break;
@@ -562,8 +561,9 @@ static _doZtFilterResult _doZtFilter(
break;
}
- // State of equals state AND result of last MATCH (possibly NOTed depending on bit 0x80)
- thisSetMatches &= (thisRuleMatches ^ ((rules[rn].t >> 7) & 1));
+ if ((rules[rn].t & 0x40))
+ thisSetMatches |= (thisRuleMatches ^ ((rules[rn].t >> 7) & 1));
+ else thisSetMatches &= (thisRuleMatches ^ ((rules[rn].t >> 7) & 1));
}
return DOZTFILTER_NO_MATCH;
@@ -580,13 +580,14 @@ Network::Network(const RuntimeEnvironment *renv,uint64_t nwid,void *uptr) :
_lastAnnouncedMulticastGroupsUpstream(0),
_mac(renv->identity.address(),nwid),
_portInitialized(false),
- _inboundConfigPacketId(0),
_lastConfigUpdate(0),
- _lastRequestedConfiguration(0),
_destroyed(false),
_netconfFailure(NETCONF_FAILURE_NONE),
_portError(0)
{
+ for(int i=0;i<ZT_NETWORK_MAX_INCOMING_UPDATES;++i)
+ _incomingConfigChunks[i].ts = 0;
+
char confn[128];
Utils::snprintf(confn,sizeof(confn),"networks.d/%.16llx.conf",_id);
@@ -598,7 +599,7 @@ Network::Network(const RuntimeEnvironment *renv,uint64_t nwid,void *uptr) :
if (conf.length()) {
dconf->load(conf.c_str());
if (nconf->fromDictionary(*dconf)) {
- this->setConfiguration(*nconf,false);
+ this->_setConfiguration(*nconf,false);
_lastConfigUpdate = 0; // we still want to re-request a new config from the network
gotConf = true;
}
@@ -646,32 +647,27 @@ bool Network::filterOutgoingPacket(
const unsigned int etherType,
const unsigned int vlanId)
{
- uint32_t remoteTagIds[ZT_MAX_NETWORK_TAGS];
- uint32_t remoteTagValues[ZT_MAX_NETWORK_TAGS];
+ const uint64_t now = RR->node->now();
Address ztFinalDest(ztDest);
- Address cc;
- const Capability *relevantCap = (const Capability *)0;
- unsigned int ccLength = 0;
+ int localCapabilityIndex = -1;
bool accept = false;
- const uint64_t now = RR->node->now();
Mutex::Lock _l(_lock);
- Membership *m = (Membership *)0;
- unsigned int remoteTagCount = 0;
- if (ztDest) {
- m = &(_memberships[ztDest]);
- remoteTagCount = m->getAllTags(_config,remoteTagIds,remoteTagValues,ZT_MAX_NETWORK_TAGS);
- }
+ Membership *const membership = (ztDest) ? _memberships.get(ztDest) : (Membership *)0;
- switch(_doZtFilter(RR,_config,false,ztSource,ztFinalDest,macSource,macDest,frameData,frameLen,etherType,vlanId,_config.rules,_config.ruleCount,_config.tags,_config.tagCount,remoteTagIds,remoteTagValues,remoteTagCount,cc,ccLength)) {
+ Address cc;
+ unsigned int ccLength = 0;
+ bool ccWatch = false;
+ switch(_doZtFilter(RR,_config,membership,false,ztSource,ztFinalDest,macSource,macDest,frameData,frameLen,etherType,vlanId,_config.rules,_config.ruleCount,cc,ccLength,ccWatch)) {
case DOZTFILTER_NO_MATCH:
for(unsigned int c=0;c<_config.capabilityCount;++c) {
- ztFinalDest = ztDest; // sanity check
+ ztFinalDest = ztDest; // sanity check, shouldn't be possible if there was no match
Address cc2;
unsigned int ccLength2 = 0;
- switch (_doZtFilter(RR,_config,false,ztSource,ztFinalDest,macSource,macDest,frameData,frameLen,etherType,vlanId,_config.capabilities[c].rules(),_config.capabilities[c].ruleCount(),_config.tags,_config.tagCount,remoteTagIds,remoteTagValues,remoteTagCount,cc2,ccLength2)) {
+ bool ccWatch2 = false;
+ switch (_doZtFilter(RR,_config,membership,false,ztSource,ztFinalDest,macSource,macDest,frameData,frameLen,etherType,vlanId,_config.capabilities[c].rules(),_config.capabilities[c].ruleCount(),cc2,ccLength2,ccWatch2)) {
case DOZTFILTER_NO_MATCH:
case DOZTFILTER_DROP: // explicit DROP in a capability just terminates its evaluation and is an anti-pattern
break;
@@ -679,16 +675,16 @@ bool Network::filterOutgoingPacket(
case DOZTFILTER_REDIRECT: // interpreted as ACCEPT but ztFinalDest will have been changed in _doZtFilter()
case DOZTFILTER_ACCEPT:
case DOZTFILTER_SUPER_ACCEPT: // no difference in behavior on outbound side
- relevantCap = &(_config.capabilities[c]);
+ localCapabilityIndex = (int)c;
accept = true;
if ((!noTee)&&(cc2)) {
Membership &m2 = _membership(cc2);
- m2.sendCredentialsIfNeeded(RR,now,cc2,_config,relevantCap);
+ m2.pushCredentials(RR,now,cc2,_config,localCapabilityIndex,false);
Packet outp(cc2,RR->identity.address(),Packet::VERB_EXT_FRAME);
outp.append(_id);
- outp.append((uint8_t)0x02); // TEE/REDIRECT from outbound side: 0x02
+ outp.append((uint8_t)(ccWatch2 ? 0x16 : 0x02));
macDest.appendTo(outp);
macSource.appendTo(outp);
outp.append((uint16_t)etherType);
@@ -715,13 +711,16 @@ bool Network::filterOutgoingPacket(
}
if (accept) {
+ if (membership)
+ membership->pushCredentials(RR,now,ztDest,_config,localCapabilityIndex,false);
+
if ((!noTee)&&(cc)) {
Membership &m2 = _membership(cc);
- m2.sendCredentialsIfNeeded(RR,now,cc,_config,relevantCap);
+ m2.pushCredentials(RR,now,cc,_config,localCapabilityIndex,false);
Packet outp(cc,RR->identity.address(),Packet::VERB_EXT_FRAME);
outp.append(_id);
- outp.append((uint8_t)0x02); // TEE/REDIRECT from outbound side: 0x02
+ outp.append((uint8_t)(ccWatch ? 0x16 : 0x02));
macDest.appendTo(outp);
macSource.appendTo(outp);
outp.append((uint16_t)etherType);
@@ -732,11 +731,11 @@ bool Network::filterOutgoingPacket(
if ((ztDest != ztFinalDest)&&(ztFinalDest)) {
Membership &m2 = _membership(ztFinalDest);
- m2.sendCredentialsIfNeeded(RR,now,ztFinalDest,_config,relevantCap);
+ m2.pushCredentials(RR,now,ztFinalDest,_config,localCapabilityIndex,false);
Packet outp(ztFinalDest,RR->identity.address(),Packet::VERB_EXT_FRAME);
outp.append(_id);
- outp.append((uint8_t)0x02); // TEE/REDIRECT from outbound side: 0x02
+ outp.append((uint8_t)0x04);
macDest.appendTo(outp);
macSource.appendTo(outp);
outp.append((uint16_t)etherType);
@@ -745,11 +744,9 @@ bool Network::filterOutgoingPacket(
RR->sw->send(outp,true);
return false; // DROP locally, since we redirected
- } else if (m) {
- m->sendCredentialsIfNeeded(RR,now,ztDest,_config,relevantCap);
+ } else {
+ return true;
}
-
- return true;
} else {
return false;
}
@@ -765,28 +762,27 @@ int Network::filterIncomingPacket(
const unsigned int etherType,
const unsigned int vlanId)
{
- uint32_t remoteTagIds[ZT_MAX_NETWORK_TAGS];
- uint32_t remoteTagValues[ZT_MAX_NETWORK_TAGS];
Address ztFinalDest(ztDest);
- Address cc;
- unsigned int ccLength = 0;
int accept = 0;
Mutex::Lock _l(_lock);
- Membership &m = _membership(sourcePeer->address());
- const unsigned int remoteTagCount = m.getAllTags(_config,remoteTagIds,remoteTagValues,ZT_MAX_NETWORK_TAGS);
+ Membership &membership = _membership(sourcePeer->address());
- switch (_doZtFilter(RR,_config,true,sourcePeer->address(),ztFinalDest,macSource,macDest,frameData,frameLen,etherType,vlanId,_config.rules,_config.ruleCount,_config.tags,_config.tagCount,remoteTagIds,remoteTagValues,remoteTagCount,cc,ccLength)) {
+ Address cc;
+ unsigned int ccLength = 0;
+ bool ccWatch = false;
+ switch (_doZtFilter(RR,_config,&membership,true,sourcePeer->address(),ztFinalDest,macSource,macDest,frameData,frameLen,etherType,vlanId,_config.rules,_config.ruleCount,cc,ccLength,ccWatch)) {
case DOZTFILTER_NO_MATCH: {
- Membership::CapabilityIterator mci(m);
+ Membership::CapabilityIterator mci(membership,_config);
const Capability *c;
- while ((c = mci.next(_config))) {
- ztFinalDest = ztDest; // sanity check
+ while ((c = mci.next())) {
+ ztFinalDest = ztDest; // sanity check, should be unmodified if there was no match
Address cc2;
unsigned int ccLength2 = 0;
- switch(_doZtFilter(RR,_config,true,sourcePeer->address(),ztFinalDest,macSource,macDest,frameData,frameLen,etherType,vlanId,c->rules(),c->ruleCount(),_config.tags,_config.tagCount,remoteTagIds,remoteTagValues,remoteTagCount,cc2,ccLength2)) {
+ bool ccWatch2 = false;
+ switch(_doZtFilter(RR,_config,&membership,true,sourcePeer->address(),ztFinalDest,macSource,macDest,frameData,frameLen,etherType,vlanId,c->rules(),c->ruleCount(),cc2,ccLength2,ccWatch2)) {
case DOZTFILTER_NO_MATCH:
case DOZTFILTER_DROP: // explicit DROP in a capability just terminates its evaluation and is an anti-pattern
break;
@@ -801,11 +797,11 @@ int Network::filterIncomingPacket(
if (accept) {
if (cc2) {
- _membership(cc2).sendCredentialsIfNeeded(RR,RR->node->now(),cc2,_config,(const Capability *)0);
+ _membership(cc2).pushCredentials(RR,RR->node->now(),cc2,_config,-1,false);
Packet outp(cc2,RR->identity.address(),Packet::VERB_EXT_FRAME);
outp.append(_id);
- outp.append((uint8_t)0x06); // TEE/REDIRECT from inbound side: 0x06
+ outp.append((uint8_t)(ccWatch2 ? 0x1c : 0x08));
macDest.appendTo(outp);
macSource.appendTo(outp);
outp.append((uint16_t)etherType);
@@ -832,11 +828,11 @@ int Network::filterIncomingPacket(
if (accept) {
if (cc) {
- _membership(cc).sendCredentialsIfNeeded(RR,RR->node->now(),cc,_config,(const Capability *)0);
+ _membership(cc).pushCredentials(RR,RR->node->now(),cc,_config,-1,false);
Packet outp(cc,RR->identity.address(),Packet::VERB_EXT_FRAME);
outp.append(_id);
- outp.append((uint8_t)0x06); // TEE/REDIRECT from inbound side: 0x06
+ outp.append((uint8_t)(ccWatch ? 0x1c : 0x08));
macDest.appendTo(outp);
macSource.appendTo(outp);
outp.append((uint16_t)etherType);
@@ -846,11 +842,11 @@ int Network::filterIncomingPacket(
}
if ((ztDest != ztFinalDest)&&(ztFinalDest)) {
- _membership(ztFinalDest).sendCredentialsIfNeeded(RR,RR->node->now(),ztFinalDest,_config,(const Capability *)0);
+ _membership(ztFinalDest).pushCredentials(RR,RR->node->now(),ztFinalDest,_config,-1,false);
Packet outp(ztFinalDest,RR->identity.address(),Packet::VERB_EXT_FRAME);
outp.append(_id);
- outp.append((uint8_t)0x06); // TEE/REDIRECT from inbound side: 0x06
+ outp.append((uint8_t)0x0a);
macDest.appendTo(outp);
macSource.appendTo(outp);
outp.append((uint16_t)etherType);
@@ -892,117 +888,145 @@ void Network::multicastUnsubscribe(const MulticastGroup &mg)
_myMulticastGroups.erase(i);
}
-bool Network::applyConfiguration(const NetworkConfig &conf)
+uint64_t Network::handleConfigChunk(const Packet &chunk,unsigned int ptr)
{
- if (_destroyed) // sanity check
- return false;
- try {
- if ((conf.networkId == _id)&&(conf.issuedTo == RR->identity.address())) {
- ZT_VirtualNetworkConfig ctmp;
- bool portInitialized;
- {
- Mutex::Lock _l(_lock);
- _config = conf;
- _lastConfigUpdate = RR->node->now();
- _netconfFailure = NETCONF_FAILURE_NONE;
- _externalConfig(&ctmp);
- portInitialized = _portInitialized;
- _portInitialized = true;
+ const unsigned int start = ptr;
+
+ ptr += 8; // skip network ID, which is already obviously known
+ const unsigned int chunkLen = chunk.at<uint16_t>(ptr); ptr += 2;
+ const void *chunkData = chunk.field(ptr,chunkLen); ptr += chunkLen;
+
+ NetworkConfig *nc = (NetworkConfig *)0;
+ uint64_t configUpdateId;
+ {
+ Mutex::Lock _l(_lock);
+
+ _IncomingConfigChunk *c = (_IncomingConfigChunk *)0;
+ uint64_t chunkId = 0;
+ unsigned long totalLength,chunkIndex;
+ if (ptr < chunk.size()) {
+ const bool fastPropagate = ((chunk[ptr++] & 0x01) != 0);
+ configUpdateId = chunk.at<uint64_t>(ptr); ptr += 8;
+ totalLength = chunk.at<uint32_t>(ptr); ptr += 4;
+ chunkIndex = chunk.at<uint32_t>(ptr); ptr += 4;
+
+ if (((chunkIndex + chunkLen) > totalLength)||(totalLength >= ZT_NETWORKCONFIG_DICT_CAPACITY)) { // >= since we need room for a null at the end
+ TRACE("discarded chunk from %s: invalid length or length overflow",chunk.source().toString().c_str());
+ return 0;
}
- _portError = RR->node->configureVirtualNetworkPort(_id,&_uPtr,(portInitialized) ? ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE : ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP,&ctmp);
- return true;
- } else {
- TRACE("ignored invalid configuration for network %.16llx (configuration contains mismatched network ID or issued-to address)",(unsigned long long)_id);
- }
- } catch (std::exception &exc) {
- TRACE("ignored invalid configuration for network %.16llx (%s)",(unsigned long long)_id,exc.what());
- } catch ( ... ) {
- TRACE("ignored invalid configuration for network %.16llx (unknown exception)",(unsigned long long)_id);
- }
- return false;
-}
-int Network::setConfiguration(const NetworkConfig &nconf,bool saveToDisk)
-{
- try {
- {
- Mutex::Lock _l(_lock);
- if (_config == nconf)
- return 1; // OK config, but duplicate of what we already have
- }
- if (applyConfiguration(nconf)) {
- if (saveToDisk) {
- char n[64];
- Utils::snprintf(n,sizeof(n),"networks.d/%.16llx.conf",_id);
- Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> d;
- if (nconf.toDictionary(d,false))
- RR->node->dataStorePut(n,(const void *)d.data(),d.sizeBytes(),true);
+ if ((chunk[ptr] != 1)||(chunk.at<uint16_t>(ptr + 1) != ZT_C25519_SIGNATURE_LEN)) {
+ TRACE("discarded chunk from %s: unrecognized signature type",chunk.source().toString().c_str());
+ return 0;
}
- return 2; // OK and configuration has changed
+ const uint8_t *sig = reinterpret_cast<const uint8_t *>(chunk.field(ptr + 3,ZT_C25519_SIGNATURE_LEN));
+
+ // We can use the signature, which is unique per chunk, to get a per-chunk ID for local deduplication use
+ for(unsigned int i=0;i<16;++i)
+ reinterpret_cast<uint8_t *>(&chunkId)[i & 7] ^= sig[i];
+
+ // Find existing or new slot for this update and check if this is a duplicate chunk
+ for(int i=0;i<ZT_NETWORK_MAX_INCOMING_UPDATES;++i) {
+ if (_incomingConfigChunks[i].updateId == configUpdateId) {
+ c = &(_incomingConfigChunks[i]);
+
+ for(unsigned long j=0;j<c->haveChunks;++j) {
+ if (c->haveChunkIds[j] == chunkId)
+ return 0;
+ }
+
+ break;
+ } else if ((!c)||(_incomingConfigChunks[i].ts < c->ts)) {
+ c = &(_incomingConfigChunks[i]);
+ }
+ }
+
+ // If it's not a duplicate, check chunk signature
+ const Identity controllerId(RR->topology->getIdentity(controller()));
+ if (!controllerId) { // we should always have the controller identity by now, otherwise how would we have queried it the first time?
+ TRACE("unable to verify chunk from %s: don't have controller identity",chunk.source().toString().c_str());
+ return 0;
+ }
+ if (!controllerId.verify(chunk.field(start,ptr - start),ptr - start,sig,ZT_C25519_SIGNATURE_LEN)) {
+ TRACE("discarded chunk from %s: signature check failed",chunk.source().toString().c_str());
+ return 0;
+ }
+
+ // New properly verified chunks can be flooded "virally" through the network
+ if (fastPropagate) {
+ Address *a = (Address *)0;
+ Membership *m = (Membership *)0;
+ Hashtable<Address,Membership>::Iterator i(_memberships);
+ while (i.next(a,m)) {
+ if ((*a != chunk.source())&&(*a != controller())) {
+ Packet outp(*a,RR->identity.address(),Packet::VERB_NETWORK_CONFIG);
+ outp.append(reinterpret_cast<const uint8_t *>(chunk.data()) + start,chunk.size() - start);
+ RR->sw->send(outp,true);
+ }
+ }
+ }
+ } else if (chunk.source() == controller()) {
+ // Legacy support for OK(NETWORK_CONFIG_REQUEST) from older controllers
+ chunkId = chunk.packetId();
+ configUpdateId = chunkId;
+ totalLength = chunkLen;
+ chunkIndex = 0;
+
+ if (totalLength >= ZT_NETWORKCONFIG_DICT_CAPACITY)
+ return 0;
+
+ for(int i=0;i<ZT_NETWORK_MAX_INCOMING_UPDATES;++i) {
+ if ((!c)||(_incomingConfigChunks[i].ts < c->ts))
+ c = &(_incomingConfigChunks[i]);
+ }
+ } else {
+ TRACE("discarded single-chunk unsigned legacy config: this is only allowed if the sender is the controller itself");
+ return 0;
}
- } catch ( ... ) {
- TRACE("ignored invalid configuration for network %.16llx",(unsigned long long)_id);
- }
- return 0;
-}
-void Network::handleInboundConfigChunk(const uint64_t inRePacketId,const void *data,unsigned int chunkSize,unsigned int chunkIndex,unsigned int totalSize)
-{
- std::string newConfig;
- if ((_inboundConfigPacketId == inRePacketId)&&(totalSize < ZT_NETWORKCONFIG_DICT_CAPACITY)&&((chunkIndex + chunkSize) <= totalSize)) {
- Mutex::Lock _l(_lock);
+ ++c->ts; // newer is higher, that's all we need
- _inboundConfigChunks[chunkIndex].append((const char *)data,chunkSize);
-
- unsigned int totalWeHave = 0;
- for(std::map<unsigned int,std::string>::iterator c(_inboundConfigChunks.begin());c!=_inboundConfigChunks.end();++c)
- totalWeHave += (unsigned int)c->second.length();
-
- if (totalWeHave == totalSize) {
- TRACE("have all chunks for network config request %.16llx, assembling...",inRePacketId);
- for(std::map<unsigned int,std::string>::iterator c(_inboundConfigChunks.begin());c!=_inboundConfigChunks.end();++c)
- newConfig.append(c->second);
- _inboundConfigPacketId = 0;
- _inboundConfigChunks.clear();
- } else if (totalWeHave > totalSize) {
- _inboundConfigPacketId = 0;
- _inboundConfigChunks.clear();
+ if (c->updateId != configUpdateId) {
+ c->updateId = configUpdateId;
+ c->haveChunks = 0;
+ c->haveBytes = 0;
}
- } else {
- return;
- }
+ if (c->haveChunks >= ZT_NETWORK_MAX_UPDATE_CHUNKS)
+ return false;
+ c->haveChunkIds[c->haveChunks++] = chunkId;
- if ((newConfig.length() > 0)&&(newConfig.length() < ZT_NETWORKCONFIG_DICT_CAPACITY)) {
- Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> *dict = new Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>(newConfig.c_str());
- NetworkConfig *nc = new NetworkConfig();
- try {
- Identity controllerId(RR->topology->getIdentity(this->controller()));
- if (controllerId) {
- if (nc->fromDictionary(*dict)) {
- this->setConfiguration(*nc,true);
- } else {
- TRACE("error parsing new config with length %u: deserialization of NetworkConfig failed (certificate error?)",(unsigned int)newConfig.length());
+ memcpy(c->data.unsafeData() + chunkIndex,chunkData,chunkLen);
+ c->haveBytes += chunkLen;
+
+ if (c->haveBytes == totalLength) {
+ c->data.unsafeData()[c->haveBytes] = (char)0; // ensure null terminated
+
+ nc = new NetworkConfig();
+ try {
+ if (!nc->fromDictionary(c->data)) {
+ delete nc;
+ nc = (NetworkConfig *)0;
}
+ } catch ( ... ) {
+ delete nc;
+ nc = (NetworkConfig *)0;
}
- delete nc;
- delete dict;
- } catch ( ... ) {
- TRACE("error parsing new config with length %u: unexpected exception",(unsigned int)newConfig.length());
- delete nc;
- delete dict;
- throw;
}
}
+
+ if (nc) {
+ this->_setConfiguration(*nc,true);
+ delete nc;
+ return configUpdateId;
+ } else {
+ return 0;
+ }
+
+ return 0;
}
void Network::requestConfiguration()
{
- // Sanity limit: do not request more often than once per second
- const uint64_t now = RR->node->now();
- if ((now - _lastRequestedConfiguration) < 1000ULL)
- return;
- _lastRequestedConfiguration = RR->node->now();
-
const Address ctrl(controller());
Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> rmd;
@@ -1022,24 +1046,30 @@ void Network::requestConfiguration()
if (ctrl == RR->identity.address()) {
if (RR->localNetworkController) {
- NetworkConfig nconf;
- switch(RR->localNetworkController->doNetworkConfigRequest(InetAddress(),RR->identity,RR->identity,_id,rmd,nconf)) {
- case NetworkController::NETCONF_QUERY_OK:
- this->setConfiguration(nconf,true);
- return;
- case NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND:
- this->setNotFound();
- return;
- case NetworkController::NETCONF_QUERY_ACCESS_DENIED:
- this->setAccessDenied();
- return;
- default:
- return;
+ NetworkConfig *nconf = new NetworkConfig();
+ try {
+ switch(RR->localNetworkController->doNetworkConfigRequest(InetAddress(),RR->identity,RR->identity,_id,rmd,*nconf)) {
+ case NetworkController::NETCONF_QUERY_OK:
+ this->_setConfiguration(*nconf,true);
+ break;
+ case NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND:
+ this->setNotFound();
+ break;
+ case NetworkController::NETCONF_QUERY_ACCESS_DENIED:
+ this->setAccessDenied();
+ break;
+ default:
+ this->setNotFound();
+ break;
+ }
+ } catch ( ... ) {
+ this->setNotFound();
}
+ delete nconf;
} else {
this->setNotFound();
- return;
}
+ return;
}
TRACE("requesting netconf for network %.16llx from controller %s",(unsigned long long)_id,ctrl.toString().c_str());
@@ -1055,15 +1085,12 @@ void Network::requestConfiguration()
} else {
outp.append((unsigned char)0,16);
}
-
- RR->node->expectReplyTo(_inboundConfigPacketId = outp.packetId());
- _inboundConfigChunks.clear();
-
+ RR->node->expectReplyTo(outp.packetId());
outp.compress();
RR->sw->send(outp,true);
}
-bool Network::gate(const SharedPtr<Peer> &peer,const Packet::Verb verb,const uint64_t packetId)
+bool Network::gate(const SharedPtr<Peer> &peer)
{
const uint64_t now = RR->node->now();
Mutex::Lock _l(_lock);
@@ -1073,21 +1100,12 @@ bool Network::gate(const SharedPtr<Peer> &peer,const Packet::Verb verb,const uin
if ( (_config.isPublic()) || ((m)&&(m->isAllowedOnNetwork(_config))) ) {
if (!m)
m = &(_membership(peer->address()));
- m->sendCredentialsIfNeeded(RR,now,peer->address(),_config,(const Capability *)0);
+ m->pushCredentials(RR,now,peer->address(),_config,-1,false);
if (m->shouldLikeMulticasts(now)) {
_announceMulticastGroupsTo(peer->address(),_allMulticastGroups());
m->likingMulticasts(now);
}
return true;
- } else {
- if (peer->rateGateRequestCredentials(now)) {
- Packet outp(peer->address(),RR->identity.address(),Packet::VERB_ERROR);
- outp.append((uint8_t)verb);
- outp.append(packetId);
- outp.append((uint8_t)Packet::ERROR_NEED_MEMBERSHIP_CERTIFICATE);
- outp.append(_id);
- RR->sw->send(outp,true);
- }
}
}
} catch ( ... ) {
@@ -1096,11 +1114,6 @@ bool Network::gate(const SharedPtr<Peer> &peer,const Packet::Verb verb,const uin
return false;
}
-bool Network::gateMulticastGatherReply(const SharedPtr<Peer> &peer,const Packet::Verb verb,const uint64_t packetId)
-{
- return ( (peer->address() == controller()) || RR->topology->isUpstream(peer->identity()) || gate(peer,verb,packetId) || _config.isAnchor(peer->address()) );
-}
-
void Network::clean()
{
const uint64_t now = RR->node->now();
@@ -1124,9 +1137,8 @@ void Network::clean()
Membership *m = (Membership *)0;
Hashtable<Address,Membership>::Iterator i(_memberships);
while (i.next(a,m)) {
- if (RR->topology->getPeerNoCache(*a))
- m->clean(_config);
- else _memberships.erase(*a);
+ if (!RR->topology->getPeerNoCache(*a))
+ _memberships.erase(*a);
}
}
}
@@ -1177,21 +1189,51 @@ void Network::learnBridgedMulticastGroup(const MulticastGroup &mg,uint64_t now)
_sendUpdatesToMembers(&mg);
}
-int Network::addCredential(const CertificateOfMembership &com)
+Membership::AddCredentialResult Network::addCredential(const CertificateOfMembership &com)
{
if (com.networkId() != _id)
- return -1;
+ return Membership::ADD_REJECTED;
const Address a(com.issuedTo());
Mutex::Lock _l(_lock);
Membership &m = _membership(a);
- const int result = m.addCredential(RR,com);
- if (result == 0) {
- m.sendCredentialsIfNeeded(RR,RR->node->now(),a,_config,(const Capability *)0);
+ const Membership::AddCredentialResult result = m.addCredential(RR,_config,com);
+ if ((result == Membership::ADD_ACCEPTED_NEW)||(result == Membership::ADD_ACCEPTED_REDUNDANT)) {
+ m.pushCredentials(RR,RR->node->now(),a,_config,-1,false);
RR->mc->addCredential(com,true);
}
return result;
}
+Membership::AddCredentialResult Network::addCredential(const Address &sentFrom,const Revocation &rev)
+{
+ if (rev.networkId() != _id)
+ return Membership::ADD_REJECTED;
+
+ Mutex::Lock _l(_lock);
+ Membership &m = _membership(rev.target());
+
+ const Membership::AddCredentialResult result = m.addCredential(RR,_config,rev);
+
+ if ((result == Membership::ADD_ACCEPTED_NEW)&&(rev.fastPropagate())) {
+ Address *a = (Address *)0;
+ Membership *m = (Membership *)0;
+ Hashtable<Address,Membership>::Iterator i(_memberships);
+ while (i.next(a,m)) {
+ if ((*a != sentFrom)&&(*a != rev.signer())) {
+ Packet outp(*a,RR->identity.address(),Packet::VERB_NETWORK_CREDENTIALS);
+ outp.append((uint8_t)0x00); // no COM
+ outp.append((uint16_t)0); // no capabilities
+ outp.append((uint16_t)0); // no tags
+ outp.append((uint16_t)1); // one revocation!
+ rev.serialize(outp);
+ RR->sw->send(outp,true);
+ }
+ }
+ }
+
+ return result;
+}
+
void Network::destroy()
{
Mutex::Lock _l(_lock);
@@ -1215,6 +1257,46 @@ ZT_VirtualNetworkStatus Network::_status() const
}
}
+int Network::_setConfiguration(const NetworkConfig &nconf,bool saveToDisk)
+{
+ // _lock is NOT locked when this is called
+ try {
+ if ((nconf.issuedTo != RR->identity.address())||(nconf.networkId != _id))
+ return 0;
+ if (_config == nconf)
+ return 1; // OK config, but duplicate of what we already have
+
+ ZT_VirtualNetworkConfig ctmp;
+ bool oldPortInitialized;
+ {
+ Mutex::Lock _l(_lock);
+ _config = nconf;
+ _lastConfigUpdate = RR->node->now();
+ _netconfFailure = NETCONF_FAILURE_NONE;
+ oldPortInitialized = _portInitialized;
+ _portInitialized = true;
+ _externalConfig(&ctmp);
+ }
+ _portError = RR->node->configureVirtualNetworkPort(_id,&_uPtr,(oldPortInitialized) ? ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE : ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP,&ctmp);
+
+ if (saveToDisk) {
+ Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> *d = new Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>();
+ try {
+ char n[64];
+ Utils::snprintf(n,sizeof(n),"networks.d/%.16llx.conf",_id);
+ if (nconf.toDictionary(*d,false))
+ RR->node->dataStorePut(n,(const void *)d->data(),d->sizeBytes(),true);
+ } catch ( ... ) {}
+ delete d;
+ }
+
+ return 2; // OK and configuration has changed
+ } catch ( ... ) {
+ TRACE("ignored invalid configuration for network %.16llx",(unsigned long long)_id);
+ }
+ return 0;
+}
+
void Network::_externalConfig(ZT_VirtualNetworkConfig *ec) const
{
// assumes _lock is locked
@@ -1308,7 +1390,7 @@ void Network::_sendUpdatesToMembers(const MulticastGroup *const newMulticastGrou
Membership *m = (Membership *)0;
Hashtable<Address,Membership>::Iterator i(_memberships);
while (i.next(a,m)) {
- m->sendCredentialsIfNeeded(RR,now,*a,_config,(const Capability *)0);
+ m->pushCredentials(RR,now,*a,_config,-1,false);
if ( ((newMulticastGroup)||(m->shouldLikeMulticasts(now))) && (m->isAllowedOnNetwork(_config)) ) {
if (!newMulticastGroup)
m->likingMulticasts(now);
diff --git a/node/Network.hpp b/node/Network.hpp
index c85e5993..527d3048 100644
--- a/node/Network.hpp
+++ b/node/Network.hpp
@@ -44,6 +44,9 @@
#include "NetworkConfig.hpp"
#include "CertificateOfMembership.hpp"
+#define ZT_NETWORK_MAX_INCOMING_UPDATES 3
+#define ZT_NETWORK_MAX_UPDATE_CHUNKS ((ZT_NETWORKCONFIG_DICT_CAPACITY / 1024) + 1)
+
namespace ZeroTier {
class RuntimeEnvironment;
@@ -63,6 +66,11 @@ public:
static const MulticastGroup BROADCAST;
/**
+ * Compute primary controller device ID from network ID
+ */
+ static inline Address controllerFor(uint64_t nwid) throw() { return Address(nwid >> 24); }
+
+ /**
* Construct a new network
*
* Note that init() should be called immediately after the network is
@@ -76,14 +84,24 @@ public:
~Network();
+ inline uint64_t id() const { return _id; }
+ inline Address controller() const { return Address(_id >> 24); }
+ inline bool multicastEnabled() const { return (_config.multicastLimit > 0); }
+ inline bool hasConfig() const { return (_config); }
+ inline uint64_t lastConfigUpdate() const throw() { return _lastConfigUpdate; }
+ inline ZT_VirtualNetworkStatus status() const { Mutex::Lock _l(_lock); return _status(); }
+ inline const NetworkConfig &config() const { return _config; }
+ inline const MAC &mac() const { return _mac; }
+
/**
* Apply filters to an outgoing packet
*
* This applies filters from our network config and, if that doesn't match,
* our capabilities in ascending order of capability ID. Additional actions
- * such as TEE may be taken, and credentials may be pushed.
+ * such as TEE may be taken, and credentials may be pushed, so this is not
+ * side-effect-free. It's basically step one in sending something over VL2.
*
- * @param noTee If true, do not TEE anything anywhere
+ * @param noTee If true, do not TEE anything anywhere (for two-pass filtering as done with multicast and bridging)
* @param ztSource Source ZeroTier address
* @param ztDest Destination ZeroTier address
* @param macSource Ethernet layer source address
@@ -134,42 +152,10 @@ public:
const unsigned int vlanId);
/**
- * @return Network ID
- */
- inline uint64_t id() const throw() { return _id; }
-
- /**
- * @return Address of network's controller (most significant 40 bits of ID)
- */
- inline Address controller() const throw() { return Address(_id >> 24); }
-
- /**
- * @param nwid Network ID
- * @return Address of network's controller
- */
- static inline Address controllerFor(uint64_t nwid) throw() { return Address(nwid >> 24); }
-
- /**
- * @return Multicast group memberships for this network's port (local, not learned via bridging)
- */
- inline std::vector<MulticastGroup> multicastGroups() const
- {
- Mutex::Lock _l(_lock);
- return _myMulticastGroups;
- }
-
- /**
- * @return All multicast groups including learned groups that are behind any bridges we're attached to
- */
- inline std::vector<MulticastGroup> allMulticastGroups() const
- {
- Mutex::Lock _l(_lock);
- return _allMulticastGroups();
- }
-
- /**
+ * Check whether we are subscribed to a multicast group
+ *
* @param mg Multicast group
- * @param includeBridgedGroups If true, also include any groups we've learned via bridging
+ * @param includeBridgedGroups If true, also check groups we've learned via bridging
* @return True if this network endpoint / peer is a member
*/
bool subscribedToMulticastGroup(const MulticastGroup &mg,bool includeBridgedGroups) const;
@@ -189,36 +175,17 @@ public:
void multicastUnsubscribe(const MulticastGroup &mg);
/**
- * Apply a NetworkConfig to this network
- *
- * @param conf Configuration in NetworkConfig form
- * @return True if configuration was accepted
- */
- bool applyConfiguration(const NetworkConfig &conf);
-
- /**
- * Set or update this network's configuration
- *
- * @param nconf Network configuration
- * @param saveToDisk IF true (default), write config to disk
- * @return 0 -- rejected, 1 -- accepted but not new, 2 -- accepted new config
- */
- int setConfiguration(const NetworkConfig &nconf,bool saveToDisk);
-
- /**
* Handle an inbound network config chunk
*
- * Only chunks whose inRePacketId matches the packet ID of the last request
- * are handled. If this chunk completes the config, it is decoded and
- * setConfiguration() is called.
+ * This is called from IncomingPacket to handle incoming network config
+ * chunks via OK(NETWORK_CONFIG_REQUEST) or NETWORK_CONFIG. It verifies
+ * each chunk and once assembled applies the configuration.
*
- * @param inRePacketId In-re packet ID from OK(NETWORK_CONFIG_REQUEST)
- * @param data Chunk data
- * @param chunkSize Size of data[]
- * @param chunkIndex Index of chunk in full config
- * @param totalSize Total size of network config
+ * @param chunk Packet containing chunk
+ * @param ptr Index of chunk and related fields in packet
+ * @return Update ID if update was fully assembled and accepted or 0 otherwise
*/
- void handleInboundConfigChunk(const uint64_t inRePacketId,const void *data,unsigned int chunkSize,unsigned int chunkIndex,unsigned int totalSize);
+ uint64_t handleConfigChunk(const Packet &chunk,unsigned int ptr);
/**
* Set netconf failure to 'access denied' -- called in IncomingPacket when controller reports this
@@ -230,7 +197,7 @@ public:
}
/**
- * Set netconf failure to 'not found' -- called by PacketDecider when controller reports this
+ * Set netconf failure to 'not found' -- called by IncomingPacket when controller reports this
*/
inline void setNotFound()
{
@@ -240,33 +207,16 @@ public:
/**
* Causes this network to request an updated configuration from its master node now
- *
- * There is a circuit breaker here to prevent this from being done more often
- * than once per second. This is to prevent things like NETWORK_CONFIG_REFRESH
- * from causing multiple requests.
*/
void requestConfiguration();
/**
* Determine whether this peer is permitted to communicate on this network
- *
- * This also performs certain periodic actions such as pushing renewed
- * credentials to peers or requesting them if not present.
- *
- * @param peer Peer to check
- * @param verb Packet verb
- * @param packetId Packet ID
- * @return True if peer is allowed to communicate on this network
- */
- bool gate(const SharedPtr<Peer> &peer,const Packet::Verb verb,const uint64_t packetId);
-
- /**
- * Check whether this peer is allowed to provide multicast info for this network
*/
- bool gateMulticastGatherReply(const SharedPtr<Peer> &peer,const Packet::Verb verb,const uint64_t packetId);
+ bool gate(const SharedPtr<Peer> &peer);
/**
- * Perform cleanup and possibly save state
+ * Do periodic cleanup and housekeeping tasks
*/
void clean();
@@ -280,46 +230,6 @@ public:
}
/**
- * @return Time of last updated configuration or 0 if none
- */
- inline uint64_t lastConfigUpdate() const throw() { return _lastConfigUpdate; }
-
- /**
- * @return Status of this network
- */
- inline ZT_VirtualNetworkStatus status() const
- {
- Mutex::Lock _l(_lock);
- return _status();
- }
-
- /**
- * @param ec Buffer to fill with externally-visible network configuration
- */
- inline void externalConfig(ZT_VirtualNetworkConfig *ec) const
- {
- Mutex::Lock _l(_lock);
- _externalConfig(ec);
- }
-
- /**
- * Get current network config
- *
- * @return Network configuration (may be a null config if we don't have one yet)
- */
- inline const NetworkConfig &config() const { return _config; }
-
- /**
- * @return True if this network has a valid config
- */
- inline bool hasConfig() const { return (_config); }
-
- /**
- * @return Ethernet MAC address for this network's local interface
- */
- inline const MAC &mac() const { return _mac; }
-
- /**
* Find the node on this network that has this MAC behind it (if any)
*
* @param mac MAC address
@@ -349,44 +259,47 @@ public:
void learnBridgedMulticastGroup(const MulticastGroup &mg,uint64_t now);
/**
- * @param com Certificate of membership
- * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or credential
+ * Validate a credential and learn it if it passes certificate and other checks
*/
- int addCredential(const CertificateOfMembership &com);
+ Membership::AddCredentialResult addCredential(const CertificateOfMembership &com);
/**
- * @param cap Capability
- * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or credential
+ * Validate a credential and learn it if it passes certificate and other checks
*/
- inline int addCredential(const Capability &cap)
+ inline Membership::AddCredentialResult addCredential(const Capability &cap)
{
if (cap.networkId() != _id)
- return -1;
+ return Membership::ADD_REJECTED;
Mutex::Lock _l(_lock);
- return _membership(cap.issuedTo()).addCredential(RR,cap);
+ return _membership(cap.issuedTo()).addCredential(RR,_config,cap);
}
/**
- * @param cap Tag
- * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or credential
+ * Validate a credential and learn it if it passes certificate and other checks
*/
- inline int addCredential(const Tag &tag)
+ inline Membership::AddCredentialResult addCredential(const Tag &tag)
{
if (tag.networkId() != _id)
- return -1;
+ return Membership::ADD_REJECTED;
Mutex::Lock _l(_lock);
- return _membership(tag.issuedTo()).addCredential(RR,tag);
+ return _membership(tag.issuedTo()).addCredential(RR,_config,tag);
}
/**
- * Blacklist COM, tags, and capabilities before this time
+ * Validate a credential and learn it if it passes certificate and other checks
+ */
+ Membership::AddCredentialResult addCredential(const Address &sentFrom,const Revocation &rev);
+
+ /**
+ * Force push credentials (COM, etc.) to a peer now
*
- * @param ts Blacklist cutoff
+ * @param to Destination peer address
+ * @param now Current time
*/
- inline void blacklistBefore(const Address &peerAddress,const uint64_t ts)
+ inline void pushCredentialsNow(const Address &to,const uint64_t now)
{
Mutex::Lock _l(_lock);
- _membership(peerAddress).blacklistBefore(ts);
+ _membership(to).pushCredentials(RR,now,to,_config,-1,true);
}
/**
@@ -399,11 +312,23 @@ public:
void destroy();
/**
- * @return Pointer to user PTR (modifiable user ptr used in API)
+ * Get this network's config for export via the ZT core API
+ *
+ * @param ec Buffer to fill with externally-visible network configuration
+ */
+ inline void externalConfig(ZT_VirtualNetworkConfig *ec) const
+ {
+ Mutex::Lock _l(_lock);
+ _externalConfig(ec);
+ }
+
+ /**
+ * @return Externally usable pointer-to-pointer exported via the core API
*/
inline void **userPtr() throw() { return &_uPtr; }
private:
+ int _setConfiguration(const NetworkConfig &nconf,bool saveToDisk);
ZT_VirtualNetworkStatus _status() const;
void _externalConfig(ZT_VirtualNetworkConfig *ec) const; // assumes _lock is locked
bool _gate(const SharedPtr<Peer> &peer);
@@ -412,25 +337,32 @@ private:
std::vector<MulticastGroup> _allMulticastGroups() const;
Membership &_membership(const Address &a);
- const RuntimeEnvironment *RR;
+ const RuntimeEnvironment *const RR;
void *_uPtr;
- uint64_t _id;
+ const uint64_t _id;
uint64_t _lastAnnouncedMulticastGroupsUpstream;
MAC _mac; // local MAC address
- volatile bool _portInitialized;
+ bool _portInitialized;
std::vector< MulticastGroup > _myMulticastGroups; // multicast groups that we belong to (according to tap)
Hashtable< MulticastGroup,uint64_t > _multicastGroupsBehindMe; // multicast groups that seem to be behind us and when we last saw them (if we are a bridge)
Hashtable< MAC,Address > _remoteBridgeRoutes; // remote addresses where given MACs are reachable (for tracking devices behind remote bridges)
- uint64_t _inboundConfigPacketId;
- std::map<unsigned int,std::string> _inboundConfigChunks;
-
NetworkConfig _config;
- volatile uint64_t _lastConfigUpdate;
- volatile uint64_t _lastRequestedConfiguration;
+ uint64_t _lastConfigUpdate;
+
+ struct _IncomingConfigChunk
+ {
+ uint64_t ts;
+ uint64_t updateId;
+ uint64_t haveChunkIds[ZT_NETWORK_MAX_UPDATE_CHUNKS];
+ unsigned long haveChunks;
+ unsigned long haveBytes;
+ Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> data;
+ };
+ _IncomingConfigChunk _incomingConfigChunks[ZT_NETWORK_MAX_INCOMING_UPDATES];
- volatile bool _destroyed;
+ bool _destroyed;
enum {
NETCONF_FAILURE_NONE,
@@ -438,7 +370,7 @@ private:
NETCONF_FAILURE_NOT_FOUND,
NETCONF_FAILURE_INIT_FAILED
} _netconfFailure;
- volatile int _portError; // return value from port config callback
+ int _portError; // return value from port config callback
Hashtable<Address,Membership> _memberships;
diff --git a/node/NetworkConfig.hpp b/node/NetworkConfig.hpp
index 5ad86855..a548e866 100644
--- a/node/NetworkConfig.hpp
+++ b/node/NetworkConfig.hpp
@@ -77,6 +77,11 @@
#define ZT_NETWORKCONFIG_FLAG_RULES_RESULT_OF_UNSUPPORTED_MATCH 0x0000000000000008ULL
/**
+ * Flag: disable frame compression
+ */
+#define ZT_NETWORKCONFIG_FLAG_DISABLE_COMPRESSION 0x0000000000000010ULL
+
+/**
* Device is an active bridge
*/
#define ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE 0x0000020000000000ULL
@@ -256,6 +261,11 @@ public:
inline bool ndpEmulation() const throw() { return ((this->flags & ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION) != 0); }
/**
+ * @return True if frames should not be compressed
+ */
+ inline bool disableCompression() const throw() { return ((this->flags & ZT_NETWORKCONFIG_FLAG_DISABLE_COMPRESSION) != 0); }
+
+ /**
* @return Network type is public (no access control)
*/
inline bool isPublic() const throw() { return (this->type == ZT_NETWORK_TYPE_PUBLIC); }
diff --git a/node/Node.cpp b/node/Node.cpp
index 2533eeb6..db9b8ea0 100644
--- a/node/Node.cpp
+++ b/node/Node.cpp
@@ -552,31 +552,6 @@ void Node::circuitTestEnd(ZT_CircuitTest *test)
}
}
-void Node::pushNetworkRefresh(uint64_t dest,uint64_t nwid,const uint64_t *blacklistAddresses,const uint64_t *blacklistBeforeTimestamps,unsigned int blacklistCount)
-{
- Packet outp(Address(dest),RR->identity.address(),Packet::VERB_NETWORK_CONFIG_REFRESH);
- outp.append(nwid);
- outp.addSize(2);
- unsigned int c = 0;
- for(unsigned int i=0;i<blacklistCount;++i) {
- if ((outp.size() + 13) >= ZT_PROTO_MAX_PACKET_LENGTH) {
- outp.setAt<uint16_t>(ZT_PACKET_IDX_PAYLOAD + 8,(uint16_t)c);
- RR->sw->send(outp,true);
- outp = Packet(Address(dest),RR->identity.address(),Packet::VERB_NETWORK_CONFIG_REFRESH);
- outp.append(nwid);
- outp.addSize(2);
- c = 0;
- }
- Address(blacklistAddresses[i]).appendTo(outp);
- outp.append(blacklistBeforeTimestamps[i]);
- ++c;
- }
- if (c > 0) {
- outp.setAt<uint16_t>(ZT_PACKET_IDX_PAYLOAD + 8,(uint16_t)c);
- RR->sw->send(outp,true);
- }
-}
-
ZT_ResultCode Node::clusterInit(
unsigned int myId,
const struct sockaddr_storage *zeroTierPhysicalEndpoints,
@@ -973,13 +948,6 @@ void ZT_Node_circuitTestEnd(ZT_Node *node,ZT_CircuitTest *test)
} catch ( ... ) {}
}
-void ZT_Node_pushNetworkRefresh(ZT_Node *node,uint64_t dest,uint64_t nwid,const uint64_t *blacklistAddresses,const uint64_t *blacklistBeforeTimestamps,unsigned int blacklistCount)
-{
- try {
- reinterpret_cast<ZeroTier::Node *>(node)->pushNetworkRefresh(dest,nwid,blacklistAddresses,blacklistBeforeTimestamps,blacklistCount);
- } catch ( ... ) {}
-}
-
enum ZT_ResultCode ZT_Node_clusterInit(
ZT_Node *node,
unsigned int myId,
diff --git a/node/Node.hpp b/node/Node.hpp
index 56869816..11462531 100644
--- a/node/Node.hpp
+++ b/node/Node.hpp
@@ -107,7 +107,6 @@ public:
void setNetconfMaster(void *networkControllerInstance);
ZT_ResultCode circuitTestBegin(ZT_CircuitTest *test,void (*reportCallback)(ZT_Node *,ZT_CircuitTest *,const ZT_CircuitTestReport *));
void circuitTestEnd(ZT_CircuitTest *test);
- void pushNetworkRefresh(uint64_t dest,uint64_t nwid,const uint64_t *blacklistAddresses,const uint64_t *blacklistBeforeTimestamps,unsigned int blacklistCount);
ZT_ResultCode clusterInit(
unsigned int myId,
const struct sockaddr_storage *zeroTierPhysicalEndpoints,
diff --git a/node/OutboundMulticast.cpp b/node/OutboundMulticast.cpp
index 6e811581..2f6bf986 100644
--- a/node/OutboundMulticast.cpp
+++ b/node/OutboundMulticast.cpp
@@ -31,6 +31,7 @@ void OutboundMulticast::init(
const RuntimeEnvironment *RR,
uint64_t timestamp,
uint64_t nwid,
+ bool disableCompression,
unsigned int limit,
unsigned int gatherLimit,
const MAC &src,
@@ -78,7 +79,8 @@ void OutboundMulticast::init(
_packet.append((uint32_t)dest.adi());
_packet.append((uint16_t)etherType);
_packet.append(payload,_frameLen);
- _packet.compress();
+ if (!disableCompression)
+ _packet.compress();
memcpy(_frameData,payload,_frameLen);
}
diff --git a/node/OutboundMulticast.hpp b/node/OutboundMulticast.hpp
index 0ded8baf..6370d0d7 100644
--- a/node/OutboundMulticast.hpp
+++ b/node/OutboundMulticast.hpp
@@ -56,6 +56,7 @@ public:
* @param RR Runtime environment
* @param timestamp Creation time
* @param nwid Network ID
+ * @param disableCompression Disable compression of frame payload
* @param limit Multicast limit for desired number of packets to send
* @param gatherLimit Number to lazily/implicitly gather with this frame or 0 for none
* @param src Source MAC address of frame or NULL to imply compute from sender ZT address
@@ -69,6 +70,7 @@ public:
const RuntimeEnvironment *RR,
uint64_t timestamp,
uint64_t nwid,
+ bool disableCompression,
unsigned int limit,
unsigned int gatherLimit,
const MAC &src,
diff --git a/node/Packet.cpp b/node/Packet.cpp
index 9ab68968..bdbc5fe4 100644
--- a/node/Packet.cpp
+++ b/node/Packet.cpp
@@ -40,13 +40,12 @@ const char *Packet::verbString(Verb v)
case VERB_MULTICAST_LIKE: return "MULTICAST_LIKE";
case VERB_NETWORK_CREDENTIALS: return "NETWORK_CREDENTIALS";
case VERB_NETWORK_CONFIG_REQUEST: return "NETWORK_CONFIG_REQUEST";
- case VERB_NETWORK_CONFIG_REFRESH: return "NETWORK_CONFIG_REFRESH";
+ case VERB_NETWORK_CONFIG: return "NETWORK_CONFIG";
case VERB_MULTICAST_GATHER: return "MULTICAST_GATHER";
case VERB_MULTICAST_FRAME: return "MULTICAST_FRAME";
case VERB_PUSH_DIRECT_PATHS: return "PUSH_DIRECT_PATHS";
case VERB_CIRCUIT_TEST: return "CIRCUIT_TEST";
case VERB_CIRCUIT_TEST_REPORT: return "CIRCUIT_TEST_REPORT";
- case VERB_REQUEST_PROOF_OF_WORK: return "REQUEST_PROOF_OF_WORK";
case VERB_USER_MESSAGE: return "USER_MESSAGE";
}
return "(unknown)";
diff --git a/node/Packet.hpp b/node/Packet.hpp
index 2ca73a84..a8738884 100644
--- a/node/Packet.hpp
+++ b/node/Packet.hpp
@@ -655,15 +655,29 @@ public:
*
* Flags:
* 0x01 - Certificate of network membership attached (DEPRECATED)
- * 0x02 - This is a TEE'd or REDIRECT'ed packet
- * 0x04 - TEE/REDIRECT'ed packet is from inbound side
- *
- * An extended frame carries full MAC addressing, making them a
- * superset of VERB_FRAME. They're used for bridging or when we
- * want to attach a certificate since FRAME does not support that.
- *
- * ERROR may be generated if a membership certificate is needed for a
- * closed network. Payload will be network ID.
+ * 0x02 - Most significant bit of subtype (see below)
+ * 0x04 - Middle bit of subtype (see below)
+ * 0x08 - Least significant bit of subtype (see below)
+ * 0x10 - ACK requested in the form of OK(EXT_FRAME)
+ *
+ * Subtypes (0..7):
+ * 0x0 - Normal frame (bridging can be determined by checking MAC)
+ * 0x1 - TEEd outbound frame
+ * 0x2 - REDIRECTed outbound frame
+ * 0x3 - WATCHed outbound frame (TEE with ACK, ACK bit also set)
+ * 0x4 - TEEd inbound frame
+ * 0x5 - REDIRECTed inbound frame
+ * 0x6 - WATCHed inbound frame
+ * 0x7 - (reserved for future use)
+ *
+ * An extended frame carries full MAC addressing, making it a
+ * superset of VERB_FRAME. It is used for bridged traffic,
+ * redirected or observed traffic via rules, and can in theory
+ * be used for multicast though MULTICAST_FRAME exists for that
+ * purpose and has additional options and capabilities.
+ *
+ * OK payload (if ACK flag is set):
+ * <[8] 64-bit network ID>
*/
VERB_EXT_FRAME = 0x07,
@@ -698,7 +712,7 @@ public:
VERB_MULTICAST_LIKE = 0x09,
/**
- * Network membership credential push:
+ * Network credentials push:
* <[...] serialized certificate of membership>
* [<[...] additional certificates of membership>]
* <[1] 0x00, null byte marking end of COM array>
@@ -706,12 +720,15 @@ public:
* <[...] one or more serialized Capability>
* <[2] 16-bit number of tags>
* <[...] one or more serialized Tags>
+ * <[2] 16-bit number of revocations>
+ * <[...] one or more serialized Revocations>
*
- * This is sent in response to ERROR_NEED_MEMBERSHIP_CERTIFICATE and may
- * be pushed at any other time to keep exchanged certificates up to date.
+ * This can be sent by anyone at any time to push network credentials.
+ * These will of course only be accepted if they are properly signed.
+ * Credentials can be for any number of networks.
*
- * COMs and other credentials need not be for the same network, since each
- * includes its own network ID and signature.
+ * The use of a zero byte to terminate the COM section is for legacy
+ * backward compatiblity. Newer fields are prefixed with a length.
*
* OK/ERROR are not generated.
*/
@@ -726,15 +743,38 @@ public:
* <[8] 64-bit timestamp of netconf we currently have>
*
* This message requests network configuration from a node capable of
- * providing it. If the optional revision is included, a response is
- * only generated if there is a newer network configuration available.
+ * providing it.
+ *
+ * Respones to this are always whole configs intended for the recipient.
+ * For patches and other updates a NETWORK_CONFIG is sent instead.
*
+ * It would be valid and correct as of 1.2.0 to use NETWORK_CONFIG always,
+ * but OK(NTEWORK_CONFIG_REQUEST) should be sent for compatibility.
+ *
* OK response payload:
* <[8] 64-bit network ID>
* <[2] 16-bit length of network configuration dictionary chunk>
* <[...] network configuration dictionary (may be incomplete)>
+ * [ ... end of legacy single chunk response ... ]
+ * <[1] 8-bit flags>
+ * <[8] 64-bit config update ID (should never be 0)>
* <[4] 32-bit total length of assembled dictionary>
- * <[4] 32-bit index of chunk in this reply>
+ * <[4] 32-bit index of chunk>
+ * [ ... end signed portion ... ]
+ * <[1] 8-bit chunk signature type>
+ * <[2] 16-bit length of chunk signature>
+ * <[...] chunk signature>
+ *
+ * The chunk signature signs the entire payload of the OK response.
+ * Currently only one signature type is supported: ed25519 (1).
+ *
+ * Each config chunk is signed to prevent memory exhaustion or
+ * traffic crowding DOS attacks against config fragment assembly.
+ *
+ * If the packet is from the network controller it is permitted to end
+ * before the config update ID or other chunking related or signature
+ * fields. This is to support older controllers that don't include
+ * these fields and may be removed in the future.
*
* ERROR response payload:
* <[8] 64-bit network ID>
@@ -742,25 +782,37 @@ public:
VERB_NETWORK_CONFIG_REQUEST = 0x0b,
/**
- * Network configuration update push:
- * <[8] network ID to refresh>
- * <[2] 16-bit number of address/timestamp pairs to blacklist>
- * [<[5] ZeroTier address of peer being revoked>]
- * [<[8] blacklist credentials older than this timestamp>]
- * [<[...] additional address/timestamp pairs>]
- *
- * This can be sent by a network controller to both request that a network
- * config be updated and push instantaneous revocations of specific peers
- * or peer credentials.
- *
- * Specific revocations can be pushed to blacklist a specific peer's
- * credentials (COM, tags, and capabilities) if older than a specified
- * timestamp. This can be used to accomplish expedited revocation of
- * a peer's access to things on a network or to the network itself among
- * those other peers that can currently reach the controller. This is not
- * the only mechanism for revocation of course, but it's the fastest.
+ * Network configuration data push:
+ * <[8] 64-bit network ID>
+ * <[2] 16-bit length of network configuration dictionary chunk>
+ * <[...] network configuration dictionary (may be incomplete)>
+ * <[1] 8-bit flags>
+ * <[8] 64-bit config update ID (should never be 0)>
+ * <[4] 32-bit total length of assembled dictionary>
+ * <[4] 32-bit index of chunk>
+ * [ ... end signed portion ... ]
+ * <[1] 8-bit chunk signature type>
+ * <[2] 16-bit length of chunk signature>
+ * <[...] chunk signature>
+ *
+ * This is a direct push variant for network config updates. It otherwise
+ * carries the same payload as OK(NETWORK_CONFIG_REQUEST) and has the same
+ * semantics.
+ *
+ * The legacy mode missing the additional chunking fields is not supported
+ * here.
+ *
+ * Flags:
+ * 0x01 - Use fast propagation
+ *
+ * An OK should be sent if the config is successfully received and
+ * accepted.
+ *
+ * OK payload:
+ * <[8] 64-bit network ID>
+ * <[8] 64-bit config update ID>
*/
- VERB_NETWORK_CONFIG_REFRESH = 0x0c,
+ VERB_NETWORK_CONFIG = 0x0c,
/**
* Request endpoints for multicast distribution:
@@ -839,8 +891,6 @@ public:
*/
VERB_MULTICAST_FRAME = 0x0e,
- // 0x0f is reserved for an old deprecated message
-
/**
* Push of potential endpoints for direct communication:
* <[2] 16-bit number of paths>
@@ -993,49 +1043,6 @@ public:
VERB_CIRCUIT_TEST_REPORT = 0x12,
/**
- * Request proof of work:
- * <[1] 8-bit proof of work type>
- * <[1] 8-bit proof of work difficulty>
- * <[2] 16-bit length of proof of work challenge>
- * <[...] proof of work challenge>
- *
- * This requests that a peer perform a proof of work calucation. It can be
- * sent by highly trusted peers (e.g. root servers, network controllers)
- * under suspected denial of service conditions in an attempt to filter
- * out "non-serious" peers and remain responsive to those proving their
- * intent to actually communicate.
- *
- * If the peer obliges to perform the work, it does so and responds with
- * an OK containing the result. Otherwise it may ignore the message or
- * response with an ERROR_INVALID_REQUEST or ERROR_UNSUPPORTED_OPERATION.
- *
- * Proof of work type IDs:
- * 0x01 - Salsa20/12+SHA512 hashcash function
- *
- * Salsa20/12+SHA512 is based on the following composite hash function:
- *
- * (1) Compute SHA512(candidate)
- * (2) Use the first 256 bits of the result of #1 as a key to encrypt
- * 131072 zero bytes with Salsa20/12 (with a zero IV).
- * (3) Compute SHA512(the result of step #2)
- * (4) Accept this candiate if the first [difficulty] bits of the result
- * from step #3 are zero. Otherwise generate a new candidate and try
- * again.
- *
- * This is performed repeatedly on candidates generated by appending the
- * supplied challenge to an arbitrary nonce until a valid candidate
- * is found. This chosen prepended nonce is then returned as the result
- * in OK.
- *
- * OK payload:
- * <[2] 16-bit length of result>
- * <[...] computed proof of work>
- *
- * ERROR has no payload.
- */
- VERB_REQUEST_PROOF_OF_WORK = 0x13,
-
- /**
* A message with arbitrary user-definable content:
* <[8] 64-bit arbitrary message type ID>
* [<[...] message payload>]
diff --git a/node/Peer.cpp b/node/Peer.cpp
index d742964a..87882dad 100644
--- a/node/Peer.cpp
+++ b/node/Peer.cpp
@@ -50,6 +50,7 @@ Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Ident
_lastWhoisRequestReceived(0),
_lastEchoRequestReceived(0),
_lastComRequestReceived(0),
+ _lastComRequestSent(0),
_lastCredentialsReceived(0),
_lastTrustEstablishedPacketReceived(0),
RR(renv),
diff --git a/node/Peer.hpp b/node/Peer.hpp
index c5ef43ed..d0589ccf 100644
--- a/node/Peer.hpp
+++ b/node/Peer.hpp
@@ -395,9 +395,9 @@ public:
}
/**
- * Rate gate requests for network COM
+ * Rate gate incoming requests for network COM
*/
- inline bool rateGateComRequest(const uint64_t now)
+ inline bool rateGateIncomingComRequest(const uint64_t now)
{
if ((now - _lastComRequestReceived) >= ZT_PEER_GENERAL_RATE_LIMIT) {
_lastComRequestReceived = now;
@@ -407,6 +407,18 @@ public:
}
/**
+ * Rate gate outgoing requests for network COM
+ */
+ inline bool rateGateOutgoingComRequest(const uint64_t now)
+ {
+ if ((now - _lastComRequestSent) >= ZT_PEER_GENERAL_RATE_LIMIT) {
+ _lastComRequestSent = now;
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Find a common set of addresses by which two peers can link, if any
*
* @param a Peer A
@@ -465,6 +477,7 @@ private:
uint64_t _lastWhoisRequestReceived;
uint64_t _lastEchoRequestReceived;
uint64_t _lastComRequestReceived;
+ uint64_t _lastComRequestSent;
uint64_t _lastCredentialsReceived;
uint64_t _lastTrustEstablishedPacketReceived;
const RuntimeEnvironment *RR;
diff --git a/node/Revocation.cpp b/node/Revocation.cpp
new file mode 100644
index 00000000..420476a4
--- /dev/null
+++ b/node/Revocation.cpp
@@ -0,0 +1,46 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "Revocation.hpp"
+#include "RuntimeEnvironment.hpp"
+#include "Identity.hpp"
+#include "Topology.hpp"
+#include "Switch.hpp"
+#include "Network.hpp"
+
+namespace ZeroTier {
+
+int Revocation::verify(const RuntimeEnvironment *RR) const
+{
+ if ((!_signedBy)||(_signedBy != Network::controllerFor(_networkId)))
+ return -1;
+ const Identity id(RR->topology->getIdentity(_signedBy));
+ if (!id) {
+ RR->sw->requestWhois(_signedBy);
+ return 1;
+ }
+ try {
+ Buffer<sizeof(Revocation) + 64> tmp;
+ this->serialize(tmp,true);
+ return (id.verify(tmp.data(),tmp.size(),_signature) ? 0 : -1);
+ } catch ( ... ) {
+ return -1;
+ }
+}
+
+} // namespace ZeroTier
diff --git a/node/Revocation.hpp b/node/Revocation.hpp
new file mode 100644
index 00000000..18916985
--- /dev/null
+++ b/node/Revocation.hpp
@@ -0,0 +1,178 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ZT_REVOCATION_HPP
+#define ZT_REVOCATION_HPP
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "Constants.hpp"
+#include "../include/ZeroTierOne.h"
+#include "Address.hpp"
+#include "C25519.hpp"
+#include "Utils.hpp"
+#include "Buffer.hpp"
+#include "Identity.hpp"
+
+/**
+ * Flag: fast propagation via rumor mill algorithm
+ */
+#define ZT_REVOCATION_FLAG_FAST_PROPAGATE 0x1ULL
+
+namespace ZeroTier {
+
+class RuntimeEnvironment;
+
+/**
+ * Revocation certificate to instantaneously revoke a COM, capability, or tag
+ */
+class Revocation
+{
+public:
+ enum CredentialType
+ {
+ CREDENTIAL_TYPE_ALL = 0,
+ CREDENTIAL_TYPE_COM = 1,
+ CREDENTIAL_TYPE_CAPABILITY = 2,
+ CREDENTIAL_TYPE_TAG = 3
+ };
+
+ Revocation()
+ {
+ memset(this,0,sizeof(Revocation));
+ }
+
+ Revocation(const uint64_t i,const uint64_t nwid,const uint64_t cid,const uint64_t thr,const uint64_t fl,const Address &tgt,const CredentialType ct) :
+ _id(i),
+ _networkId(nwid),
+ _credentialId(cid),
+ _threshold(thr),
+ _flags(fl),
+ _target(tgt),
+ _signedBy(),
+ _type(ct) {}
+
+ inline uint64_t id() const { return _id; }
+ inline uint64_t networkId() const { return _networkId; }
+ inline uint64_t credentialId() const { return _credentialId; }
+ inline uint64_t threshold() const { return _threshold; }
+ inline const Address &target() const { return _target; }
+ inline const Address &signer() const { return _signedBy; }
+ inline CredentialType type() const { return _type; }
+
+ inline bool fastPropagate() const { return ((_flags & ZT_REVOCATION_FLAG_FAST_PROPAGATE) != 0); }
+
+ /**
+ * @param signer Signing identity, must have private key
+ * @return True if signature was successful
+ */
+ inline bool sign(const Identity &signer)
+ {
+ if (signer.hasPrivate()) {
+ Buffer<sizeof(Revocation) + 64> tmp;
+ this->serialize(tmp,true);
+ _signedBy = signer.address();
+ _signature = signer.sign(tmp.data(),tmp.size());
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Verify this revocation's signature
+ *
+ * @param RR Runtime environment to provide for peer lookup, etc.
+ * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or chain
+ */
+ int verify(const RuntimeEnvironment *RR) const;
+
+ template<unsigned int C>
+ inline void serialize(Buffer<C> &b,const bool forSign = false) const
+ {
+ if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
+
+ b.append(_id);
+ b.append(_networkId);
+ b.append(_credentialId);
+ b.append(_threshold);
+ b.append(_flags);
+ _target.appendTo(b);
+ _signedBy.appendTo(b);
+ b.append((uint8_t)_type);
+
+ if (!forSign) {
+ b.append((uint8_t)1); // 1 == Ed25519 signature
+ b.append((uint16_t)ZT_C25519_SIGNATURE_LEN);
+ b.append(_signature.data,ZT_C25519_SIGNATURE_LEN);
+ }
+
+ // This is the size of any additional fields, currently 0.
+ b.append((uint16_t)0);
+
+ if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
+ }
+
+ template<unsigned int C>
+ inline unsigned int deserialize(const Buffer<C> &b,unsigned int startAt = 0)
+ {
+ memset(this,0,sizeof(Revocation));
+
+ unsigned int p = startAt;
+
+ _id = b.template at<uint64_t>(p); p += 8;
+ _networkId = b.template at<uint64_t>(p); p += 8;
+ _credentialId = b.template at<uint64_t>(p); p += 8;
+ _threshold = b.template at<uint64_t>(p); p += 8;
+ _flags = b.template at<uint64_t>(p); p += 8;
+ _target.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH;
+ _signedBy.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH;
+ _type = (CredentialType)b[p++];
+
+ if (b[p++] == 1) {
+ if (b.template at<uint16_t>(p) == ZT_C25519_SIGNATURE_LEN) {
+ p += 2;
+ memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN);
+ p += ZT_C25519_SIGNATURE_LEN;
+ } else throw std::runtime_error("invalid signature");
+ }
+
+ p += 2 + b.template at<uint16_t>(p);
+ if (p > b.size())
+ throw std::runtime_error("extended field overflow");
+
+ return (p - startAt);
+ }
+
+private:
+ uint64_t _id;
+ uint64_t _networkId;
+ uint64_t _credentialId;
+ uint64_t _threshold;
+ uint64_t _flags;
+ Address _target;
+ Address _signedBy;
+ CredentialType _type;
+ C25519::Signature _signature;
+};
+
+} // namespace ZeroTier
+
+#endif
diff --git a/node/Switch.cpp b/node/Switch.cpp
index e3d57835..82b13483 100644
--- a/node/Switch.cpp
+++ b/node/Switch.cpp
@@ -334,26 +334,16 @@ void Switch::onLocalEthernet(const SharedPtr<Network> &network,const MAC &from,c
if (!network->hasConfig())
return;
- // Sanity check -- bridge loop? OS problem?
- if (to == network->mac())
- return;
-
// Check if this packet is from someone other than the tap -- i.e. bridged in
- bool fromBridged = false;
- if (from != network->mac()) {
+ bool fromBridged;
+ if ((fromBridged = (from != network->mac()))) {
if (!network->config().permitsBridging(RR->identity.address())) {
TRACE("%.16llx: %s -> %s %s not forwarded, bridging disabled or this peer not a bridge",network->id(),from.toString().c_str(),to.toString().c_str(),etherTypeName(etherType));
return;
}
- fromBridged = true;
}
if (to.isMulticast()) {
- if (network->config().multicastLimit == 0) {
- TRACE("%.16llx: dropped multicast: not allowed on network",network->id());
- return;
- }
-
MulticastGroup multicastGroup(to,0);
if (to.isBroadcast()) {
@@ -457,6 +447,12 @@ void Switch::onLocalEthernet(const SharedPtr<Network> &network,const MAC &from,c
} // else no NDP emulation
}
+ // Check this after NDP emulation, since that has to be allowed in exactly this case
+ if (network->config().multicastLimit == 0) {
+ TRACE("%.16llx: dropped multicast: not allowed on network",network->id());
+ return;
+ }
+
/* Learn multicast groups for bridged-in hosts.
* Note that some OSes, most notably Linux, do this for you by learning
* multicast addresses on bridge interfaces and subscribing each slave.
@@ -476,12 +472,16 @@ void Switch::onLocalEthernet(const SharedPtr<Network> &network,const MAC &from,c
network->config().multicastLimit,
RR->node->now(),
network->id(),
+ network->config().disableCompression(),
network->config().activeBridges(),
multicastGroup,
(fromBridged) ? from : MAC(),
etherType,
data,
len);
+ } else if (to == network->mac()) {
+ // Destination is this node, so just reinject it
+ RR->node->putFrame(network->id(),network->userPtr(),from,to,etherType,vlanId,data,len);
} else if (to[0] == MAC::firstOctetForNetwork(network->id())) {
// Destination is another ZeroTier peer on the same network
@@ -501,14 +501,16 @@ void Switch::onLocalEthernet(const SharedPtr<Network> &network,const MAC &from,c
from.appendTo(outp);
outp.append((uint16_t)etherType);
outp.append(data,len);
- outp.compress();
+ if (!network->config().disableCompression())
+ outp.compress();
send(outp,true);
} else {
Packet outp(toZT,RR->identity.address(),Packet::VERB_FRAME);
outp.append(network->id());
outp.append((uint16_t)etherType);
outp.append(data,len);
- outp.compress();
+ if (!network->config().disableCompression())
+ outp.compress();
send(outp,true);
}
@@ -565,7 +567,8 @@ void Switch::onLocalEthernet(const SharedPtr<Network> &network,const MAC &from,c
from.appendTo(outp);
outp.append((uint16_t)etherType);
outp.append(data,len);
- outp.compress();
+ if (!network->config().disableCompression())
+ outp.compress();
send(outp,true);
} else {
TRACE("%.16llx: %s -> %s %s packet not sent: filterOutgoingPacket() returned false",network->id(),from.toString().c_str(),to.toString().c_str(),etherTypeName(etherType));
diff --git a/node/Tag.cpp b/node/Tag.cpp
index 352ecde8..eb4026bc 100644
--- a/node/Tag.cpp
+++ b/node/Tag.cpp
@@ -27,7 +27,7 @@ namespace ZeroTier {
int Tag::verify(const RuntimeEnvironment *RR) const
{
- if ((!_signedBy)||(_signedBy != Network::controllerFor(_nwid)))
+ if ((!_signedBy)||(_signedBy != Network::controllerFor(_networkId)))
return -1;
const Identity id(RR->topology->getIdentity(_signedBy));
if (!id) {
diff --git a/node/Tag.hpp b/node/Tag.hpp
index 14cc3a5d..97228157 100644
--- a/node/Tag.hpp
+++ b/node/Tag.hpp
@@ -67,7 +67,7 @@ public:
* @param value Tag value
*/
Tag(const uint64_t nwid,const uint64_t ts,const Address &issuedTo,const uint32_t id,const uint32_t value) :
- _nwid(nwid),
+ _networkId(nwid),
_ts(ts),
_id(id),
_value(value),
@@ -76,7 +76,7 @@ public:
{
}
- inline uint64_t networkId() const { return _nwid; }
+ inline uint64_t networkId() const { return _networkId; }
inline uint64_t timestamp() const { return _ts; }
inline uint32_t id() const { return _id; }
inline const uint32_t &value() const { return _value; }
@@ -91,13 +91,13 @@ public:
*/
inline bool sign(const Identity &signer)
{
- try {
- Buffer<(sizeof(Tag) * 2)> tmp;
+ if (signer.hasPrivate()) {
+ Buffer<sizeof(Tag) + 64> tmp;
_signedBy = signer.address();
this->serialize(tmp,true);
_signature = signer.sign(tmp.data(),tmp.size());
return true;
- } catch ( ... ) {}
+ }
return false;
}
@@ -115,7 +115,7 @@ public:
if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
// These are the same between Tag and Capability
- b.append(_nwid);
+ b.append(_networkId);
b.append(_ts);
b.append(_id);
@@ -140,7 +140,7 @@ public:
unsigned int p = startAt;
// These are the same between Tag and Capability
- _nwid = b.template at<uint64_t>(p); p += 8;
+ _networkId = b.template at<uint64_t>(p); p += 8;
_ts = b.template at<uint64_t>(p); p += 8;
_id = b.template at<uint32_t>(p); p += 4;
@@ -168,8 +168,22 @@ public:
inline bool operator==(const Tag &t) const { return (memcmp(this,&t,sizeof(Tag)) == 0); }
inline bool operator!=(const Tag &t) const { return (memcmp(this,&t,sizeof(Tag)) != 0); }
+ // For searching sorted arrays or lists of Tags by ID
+ struct IdComparePredicate
+ {
+ inline bool operator()(const Tag &a,const Tag &b) const { return (a.id() < b.id()); }
+ inline bool operator()(const uint32_t a,const Tag &b) const { return (a < b.id()); }
+ inline bool operator()(const Tag &a,const uint32_t b) const { return (a.id() < b); }
+ inline bool operator()(const Tag *a,const Tag *b) const { return (a->id() < b->id()); }
+ inline bool operator()(const Tag *a,const Tag &b) const { return (a->id() < b.id()); }
+ inline bool operator()(const Tag &a,const Tag *b) const { return (a.id() < b->id()); }
+ inline bool operator()(const uint32_t a,const Tag *b) const { return (a < b->id()); }
+ inline bool operator()(const Tag *a,const uint32_t b) const { return (a->id() < b); }
+ inline bool operator()(const uint32_t a,const uint32_t b) const { return (a < b); }
+ };
+
private:
- uint64_t _nwid;
+ uint64_t _networkId;
uint64_t _ts;
uint32_t _id;
uint32_t _value;
diff --git a/objects.mk b/objects.mk
index f92a907e..5738e769 100644
--- a/objects.mk
+++ b/objects.mk
@@ -17,6 +17,7 @@ OBJS=\
node/Path.o \
node/Peer.o \
node/Poly1305.o \
+ node/Revocation.o \
node/Salsa20.o \
node/SelfAwareness.o \
node/SHA512.o \
diff --git a/one.cpp b/one.cpp
index 6ad5c8e6..79e8caf8 100644
--- a/one.cpp
+++ b/one.cpp
@@ -44,6 +44,10 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
+
+#ifdef __linux__
+#include "osdep/LinuxDropPrivileges.hpp"
+#endif
#endif
#include <string>
@@ -900,7 +904,7 @@ static void printHelp(const char *cn,FILE *out)
fprintf(out,"Available switches:" ZT_EOL_S);
fprintf(out," -h - Display this help" ZT_EOL_S);
fprintf(out," -v - Show version" ZT_EOL_S);
- fprintf(out," -U - Run as unprivileged user (skip privilege check)" ZT_EOL_S);
+ fprintf(out," -U - Skip privilege check and do not attempt to drop privileges" ZT_EOL_S);
fprintf(out," -p<port> - Port for UDP and TCP/HTTP (default: 9993, 0 for random)" ZT_EOL_S);
#ifdef __UNIX_LIKE__
@@ -1141,6 +1145,14 @@ int main(int argc,char **argv)
#endif // __WINDOWS__
#ifdef __UNIX_LIKE__
+
+#ifndef ZT_ONE_RUN_AS_ROOT
+#ifdef __linux__
+ if (!skipRootCheck)
+ dropPrivileges(homeDir);
+#endif
+#endif
+
std::string pidPath(homeDir + ZT_PATH_SEPARATOR_S + ZT_PID_PATH);
{
// Write .pid file to home folder
diff --git a/osdep/LinuxDropPrivileges.cpp b/osdep/LinuxDropPrivileges.cpp
new file mode 100644
index 00000000..dab85bd8
--- /dev/null
+++ b/osdep/LinuxDropPrivileges.cpp
@@ -0,0 +1,164 @@
+#include "LinuxDropPrivileges.hpp"
+#include <linux/capability.h>
+#include <linux/securebits.h>
+#include <sys/prctl.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+namespace ZeroTier {
+
+#ifndef PR_CAP_AMBIENT
+// if we are on old libc, dropPrivileges is nop
+void dropPrivileges(std::string homeDir) {}
+
+#else
+
+const char* TARGET_USER_NAME = "zerotier-one";
+
+struct cap_header_struct {
+ __u32 version;
+ int pid;
+};
+
+struct cap_data_struct {
+ __u32 effective;
+ __u32 permitted;
+ __u32 inheritable;
+};
+
+// libc doesn't export capset, it is instead located in libcap
+// We ignore libcap and call it manually.
+
+int capset(cap_header_struct* hdrp, cap_data_struct* datap) {
+ return syscall(SYS_capset, hdrp, datap);
+}
+
+void notDropping(std::string homeDir) {
+ struct stat buf;
+ if (lstat(homeDir.c_str(), &buf) < 0) {
+ if (buf.st_uid != 0 || buf.st_gid != 0) {
+ fprintf(stderr, "ERROR: failed to drop privileges. Refusing to run as root, because %s was already used in nonprivileged mode.\n", homeDir.c_str());
+ exit(1);
+ }
+ }
+ fprintf(stderr, "WARNING: failed to drop privileges, running as root\n");
+}
+
+int setCapabilities(int flags) {
+ cap_header_struct capheader = {_LINUX_CAPABILITY_VERSION_1, 0};
+ cap_data_struct capdata;
+ capdata.inheritable = capdata.permitted = capdata.effective = flags;
+ return capset(&capheader, &capdata);
+}
+
+void createOwnedHomedir(std::string homeDir, struct passwd* targetUser) {
+ struct stat buf;
+ if (lstat(homeDir.c_str(), &buf) < 0) {
+ if (errno == ENOENT) {
+ mkdir(homeDir.c_str(), 0755);
+ } else {
+ perror("cannot access home directory");
+ exit(1);
+ }
+ }
+
+ if (buf.st_uid != 0 || buf.st_gid != 0) {
+ // should be already owned by zerotier-one
+ if (targetUser->pw_uid != buf.st_uid) {
+ fprintf(stderr, "ERROR: %s not owned by zerotier-one or root\n", homeDir.c_str());
+ exit(1);
+ }
+ return;
+ }
+
+ // Change homedir owner to zerotier-one user. This is safe, because this directory is writable only by root, so no one could have created malicious hardlink.
+ long p = (long)fork();
+ int exitcode = -1;
+ if (p > 0) {
+ waitpid(p, &exitcode, 0);
+ } else if (p == 0) {
+ std::string ownerString = std::to_string(targetUser->pw_uid) + ":" + std::to_string(targetUser->pw_gid);
+ execlp("chown", "chown", "-R", ownerString.c_str(), "--", homeDir.c_str(), NULL);
+ _exit(-1);
+ }
+
+ if (exitcode != 0) {
+ fprintf(stderr, "failed to change owner of %s to %s\n", homeDir.c_str(), targetUser->pw_name);
+ exit(1);
+ }
+}
+
+void dropPrivileges(std::string homeDir) {
+ // dropPrivileges switches to zerotier-one user while retaining CAP_NET_ADMIN
+ // and CAP_NET_RAW capabilities.
+ struct passwd* targetUser = getpwnam(TARGET_USER_NAME);
+ if (targetUser == NULL) {
+ // zerotier-one user not configured by package
+ return;
+ }
+
+ if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_NET_RAW, 0, 0) < 0) {
+ // Kernel has no support for ambient capabilities.
+ notDropping(homeDir);
+ return;
+ }
+
+ if (prctl(PR_SET_SECUREBITS, SECBIT_KEEP_CAPS | SECBIT_NOROOT) < 0) {
+ notDropping(homeDir);
+ return;
+ }
+
+ createOwnedHomedir(homeDir, targetUser);
+
+ if (setCapabilities((1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | (1 << CAP_SETUID) | (1 << CAP_SETGID)) < 0) {
+ fprintf(stderr, "ERROR: failed to set capabilities (not running as real root?)\n");
+ exit(1);
+ }
+
+ int oldDumpable = prctl(PR_GET_DUMPABLE);
+
+ if (prctl(PR_SET_DUMPABLE, 0) < 0) {
+ // Disable ptracing. Otherwise there is a small window when previous
+ // compromised ZeroTier process could ptrace us, when we still have CAP_SETUID.
+ // (this is mitigated anyway on most distros by ptrace_scope=1)
+ perror("prctl(PR_SET_DUMPABLE)");
+ exit(1);
+ }
+
+ if (setgid(targetUser->pw_gid) < 0) {
+ perror("setgid");
+ exit(1);
+ }
+ if (setuid(targetUser->pw_uid) < 0) {
+ perror("setuid");
+ exit(1);
+ }
+
+ if (setCapabilities((1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW)) < 0) {
+ perror("could not drop capabilities after setuid");
+ exit(1);
+ }
+
+ if (prctl(PR_SET_DUMPABLE, oldDumpable) < 0) {
+ perror("could not restore dumpable flag");
+ exit(1);
+ }
+
+ if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, CAP_NET_ADMIN, 0, 0) < 0) {
+ perror("could not raise ambient CAP_NET_ADMIN");
+ exit(1);
+ }
+
+ if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, CAP_NET_RAW, 0, 0) < 0) {
+ perror("could not raise ambient CAP_NET_RAW");
+ exit(1);
+ }
+}
+
+#endif
+}
diff --git a/osdep/LinuxDropPrivileges.hpp b/osdep/LinuxDropPrivileges.hpp
new file mode 100644
index 00000000..111f682e
--- /dev/null
+++ b/osdep/LinuxDropPrivileges.hpp
@@ -0,0 +1,9 @@
+#ifndef ZT_LINUXDROPPRIVILEGES_HPP
+#define ZT_LINUXDROPPRIVILEGES_HPP
+#include <string>
+
+namespace ZeroTier {
+ void dropPrivileges(std::string homeDir);
+}
+
+#endif
diff --git a/osdep/ManagedRoute.cpp b/osdep/ManagedRoute.cpp
index 127f1b7d..1fc6c78e 100644
--- a/osdep/ManagedRoute.cpp
+++ b/osdep/ManagedRoute.cpp
@@ -524,11 +524,11 @@ void ManagedRoute::remove()
#endif // __BSD__ ------------------------------------------------------------
#ifdef __LINUX__ // ----------------------------------------------------------
- _routeCmd("del",*r,_via,(_via) ? (const char *)0 : _device);
+ _routeCmd("del",r->first,_via,(_via) ? (const char *)0 : _device);
#endif // __LINUX__ ----------------------------------------------------------
#ifdef __WINDOWS__ // --------------------------------------------------------
- _winRoute(true,interfaceLuid,interfaceIndex,*r,_via);
+ _winRoute(true,interfaceLuid,interfaceIndex,r->first,_via);
#endif // __WINDOWS__ --------------------------------------------------------
}
diff --git a/osdep/NeighborDiscovery.cpp b/osdep/NeighborDiscovery.cpp
new file mode 100644
index 00000000..68b67794
--- /dev/null
+++ b/osdep/NeighborDiscovery.cpp
@@ -0,0 +1,264 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "NeighborDiscovery.hpp"
+#include "OSUtils.hpp"
+
+#include "../include/ZeroTierOne.h"
+
+#include <assert.h>
+
+namespace ZeroTier {
+
+uint16_t calc_checksum (uint16_t *addr, int len)
+{
+ int count = len;
+ register uint32_t sum = 0;
+ uint16_t answer = 0;
+
+ // Sum up 2-byte values until none or only one byte left.
+ while (count > 1) {
+ sum += *(addr++);
+ count -= 2;
+ }
+
+ // Add left-over byte, if any.
+ if (count > 0) {
+ sum += *(uint8_t *) addr;
+ }
+
+ // Fold 32-bit sum into 16 bits; we lose information by doing this,
+ // increasing the chances of a collision.
+ // sum = (lower 16 bits) + (upper 16 bits shifted right 16 bits)
+ while (sum >> 16) {
+ sum = (sum & 0xffff) + (sum >> 16);
+ }
+
+ // Checksum is one's compliment of sum.
+ answer = ~sum;
+
+ return (answer);
+}
+
+struct _pseudo_header {
+ uint8_t sourceAddr[16];
+ uint8_t targetAddr[16];
+ uint32_t length;
+ uint8_t zeros[3];
+ uint8_t next; // 58
+};
+
+struct _option {
+ _option(int optionType)
+ : type(optionType)
+ , length(8)
+ {
+ memset(mac, 0, sizeof(mac));
+ }
+
+ uint8_t type;
+ uint8_t length;
+ uint8_t mac[6];
+};
+
+struct _neighbor_solicitation {
+ _neighbor_solicitation()
+ : type(135)
+ , code(0)
+ , checksum(0)
+ , option(1)
+ {
+ memset(&reserved, 0, sizeof(reserved));
+ memset(target, 0, sizeof(target));
+ }
+
+ void calculateChecksum(const sockaddr_storage &sourceIp, const sockaddr_storage &destIp) {
+ _pseudo_header ph;
+ memset(&ph, 0, sizeof(_pseudo_header));
+ const sockaddr_in6 *src = (const sockaddr_in6*)&sourceIp;
+ const sockaddr_in6 *dest = (const sockaddr_in6*)&destIp;
+
+ memcpy(ph.sourceAddr, &src->sin6_addr, sizeof(struct in6_addr));
+ memcpy(ph.targetAddr, &dest->sin6_addr, sizeof(struct in6_addr));
+ ph.next = 58;
+ ph.length = htonl(sizeof(_neighbor_solicitation));
+
+ size_t len = sizeof(_pseudo_header) + sizeof(_neighbor_solicitation);
+ uint8_t *tmp = (uint8_t*)malloc(len);
+ memcpy(tmp, &ph, sizeof(_pseudo_header));
+ memcpy(tmp+sizeof(_pseudo_header), this, sizeof(_neighbor_solicitation));
+
+ checksum = calc_checksum((uint16_t*)tmp, len);
+
+ free(tmp);
+ tmp = NULL;
+ }
+
+ uint8_t type; // 135
+ uint8_t code; // 0
+ uint16_t checksum;
+ uint32_t reserved;
+ uint8_t target[16];
+ _option option;
+};
+
+struct _neighbor_advertisement {
+ _neighbor_advertisement()
+ : type(136)
+ , code(0)
+ , checksum(0)
+ , rso(0x40)
+ , option(2)
+ {
+ memset(padding, 0, sizeof(padding));
+ memset(target, 0, sizeof(target));
+ }
+
+ void calculateChecksum(const sockaddr_storage &sourceIp, const sockaddr_storage &destIp) {
+ _pseudo_header ph;
+ memset(&ph, 0, sizeof(_pseudo_header));
+ const sockaddr_in6 *src = (const sockaddr_in6*)&sourceIp;
+ const sockaddr_in6 *dest = (const sockaddr_in6*)&destIp;
+
+ memcpy(ph.sourceAddr, &src->sin6_addr, sizeof(struct in6_addr));
+ memcpy(ph.targetAddr, &dest->sin6_addr, sizeof(struct in6_addr));
+ ph.next = 58;
+ ph.length = htonl(sizeof(_neighbor_advertisement));
+
+ size_t len = sizeof(_pseudo_header) + sizeof(_neighbor_advertisement);
+ uint8_t *tmp = (uint8_t*)malloc(len);
+ memcpy(tmp, &ph, sizeof(_pseudo_header));
+ memcpy(tmp+sizeof(_pseudo_header), this, sizeof(_neighbor_advertisement));
+
+ checksum = calc_checksum((uint16_t*)tmp, len);
+
+ free(tmp);
+ tmp = NULL;
+ }
+
+ uint8_t type; // 136
+ uint8_t code; // 0
+ uint16_t checksum;
+ uint8_t rso;
+ uint8_t padding[3];
+ uint8_t target[16];
+ _option option;
+};
+
+NeighborDiscovery::NeighborDiscovery()
+ : _cache(256)
+ , _lastCleaned(OSUtils::now())
+{}
+
+void NeighborDiscovery::addLocal(const sockaddr_storage &address, const MAC &mac)
+{
+ _NDEntry &e = _cache[InetAddress(address)];
+ e.lastQuerySent = 0;
+ e.lastResponseReceived = 0;
+ e.mac = mac;
+ e.local = true;
+}
+
+void NeighborDiscovery::remove(const sockaddr_storage &address)
+{
+ _cache.erase(InetAddress(address));
+}
+
+sockaddr_storage NeighborDiscovery::processIncomingND(const uint8_t *nd, unsigned int len, const sockaddr_storage &localIp, uint8_t *response, unsigned int &responseLen, MAC &responseDest)
+{
+ assert(sizeof(_neighbor_solicitation) == 28);
+ assert(sizeof(_neighbor_advertisement) == 32);
+
+ const uint64_t now = OSUtils::now();
+ sockaddr_storage ip = ZT_SOCKADDR_NULL;
+
+ if (len >= sizeof(_neighbor_solicitation) && nd[0] == 0x87) {
+ // respond to Neighbor Solicitation request for local address
+ _neighbor_solicitation solicitation;
+ memcpy(&solicitation, nd, len);
+ InetAddress targetAddress(solicitation.target, 16, 0);
+ _NDEntry *targetEntry = _cache.get(targetAddress);
+ if (targetEntry && targetEntry->local) {
+ _neighbor_advertisement adv;
+ targetEntry->mac.copyTo(adv.option.mac, 6);
+ memcpy(adv.target, solicitation.target, 16);
+ adv.calculateChecksum(localIp, targetAddress);
+ memcpy(response, &adv, sizeof(_neighbor_advertisement));
+ responseLen = sizeof(_neighbor_advertisement);
+ responseDest.setTo(solicitation.option.mac, 6);
+ }
+ } else if (len >= sizeof(_neighbor_advertisement) && nd[0] == 0x88) {
+ _neighbor_advertisement adv;
+ memcpy(&adv, nd, len);
+ InetAddress responseAddress(adv.target, 16, 0);
+ _NDEntry *queryEntry = _cache.get(responseAddress);
+ if(queryEntry && !queryEntry->local && (now - queryEntry->lastQuerySent <= ZT_ND_QUERY_MAX_TTL)) {
+ queryEntry->lastResponseReceived = now;
+ queryEntry->mac.setTo(adv.option.mac, 6);
+ ip = responseAddress;
+ }
+ }
+
+ if ((now - _lastCleaned) >= ZT_ND_EXPIRE) {
+ _lastCleaned = now;
+ Hashtable<InetAddress, _NDEntry>::Iterator i(_cache);
+ InetAddress *k = NULL;
+ _NDEntry *v = NULL;
+ while (i.next(k, v)) {
+ if(!v->local && (now - v->lastResponseReceived) >= ZT_ND_EXPIRE) {
+ _cache.erase(*k);
+ }
+ }
+ }
+
+ return ip;
+}
+
+MAC NeighborDiscovery::query(const MAC &localMac, const sockaddr_storage &localIp, const sockaddr_storage &targetIp, uint8_t *query, unsigned int &queryLen, MAC &queryDest)
+{
+ const uint64_t now = OSUtils::now();
+
+ InetAddress localAddress(localIp);
+ localAddress.setPort(0);
+ InetAddress targetAddress(targetIp);
+ targetAddress.setPort(0);
+
+ _NDEntry &e = _cache[targetAddress];
+
+ if ( (e.mac && ((now - e.lastResponseReceived) >= (ZT_ND_EXPIRE / 3))) ||
+ (!e.mac && ((now - e.lastQuerySent) >= ZT_ND_QUERY_INTERVAL))) {
+ e.lastQuerySent = now;
+
+ _neighbor_solicitation ns;
+ memcpy(ns.target, targetAddress.rawIpData(), 16);
+ localMac.copyTo(ns.option.mac, 6);
+ ns.calculateChecksum(localIp, targetIp);
+ if (e.mac) {
+ queryDest = e.mac;
+ } else {
+ queryDest = (uint64_t)0xffffffffffffULL;
+ }
+ } else {
+ queryLen = 0;
+ queryDest.zero();
+ }
+
+ return e.mac;
+}
+
+}
diff --git a/osdep/NeighborDiscovery.hpp b/osdep/NeighborDiscovery.hpp
new file mode 100644
index 00000000..47831bda
--- /dev/null
+++ b/osdep/NeighborDiscovery.hpp
@@ -0,0 +1,76 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ZT_NEIGHBORDISCOVERY_HPP
+#define ZT_NEIGHBORDISCOVERY_HPP
+
+#include "../node/Hashtable.hpp"
+#include "../node/MAC.hpp"
+#include "../node/InetAddress.hpp"
+
+
+#define ZT_ND_QUERY_INTERVAL 2000
+
+#define ZT_ND_QUERY_MAX_TTL 5000
+
+#define ZT_ND_EXPIRE 600000
+
+
+namespace ZeroTier {
+
+class NeighborDiscovery
+{
+public:
+ NeighborDiscovery();
+
+ /**
+ * Set a local IP entry that we should respond to Neighbor Requests withPrefix64k
+ *
+ * @param mac Our local MAC address
+ * @param ip Our IPv6 address
+ */
+ void addLocal(const sockaddr_storage &address, const MAC &mac);
+
+ /**
+ * Delete a local IP entry or cached Neighbor entry
+ *
+ * @param address IPv6 address to remove
+ */
+ void remove(const sockaddr_storage &address);
+
+ sockaddr_storage processIncomingND(const uint8_t *nd, unsigned int len, const sockaddr_storage &localIp, uint8_t *response, unsigned int &responseLen, MAC &responseDest);
+
+ MAC query(const MAC &localMac, const sockaddr_storage &localIp, const sockaddr_storage &targetIp, uint8_t *query, unsigned int &queryLen, MAC &queryDest);
+
+private:
+ struct _NDEntry
+ {
+ _NDEntry() : lastQuerySent(0), lastResponseReceived(0), mac(), local(false) {}
+ uint64_t lastQuerySent;
+ uint64_t lastResponseReceived;
+ MAC mac;
+ bool local;
+ };
+
+ Hashtable<InetAddress, _NDEntry> _cache;
+ uint64_t _lastCleaned;
+};
+
+} // namespace ZeroTier
+
+#endif
diff --git a/osdep/OSUtils.cpp b/osdep/OSUtils.cpp
index 086bb269..c652e272 100644
--- a/osdep/OSUtils.cpp
+++ b/osdep/OSUtils.cpp
@@ -170,9 +170,11 @@ bool OSUtils::rmDashRf(const char *path)
return true;
dptr = (struct dirent *)0;
for(;;) {
- if (readdir_r(d,&de,&dptr))
+ if (readdir_r(d,&de,&dptr) != 0)
+ break;
+ if (!dptr)
break;
- if ((dptr)&&(strcmp(dptr->d_name,".") != 0)&&(strcmp(dptr->d_name,"..") != 0)) {
+ if ((strcmp(dptr->d_name,".") != 0)&&(strcmp(dptr->d_name,"..") != 0)&&(strlen(dptr->d_name) > 0)) {
std::string p(path);
p.push_back(ZT_PATH_SEPARATOR);
p.append(dptr->d_name);
@@ -180,7 +182,7 @@ bool OSUtils::rmDashRf(const char *path)
if (!rmDashRf(p.c_str()))
return false;
}
- } else break;
+ }
}
closedir(d);
return (rmdir(path) == 0);