diff --git a/src/proxy.cpp b/src/proxy.cpp
index 919ed67..8c9199b 100644
--- a/src/proxy.cpp
+++ b/src/proxy.cpp
@@ -444,7 +444,7 @@ namespace ZeroTier
closeConnection(sock);
}
- void NetconEthernetTap::phyOnTcpWritable(PhySocket *sock,void **uptr/*, bool lwip_invoked*/)
+ void NetconEthernetTap::phyOnTcpWritable(PhySocket *sock,void **uptr, bool lwip_invoked)
{
DEBUG_INFO("sock=%p", (void*)&sock);
handleRead(sock,uptr,true);
diff --git a/src/tap.cpp b/src/tap.cpp
index fd32441..104c9a1 100644
--- a/src/tap.cpp
+++ b/src/tap.cpp
@@ -762,27 +762,24 @@ void NetconEthernetTap::lwIP_init_interface(const InetAddress &ip)
std::sort(_ips.begin(),_ips.end());
#if defined(SDK_IPV4)
- if (ip.isV4()) {
- DEBUG_INFO("local_addr=%s", ip.toString().c_str());
- // convert address
+ if (ip.isV4()) {
+ // Set IP
static ip_addr_t ipaddr, netmask, gw;
- IP4_ADDR((ip4_addr_t *)&gw,127,0,0,1);
- ((ip4_addr_t *)&ipaddr)->addr = *((u32_t *)ip.rawIpData());
- ((ip4_addr_t *)&netmask)->addr = *((u32_t *)ip.netmask().rawIpData());
- // initialize netif
+ IP4_ADDR(&gw,127,0,0,1);
+ ipaddr.addr = *((u32_t *)ip.rawIpData());
+ netmask.addr = *((u32_t *)ip.netmask().rawIpData());
lwipstack->__netif_add(&interface,&ipaddr, &netmask, &gw, NULL, tapif_init, lwipstack->_ethernet_input);
interface.state = this;
interface.output = lwipstack->_etharp_output;
_mac.copyTo(interface.hwaddr, 6);
interface.mtu = _mtu;
interface.name[0] = 'l';
- interface.name[1] = 'w';
- interface.name[2] = '4';
+ interface.name[1] = '4';
interface.linkoutput = low_level_output;
interface.hwaddr_len = 6;
- interface.flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_IGMP;
+ interface.flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_IGMP | NETIF_FLAG_LINK_UP | NETIF_FLAG_UP;
lwipstack->__netif_set_default(&interface);
- lwipstack->__netif_set_up(&interface);
+ lwipstack->__netif_set_up(&interface);
}
#endif
@@ -797,8 +794,7 @@ void NetconEthernetTap::lwIP_init_interface(const InetAddress &ip)
// initialize netif
interface6.mtu = _mtu;
interface6.name[0] = 'l';
- interface6.name[1] = 'w';
- interface6.name[2] = '6';
+ interface6.name[1] = '6';
interface6.hwaddr_len = 6;
interface6.linkoutput = low_level_output;
interface6.ip6_autoconfig_enabled = 1;
@@ -1755,6 +1751,11 @@ void NetconEthernetTap::handleBind(PhySocket *sock, PhySocket *rpcSock, void **u
inet_ntop(AF_INET, &(connaddr->sin_addr), addrstr, INET_ADDRSTRLEN);
sprintf(addrstr, "%s:%d", addrstr, lwipstack->__lwip_ntohs(connaddr->sin_port));
}
+
+ struct sockaddr_in *rawAddr4 = (struct sockaddr_in *) &bind_rpc->addr;
+ ba = convert_ip(rawAddr4);
+ port = lwipstack->__lwip_ntohs(rawAddr4->sin_port);
+
#endif
// ipv6
@@ -2070,6 +2071,9 @@ void NetconEthernetTap::handleConnect(PhySocket *sock, PhySocket *rpcSock, Conne
inet_ntop(AF_INET, &(connaddr->sin_addr), addrstr, INET_ADDRSTRLEN);
sprintf(addrstr, "%s:%d", addrstr, lwipstack->__lwip_ntohs(connaddr->sin_port));
}
+ struct sockaddr_in *rawAddr4 = (struct sockaddr_in *) &connect_rpc->addr;
+ ba = convert_ip(rawAddr4);
+ port = lwipstack->__lwip_ntohs(rawAddr4->sin_port);
#endif
// ipv6
@@ -2106,7 +2110,7 @@ void NetconEthernetTap::handleConnect(PhySocket *sock, PhySocket *rpcSock, Conne
sendReturnValue(rpcSock, -1, EAGAIN);
return;
}
- if((err = lwipstack->__tcp_connect(conn->TCP_pcb,(ip_addr_t *)&ba,port,nc_connected)) < 0)
+ if((err = lwipstack->__tcp_connect(conn->TCP_pcb,&ba,port,nc_connected)) < 0)
{
if(err == ERR_ISCONN) {
sendReturnValue(rpcSock, -1, EISCONN); // Already in connected state
diff --git a/src/tap.hpp b/src/tap.hpp
index bc14436..0b9ef02 100644
--- a/src/tap.hpp
+++ b/src/tap.hpp
@@ -56,6 +56,11 @@
#include "pico_protocol.h"
#endif
+#define ip4_addr1b(ipaddr) (((u8_t*)(ipaddr))[0])
+#define ip4_addr2b(ipaddr) (((u8_t*)(ipaddr))[1])
+#define ip4_addr3b(ipaddr) (((u8_t*)(ipaddr))[2])
+#define ip4_addr4b(ipaddr) (((u8_t*)(ipaddr))[3])
+
// lwIP structs
struct tcp_pcb;
struct udp_pcb;
@@ -459,7 +464,7 @@ namespace ZeroTier {
void phyOnTcpData(PhySocket *sock,void **uptr,void *data,unsigned long len);
void handleRead(PhySocket *sock,void **uptr,bool lwip_invoked);
- void phyOnTcpWritable(PhySocket *sock,void **uptr);
+ void phyOnTcpWritable(PhySocket *sock,void **uptr, bool lwip_invoked);
/*
* Signals us to close the TcpConnection associated with this PhySocket
@@ -495,14 +500,14 @@ namespace ZeroTier {
ip_addr_t convert_ip(struct sockaddr_in * addr)
{
ip_addr_t conn_addr;
- /*
+
struct sockaddr_in *ipv4 = addr;
- short a = ip4_addr1(&(ipv4->sin_addr));
- short b = ip4_addr2(&(ipv4->sin_addr));
- short c = ip4_addr3(&(ipv4->sin_addr));
- short d = ip4_addr4(&(ipv4->sin_addr));
+ short a = ip4_addr1b(&(ipv4->sin_addr));
+ short b = ip4_addr2b(&(ipv4->sin_addr));
+ short c = ip4_addr3b(&(ipv4->sin_addr));
+ short d = ip4_addr4b(&(ipv4->sin_addr));
IP4_ADDR(&conn_addr, a,b,c,d);
- */
+
return conn_addr;
}
diff --git a/zerotierone/SECURITY.md b/zerotierone/SECURITY.md
new file mode 100644
index 0000000..5ca125e
--- /dev/null
+++ b/zerotierone/SECURITY.md
@@ -0,0 +1,84 @@
+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/zerotierone/attic/Filter.cpp b/zerotierone/attic/Filter.cpp
new file mode 100644
index 0000000..a701e8b
--- /dev/null
+++ b/zerotierone/attic/Filter.cpp
@@ -0,0 +1,408 @@
+/*
+ * 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 .
+ *
+ * --
+ *
+ * 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
+#include
+#include
+#include
+
+#include
+
+#include "RuntimeEnvironment.hpp"
+#include "Logger.hpp"
+#include "Filter.hpp"
+#include "Utils.hpp"
+
+namespace ZeroTier {
+
+const char *const Filter::UNKNOWN_NAME = "(unknown)";
+const Range Filter::ANY;
+
+static inline Range __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 , -, or *");
+ }
+ }
+ }
+ return Range(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::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::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/zerotierone/attic/Filter.hpp b/zerotierone/attic/Filter.hpp
new file mode 100644
index 0000000..4bea371
--- /dev/null
+++ b/zerotierone/attic/Filter.hpp
@@ -0,0 +1,284 @@
+/*
+ * 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 .
+ *
+ * --
+ *
+ * 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
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#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 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 &etype,const Range &prot,const Range &prt)
+ throw() :
+ _etherType(etype),
+ _protocol(prot),
+ _port(prt)
+ {
+ }
+
+ inline const Range ðerType() const throw() { return _etherType; }
+ inline const Range &protocol() const throw() { return _protocol; }
+ inline const Range &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 _etherType;
+ Range _protocol;
+ Range _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::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 _rules;
+};
+
+} // namespace ZeroTier
+
+#endif
diff --git a/zerotierone/controller/SqliteNetworkController.cpp b/zerotierone/controller/SqliteNetworkController.cpp
new file mode 100644
index 0000000..4d75620
--- /dev/null
+++ b/zerotierone/controller/SqliteNetworkController.cpp
@@ -0,0 +1,2212 @@
+/*
+ * 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 .
+ *
+ * --
+ *
+ * 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
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include "../include/ZeroTierOne.h"
+#include "../node/Constants.hpp"
+
+#include "../ext/json-parser/json.h"
+
+#include "SqliteNetworkController.hpp"
+
+#include "../node/Node.hpp"
+#include "../node/Utils.hpp"
+#include "../node/CertificateOfMembership.hpp"
+#include "../node/NetworkConfig.hpp"
+#include "../node/NetworkConfigRequestMetaData.hpp"
+#include "../node/InetAddress.hpp"
+#include "../node/MAC.hpp"
+#include "../node/Address.hpp"
+
+#include "../osdep/OSUtils.hpp"
+
+// Include ZT_NETCONF_SCHEMA_SQL constant to init database
+#include "schema.sql.c"
+
+// Stored in database as schemaVersion key in Config.
+// If not present, database is assumed to be empty and at the current schema version
+// and this key/value is added automatically.
+#define ZT_NETCONF_SQLITE_SCHEMA_VERSION 2
+#define ZT_NETCONF_SQLITE_SCHEMA_VERSION_STR "2"
+
+// API version reported via JSON control plane
+#define ZT_NETCONF_CONTROLLER_API_VERSION 1
+
+// Min duration between requests for an address/nwid combo to prevent floods
+#define ZT_NETCONF_MIN_REQUEST_PERIOD 1000
+
+// Delay between backups in milliseconds
+#define ZT_NETCONF_BACKUP_PERIOD 300000
+
+// Number of NodeHistory entries to maintain per node and network (can be changed)
+#define ZT_NETCONF_NODE_HISTORY_LENGTH 64
+
+// 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)
+
+namespace ZeroTier {
+
+namespace {
+
+static std::string _jsonEscape(const char *s)
+{
+ if (!s)
+ return std::string();
+ std::string buf;
+ for(const char *p=s;(*p);++p) {
+ switch(*p) {
+ case '\t': buf.append("\\t"); break;
+ case '\b': buf.append("\\b"); break;
+ case '\r': buf.append("\\r"); break;
+ case '\n': buf.append("\\n"); break;
+ case '\f': buf.append("\\f"); break;
+ case '"': buf.append("\\\""); break;
+ case '\\': buf.append("\\\\"); break;
+ case '/': buf.append("\\/"); break;
+ default: buf.push_back(*p); break;
+ }
+ }
+ return buf;
+}
+static std::string _jsonEscape(const std::string &s) { return _jsonEscape(s.c_str()); }
+
+struct MemberRecord {
+ int64_t rowid;
+ char nodeId[16];
+ bool authorized;
+ bool activeBridge;
+};
+
+struct NetworkRecord {
+ char id[24];
+ const char *name;
+ const char *v4AssignMode;
+ const char *v6AssignMode;
+ bool isPrivate;
+ bool enableBroadcast;
+ bool allowPassiveBridging;
+ int multicastLimit;
+ uint64_t creationTime;
+ uint64_t revision;
+ uint64_t memberRevisionCounter;
+};
+
+} // anonymous namespace
+
+SqliteNetworkController::SqliteNetworkController(Node *node,const char *dbPath,const char *circuitTestPath) :
+ _node(node),
+ _backupThreadRun(true),
+ _dbPath(dbPath),
+ _circuitTestPath(circuitTestPath),
+ _db((sqlite3 *)0)
+{
+ if (sqlite3_open_v2(dbPath,&_db,SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE,(const char *)0) != SQLITE_OK)
+ throw std::runtime_error("SqliteNetworkController cannot open database file");
+ sqlite3_busy_timeout(_db,10000);
+
+ sqlite3_exec(_db,"PRAGMA synchronous = OFF",0,0,0);
+ sqlite3_exec(_db,"PRAGMA journal_mode = MEMORY",0,0,0);
+
+ sqlite3_stmt *s = (sqlite3_stmt *)0;
+ if ((sqlite3_prepare_v2(_db,"SELECT v FROM Config WHERE k = 'schemaVersion';",-1,&s,(const char **)0) == SQLITE_OK)&&(s)) {
+ int schemaVersion = -1234;
+ if (sqlite3_step(s) == SQLITE_ROW) {
+ schemaVersion = sqlite3_column_int(s,0);
+ }
+
+ sqlite3_finalize(s);
+
+ if (schemaVersion == -1234) {
+ sqlite3_close(_db);
+ throw std::runtime_error("SqliteNetworkController schemaVersion not found in Config table (init failure?)");
+ } else if (schemaVersion == 1) {
+ // Create NodeHistory table to upgrade from version 1 to version 2
+ if (sqlite3_exec(_db,
+ "CREATE TABLE NodeHistory (\n"
+ " nodeId char(10) NOT NULL REFERENCES Node(id) ON DELETE CASCADE,\n"
+ " networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"
+ " networkVisitCounter INTEGER NOT NULL DEFAULT(0),\n"
+ " networkRequestAuthorized INTEGER NOT NULL DEFAULT(0),\n"
+ " requestTime INTEGER NOT NULL DEFAULT(0),\n"
+ " clientMajorVersion INTEGER NOT NULL DEFAULT(0),\n"
+ " clientMinorVersion INTEGER NOT NULL DEFAULT(0),\n"
+ " clientRevision INTEGER NOT NULL DEFAULT(0),\n"
+ " networkRequestMetaData VARCHAR(1024),\n"
+ " fromAddress VARCHAR(128)\n"
+ ");\n"
+ "\n"
+ "CREATE INDEX NodeHistory_nodeId ON NodeHistory (nodeId);\n"
+ "CREATE INDEX NodeHistory_networkId ON NodeHistory (networkId);\n"
+ "CREATE INDEX NodeHistory_requestTime ON NodeHistory (requestTime);\n"
+ "\n"
+ "UPDATE \"Config\" SET \"v\" = 2 WHERE \"k\" = 'schemaVersion';\n"
+ ,0,0,0) != SQLITE_OK) {
+ char err[1024];
+ Utils::snprintf(err,sizeof(err),"SqliteNetworkController cannot upgrade the database to version 2: %s",sqlite3_errmsg(_db));
+ sqlite3_close(_db);
+ throw std::runtime_error(err);
+ }
+ } else if (schemaVersion != ZT_NETCONF_SQLITE_SCHEMA_VERSION) {
+ sqlite3_close(_db);
+ throw std::runtime_error("SqliteNetworkController database schema version mismatch");
+ }
+ } else {
+ // Prepare statement will fail if Config table doesn't exist, which means our DB
+ // needs to be initialized.
+ if (sqlite3_exec(_db,ZT_NETCONF_SCHEMA_SQL"INSERT INTO Config (k,v) VALUES ('schemaVersion',"ZT_NETCONF_SQLITE_SCHEMA_VERSION_STR");",0,0,0) != SQLITE_OK) {
+ char err[1024];
+ Utils::snprintf(err,sizeof(err),"SqliteNetworkController cannot initialize database and/or insert schemaVersion into Config table: %s",sqlite3_errmsg(_db));
+ sqlite3_close(_db);
+ throw std::runtime_error(err);
+ }
+ }
+
+ if (
+
+ /* Network */
+ (sqlite3_prepare_v2(_db,"SELECT name,private,enableBroadcast,allowPassiveBridging,v4AssignMode,v6AssignMode,multicastLimit,creationTime,revision,memberRevisionCounter,(SELECT COUNT(1) FROM Member WHERE Member.networkId = Network.id AND Member.authorized > 0) FROM Network WHERE id = ?",-1,&_sGetNetworkById,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"SELECT revision FROM Network WHERE id = ?",-1,&_sGetNetworkRevision,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"UPDATE Network SET revision = ? WHERE id = ?",-1,&_sSetNetworkRevision,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"INSERT INTO Network (id,name,creationTime,revision) VALUES (?,?,?,1)",-1,&_sCreateNetwork,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"DELETE FROM Network WHERE id = ?",-1,&_sDeleteNetwork,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"SELECT id FROM Network ORDER BY id ASC",-1,&_sListNetworks,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"UPDATE Network SET memberRevisionCounter = (memberRevisionCounter + 1) WHERE id = ?",-1,&_sIncrementMemberRevisionCounter,(const char **)0) != SQLITE_OK)
+
+ /* Node */
+ ||(sqlite3_prepare_v2(_db,"SELECT identity FROM Node WHERE id = ?",-1,&_sGetNodeIdentity,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"INSERT OR REPLACE INTO Node (id,identity) VALUES (?,?)",-1,&_sCreateOrReplaceNode,(const char **)0) != SQLITE_OK)
+
+ /* NodeHistory */
+ ||(sqlite3_prepare_v2(_db,"SELECT IFNULL(MAX(networkVisitCounter),0) FROM NodeHistory WHERE networkId = ? AND nodeId = ?",-1,&_sGetMaxNodeHistoryNetworkVisitCounter,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"INSERT INTO NodeHistory (nodeId,networkId,networkVisitCounter,networkRequestAuthorized,requestTime,clientMajorVersion,clientMinorVersion,clientRevision,networkRequestMetaData,fromAddress) VALUES (?,?,?,?,?,?,?,?,?,?)",-1,&_sAddNodeHistoryEntry,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"DELETE FROM NodeHistory WHERE networkId = ? AND nodeId = ? AND networkVisitCounter <= ?",-1,&_sDeleteOldNodeHistoryEntries,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"SELECT nodeId,requestTime,clientMajorVersion,clientMinorVersion,clientRevision,fromAddress,networkRequestAuthorized FROM NodeHistory WHERE networkId = ? AND requestTime IN (SELECT MAX(requestTime) FROM NodeHistory WHERE networkId = ? AND requestTime >= ? GROUP BY nodeId) ORDER BY nodeId ASC,requestTime DESC",-1,&_sGetActiveNodesOnNetwork,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"SELECT networkVisitCounter,networkRequestAuthorized,requestTime,clientMajorVersion,clientMinorVersion,clientRevision,networkRequestMetaData,fromAddress FROM NodeHistory WHERE networkId = ? AND nodeId = ? ORDER BY requestTime DESC",-1,&_sGetNodeHistory,(const char **)0) != SQLITE_OK)
+
+ /* Rule */
+ ||(sqlite3_prepare_v2(_db,"SELECT etherType FROM Rule WHERE networkId = ? AND \"action\" = 'accept'",-1,&_sGetEtherTypesFromRuleTable,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"INSERT INTO Rule (networkId,ruleNo,nodeId,sourcePort,destPort,vlanId,vlanPcP,etherType,macSource,macDest,ipSource,ipDest,ipTos,ipProtocol,ipSourcePort,ipDestPort,flags,invFlags,\"action\") VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",-1,&_sCreateRule,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"SELECT ruleNo,nodeId,sourcePort,destPort,vlanId,vlanPcp,etherType,macSource,macDest,ipSource,ipDest,ipTos,ipProtocol,ipSourcePort,ipDestPort,\"flags\",invFlags,\"action\" FROM Rule WHERE networkId = ? ORDER BY ruleNo ASC",-1,&_sListRules,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"DELETE FROM Rule WHERE networkId = ?",-1,&_sDeleteRulesForNetwork,(const char **)0) != SQLITE_OK)
+
+ /* IpAssignmentPool */
+ ||(sqlite3_prepare_v2(_db,"SELECT ipRangeStart,ipRangeEnd FROM IpAssignmentPool WHERE networkId = ? AND ipVersion = ?",-1,&_sGetIpAssignmentPools,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"SELECT ipRangeStart,ipRangeEnd,ipVersion FROM IpAssignmentPool WHERE networkId = ? ORDER BY ipRangeStart ASC",-1,&_sGetIpAssignmentPools2,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"INSERT INTO IpAssignmentPool (networkId,ipRangeStart,ipRangeEnd,ipVersion) VALUES (?,?,?,?)",-1,&_sCreateIpAssignmentPool,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"DELETE FROM IpAssignmentPool WHERE networkId = ?",-1,&_sDeleteIpAssignmentPoolsForNetwork,(const char **)0) != SQLITE_OK)
+
+ /* IpAssignment */
+ ||(sqlite3_prepare_v2(_db,"SELECT \"type\",ip,ipNetmaskBits FROM IpAssignment WHERE networkId = ? AND (nodeId = ? OR nodeId IS NULL) AND ipVersion = ?",-1,&_sGetIpAssignmentsForNode,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"SELECT ip,ipNetmaskBits,ipVersion FROM IpAssignment WHERE networkId = ? AND nodeId = ? AND \"type\" = ? ORDER BY ip ASC",-1,&_sGetIpAssignmentsForNode2,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"SELECT ip,ipNetmaskBits,ipVersion FROM IpAssignment WHERE networkId = ? AND nodeId IS NULL AND \"type\" = ?",-1,&_sGetLocalRoutes,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"SELECT 1 FROM IpAssignment WHERE networkId = ? AND ip = ? AND ipVersion = ? AND \"type\" = ?",-1,&_sCheckIfIpIsAllocated,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"INSERT INTO IpAssignment (networkId,nodeId,\"type\",ip,ipNetmaskBits,ipVersion) VALUES (?,?,?,?,?,?)",-1,&_sAllocateIp,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"DELETE FROM IpAssignment WHERE networkId = ? AND nodeId = ? AND \"type\" = ?",-1,&_sDeleteIpAllocations,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"DELETE FROM IpAssignment WHERE networkId = ? AND nodeId IS NULL AND \"type\" = ?",-1,&_sDeleteLocalRoutes,(const char **)0) != SQLITE_OK)
+
+ /* Relay */
+ ||(sqlite3_prepare_v2(_db,"SELECT \"address\",\"phyAddress\" FROM Relay WHERE \"networkId\" = ? ORDER BY \"address\" ASC",-1,&_sGetRelays,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"DELETE FROM Relay WHERE networkId = ?",-1,&_sDeleteRelaysForNetwork,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"INSERT INTO Relay (\"networkId\",\"address\",\"phyAddress\") VALUES (?,?,?)",-1,&_sCreateRelay,(const char **)0) != SQLITE_OK)
+
+ /* Member */
+ ||(sqlite3_prepare_v2(_db,"SELECT rowid,authorized,activeBridge FROM Member WHERE networkId = ? AND nodeId = ?",-1,&_sGetMember,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"SELECT m.authorized,m.activeBridge,m.memberRevision,n.identity FROM Member AS m LEFT OUTER JOIN Node AS n ON n.id = m.nodeId WHERE m.networkId = ? AND m.nodeId = ?",-1,&_sGetMember2,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"INSERT INTO Member (networkId,nodeId,authorized,activeBridge,memberRevision) VALUES (?,?,?,0,(SELECT memberRevisionCounter FROM Network WHERE id = ?))",-1,&_sCreateMember,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"SELECT nodeId FROM Member WHERE networkId = ? AND activeBridge > 0 AND authorized > 0",-1,&_sGetActiveBridges,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"SELECT m.nodeId,m.memberRevision FROM Member AS m WHERE m.networkId = ? ORDER BY m.nodeId ASC",-1,&_sListNetworkMembers,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"UPDATE Member SET authorized = ?,memberRevision = (SELECT memberRevisionCounter FROM Network WHERE id = ?) WHERE rowid = ?",-1,&_sUpdateMemberAuthorized,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"UPDATE Member SET activeBridge = ?,memberRevision = (SELECT memberRevisionCounter FROM Network WHERE id = ?) WHERE rowid = ?",-1,&_sUpdateMemberActiveBridge,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"DELETE FROM Member WHERE networkId = ? AND nodeId = ?",-1,&_sDeleteMember,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"DELETE FROM Member WHERE networkId = ?",-1,&_sDeleteAllNetworkMembers,(const char **)0) != SQLITE_OK)
+
+ /* Gateway */
+ ||(sqlite3_prepare_v2(_db,"SELECT \"ip\",ipVersion,metric FROM Gateway WHERE networkId = ? ORDER BY metric ASC",-1,&_sGetGateways,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"DELETE FROM Gateway WHERE networkId = ?",-1,&_sDeleteGateways,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"INSERT INTO Gateway (networkId,\"ip\",ipVersion,metric) VALUES (?,?,?,?)",-1,&_sCreateGateway,(const char **)0) != SQLITE_OK)
+
+ /* Config */
+ ||(sqlite3_prepare_v2(_db,"SELECT \"v\" FROM \"Config\" WHERE \"k\" = ?",-1,&_sGetConfig,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"INSERT OR REPLACE INTO \"Config\" (\"k\",\"v\") VALUES (?,?)",-1,&_sSetConfig,(const char **)0) != SQLITE_OK)
+
+ ) {
+ std::string err(std::string("SqliteNetworkController unable to initialize one or more prepared statements: ") + sqlite3_errmsg(_db));
+ sqlite3_close(_db);
+ throw std::runtime_error(err);
+ }
+
+ /* Generate a 128-bit / 32-character "instance ID" if one isn't already
+ * defined. Clients can use this to determine if this is the same controller
+ * database they know and love. */
+ sqlite3_reset(_sGetConfig);
+ sqlite3_bind_text(_sGetConfig,1,"instanceId",10,SQLITE_STATIC);
+ if (sqlite3_step(_sGetConfig) != SQLITE_ROW) {
+ unsigned char sr[32];
+ Utils::getSecureRandom(sr,32);
+ for(unsigned int i=0;i<32;++i)
+ _instanceId.push_back("0123456789abcdef"[(unsigned int)sr[i] & 0xf]);
+
+ sqlite3_reset(_sSetConfig);
+ sqlite3_bind_text(_sSetConfig,1,"instanceId",10,SQLITE_STATIC);
+ sqlite3_bind_text(_sSetConfig,2,_instanceId.c_str(),-1,SQLITE_STATIC);
+ if (sqlite3_step(_sSetConfig) != SQLITE_DONE)
+ throw std::runtime_error("SqliteNetworkController unable to read or initialize instanceId");
+ } else {
+ const char *iid = reinterpret_cast(sqlite3_column_text(_sGetConfig,0));
+ if (!iid)
+ throw std::runtime_error("SqliteNetworkController unable to read instanceId (it's NULL)");
+ _instanceId = iid;
+ }
+
+ _backupThread = Thread::start(this);
+}
+
+SqliteNetworkController::~SqliteNetworkController()
+{
+ _backupThreadRun = false;
+ Thread::join(_backupThread);
+
+ Mutex::Lock _l(_lock);
+ if (_db) {
+ sqlite3_finalize(_sGetNetworkById);
+ sqlite3_finalize(_sGetMember);
+ sqlite3_finalize(_sCreateMember);
+ sqlite3_finalize(_sGetNodeIdentity);
+ sqlite3_finalize(_sCreateOrReplaceNode);
+ sqlite3_finalize(_sGetMaxNodeHistoryNetworkVisitCounter);
+ sqlite3_finalize(_sAddNodeHistoryEntry);
+ sqlite3_finalize(_sDeleteOldNodeHistoryEntries);
+ sqlite3_finalize(_sGetActiveNodesOnNetwork);
+ sqlite3_finalize(_sGetNodeHistory);
+ sqlite3_finalize(_sGetEtherTypesFromRuleTable);
+ sqlite3_finalize(_sGetActiveBridges);
+ sqlite3_finalize(_sGetIpAssignmentsForNode);
+ sqlite3_finalize(_sGetIpAssignmentPools);
+ sqlite3_finalize(_sGetLocalRoutes);
+ sqlite3_finalize(_sCheckIfIpIsAllocated);
+ sqlite3_finalize(_sAllocateIp);
+ sqlite3_finalize(_sDeleteIpAllocations);
+ sqlite3_finalize(_sDeleteLocalRoutes);
+ sqlite3_finalize(_sGetRelays);
+ sqlite3_finalize(_sListNetworks);
+ sqlite3_finalize(_sListNetworkMembers);
+ sqlite3_finalize(_sGetMember2);
+ sqlite3_finalize(_sGetIpAssignmentPools2);
+ sqlite3_finalize(_sListRules);
+ sqlite3_finalize(_sCreateRule);
+ sqlite3_finalize(_sCreateNetwork);
+ sqlite3_finalize(_sGetNetworkRevision);
+ sqlite3_finalize(_sSetNetworkRevision);
+ sqlite3_finalize(_sGetIpAssignmentsForNode2);
+ sqlite3_finalize(_sDeleteRelaysForNetwork);
+ sqlite3_finalize(_sCreateRelay);
+ sqlite3_finalize(_sDeleteIpAssignmentPoolsForNetwork);
+ sqlite3_finalize(_sDeleteRulesForNetwork);
+ sqlite3_finalize(_sCreateIpAssignmentPool);
+ sqlite3_finalize(_sUpdateMemberAuthorized);
+ sqlite3_finalize(_sUpdateMemberActiveBridge);
+ sqlite3_finalize(_sDeleteMember);
+ sqlite3_finalize(_sDeleteAllNetworkMembers);
+ sqlite3_finalize(_sDeleteNetwork);
+ sqlite3_finalize(_sGetGateways);
+ sqlite3_finalize(_sDeleteGateways);
+ sqlite3_finalize(_sCreateGateway);
+ sqlite3_finalize(_sIncrementMemberRevisionCounter);
+ sqlite3_finalize(_sGetConfig);
+ sqlite3_finalize(_sSetConfig);
+ sqlite3_close(_db);
+ }
+}
+
+NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(const InetAddress &fromAddr,const Identity &signingId,const Identity &identity,uint64_t nwid,const NetworkConfigRequestMetaData &metaData,Buffer<8194> &netconf)
+{
+ Mutex::Lock _l(_lock);
+ return _doNetworkConfigRequest(fromAddr,signingId,identity,nwid,metaData,netconf);
+}
+
+unsigned int SqliteNetworkController::handleControlPlaneHttpGET(
+ const std::vector &path,
+ const std::map &urlArgs,
+ const std::map &headers,
+ const std::string &body,
+ std::string &responseBody,
+ std::string &responseContentType)
+{
+ Mutex::Lock _l(_lock);
+ return _doCPGet(path,urlArgs,headers,body,responseBody,responseContentType);
+}
+
+unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
+ const std::vector &path,
+ const std::map &urlArgs,
+ const std::map &headers,
+ const std::string &body,
+ std::string &responseBody,
+ std::string &responseContentType)
+{
+ if (path.empty())
+ return 404;
+ Mutex::Lock _l(_lock);
+
+ if (path[0] == "network") {
+
+ if ((path.size() >= 2)&&(path[1].length() == 16)) {
+ uint64_t nwid = Utils::hexStrToU64(path[1].c_str());
+ char nwids[24];
+ Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
+
+ int64_t revision = 0;
+ sqlite3_reset(_sGetNetworkRevision);
+ sqlite3_bind_text(_sGetNetworkRevision,1,nwids,16,SQLITE_STATIC);
+ bool networkExists = false;
+ if (sqlite3_step(_sGetNetworkRevision) == SQLITE_ROW) {
+ networkExists = true;
+ revision = sqlite3_column_int64(_sGetNetworkRevision,0);
+ }
+
+ if (path.size() >= 3) {
+
+ if (!networkExists)
+ return 404;
+
+ if ((path.size() == 4)&&(path[2] == "member")&&(path[3].length() == 10)) {
+ uint64_t address = Utils::hexStrToU64(path[3].c_str());
+ char addrs[24];
+ Utils::snprintf(addrs,sizeof(addrs),"%.10llx",address);
+
+ int64_t addToNetworkRevision = 0;
+
+ int64_t memberRowId = 0;
+ sqlite3_reset(_sGetMember);
+ sqlite3_bind_text(_sGetMember,1,nwids,16,SQLITE_STATIC);
+ sqlite3_bind_text(_sGetMember,2,addrs,10,SQLITE_STATIC);
+ bool memberExists = false;
+ if (sqlite3_step(_sGetMember) == SQLITE_ROW) {
+ memberExists = true;
+ memberRowId = sqlite3_column_int64(_sGetMember,0);
+ }
+
+ if (!memberExists) {
+ sqlite3_reset(_sCreateMember);
+ sqlite3_bind_text(_sCreateMember,1,nwids,16,SQLITE_STATIC);
+ sqlite3_bind_text(_sCreateMember,2,addrs,10,SQLITE_STATIC);
+ sqlite3_bind_int(_sCreateMember,3,0);
+ sqlite3_bind_text(_sCreateMember,4,nwids,16,SQLITE_STATIC);
+ if (sqlite3_step(_sCreateMember) != SQLITE_DONE)
+ return 500;
+ memberRowId = (int64_t)sqlite3_last_insert_rowid(_db);
+
+ sqlite3_reset(_sIncrementMemberRevisionCounter);
+ sqlite3_bind_text(_sIncrementMemberRevisionCounter,1,nwids,16,SQLITE_STATIC);
+ sqlite3_step(_sIncrementMemberRevisionCounter);
+ addToNetworkRevision = 1;
+ }
+
+ json_value *j = json_parse(body.c_str(),body.length());
+ if (j) {
+ if (j->type == json_object) {
+ for(unsigned int k=0;ku.object.length;++k) {
+
+ if (!strcmp(j->u.object.values[k].name,"authorized")) {
+ if (j->u.object.values[k].value->type == json_boolean) {
+ sqlite3_reset(_sUpdateMemberAuthorized);
+ sqlite3_bind_int(_sUpdateMemberAuthorized,1,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1);
+ sqlite3_bind_text(_sUpdateMemberAuthorized,2,nwids,16,SQLITE_STATIC);
+ sqlite3_bind_int64(_sUpdateMemberAuthorized,3,memberRowId);
+ if (sqlite3_step(_sUpdateMemberAuthorized) != SQLITE_DONE)
+ return 500;
+
+ sqlite3_reset(_sIncrementMemberRevisionCounter);
+ sqlite3_bind_text(_sIncrementMemberRevisionCounter,1,nwids,16,SQLITE_STATIC);
+ sqlite3_step(_sIncrementMemberRevisionCounter);
+ addToNetworkRevision = 1;
+ }
+ } else if (!strcmp(j->u.object.values[k].name,"activeBridge")) {
+ if (j->u.object.values[k].value->type == json_boolean) {
+ sqlite3_reset(_sUpdateMemberActiveBridge);
+ sqlite3_bind_int(_sUpdateMemberActiveBridge,1,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1);
+ sqlite3_bind_text(_sUpdateMemberActiveBridge,2,nwids,16,SQLITE_STATIC);
+ sqlite3_bind_int64(_sUpdateMemberActiveBridge,3,memberRowId);
+ if (sqlite3_step(_sUpdateMemberActiveBridge) != SQLITE_DONE)
+ return 500;
+
+ sqlite3_reset(_sIncrementMemberRevisionCounter);
+ sqlite3_bind_text(_sIncrementMemberRevisionCounter,1,nwids,16,SQLITE_STATIC);
+ sqlite3_step(_sIncrementMemberRevisionCounter);
+ addToNetworkRevision = 1;
+ }
+ } else if (!strcmp(j->u.object.values[k].name,"ipAssignments")) {
+ if (j->u.object.values[k].value->type == json_array) {
+ sqlite3_reset(_sDeleteIpAllocations);
+ sqlite3_bind_text(_sDeleteIpAllocations,1,nwids,16,SQLITE_STATIC);
+ sqlite3_bind_text(_sDeleteIpAllocations,2,addrs,10,SQLITE_STATIC);
+ sqlite3_bind_int(_sDeleteIpAllocations,3,(int)ZT_IP_ASSIGNMENT_TYPE_ADDRESS);
+ if (sqlite3_step(_sDeleteIpAllocations) != SQLITE_DONE)
+ return 500;
+ for(unsigned int kk=0;kku.object.values[k].value->u.array.length;++kk) {
+ json_value *ipalloc = j->u.object.values[k].value->u.array.values[kk];
+ if (ipalloc->type == json_string) {
+ InetAddress a(ipalloc->u.string.ptr);
+ char ipBlob[16];
+ int ipVersion = 0;
+ switch(a.ss_family) {
+ case AF_INET:
+ if ((a.netmaskBits() > 0)&&(a.netmaskBits() <= 32)) {
+ memset(ipBlob,0,12);
+ memcpy(ipBlob + 12,a.rawIpData(),4);
+ ipVersion = 4;
+ }
+ break;
+ case AF_INET6:
+ if ((a.netmaskBits() > 0)&&(a.netmaskBits() <= 128)) {
+ memcpy(ipBlob,a.rawIpData(),16);
+ ipVersion = 6;
+ }
+ break;
+ }
+ if (ipVersion > 0) {
+ sqlite3_reset(_sAllocateIp);
+ sqlite3_bind_text(_sAllocateIp,1,nwids,16,SQLITE_STATIC);
+ sqlite3_bind_text(_sAllocateIp,2,addrs,10,SQLITE_STATIC);
+ sqlite3_bind_int(_sAllocateIp,3,(int)ZT_IP_ASSIGNMENT_TYPE_ADDRESS);
+ sqlite3_bind_blob(_sAllocateIp,4,(const void *)ipBlob,16,SQLITE_STATIC);
+ sqlite3_bind_int(_sAllocateIp,5,(int)a.netmaskBits());
+ sqlite3_bind_int(_sAllocateIp,6,ipVersion);
+ if (sqlite3_step(_sAllocateIp) != SQLITE_DONE)
+ return 500;
+ }
+ }
+ }
+ addToNetworkRevision = 1;
+ }
+ } else if (!strcmp(j->u.object.values[k].name,"identity")) {
+ // Identity is technically an immutable field, but if the member's Node has
+ // no identity we allow it to be populated. This is primarily for migrating
+ // node data from another controller.
+ json_value *idstr = j->u.object.values[k].value;
+ if (idstr->type == json_string) {
+ bool alreadyHaveIdentity = false;
+
+ sqlite3_reset(_sGetNodeIdentity);
+ sqlite3_bind_text(_sGetNodeIdentity,1,addrs,10,SQLITE_STATIC);
+ if (sqlite3_step(_sGetNodeIdentity) == SQLITE_ROW) {
+ const char *tmp2 = (const char *)sqlite3_column_text(_sGetNodeIdentity,0);
+ if ((tmp2)&&(tmp2[0]))
+ alreadyHaveIdentity = true;
+ }
+
+ if (!alreadyHaveIdentity) {
+ try {
+ Identity id2(idstr->u.string.ptr);
+ if (id2) {
+ std::string idstr2(id2.toString(false)); // object must persist until after sqlite3_step() for SQLITE_STATIC
+ sqlite3_reset(_sCreateOrReplaceNode);
+ sqlite3_bind_text(_sCreateOrReplaceNode,1,addrs,10,SQLITE_STATIC);
+ sqlite3_bind_text(_sCreateOrReplaceNode,2,idstr2.c_str(),-1,SQLITE_STATIC);
+ sqlite3_step(_sCreateOrReplaceNode);
+ }
+ } catch ( ... ) {} // ignore invalid identities
+ }
+ }
+ }
+
+ }
+ }
+ json_value_free(j);
+ }
+
+ if ((addToNetworkRevision > 0)&&(revision > 0)) {
+ sqlite3_reset(_sSetNetworkRevision);
+ sqlite3_bind_int64(_sSetNetworkRevision,1,revision + addToNetworkRevision);
+ sqlite3_bind_text(_sSetNetworkRevision,2,nwids,16,SQLITE_STATIC);
+ sqlite3_step(_sSetNetworkRevision);
+ }
+
+ return _doCPGet(path,urlArgs,headers,body,responseBody,responseContentType);
+ } else if ((path.size() == 3)&&(path[2] == "test")) {
+ ZT_CircuitTest *test = (ZT_CircuitTest *)malloc(sizeof(ZT_CircuitTest));
+ memset(test,0,sizeof(ZT_CircuitTest));
+
+ Utils::getSecureRandom(&(test->testId),sizeof(test->testId));
+ test->credentialNetworkId = nwid;
+ test->ptr = (void *)this;
+
+ json_value *j = json_parse(body.c_str(),body.length());
+ if (j) {
+ if (j->type == json_object) {
+ for(unsigned int k=0;ku.object.length;++k) {
+
+ if (!strcmp(j->u.object.values[k].name,"hops")) {
+ if (j->u.object.values[k].value->type == json_array) {
+ for(unsigned int kk=0;kku.object.values[k].value->u.array.length;++kk) {
+ json_value *hop = j->u.object.values[k].value->u.array.values[kk];
+ if (hop->type == json_array) {
+ for(unsigned int kkk=0;kkku.array.length;++kkk) {
+ if (hop->u.array.values[kkk]->type == json_string) {
+ test->hops[test->hopCount].addresses[test->hops[test->hopCount].breadth++] = Utils::hexStrToU64(hop->u.array.values[kkk]->u.string.ptr) & 0xffffffffffULL;
+ }
+ }
+ ++test->hopCount;
+ }
+ }
+ }
+ } else if (!strcmp(j->u.object.values[k].name,"reportAtEveryHop")) {
+ if (j->u.object.values[k].value->type == json_boolean)
+ test->reportAtEveryHop = (j->u.object.values[k].value->u.boolean == 0) ? 0 : 1;
+ }
+
+ }
+ }
+ json_value_free(j);
+ }
+
+ if (!test->hopCount) {
+ ::free((void *)test);
+ return 500;
+ }
+
+ test->timestamp = OSUtils::now();
+
+ _CircuitTestEntry &te = _circuitTests[test->testId];
+ te.test = test;
+ te.jsonResults = "";
+
+ _node->circuitTestBegin(test,&(SqliteNetworkController::_circuitTestCallback));
+
+ char json[1024];
+ Utils::snprintf(json,sizeof(json),"{\"testId\":\"%.16llx\"}",test->testId);
+ responseBody = json;
+ responseContentType = "application/json";
+
+ return 200;
+ } // else 404
+
+ } else {
+ std::vector path_copy(path);
+
+ if (!networkExists) {
+ if (path[1].substr(10) == "______") {
+ // A special POST /network/##########______ feature lets users create a network
+ // with an arbitrary unused network number at this controller.
+ nwid = 0;
+
+ uint64_t nwidPrefix = (Utils::hexStrToU64(path[1].substr(0,10).c_str()) << 24) & 0xffffffffff000000ULL;
+ uint64_t nwidPostfix = 0;
+ Utils::getSecureRandom(&nwidPostfix,sizeof(nwidPostfix));
+ uint64_t nwidOriginalPostfix = nwidPostfix;
+ do {
+ uint64_t tryNwid = nwidPrefix | (nwidPostfix & 0xffffffULL);
+ if (!nwidPostfix)
+ tryNwid |= 1;
+ Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)tryNwid);
+
+ sqlite3_reset(_sGetNetworkRevision);
+ sqlite3_bind_text(_sGetNetworkRevision,1,nwids,16,SQLITE_STATIC);
+ if (sqlite3_step(_sGetNetworkRevision) != SQLITE_ROW) {
+ nwid = tryNwid;
+ break;
+ }
+
+ ++nwidPostfix;
+ } while (nwidPostfix != nwidOriginalPostfix);
+
+ // 503 means we have no more free IDs for this prefix. You shouldn't host anywhere
+ // near 16 million networks on the same controller, so shouldn't happen.
+ if (!nwid)
+ return 503;
+ }
+
+ sqlite3_reset(_sCreateNetwork);
+ sqlite3_bind_text(_sCreateNetwork,1,nwids,16,SQLITE_STATIC);
+ sqlite3_bind_text(_sCreateNetwork,2,"",0,SQLITE_STATIC);
+ sqlite3_bind_int64(_sCreateNetwork,3,(long long)OSUtils::now());
+ if (sqlite3_step(_sCreateNetwork) != SQLITE_DONE)
+ return 500;
+ path_copy[1].assign(nwids);
+ }
+
+ json_value *j = json_parse(body.c_str(),body.length());
+ if (j) {
+ if (j->type == json_object) {
+ for(unsigned int k=0;ku.object.length;++k) {
+ sqlite3_stmt *stmt = (sqlite3_stmt *)0;
+
+ if (!strcmp(j->u.object.values[k].name,"name")) {
+ if ((j->u.object.values[k].value->type == json_string)&&(j->u.object.values[k].value->u.string.ptr[0])) {
+ if (sqlite3_prepare_v2(_db,"UPDATE Network SET \"name\" = ? WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK)
+ sqlite3_bind_text(stmt,1,j->u.object.values[k].value->u.string.ptr,-1,SQLITE_STATIC);
+ }
+ } else if (!strcmp(j->u.object.values[k].name,"private")) {
+ if (j->u.object.values[k].value->type == json_boolean) {
+ if (sqlite3_prepare_v2(_db,"UPDATE Network SET \"private\" = ? WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK)
+ sqlite3_bind_int(stmt,1,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1);
+ }
+ } else if (!strcmp(j->u.object.values[k].name,"enableBroadcast")) {
+ if (j->u.object.values[k].value->type == json_boolean) {
+ if (sqlite3_prepare_v2(_db,"UPDATE Network SET enableBroadcast = ? WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK)
+ sqlite3_bind_int(stmt,1,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1);
+ }
+ } else if (!strcmp(j->u.object.values[k].name,"allowPassiveBridging")) {
+ if (j->u.object.values[k].value->type == json_boolean) {
+ if (sqlite3_prepare_v2(_db,"UPDATE Network SET allowPassiveBridging = ? WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK)
+ sqlite3_bind_int(stmt,1,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1);
+ }
+ } else if (!strcmp(j->u.object.values[k].name,"v4AssignMode")) {
+ if (j->u.object.values[k].value->type == json_string) {
+ if (sqlite3_prepare_v2(_db,"UPDATE Network SET v4AssignMode = ? WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK)
+ sqlite3_bind_text(stmt,1,j->u.object.values[k].value->u.string.ptr,-1,SQLITE_STATIC);
+ }
+ } else if (!strcmp(j->u.object.values[k].name,"v6AssignMode")) {
+ if (j->u.object.values[k].value->type == json_string) {
+ if (sqlite3_prepare_v2(_db,"UPDATE Network SET v6AssignMode = ? WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK)
+ sqlite3_bind_text(stmt,1,j->u.object.values[k].value->u.string.ptr,-1,SQLITE_STATIC);
+ }
+ } else if (!strcmp(j->u.object.values[k].name,"multicastLimit")) {
+ if (j->u.object.values[k].value->type == json_integer) {
+ if (sqlite3_prepare_v2(_db,"UPDATE Network SET multicastLimit = ? WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK)
+ sqlite3_bind_int(stmt,1,(int)j->u.object.values[k].value->u.integer);
+ }
+ } else if (!strcmp(j->u.object.values[k].name,"relays")) {
+ if (j->u.object.values[k].value->type == json_array) {
+ std::map nodeIdToPhyAddress;
+ for(unsigned int kk=0;kku.object.values[k].value->u.array.length;++kk) {
+ json_value *relay = j->u.object.values[k].value->u.array.values[kk];
+ const char *address = (const char *)0;
+ const char *phyAddress = (const char *)0;
+ if ((relay)&&(relay->type == json_object)) {
+ for(unsigned int rk=0;rku.object.length;++rk) {
+ if ((!strcmp(relay->u.object.values[rk].name,"address"))&&(relay->u.object.values[rk].value->type == json_string))
+ address = relay->u.object.values[rk].value->u.string.ptr;
+ else if ((!strcmp(relay->u.object.values[rk].name,"phyAddress"))&&(relay->u.object.values[rk].value->type == json_string))
+ phyAddress = relay->u.object.values[rk].value->u.string.ptr;
+ }
+ }
+ if ((address)&&(phyAddress))
+ nodeIdToPhyAddress[Address(address)] = InetAddress(phyAddress);
+ }
+
+ sqlite3_reset(_sDeleteRelaysForNetwork);
+ sqlite3_bind_text(_sDeleteRelaysForNetwork,1,nwids,16,SQLITE_STATIC);
+ sqlite3_step(_sDeleteRelaysForNetwork);
+
+ for(std::map::iterator rl(nodeIdToPhyAddress.begin());rl!=nodeIdToPhyAddress.end();++rl) {
+ sqlite3_reset(_sCreateRelay);
+ sqlite3_bind_text(_sCreateRelay,1,nwids,16,SQLITE_STATIC);
+ std::string a(rl->first.toString()),b(rl->second.toString()); // don't destroy strings until sqlite3_step()
+ sqlite3_bind_text(_sCreateRelay,2,a.c_str(),-1,SQLITE_STATIC);
+ sqlite3_bind_text(_sCreateRelay,3,b.c_str(),-1,SQLITE_STATIC);
+ sqlite3_step(_sCreateRelay);
+ }
+ }
+ } else if (!strcmp(j->u.object.values[k].name,"gateways")) {
+ sqlite3_reset(_sDeleteGateways);
+ sqlite3_bind_text(_sDeleteGateways,1,nwids,16,SQLITE_STATIC);
+ sqlite3_step(_sDeleteGateways);
+ if (j->u.object.values[k].value->type == json_array) {
+ for(unsigned int kk=0;kku.object.values[k].value->u.array.length;++kk) {
+ json_value *gateway = j->u.object.values[k].value->u.array.values[kk];
+ if ((gateway)&&(gateway->type == json_string)) {
+ InetAddress gwip(gateway->u.string.ptr);
+ sqlite3_reset(_sCreateGateway);
+ sqlite3_bind_text(_sCreateGateway,1,nwids,16,SQLITE_STATIC);
+ sqlite3_bind_int(_sCreateGateway,4,(int)gwip.metric());
+ if (gwip.ss_family == AF_INET) {
+ char ipBlob[16];
+ memset(ipBlob,0,12);
+ memcpy(ipBlob + 12,gwip.rawIpData(),4);
+ sqlite3_bind_blob(_sCreateGateway,2,(const void *)ipBlob,16,SQLITE_STATIC);
+ sqlite3_bind_int(_sCreateGateway,3,4);
+ sqlite3_step(_sCreateGateway);
+ } else if (gwip.ss_family == AF_INET6) {
+ sqlite3_bind_blob(_sCreateGateway,2,gwip.rawIpData(),16,SQLITE_STATIC);
+ sqlite3_bind_int(_sCreateGateway,3,6);
+ sqlite3_step(_sCreateGateway);
+ }
+ }
+ }
+ }
+ } else if (!strcmp(j->u.object.values[k].name,"ipLocalRoutes")) {
+ sqlite3_reset(_sDeleteLocalRoutes);
+ sqlite3_bind_text(_sDeleteLocalRoutes,1,nwids,16,SQLITE_STATIC);
+ sqlite3_bind_int(_sDeleteLocalRoutes,2,(int)ZT_IP_ASSIGNMENT_TYPE_NETWORK);
+ sqlite3_step(_sDeleteLocalRoutes);
+ if (j->u.object.values[k].value->type == json_array) {
+ for(unsigned int kk=0;kku.object.values[k].value->u.array.length;++kk) {
+ json_value *localRoute = j->u.object.values[k].value->u.array.values[kk];
+ if ((localRoute)&&(localRoute->type == json_string)) {
+ InetAddress lr(localRoute->u.string.ptr);
+ if (lr.ss_family == AF_INET) {
+ char ipBlob[16];
+ memset(ipBlob,0,12);
+ memcpy(ipBlob + 12,lr.rawIpData(),4);
+ sqlite3_reset(_sAllocateIp);
+ sqlite3_bind_text(_sAllocateIp,1,nwids,16,SQLITE_STATIC);
+ sqlite3_bind_null(_sAllocateIp,2);
+ sqlite3_bind_int(_sAllocateIp,3,(int)ZT_IP_ASSIGNMENT_TYPE_NETWORK);
+ sqlite3_bind_blob(_sAllocateIp,4,(const void *)ipBlob,16,SQLITE_STATIC);
+ sqlite3_bind_int(_sAllocateIp,5,lr.netmaskBits());
+ sqlite3_bind_int(_sAllocateIp,6,4);
+ sqlite3_step(_sAllocateIp);
+ } else if (lr.ss_family == AF_INET6) {
+ sqlite3_reset(_sAllocateIp);
+ sqlite3_bind_text(_sAllocateIp,1,nwids,16,SQLITE_STATIC);
+ sqlite3_bind_null(_sAllocateIp,2);
+ sqlite3_bind_int(_sAllocateIp,3,(int)ZT_IP_ASSIGNMENT_TYPE_NETWORK);
+ sqlite3_bind_blob(_sAllocateIp,4,lr.rawIpData(),16,SQLITE_STATIC);
+ sqlite3_bind_int(_sAllocateIp,5,lr.netmaskBits());
+ sqlite3_bind_int(_sAllocateIp,6,6);
+ sqlite3_step(_sAllocateIp);
+ }
+ }
+ }
+ }
+ } else if (!strcmp(j->u.object.values[k].name,"ipAssignmentPools")) {
+ if (j->u.object.values[k].value->type == json_array) {
+ std::vector< std::pair > pools;
+ for(unsigned int kk=0;kku.object.values[k].value->u.array.length;++kk) {
+ json_value *pool = j->u.object.values[k].value->u.array.values[kk];
+ const char *iprs = (const char *)0;
+ const char *ipre = (const char *)0;
+ if ((pool)&&(pool->type == json_object)) {
+ for(unsigned int rk=0;rku.object.length;++rk) {
+ if ((!strcmp(pool->u.object.values[rk].name,"ipRangeStart"))&&(pool->u.object.values[rk].value->type == json_string))
+ iprs = pool->u.object.values[rk].value->u.string.ptr;
+ else if ((!strcmp(pool->u.object.values[rk].name,"ipRangeEnd"))&&(pool->u.object.values[rk].value->type == json_string))
+ ipre = pool->u.object.values[rk].value->u.string.ptr;
+ }
+ }
+ if ((iprs)&&(ipre)) {
+ InetAddress iprs2(iprs);
+ InetAddress ipre2(ipre);
+ if (iprs2.ss_family == ipre2.ss_family) {
+ iprs2.setPort(0);
+ ipre2.setPort(0);
+ pools.push_back(std::pair(iprs2,ipre2));
+ }
+ }
+ }
+ std::sort(pools.begin(),pools.end());
+ pools.erase(std::unique(pools.begin(),pools.end()),pools.end());
+
+ sqlite3_reset(_sDeleteIpAssignmentPoolsForNetwork);
+ sqlite3_bind_text(_sDeleteIpAssignmentPoolsForNetwork,1,nwids,16,SQLITE_STATIC);
+ sqlite3_step(_sDeleteIpAssignmentPoolsForNetwork);
+
+ for(std::vector< std::pair >::const_iterator p(pools.begin());p!=pools.end();++p) {
+ char ipBlob1[16],ipBlob2[16];
+ sqlite3_reset(_sCreateIpAssignmentPool);
+ sqlite3_bind_text(_sCreateIpAssignmentPool,1,nwids,16,SQLITE_STATIC);
+ if (p->first.ss_family == AF_INET) {
+ memset(ipBlob1,0,12);
+ memcpy(ipBlob1 + 12,p->first.rawIpData(),4);
+ memset(ipBlob2,0,12);
+ memcpy(ipBlob2 + 12,p->second.rawIpData(),4);
+ sqlite3_bind_blob(_sCreateIpAssignmentPool,2,(const void *)ipBlob1,16,SQLITE_STATIC);
+ sqlite3_bind_blob(_sCreateIpAssignmentPool,3,(const void *)ipBlob2,16,SQLITE_STATIC);
+ sqlite3_bind_int(_sCreateIpAssignmentPool,4,4);
+ } else if (p->first.ss_family == AF_INET6) {
+ sqlite3_bind_blob(_sCreateIpAssignmentPool,2,p->first.rawIpData(),16,SQLITE_STATIC);
+ sqlite3_bind_blob(_sCreateIpAssignmentPool,3,p->second.rawIpData(),16,SQLITE_STATIC);
+ sqlite3_bind_int(_sCreateIpAssignmentPool,4,6);
+ } else continue;
+ sqlite3_step(_sCreateIpAssignmentPool);
+ }
+ }
+ } else if (!strcmp(j->u.object.values[k].name,"rules")) {
+ if (j->u.object.values[k].value->type == json_array) {
+ sqlite3_reset(_sDeleteRulesForNetwork);
+ sqlite3_bind_text(_sDeleteRulesForNetwork,1,nwids,16,SQLITE_STATIC);
+ sqlite3_step(_sDeleteRulesForNetwork);
+
+ for(unsigned int kk=0;kku.object.values[k].value->u.array.length;++kk) {
+ json_value *rj = j->u.object.values[k].value->u.array.values[kk];
+ if ((rj)&&(rj->type == json_object)) {
+ struct { // NULL pointers indicate missing or NULL -- wildcards
+ const json_int_t *ruleNo;
+ const char *nodeId;
+ const char *sourcePort;
+ const char *destPort;
+ const json_int_t *vlanId;
+ const json_int_t *vlanPcp;
+ const json_int_t *etherType;
+ const char *macSource;
+ const char *macDest;
+ const char *ipSource;
+ const char *ipDest;
+ const json_int_t *ipTos;
+ const json_int_t *ipProtocol;
+ const json_int_t *ipSourcePort;
+ const json_int_t *ipDestPort;
+ const json_int_t *flags;
+ const json_int_t *invFlags;
+ const char *action;
+ } rule;
+ memset(&rule,0,sizeof(rule));
+
+ for(unsigned int rk=0;rku.object.length;++rk) {
+ if ((!strcmp(rj->u.object.values[rk].name,"ruleNo"))&&(rj->u.object.values[rk].value->type == json_integer))
+ rule.ruleNo = &(rj->u.object.values[rk].value->u.integer);
+ else if ((!strcmp(rj->u.object.values[rk].name,"nodeId"))&&(rj->u.object.values[rk].value->type == json_string))
+ rule.nodeId = rj->u.object.values[rk].value->u.string.ptr;
+ else if ((!strcmp(rj->u.object.values[rk].name,"sourcePort"))&&(rj->u.object.values[rk].value->type == json_string))
+ rule.sourcePort = rj->u.object.values[rk].value->u.string.ptr;
+ else if ((!strcmp(rj->u.object.values[rk].name,"destPort"))&&(rj->u.object.values[rk].value->type == json_string))
+ rule.destPort = rj->u.object.values[rk].value->u.string.ptr;
+ else if ((!strcmp(rj->u.object.values[rk].name,"vlanId"))&&(rj->u.object.values[rk].value->type == json_integer))
+ rule.vlanId = &(rj->u.object.values[rk].value->u.integer);
+ else if ((!strcmp(rj->u.object.values[rk].name,"vlanPcp"))&&(rj->u.object.values[rk].value->type == json_integer))
+ rule.vlanPcp = &(rj->u.object.values[rk].value->u.integer);
+ else if ((!strcmp(rj->u.object.values[rk].name,"etherType"))&&(rj->u.object.values[rk].value->type == json_integer))
+ rule.etherType = &(rj->u.object.values[rk].value->u.integer);
+ else if ((!strcmp(rj->u.object.values[rk].name,"macSource"))&&(rj->u.object.values[rk].value->type == json_string))
+ rule.macSource = rj->u.object.values[rk].value->u.string.ptr;
+ else if ((!strcmp(rj->u.object.values[rk].name,"macDest"))&&(rj->u.object.values[rk].value->type == json_string))
+ rule.macDest = rj->u.object.values[rk].value->u.string.ptr;
+ else if ((!strcmp(rj->u.object.values[rk].name,"ipSource"))&&(rj->u.object.values[rk].value->type == json_string))
+ rule.ipSource = rj->u.object.values[rk].value->u.string.ptr;
+ else if ((!strcmp(rj->u.object.values[rk].name,"ipDest"))&&(rj->u.object.values[rk].value->type == json_string))
+ rule.ipDest = rj->u.object.values[rk].value->u.string.ptr;
+ else if ((!strcmp(rj->u.object.values[rk].name,"ipTos"))&&(rj->u.object.values[rk].value->type == json_integer))
+ rule.ipTos = &(rj->u.object.values[rk].value->u.integer);
+ else if ((!strcmp(rj->u.object.values[rk].name,"ipProtocol"))&&(rj->u.object.values[rk].value->type == json_integer))
+ rule.ipProtocol = &(rj->u.object.values[rk].value->u.integer);
+ else if ((!strcmp(rj->u.object.values[rk].name,"ipSourcePort"))&&(rj->u.object.values[rk].value->type == json_integer))
+ rule.ipSourcePort = &(rj->u.object.values[rk].value->u.integer);
+ else if ((!strcmp(rj->u.object.values[rk].name,"ipDestPort"))&&(rj->u.object.values[rk].value->type == json_integer))
+ rule.ipDestPort = &(rj->u.object.values[rk].value->u.integer);
+ else if ((!strcmp(rj->u.object.values[rk].name,"flags"))&&(rj->u.object.values[rk].value->type == json_integer))
+ rule.flags = &(rj->u.object.values[rk].value->u.integer);
+ else if ((!strcmp(rj->u.object.values[rk].name,"invFlags"))&&(rj->u.object.values[rk].value->type == json_integer))
+ rule.invFlags = &(rj->u.object.values[rk].value->u.integer);
+ else if ((!strcmp(rj->u.object.values[rk].name,"action"))&&(rj->u.object.values[rk].value->type == json_string))
+ rule.action = rj->u.object.values[rk].value->u.string.ptr;
+ }
+
+ if ((rule.ruleNo)&&(rule.action)&&(rule.action[0])) {
+ char mactmp1[16],mactmp2[16];
+ sqlite3_reset(_sCreateRule);
+ sqlite3_bind_text(_sCreateRule,1,nwids,16,SQLITE_STATIC);
+ sqlite3_bind_int64(_sCreateRule,2,*rule.ruleNo);
+
+ // Optional values: null by default
+ for(int i=3;i<=18;++i)
+ sqlite3_bind_null(_sCreateRule,i);
+ if ((rule.nodeId)&&(strlen(rule.nodeId) == 10)) sqlite3_bind_text(_sCreateRule,3,rule.nodeId,10,SQLITE_STATIC);
+ if ((rule.sourcePort)&&(strlen(rule.sourcePort) == 10)) sqlite3_bind_text(_sCreateRule,4,rule.sourcePort,10,SQLITE_STATIC);
+ if ((rule.destPort)&&(strlen(rule.destPort) == 10)) sqlite3_bind_text(_sCreateRule,5,rule.destPort,10,SQLITE_STATIC);
+ if (rule.vlanId) sqlite3_bind_int(_sCreateRule,6,(int)*rule.vlanId);
+ if (rule.vlanPcp) sqlite3_bind_int(_sCreateRule,7,(int)*rule.vlanPcp);
+ if (rule.etherType) sqlite3_bind_int(_sCreateRule,8,(int)*rule.etherType & (int)0xffff);
+ if (rule.macSource) {
+ MAC m(rule.macSource);
+ Utils::snprintf(mactmp1,sizeof(mactmp1),"%.12llx",(unsigned long long)m.toInt());
+ sqlite3_bind_text(_sCreateRule,9,mactmp1,-1,SQLITE_STATIC);
+ }
+ if (rule.macDest) {
+ MAC m(rule.macDest);
+ Utils::snprintf(mactmp2,sizeof(mactmp2),"%.12llx",(unsigned long long)m.toInt());
+ sqlite3_bind_text(_sCreateRule,10,mactmp2,-1,SQLITE_STATIC);
+ }
+ if (rule.ipSource) sqlite3_bind_text(_sCreateRule,11,rule.ipSource,-1,SQLITE_STATIC);
+ if (rule.ipDest) sqlite3_bind_text(_sCreateRule,12,rule.ipDest,-1,SQLITE_STATIC);
+ if (rule.ipTos) sqlite3_bind_int(_sCreateRule,13,(int)*rule.ipTos);
+ if (rule.ipProtocol) sqlite3_bind_int(_sCreateRule,14,(int)*rule.ipProtocol);
+ if (rule.ipSourcePort) sqlite3_bind_int(_sCreateRule,15,(int)*rule.ipSourcePort & (int)0xffff);
+ if (rule.ipDestPort) sqlite3_bind_int(_sCreateRule,16,(int)*rule.ipDestPort & (int)0xffff);
+ if (rule.flags) sqlite3_bind_int64(_sCreateRule,17,(int64_t)*rule.flags);
+ if (rule.invFlags) sqlite3_bind_int64(_sCreateRule,18,(int64_t)*rule.invFlags);
+
+ sqlite3_bind_text(_sCreateRule,19,rule.action,-1,SQLITE_STATIC);
+ sqlite3_step(_sCreateRule);
+ }
+ }
+ }
+ }
+ }
+
+ if (stmt) {
+ sqlite3_bind_text(stmt,2,nwids,16,SQLITE_STATIC);
+ sqlite3_step(stmt);
+ sqlite3_finalize(stmt);
+ }
+ }
+ }
+ json_value_free(j);
+ }
+
+ sqlite3_reset(_sSetNetworkRevision);
+ sqlite3_bind_int64(_sSetNetworkRevision,1,revision += 1);
+ sqlite3_bind_text(_sSetNetworkRevision,2,nwids,16,SQLITE_STATIC);
+ sqlite3_step(_sSetNetworkRevision);
+
+ return _doCPGet(path_copy,urlArgs,headers,body,responseBody,responseContentType);
+ }
+
+ } // else 404
+
+ } // else 404
+
+ return 404;
+}
+
+unsigned int SqliteNetworkController::handleControlPlaneHttpDELETE(
+ const std::vector &path,
+ const std::map &urlArgs,
+ const std::map &headers,
+ const std::string &body,
+ std::string &responseBody,
+ std::string &responseContentType)
+{
+ if (path.empty())
+ return 404;
+ Mutex::Lock _l(_lock);
+
+ if (path[0] == "network") {
+
+ if ((path.size() >= 2)&&(path[1].length() == 16)) {
+ uint64_t nwid = Utils::hexStrToU64(path[1].c_str());
+ char nwids[24];
+ Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
+
+ sqlite3_reset(_sGetNetworkById);
+ sqlite3_bind_text(_sGetNetworkById,1,nwids,16,SQLITE_STATIC);
+ if (sqlite3_step(_sGetNetworkById) != SQLITE_ROW)
+ return 404;
+
+ if (path.size() >= 3) {
+
+ if ((path.size() == 4)&&(path[2] == "member")&&(path[3].length() == 10)) {
+ uint64_t address = Utils::hexStrToU64(path[3].c_str());
+ char addrs[24];
+ Utils::snprintf(addrs,sizeof(addrs),"%.10llx",address);
+
+ sqlite3_reset(_sGetMember);
+ sqlite3_bind_text(_sGetMember,1,nwids,16,SQLITE_STATIC);
+ sqlite3_bind_text(_sGetMember,2,addrs,10,SQLITE_STATIC);
+ if (sqlite3_step(_sGetMember) != SQLITE_ROW)
+ return 404;
+
+ sqlite3_reset(_sDeleteIpAllocations);
+ sqlite3_bind_text(_sDeleteIpAllocations,1,nwids,16,SQLITE_STATIC);
+ sqlite3_bind_text(_sDeleteIpAllocations,2,addrs,10,SQLITE_STATIC);
+ sqlite3_bind_int(_sDeleteIpAllocations,3,(int)ZT_IP_ASSIGNMENT_TYPE_ADDRESS);
+ if (sqlite3_step(_sDeleteIpAllocations) == SQLITE_DONE) {
+ sqlite3_reset(_sDeleteMember);
+ sqlite3_bind_text(_sDeleteMember,1,nwids,16,SQLITE_STATIC);
+ sqlite3_bind_text(_sDeleteMember,2,addrs,10,SQLITE_STATIC);
+ if (sqlite3_step(_sDeleteMember) != SQLITE_DONE)
+ return 500;
+ } else return 500;
+
+ return 200;
+ }
+
+ } else {
+
+ sqlite3_reset(_sDeleteNetwork);
+ sqlite3_bind_text(_sDeleteNetwork,1,nwids,16,SQLITE_STATIC);
+ if (sqlite3_step(_sDeleteNetwork) == SQLITE_DONE) {
+ sqlite3_reset(_sDeleteAllNetworkMembers);
+ sqlite3_bind_text(_sDeleteAllNetworkMembers,1,nwids,16,SQLITE_STATIC);
+ sqlite3_step(_sDeleteAllNetworkMembers);
+ return 200;
+ } else return 500;
+
+ }
+ } // else 404
+
+ } // else 404
+
+ return 404;
+}
+
+void SqliteNetworkController::threadMain()
+ throw()
+{
+ uint64_t lastBackupTime = OSUtils::now();
+ uint64_t lastCleanupTime = OSUtils::now();
+
+ while (_backupThreadRun) {
+ if ((OSUtils::now() - lastCleanupTime) >= 5000) {
+ const uint64_t now = OSUtils::now();
+ lastCleanupTime = now;
+
+ Mutex::Lock _l(_lock);
+
+ // Clean out really old circuit tests to prevent memory build-up
+ for(std::map< uint64_t,_CircuitTestEntry >::iterator ct(_circuitTests.begin());ct!=_circuitTests.end();) {
+ if (!ct->second.test) {
+ _circuitTests.erase(ct++);
+ } else if ((now - ct->second.test->timestamp) >= ZT_SQLITENETWORKCONTROLLER_CIRCUIT_TEST_TIMEOUT) {
+ _node->circuitTestEnd(ct->second.test);
+ ::free((void *)ct->second.test);
+ _circuitTests.erase(ct++);
+ } else ++ct;
+ }
+ }
+
+ if ((OSUtils::now() - lastBackupTime) >= ZT_NETCONF_BACKUP_PERIOD) {
+ lastBackupTime = OSUtils::now();
+
+ char backupPath[4096],backupPath2[4096];
+ Utils::snprintf(backupPath,sizeof(backupPath),"%s.backupInProgress",_dbPath.c_str());
+ Utils::snprintf(backupPath2,sizeof(backupPath),"%s.backup",_dbPath.c_str());
+ OSUtils::rm(backupPath); // delete any unfinished backups
+
+ sqlite3 *bakdb = (sqlite3 *)0;
+ sqlite3_backup *bak = (sqlite3_backup *)0;
+ if (sqlite3_open_v2(backupPath,&bakdb,SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE,(const char *)0) != SQLITE_OK) {
+ fprintf(stderr,"SqliteNetworkController: CRITICAL: backup failed on sqlite3_open_v2()"ZT_EOL_S);
+ continue;
+ }
+ bak = sqlite3_backup_init(bakdb,"main",_db,"main");
+ if (!bak) {
+ sqlite3_close(bakdb);
+ OSUtils::rm(backupPath); // delete any unfinished backups
+ fprintf(stderr,"SqliteNetworkController: CRITICAL: backup failed on sqlite3_backup_init()"ZT_EOL_S);
+ continue;
+ }
+
+ int rc = SQLITE_OK;
+ for(;;) {
+ if (!_backupThreadRun) {
+ sqlite3_backup_finish(bak);
+ sqlite3_close(bakdb);
+ OSUtils::rm(backupPath);
+ return;
+ }
+ _lock.lock();
+ rc = sqlite3_backup_step(bak,64);
+ _lock.unlock();
+ if ((rc == SQLITE_OK)||(rc == SQLITE_LOCKED)||(rc == SQLITE_BUSY))
+ Thread::sleep(50);
+ else break;
+ }
+
+ sqlite3_backup_finish(bak);
+ sqlite3_close(bakdb);
+
+ OSUtils::rm(backupPath2);
+ ::rename(backupPath,backupPath2);
+ }
+
+ Thread::sleep(250);
+ }
+}
+
+unsigned int SqliteNetworkController::_doCPGet(
+ const std::vector &path,
+ const std::map &urlArgs,
+ const std::map &headers,
+ const std::string &body,
+ std::string &responseBody,
+ std::string &responseContentType)
+{
+ // Assumes _lock is locked
+ char json[65536];
+
+ if ((path.size() > 0)&&(path[0] == "network")) {
+
+ if ((path.size() >= 2)&&(path[1].length() == 16)) {
+ uint64_t nwid = Utils::hexStrToU64(path[1].c_str());
+ char nwids[24];
+ Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
+
+ if (path.size() >= 3) {
+ // /network//...
+
+ if (path[2] == "member") {
+
+ if (path.size() >= 4) {
+ // Get specific member info
+
+ uint64_t address = Utils::hexStrToU64(path[3].c_str());
+ char addrs[24];
+ Utils::snprintf(addrs,sizeof(addrs),"%.10llx",address);
+
+ sqlite3_reset(_sGetMember2);
+ sqlite3_bind_text(_sGetMember2,1,nwids,16,SQLITE_STATIC);
+ sqlite3_bind_text(_sGetMember2,2,addrs,10,SQLITE_STATIC);
+ if (sqlite3_step(_sGetMember2) == SQLITE_ROW) {
+ const char *memberIdStr = (const char *)sqlite3_column_text(_sGetMember2,3);
+
+ // If testSingingId is included in the URL or X-ZT1-TestSigningId in the headers
+ // and if it contains an identity with a secret portion, the resturned JSON
+ // will contain an extra field called _testConf. This will contain several
+ // fields that report the result of doNetworkConfigRequest() for this member.
+ std::string testFields;
+ /*
+ {
+ Identity testOutputSigningId;
+ std::map::const_iterator sid(urlArgs.find("testSigningId"));
+ if (sid != urlArgs.end()) {
+ testOutputSigningId.fromString(sid->second.c_str());
+ } else {
+ sid = headers.find("x-zt1-testsigningid");
+ if (sid != headers.end())
+ testOutputSigningId.fromString(sid->second.c_str());
+ }
+
+ if ((testOutputSigningId.hasPrivate())&&(memberIdStr)) {
+ Dictionary testNetconf;
+ NetworkController::ResultCode rc = this->_doNetworkConfigRequest(
+ InetAddress(),
+ testOutputSigningId,
+ Identity(memberIdStr),
+ nwid,
+ NetworkConfigRequestMetaData(), // TODO: allow passing of meta-data for testing
+ testNetconf);
+ char rcs[16];
+ Utils::snprintf(rcs,sizeof(rcs),"%d,\n",(int)rc);
+ testFields.append("\t\"_test\": {\n");
+ testFields.append("\t\t\"resultCode\": "); testFields.append(rcs);
+ testFields.append("\t\t\"result\": \""); testFields.append(_jsonEscape(testNetconf.toString().c_str()).c_str()); testFields.append("\",\n");
+ testFields.append("\t\t\"resultJson\": {\n");
+ for(Dictionary::const_iterator i(testNetconf.begin());i!=testNetconf.end();++i) {
+ if (i != testNetconf.begin())
+ testFields.append(",\n");
+ testFields.append("\t\t\t\"");
+ testFields.append(i->first);
+ testFields.append("\": \"");
+ testFields.append(_jsonEscape(i->second.c_str()));
+ testFields.push_back('"');
+ }
+ testFields.append("\n\t\t}\n");
+ testFields.append("\t},\n");
+ }
+ }
+ */
+
+ Utils::snprintf(json,sizeof(json),
+ "{\n%s"
+ "\t\"nwid\": \"%s\",\n"
+ "\t\"address\": \"%s\",\n"
+ "\t\"controllerInstanceId\": \"%s\",\n"
+ "\t\"authorized\": %s,\n"
+ "\t\"activeBridge\": %s,\n"
+ "\t\"memberRevision\": %llu,\n"
+ "\t\"clock\": %llu,\n"
+ "\t\"identity\": \"%s\",\n"
+ "\t\"ipAssignments\": [",
+ testFields.c_str(),
+ nwids,
+ addrs,
+ _instanceId.c_str(),
+ (sqlite3_column_int(_sGetMember2,0) > 0) ? "true" : "false",
+ (sqlite3_column_int(_sGetMember2,1) > 0) ? "true" : "false",
+ (unsigned long long)sqlite3_column_int64(_sGetMember2,2),
+ (unsigned long long)OSUtils::now(),
+ _jsonEscape(memberIdStr).c_str());
+ responseBody = json;
+
+ sqlite3_reset(_sGetIpAssignmentsForNode2);
+ sqlite3_bind_text(_sGetIpAssignmentsForNode2,1,nwids,16,SQLITE_STATIC);
+ sqlite3_bind_text(_sGetIpAssignmentsForNode2,2,addrs,10,SQLITE_STATIC);
+ sqlite3_bind_int(_sGetIpAssignmentsForNode2,3,(int)ZT_IP_ASSIGNMENT_TYPE_ADDRESS);
+ bool firstIp = true;
+ while (sqlite3_step(_sGetIpAssignmentsForNode2) == SQLITE_ROW) {
+ int ipversion = sqlite3_column_int(_sGetIpAssignmentsForNode2,2);
+ char ipBlob[16];
+ memcpy(ipBlob,(const void *)sqlite3_column_blob(_sGetIpAssignmentsForNode2,0),16);
+ InetAddress ip(
+ (const void *)(ipversion == 6 ? ipBlob : &ipBlob[12]),
+ (ipversion == 6 ? 16 : 4),
+ (unsigned int)sqlite3_column_int(_sGetIpAssignmentsForNode2,1)
+ );
+ responseBody.append(firstIp ? "\"" : ",\"");
+ responseBody.append(_jsonEscape(ip.toString()));
+ responseBody.push_back('"');
+ firstIp = false;
+ }
+
+ responseBody.append("],\n\t\"recentLog\": [");
+
+ sqlite3_reset(_sGetNodeHistory);
+ sqlite3_bind_text(_sGetNodeHistory,1,nwids,16,SQLITE_STATIC);
+ sqlite3_bind_text(_sGetNodeHistory,2,addrs,10,SQLITE_STATIC);
+ bool firstHistory = true;
+ while (sqlite3_step(_sGetNodeHistory) == SQLITE_ROW) {
+ responseBody.append(firstHistory ? "{" : ",{");
+ responseBody.append("\"ts\":");
+ responseBody.append((const char *)sqlite3_column_text(_sGetNodeHistory,2));
+ responseBody.append((sqlite3_column_int(_sGetNodeHistory,1) == 0) ? ",\"authorized\":false,\"clientMajorVersion\":" : ",\"authorized\":true,\"clientMajorVersion\":");
+ responseBody.append((const char *)sqlite3_column_text(_sGetNodeHistory,3));
+ responseBody.append(",\"clientMinorVersion\":");
+ responseBody.append((const char *)sqlite3_column_text(_sGetNodeHistory,4));
+ responseBody.append(",\"clientRevision\":");
+ responseBody.append((const char *)sqlite3_column_text(_sGetNodeHistory,5));
+ responseBody.append(",\"fromAddr\":");
+ const char *fa = (const char *)sqlite3_column_text(_sGetNodeHistory,7);
+ if (fa) {
+ responseBody.push_back('"');
+ responseBody.append(_jsonEscape(fa));
+ responseBody.append("\"}");
+ } else responseBody.append("null}");
+ firstHistory = false;
+ }
+ responseBody.append("]\n}\n");
+
+ responseContentType = "application/json";
+ return 200;
+ } // else 404
+
+ } else {
+ // List members
+
+ sqlite3_reset(_sListNetworkMembers);
+ sqlite3_bind_text(_sListNetworkMembers,1,nwids,16,SQLITE_STATIC);
+ responseBody.push_back('{');
+ bool firstMember = true;
+ while (sqlite3_step(_sListNetworkMembers) == SQLITE_ROW) {
+ responseBody.append(firstMember ? "\"" : ",\"");
+ firstMember = false;
+ responseBody.append((const char *)sqlite3_column_text(_sListNetworkMembers,0));
+ responseBody.append("\":");
+ responseBody.append((const char *)sqlite3_column_text(_sListNetworkMembers,1));
+ }
+ responseBody.push_back('}');
+ responseContentType = "application/json";
+ return 200;
+
+ }
+
+ } else if ((path[2] == "active")&&(path.size() == 3)) {
+
+ sqlite3_reset(_sGetActiveNodesOnNetwork);
+ sqlite3_bind_text(_sGetActiveNodesOnNetwork,1,nwids,16,SQLITE_STATIC);
+ sqlite3_bind_text(_sGetActiveNodesOnNetwork,2,nwids,16,SQLITE_STATIC);
+ sqlite3_bind_int64(_sGetActiveNodesOnNetwork,3,(int64_t)(OSUtils::now() - ZT_NETCONF_NODE_ACTIVE_THRESHOLD));
+
+ responseBody.push_back('{');
+ bool firstMember = true;
+ uint64_t lastNodeId = 0;
+ while (sqlite3_step(_sGetActiveNodesOnNetwork) == SQLITE_ROW) {
+ const char *nodeId = (const char *)sqlite3_column_text(_sGetActiveNodesOnNetwork,0);
+ if (nodeId) {
+ const uint64_t nodeIdInt = Utils::hexStrToU64(nodeId);
+ if (nodeIdInt == lastNodeId) // technically that SQL query could (rarely) generate a duplicate for a given nodeId, in which case we want the first
+ continue;
+ lastNodeId = nodeIdInt;
+
+ responseBody.append(firstMember ? "\"" : ",\"");
+ firstMember = false;
+ responseBody.append(nodeId);
+ responseBody.append("\":{");
+ responseBody.append("\"ts\":");
+ responseBody.append((const char *)sqlite3_column_text(_sGetActiveNodesOnNetwork,1));
+ responseBody.append((sqlite3_column_int(_sGetActiveNodesOnNetwork,6) > 0) ? ",\"authorized\":true" : ",\"authorized\":false");
+ responseBody.append(",\"clientMajorVersion\":");
+ responseBody.append((const char *)sqlite3_column_text(_sGetActiveNodesOnNetwork,2));
+ responseBody.append(",\"clientMinorVersion\":");
+ responseBody.append((const char *)sqlite3_column_text(_sGetActiveNodesOnNetwork,3));
+ responseBody.append(",\"clientRevision\":");
+ responseBody.append((const char *)sqlite3_column_text(_sGetActiveNodesOnNetwork,4));
+ const char *fromAddr = (const char *)sqlite3_column_text(_sGetActiveNodesOnNetwork,5);
+ if ((fromAddr)&&(fromAddr[0])) {
+ responseBody.append(",\"fromAddr\":\"");
+ responseBody.append(_jsonEscape(fromAddr));
+ responseBody.append("\"}");
+ } else {
+ responseBody.append(",\"fromAddr\":null}");
+ }
+ }
+ }
+ responseBody.push_back('}');
+
+ responseContentType = "application/json";
+ return 200;
+
+ } else if ((path[2] == "test")&&(path.size() >= 4)) {
+
+ std::map< uint64_t,_CircuitTestEntry >::iterator cte(_circuitTests.find(Utils::hexStrToU64(path[3].c_str())));
+ if ((cte != _circuitTests.end())&&(cte->second.test)) {
+
+ responseBody = "[";
+ responseBody.append(cte->second.jsonResults);
+ responseBody.push_back(']');
+ responseContentType = "application/json";
+
+ return 200;
+
+ } // else 404
+
+ } // else 404
+
+ } else {
+
+ sqlite3_reset(_sGetNetworkById);
+ sqlite3_bind_text(_sGetNetworkById,1,nwids,16,SQLITE_STATIC);
+ if (sqlite3_step(_sGetNetworkById) == SQLITE_ROW) {
+ Utils::snprintf(json,sizeof(json),
+ "{\n"
+ "\t\"nwid\": \"%s\",\n"
+ "\t\"controllerInstanceId\": \"%s\",\n"
+ "\t\"clock\": %llu,\n"
+ "\t\"name\": \"%s\",\n"
+ "\t\"private\": %s,\n"
+ "\t\"enableBroadcast\": %s,\n"
+ "\t\"allowPassiveBridging\": %s,\n"
+ "\t\"v4AssignMode\": \"%s\",\n"
+ "\t\"v6AssignMode\": \"%s\",\n"
+ "\t\"multicastLimit\": %d,\n"
+ "\t\"creationTime\": %llu,\n"
+ "\t\"revision\": %llu,\n"
+ "\t\"memberRevisionCounter\": %llu,\n"
+ "\t\"authorizedMemberCount\": %llu,\n"
+ "\t\"relays\": [",
+ nwids,
+ _instanceId.c_str(),
+ (unsigned long long)OSUtils::now(),
+ _jsonEscape((const char *)sqlite3_column_text(_sGetNetworkById,0)).c_str(),
+ (sqlite3_column_int(_sGetNetworkById,1) > 0) ? "true" : "false",
+ (sqlite3_column_int(_sGetNetworkById,2) > 0) ? "true" : "false",
+ (sqlite3_column_int(_sGetNetworkById,3) > 0) ? "true" : "false",
+ _jsonEscape((const char *)sqlite3_column_text(_sGetNetworkById,4)).c_str(),
+ _jsonEscape((const char *)sqlite3_column_text(_sGetNetworkById,5)).c_str(),
+ sqlite3_column_int(_sGetNetworkById,6),
+ (unsigned long long)sqlite3_column_int64(_sGetNetworkById,7),
+ (unsigned long long)sqlite3_column_int64(_sGetNetworkById,8),
+ (unsigned long long)sqlite3_column_int64(_sGetNetworkById,9),
+ (unsigned long long)sqlite3_column_int64(_sGetNetworkById,10));
+ responseBody = json;
+
+ sqlite3_reset(_sGetRelays);
+ sqlite3_bind_text(_sGetRelays,1,nwids,16,SQLITE_STATIC);
+ bool firstRelay = true;
+ while (sqlite3_step(_sGetRelays) == SQLITE_ROW) {
+ responseBody.append(firstRelay ? "\n\t\t" : ",\n\t\t");
+ firstRelay = false;
+ responseBody.append("{\"address\":\"");
+ responseBody.append((const char *)sqlite3_column_text(_sGetRelays,0));
+ responseBody.append("\",\"phyAddress\":\"");
+ responseBody.append(_jsonEscape((const char *)sqlite3_column_text(_sGetRelays,1)));
+ responseBody.append("\"}");
+ }
+
+ responseBody.append("],\n\t\"gateways\": [");
+
+ sqlite3_reset(_sGetGateways);
+ sqlite3_bind_text(_sGetGateways,1,nwids,16,SQLITE_STATIC);
+ bool firstGateway = true;
+ while (sqlite3_step(_sGetGateways) == SQLITE_ROW) {
+ char tmp[128];
+ const unsigned char *ip = (const unsigned char *)sqlite3_column_blob(_sGetGateways,0);
+ switch(sqlite3_column_int(_sGetGateways,1)) { // ipVersion
+ case 4:
+ Utils::snprintf(tmp,sizeof(tmp),"%s%d.%d.%d.%d/%d\"",
+ (firstGateway) ? "\"" : ",\"",
+ (int)ip[12],
+ (int)ip[13],
+ (int)ip[14],
+ (int)ip[15],
+ (int)sqlite3_column_int(_sGetGateways,2)); // metric
+ break;
+ case 6:
+ Utils::snprintf(tmp,sizeof(tmp),"%s%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x/%d\"",
+ (firstGateway) ? "\"" : ",\"",
+ (int)ip[0],
+ (int)ip[1],
+ (int)ip[2],
+ (int)ip[3],
+ (int)ip[4],
+ (int)ip[5],
+ (int)ip[6],
+ (int)ip[7],
+ (int)ip[8],
+ (int)ip[9],
+ (int)ip[10],
+ (int)ip[11],
+ (int)ip[12],
+ (int)ip[13],
+ (int)ip[14],
+ (int)ip[15],
+ (int)sqlite3_column_int(_sGetGateways,2)); // metric
+ break;
+ }
+ responseBody.append(tmp);
+ firstGateway = false;
+ }
+
+ responseBody.append("],\n\t\"ipLocalRoutes\": [");
+
+ sqlite3_reset(_sGetLocalRoutes);
+ sqlite3_bind_text(_sGetLocalRoutes,1,nwids,16,SQLITE_STATIC);
+ sqlite3_bind_int(_sGetLocalRoutes,2,(int)ZT_IP_ASSIGNMENT_TYPE_NETWORK);
+ bool firstLocalRoute = true;
+ while (sqlite3_step(_sGetLocalRoutes) == SQLITE_ROW) {
+ char tmp[128];
+ const unsigned char *ip = (const unsigned char *)sqlite3_column_blob(_sGetLocalRoutes,0);
+ switch (sqlite3_column_int(_sGetLocalRoutes,2)) {
+ case 4:
+ Utils::snprintf(tmp,sizeof(tmp),"%s%d.%d.%d.%d/%d\"",
+ (firstLocalRoute) ? "\"" : ",\"",
+ (int)ip[12],
+ (int)ip[13],
+ (int)ip[14],
+ (int)ip[15],
+ (int)sqlite3_column_int(_sGetLocalRoutes,1)); // netmask bits
+ break;
+ case 6:
+ Utils::snprintf(tmp,sizeof(tmp),"%s%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x/%d\"",
+ (firstLocalRoute) ? "\"" : ",\"",
+ (int)ip[0],
+ (int)ip[1],
+ (int)ip[2],
+ (int)ip[3],
+ (int)ip[4],
+ (int)ip[5],
+ (int)ip[6],
+ (int)ip[7],
+ (int)ip[8],
+ (int)ip[9],
+ (int)ip[10],
+ (int)ip[11],
+ (int)ip[12],
+ (int)ip[13],
+ (int)ip[14],
+ (int)ip[15],
+ (int)sqlite3_column_int(_sGetLocalRoutes,1)); // netmask bits
+ break;
+ }
+ responseBody.append(tmp);
+ firstLocalRoute = false;
+ }
+
+ responseBody.append("],\n\t\"ipAssignmentPools\": [");
+
+ sqlite3_reset(_sGetIpAssignmentPools2);
+ sqlite3_bind_text(_sGetIpAssignmentPools2,1,nwids,16,SQLITE_STATIC);
+ bool firstIpAssignmentPool = true;
+ while (sqlite3_step(_sGetIpAssignmentPools2) == SQLITE_ROW) {
+ const char *ipRangeStartB = reinterpret_cast(sqlite3_column_blob(_sGetIpAssignmentPools2,0));
+ const char *ipRangeEndB = reinterpret_cast(sqlite3_column_blob(_sGetIpAssignmentPools2,1));
+ if ((ipRangeStartB)&&(ipRangeEndB)) {
+ InetAddress ipps,ippe;
+ int ipVersion = sqlite3_column_int(_sGetIpAssignmentPools2,2);
+ if (ipVersion == 4) {
+ ipps.set((const void *)(ipRangeStartB + 12),4,0);
+ ippe.set((const void *)(ipRangeEndB + 12),4,0);
+ } else if (ipVersion == 6) {
+ ipps.set((const void *)ipRangeStartB,16,0);
+ ippe.set((const void *)ipRangeEndB,16,0);
+ }
+ if (ipps) {
+ responseBody.append(firstIpAssignmentPool ? "\n\t\t" : ",\n\t\t");
+ firstIpAssignmentPool = false;
+ Utils::snprintf(json,sizeof(json),"{\"ipRangeStart\":\"%s\",\"ipRangeEnd\":\"%s\"}",
+ _jsonEscape(ipps.toIpString()).c_str(),
+ _jsonEscape(ippe.toIpString()).c_str());
+ responseBody.append(json);
+ }
+ }
+ }
+
+ responseBody.append("],\n\t\"rules\": [");
+
+ sqlite3_reset(_sListRules);
+ sqlite3_bind_text(_sListRules,1,nwids,16,SQLITE_STATIC);
+ bool firstRule = true;
+ while (sqlite3_step(_sListRules) == SQLITE_ROW) {
+ responseBody.append(firstRule ? "\n\t{\n" : ",{\n");
+ firstRule = false;
+ Utils::snprintf(json,sizeof(json),"\t\t\"ruleNo\": %lld,\n",sqlite3_column_int64(_sListRules,0));
+ responseBody.append(json);
+ if (sqlite3_column_type(_sListRules,1) != SQLITE_NULL) {
+ Utils::snprintf(json,sizeof(json),"\t\t\"nodeId\": \"%s\",\n",(const char *)sqlite3_column_text(_sListRules,1));
+ responseBody.append(json);
+ }
+ if (sqlite3_column_type(_sListRules,2) != SQLITE_NULL) {
+ Utils::snprintf(json,sizeof(json),"\t\t\"sourcePort\": \"%s\",\n",(const char *)sqlite3_column_text(_sListRules,2));
+ responseBody.append(json);
+ }
+ if (sqlite3_column_type(_sListRules,3) != SQLITE_NULL) {
+ Utils::snprintf(json,sizeof(json),"\t\t\"destPort\": \"%s\",\n",(const char *)sqlite3_column_text(_sListRules,3));
+ responseBody.append(json);
+ }
+ if (sqlite3_column_type(_sListRules,4) != SQLITE_NULL) {
+ Utils::snprintf(json,sizeof(json),"\t\t\"vlanId\": %d,\n",sqlite3_column_int(_sListRules,4));
+ responseBody.append(json);
+ }
+ if (sqlite3_column_type(_sListRules,5) != SQLITE_NULL) {
+ Utils::snprintf(json,sizeof(json),"\t\t\"vlanPcp\": %d,\n",sqlite3_column_int(_sListRules,5));
+ responseBody.append(json);
+ }
+ if (sqlite3_column_type(_sListRules,6) != SQLITE_NULL) {
+ Utils::snprintf(json,sizeof(json),"\t\t\"etherType\": %d,\n",sqlite3_column_int(_sListRules,6));
+ responseBody.append(json);
+ }
+ if (sqlite3_column_type(_sListRules,7) != SQLITE_NULL) {
+ Utils::snprintf(json,sizeof(json),"\t\t\"macSource\": \"%s\",\n",MAC((const char *)sqlite3_column_text(_sListRules,7)).toString().c_str());
+ responseBody.append(json);
+ }
+ if (sqlite3_column_type(_sListRules,8) != SQLITE_NULL) {
+ Utils::snprintf(json,sizeof(json),"\t\t\"macDest\": \"%s\",\n",MAC((const char *)sqlite3_column_text(_sListRules,8)).toString().c_str());
+ responseBody.append(json);
+ }
+ if (sqlite3_column_type(_sListRules,9) != SQLITE_NULL) {
+ Utils::snprintf(json,sizeof(json),"\t\t\"ipSource\": \"%s\",\n",_jsonEscape((const char *)sqlite3_column_text(_sListRules,9)).c_str());
+ responseBody.append(json);
+ }
+ if (sqlite3_column_type(_sListRules,10) != SQLITE_NULL) {
+ Utils::snprintf(json,sizeof(json),"\t\t\"ipDest\": \"%s\",\n",_jsonEscape((const char *)sqlite3_column_text(_sListRules,10)).c_str());
+ responseBody.append(json);
+ }
+ if (sqlite3_column_type(_sListRules,11) != SQLITE_NULL) {
+ Utils::snprintf(json,sizeof(json),"\t\t\"ipTos\": %d,\n",sqlite3_column_int(_sListRules,11));
+ responseBody.append(json);
+ }
+ if (sqlite3_column_type(_sListRules,12) != SQLITE_NULL) {
+ Utils::snprintf(json,sizeof(json),"\t\t\"ipProtocol\": %d,\n",sqlite3_column_int(_sListRules,12));
+ responseBody.append(json);
+ }
+ if (sqlite3_column_type(_sListRules,13) != SQLITE_NULL) {
+ Utils::snprintf(json,sizeof(json),"\t\t\"ipSourcePort\": %d,\n",sqlite3_column_int(_sListRules,13));
+ responseBody.append(json);
+ }
+ if (sqlite3_column_type(_sListRules,14) != SQLITE_NULL) {
+ Utils::snprintf(json,sizeof(json),"\t\t\"ipDestPort\": %d,\n",sqlite3_column_int(_sListRules,14));
+ responseBody.append(json);
+ }
+ if (sqlite3_column_type(_sListRules,15) != SQLITE_NULL) {
+ Utils::snprintf(json,sizeof(json),"\t\t\"flags\": %lu,\n",(unsigned long)sqlite3_column_int64(_sListRules,15));
+ responseBody.append(json);
+ }
+ if (sqlite3_column_type(_sListRules,16) != SQLITE_NULL) {
+ Utils::snprintf(json,sizeof(json),"\t\t\"invFlags\": %lu,\n",(unsigned long)sqlite3_column_int64(_sListRules,16));
+ responseBody.append(json);
+ }
+ responseBody.append("\t\t\"action\": \"");
+ responseBody.append(_jsonEscape( (sqlite3_column_type(_sListRules,17) == SQLITE_NULL) ? "drop" : (const char *)sqlite3_column_text(_sListRules,17) ));
+ responseBody.append("\"\n\t}");
+ }
+
+ responseBody.append("]\n}\n");
+ responseContentType = "application/json";
+ return 200;
+ } // else 404
+ }
+ } else if (path.size() == 1) {
+ // list networks
+ sqlite3_reset(_sListNetworks);
+ responseContentType = "application/json";
+ responseBody = "[";
+ bool first = true;
+ while (sqlite3_step(_sListNetworks) == SQLITE_ROW) {
+ if (first) {
+ first = false;
+ responseBody.push_back('"');
+ } else responseBody.append(",\"");
+ responseBody.append((const char *)sqlite3_column_text(_sListNetworks,0));
+ responseBody.push_back('"');
+ }
+ responseBody.push_back(']');
+ return 200;
+ } // else 404
+
+ } else {
+ // GET /controller returns status and API version if controller is supported
+ Utils::snprintf(json,sizeof(json),"{\n\t\"controller\": true,\n\t\"apiVersion\": %d,\n\t\"clock\": %llu,\n\t\"instanceId\": \"%s\"\n}\n",ZT_NETCONF_CONTROLLER_API_VERSION,(unsigned long long)OSUtils::now(),_instanceId.c_str());
+ responseBody = json;
+ responseContentType = "application/json";
+ return 200;
+ }
+
+ return 404;
+}
+
+NetworkController::ResultCode SqliteNetworkController::_doNetworkConfigRequest(const InetAddress &fromAddr,const Identity &signingId,const Identity &identity,uint64_t nwid,const NetworkConfigRequestMetaData &metaData,Buffer<8194> &netconf)
+{
+ // Assumes _lock is locked
+
+ if (((!signingId)||(!signingId.hasPrivate()))||(signingId.address().toInt() != (nwid >> 24))) {
+ return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR;
+ }
+
+ // Note: we can't reuse prepared statements that return const char * pointers without
+ // making our own copy in e.g. a std::string first.
+
+ const bool clientIs104 = (Utils::compareVersion(metaData.majorVersion,metaData.minorVersion,metaData.revision,1,0,4) >= 0);
+ const uint64_t now = OSUtils::now();
+
+ // Check rate limit circuit breaker to prevent flooding
+ {
+ uint64_t &lrt = _lastRequestTime[std::pair(identity.address().toInt(),nwid)];
+ if ((now - lrt) <= ZT_NETCONF_MIN_REQUEST_PERIOD)
+ return NetworkController::NETCONF_QUERY_IGNORE;
+ lrt = now;
+ }
+
+ NetworkRecord network;
+ memset(&network,0,sizeof(network));
+ Utils::snprintf(network.id,sizeof(network.id),"%.16llx",(unsigned long long)nwid);
+
+ MemberRecord member;
+ memset(&member,0,sizeof(member));
+ Utils::snprintf(member.nodeId,sizeof(member.nodeId),"%.10llx",(unsigned long long)identity.address().toInt());
+
+ // Create Node record or do full identity check if we already have one
+
+ sqlite3_reset(_sGetNodeIdentity);
+ sqlite3_bind_text(_sGetNodeIdentity,1,member.nodeId,10,SQLITE_STATIC);
+ if (sqlite3_step(_sGetNodeIdentity) == SQLITE_ROW) {
+ try {
+ Identity alreadyKnownIdentity((const char *)sqlite3_column_text(_sGetNodeIdentity,0));
+ if (alreadyKnownIdentity != identity)
+ return NetworkController::NETCONF_QUERY_ACCESS_DENIED;
+ } catch ( ... ) { // identity stored in database is not valid or is NULL
+ return NetworkController::NETCONF_QUERY_ACCESS_DENIED;
+ }
+ } else {
+ std::string idstr(identity.toString(false));
+ sqlite3_reset(_sCreateOrReplaceNode);
+ sqlite3_bind_text(_sCreateOrReplaceNode,1,member.nodeId,10,SQLITE_STATIC);
+ sqlite3_bind_text(_sCreateOrReplaceNode,2,idstr.c_str(),-1,SQLITE_STATIC);
+ if (sqlite3_step(_sCreateOrReplaceNode) != SQLITE_DONE) {
+ return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ // Fetch Network record
+
+ sqlite3_reset(_sGetNetworkById);
+ sqlite3_bind_text(_sGetNetworkById,1,network.id,16,SQLITE_STATIC);
+ if (sqlite3_step(_sGetNetworkById) == SQLITE_ROW) {
+ network.name = (const char *)sqlite3_column_text(_sGetNetworkById,0);
+ network.isPrivate = (sqlite3_column_int(_sGetNetworkById,1) > 0);
+ network.enableBroadcast = (sqlite3_column_int(_sGetNetworkById,2) > 0);
+ network.allowPassiveBridging = (sqlite3_column_int(_sGetNetworkById,3) > 0);
+ network.v4AssignMode = (const char *)sqlite3_column_text(_sGetNetworkById,4);
+ network.v6AssignMode = (const char *)sqlite3_column_text(_sGetNetworkById,5);
+ network.multicastLimit = sqlite3_column_int(_sGetNetworkById,6);
+ network.creationTime = (uint64_t)sqlite3_column_int64(_sGetNetworkById,7);
+ network.revision = (uint64_t)sqlite3_column_int64(_sGetNetworkById,8);
+ network.memberRevisionCounter = (uint64_t)sqlite3_column_int64(_sGetNetworkById,9);
+ } else {
+ return NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND;
+ }
+
+ // Fetch Member record
+
+ bool foundMember = false;
+ sqlite3_reset(_sGetMember);
+ sqlite3_bind_text(_sGetMember,1,network.id,16,SQLITE_STATIC);
+ sqlite3_bind_text(_sGetMember,2,member.nodeId,10,SQLITE_STATIC);
+ if (sqlite3_step(_sGetMember) == SQLITE_ROW) {
+ foundMember = true;
+ member.rowid = (int64_t)sqlite3_column_int64(_sGetMember,0);
+ member.authorized = (sqlite3_column_int(_sGetMember,1) > 0);
+ member.activeBridge = (sqlite3_column_int(_sGetMember,2) > 0);
+ }
+
+ // Create Member record for unknown nodes, auto-authorizing if network is public
+
+ if (!foundMember) {
+ member.authorized = (network.isPrivate ? false : true);
+ member.activeBridge = false;
+ sqlite3_reset(_sCreateMember);
+ sqlite3_bind_text(_sCreateMember,1,network.id,16,SQLITE_STATIC);
+ sqlite3_bind_text(_sCreateMember,2,member.nodeId,10,SQLITE_STATIC);
+ sqlite3_bind_int(_sCreateMember,3,(member.authorized ? 1 : 0));
+ sqlite3_bind_text(_sCreateMember,4,network.id,16,SQLITE_STATIC);
+ if (sqlite3_step(_sCreateMember) != SQLITE_DONE) {
+ return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR;
+ }
+ member.rowid = (int64_t)sqlite3_last_insert_rowid(_db);
+
+ sqlite3_reset(_sIncrementMemberRevisionCounter);
+ sqlite3_bind_text(_sIncrementMemberRevisionCounter,1,network.id,16,SQLITE_STATIC);
+ sqlite3_step(_sIncrementMemberRevisionCounter);
+ }
+
+ // Update NodeHistory with new log entry and delete expired entries
+
+ {
+ int64_t nextVC = 1;
+ sqlite3_reset(_sGetMaxNodeHistoryNetworkVisitCounter);
+ sqlite3_bind_text(_sGetMaxNodeHistoryNetworkVisitCounter,1,network.id,16,SQLITE_STATIC);
+ sqlite3_bind_text(_sGetMaxNodeHistoryNetworkVisitCounter,2,member.nodeId,10,SQLITE_STATIC);
+ if (sqlite3_step(_sGetMaxNodeHistoryNetworkVisitCounter) == SQLITE_ROW) {
+ nextVC = (int64_t)sqlite3_column_int64(_sGetMaxNodeHistoryNetworkVisitCounter,0) + 1;
+ }
+
+ std::string fastr;
+ if (fromAddr)
+ fastr = fromAddr.toString();
+
+ sqlite3_reset(_sAddNodeHistoryEntry);
+ sqlite3_bind_text(_sAddNodeHistoryEntry,1,member.nodeId,10,SQLITE_STATIC);
+ sqlite3_bind_text(_sAddNodeHistoryEntry,2,network.id,16,SQLITE_STATIC);
+ sqlite3_bind_int64(_sAddNodeHistoryEntry,3,nextVC);
+ sqlite3_bind_int(_sAddNodeHistoryEntry,4,(member.authorized ? 1 : 0));
+ sqlite3_bind_int64(_sAddNodeHistoryEntry,5,(long long)now);
+ sqlite3_bind_int(_sAddNodeHistoryEntry,6,(int)metaData.majorVersion);
+ sqlite3_bind_int(_sAddNodeHistoryEntry,7,(int)metaData.minorVersion);
+ sqlite3_bind_int(_sAddNodeHistoryEntry,8,(int)metaData.revision);
+ sqlite3_bind_text(_sAddNodeHistoryEntry,9,"",-1,SQLITE_STATIC);
+ if (fastr.length() > 0)
+ sqlite3_bind_text(_sAddNodeHistoryEntry,10,fastr.c_str(),-1,SQLITE_STATIC);
+ else sqlite3_bind_null(_sAddNodeHistoryEntry,10);
+ sqlite3_step(_sAddNodeHistoryEntry);
+
+ nextVC -= ZT_NETCONF_NODE_HISTORY_LENGTH;
+ if (nextVC >= 0) {
+ sqlite3_reset(_sDeleteOldNodeHistoryEntries);
+ sqlite3_bind_text(_sDeleteOldNodeHistoryEntries,1,network.id,16,SQLITE_STATIC);
+ sqlite3_bind_text(_sDeleteOldNodeHistoryEntries,2,member.nodeId,10,SQLITE_STATIC);
+ sqlite3_bind_int64(_sDeleteOldNodeHistoryEntries,3,nextVC);
+ sqlite3_step(_sDeleteOldNodeHistoryEntries);
+ }
+ }
+
+ // Check member authorization
+
+ if (!member.authorized)
+ return NetworkController::NETCONF_QUERY_ACCESS_DENIED;
+
+ // Create network configuration -- we create both legacy and new types and send both for backward compatibility
+
+ Dictionary legacy;
+ NetworkConfig nc;
+
+ nc.networkId = Utils::hexStrToU64(network.id);
+ nc.type = network.isPrivate ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC;
+ nc.timestamp = now;
+ nc.revision = network.revision;
+ nc.issuedTo = member.nodeId;
+ if (network.enableBroadcast) nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST;
+ if (network.allowPassiveBridging) nc.flags |= ZT_NETWORKCONFIG_FLAG_ALLOW_PASSIVE_BRIDGING;
+ memcpy(nc.name,network.name,std::min((unsigned int)ZT_MAX_NETWORK_SHORT_NAME_LENGTH,(unsigned int)strlen(network.name)));
+
+ char tss[24],rs[24];
+ Utils::snprintf(tss,sizeof(tss),"%.16llx",(unsigned long long)now);
+ Utils::snprintf(rs,sizeof(rs),"%.16llx",(unsigned long long)network.revision);
+ legacy[ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP] = tss;
+ legacy[ZT_NETWORKCONFIG_DICT_KEY_REVISION] = rs;
+ legacy[ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID] = network.id;
+ legacy[ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO] = member.nodeId;
+ legacy[ZT_NETWORKCONFIG_DICT_KEY_PRIVATE] = network.isPrivate ? "1" : "0";
+ legacy[ZT_NETWORKCONFIG_DICT_KEY_NAME] = (network.name) ? network.name : "";
+ legacy[ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST] = network.enableBroadcast ? "1" : "0";
+ legacy[ZT_NETWORKCONFIG_DICT_KEY_ALLOW_PASSIVE_BRIDGING] = network.allowPassiveBridging ? "1" : "0";
+
+ { // TODO: right now only etherTypes are supported in rules
+ std::vector allowedEtherTypes;
+ sqlite3_reset(_sGetEtherTypesFromRuleTable);
+ sqlite3_bind_text(_sGetEtherTypesFromRuleTable,1,network.id,16,SQLITE_STATIC);
+ while (sqlite3_step(_sGetEtherTypesFromRuleTable) == SQLITE_ROW) {
+ if (sqlite3_column_type(_sGetEtherTypesFromRuleTable,0) == SQLITE_NULL) {
+ allowedEtherTypes.clear();
+ allowedEtherTypes.push_back(0); // NULL 'allow' matches ANY
+ break;
+ } else {
+ int et = sqlite3_column_int(_sGetEtherTypesFromRuleTable,0);
+ if ((et >= 0)&&(et <= 0xffff))
+ allowedEtherTypes.push_back(et);
+ }
+ }
+ std::sort(allowedEtherTypes.begin(),allowedEtherTypes.end());
+ allowedEtherTypes.erase(std::unique(allowedEtherTypes.begin(),allowedEtherTypes.end()),allowedEtherTypes.end());
+
+ for(long i=0,k=0;((i<(long)allowedEtherTypes.size())&&(k 0) {
+ nc.rules[k].t = ZT_NETWORK_RULE_MATCH_ETHERTYPE;
+ nc.rules[k].v.etherType = (uint16_t)allowedEtherTypes[i];
+ ++k;
+ }
+ nc.rules[k++].t = ZT_NETWORK_RULE_ACTION_ACCEPT;
+ }
+
+ std::string allowedEtherTypesCsv;
+ for(std::vector::const_iterator i(allowedEtherTypes.begin());i!=allowedEtherTypes.end();++i) {
+ if (allowedEtherTypesCsv.length())
+ allowedEtherTypesCsv.push_back(',');
+ char tmp[16];
+ Utils::snprintf(tmp,sizeof(tmp),"%.4x",(unsigned int)*i);
+ allowedEtherTypesCsv.append(tmp);
+ }
+ legacy[ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES] = allowedEtherTypesCsv;
+ }
+
+ nc.multicastLimit = network.multicastLimit;
+ if (network.multicastLimit > 0) {
+ char ml[16];
+ Utils::snprintf(ml,sizeof(ml),"%lx",(unsigned long)network.multicastLimit);
+ legacy[ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT] = ml;
+ }
+
+ bool amActiveBridge = false;
+ {
+ std::string activeBridges;
+ sqlite3_reset(_sGetActiveBridges);
+ sqlite3_bind_text(_sGetActiveBridges,1,network.id,16,SQLITE_STATIC);
+ while (sqlite3_step(_sGetActiveBridges) == SQLITE_ROW) {
+ const char *ab = (const char *)sqlite3_column_text(_sGetActiveBridges,0);
+ if ((ab)&&(strlen(ab) == 10)) {
+ const uint64_t ab2 = Utils::hexStrToU64(ab);
+ if ((ab2)&&(nc.specialistCount < ZT_MAX_NETWORK_SPECIALISTS)) {
+ nc.specialists[nc.specialistCount++] = ab2 | ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE;
+ }
+
+ if (activeBridges.length())
+ activeBridges.push_back(',');
+ activeBridges.append(ab);
+
+ if (!strcmp(member.nodeId,ab))
+ amActiveBridge = true;
+ }
+ if (activeBridges.length() > 1024) // sanity check -- you can't have too many active bridges at the moment
+ break;
+ }
+ if (activeBridges.length())
+ legacy[ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES] = activeBridges;
+ }
+
+ // Do not send relays to 1.1.0 since it had a serious bug in using them
+ // 1.1.0 will still work, it'll just fall back to roots instead of using network preferred relays
+ if (!((metaData.majorVersion == 1)&&(metaData.minorVersion == 1)&&(metaData.revision == 0))) {
+ std::string relays;
+ sqlite3_reset(_sGetRelays);
+ sqlite3_bind_text(_sGetRelays,1,network.id,16,SQLITE_STATIC);
+ while (sqlite3_step(_sGetRelays) == SQLITE_ROW) {
+ const char *n = (const char *)sqlite3_column_text(_sGetRelays,0);
+ const char *a = (const char *)sqlite3_column_text(_sGetRelays,1);
+ if ((n)&&(a)) {
+ Address node(n);
+ InetAddress addr(a);
+ if (node) {
+ bool sf = false;
+ for(unsigned int k=0;k 0) ? "," : "",
+ (int)ip[12],
+ (int)ip[13],
+ (int)ip[14],
+ (int)ip[15],
+ (int)sqlite3_column_int(_sGetGateways,2)); // metric
+ gateways.append(tmp);
+ break;
+ case 6:
+ Utils::snprintf(tmp,sizeof(tmp),"%s%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x/%d",
+ (gateways.length() > 0) ? "," : "",
+ (int)ip[0],
+ (int)ip[1],
+ (int)ip[2],
+ (int)ip[3],
+ (int)ip[4],
+ (int)ip[5],
+ (int)ip[6],
+ (int)ip[7],
+ (int)ip[8],
+ (int)ip[9],
+ (int)ip[10],
+ (int)ip[11],
+ (int)ip[12],
+ (int)ip[13],
+ (int)ip[14],
+ (int)ip[15],
+ (int)sqlite3_column_int(_sGetGateways,2)); // metric
+ gateways.append(tmp);
+ break;
+ }
+ }
+ if (gateways.length())
+ netconf[ZT_NETWORKCONFIG_DICT_KEY_GATEWAYS] = gateways;
+ }
+ */
+
+ if ((network.v4AssignMode)&&(!strcmp(network.v4AssignMode,"zt"))) {
+ std::string v4s;
+ std::vector< std::pair > routedNetworks;
+ bool haveStaticIpAssignment = false;
+
+ sqlite3_reset(_sGetIpAssignmentsForNode);
+ sqlite3_bind_text(_sGetIpAssignmentsForNode,1,network.id,16,SQLITE_STATIC);
+ sqlite3_bind_text(_sGetIpAssignmentsForNode,2,member.nodeId,10,SQLITE_STATIC);
+ sqlite3_bind_int(_sGetIpAssignmentsForNode,3,4); // 4 == IPv4
+ while (sqlite3_step(_sGetIpAssignmentsForNode) == SQLITE_ROW) {
+ const unsigned char *const ip = (const unsigned char *)sqlite3_column_blob(_sGetIpAssignmentsForNode,1);
+ if ((!ip)||(sqlite3_column_bytes(_sGetIpAssignmentsForNode,1) != 16))
+ continue;
+ int ipNetmaskBits = sqlite3_column_int(_sGetIpAssignmentsForNode,2);
+ if ((ipNetmaskBits <= 0)||(ipNetmaskBits > 32))
+ continue;
+
+ char ips[32];
+ Utils::snprintf(ips,sizeof(ips),"%d.%d.%d.%d/%d",(int)ip[12],(int)ip[13],(int)ip[14],(int)ip[15],ipNetmaskBits);
+
+ const IpAssignmentType ipt = (IpAssignmentType)sqlite3_column_int(_sGetIpAssignmentsForNode,0);
+ switch(ipt) {
+ case ZT_IP_ASSIGNMENT_TYPE_ADDRESS:
+ haveStaticIpAssignment = true;
+ break;
+ case ZT_IP_ASSIGNMENT_TYPE_NETWORK:
+ routedNetworks.push_back(std::pair(Utils::ntoh(*(reinterpret_cast(ip + 12))),ipNetmaskBits));
+ break;
+ default:
+ continue;
+ }
+
+ if ((ipt == ZT_IP_ASSIGNMENT_TYPE_ADDRESS)&&(nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) {
+ InetAddress tmp2(ips);
+ if (tmp2)
+ nc.staticIps[nc.staticIpCount++] = tmp2;
+ }
+ // TODO: add routed networks to nc
+
+ // 1.0.4 or newer clients support network routes in addition to IPs.
+ // Older clients only support IP address / netmask entries.
+ if ((clientIs104)||(ipt == ZT_IP_ASSIGNMENT_TYPE_ADDRESS)) {
+ if (v4s.length())
+ v4s.push_back(',');
+ v4s.append(ips);
+ }
+ }
+
+ if ((!haveStaticIpAssignment)&&(!amActiveBridge)) {
+ // Attempt to auto-assign an IPv4 address from an available routed pool
+ sqlite3_reset(_sGetIpAssignmentPools);
+ sqlite3_bind_text(_sGetIpAssignmentPools,1,network.id,16,SQLITE_STATIC);
+ sqlite3_bind_int(_sGetIpAssignmentPools,2,4); // 4 == IPv4
+
+ while (sqlite3_step(_sGetIpAssignmentPools) == SQLITE_ROW) {
+ const unsigned char *ipRangeStartB = reinterpret_cast(sqlite3_column_blob(_sGetIpAssignmentPools,0));
+ const unsigned char *ipRangeEndB = reinterpret_cast(sqlite3_column_blob(_sGetIpAssignmentPools,1));
+ if ((!ipRangeStartB)||(!ipRangeEndB)||(sqlite3_column_bytes(_sGetIpAssignmentPools,0) != 16)||(sqlite3_column_bytes(_sGetIpAssignmentPools,1) != 16))
+ continue;
+
+ uint32_t ipRangeStart = Utils::ntoh(*(reinterpret_cast(ipRangeStartB + 12)));
+ uint32_t ipRangeEnd = Utils::ntoh(*(reinterpret_cast(ipRangeEndB + 12)));
+ if ((ipRangeEnd <= ipRangeStart)||(ipRangeStart == 0))
+ continue;
+ uint32_t ipRangeLen = ipRangeEnd - ipRangeStart;
+
+ // Start with the LSB of the member's address
+ uint32_t ipTrialCounter = (uint32_t)(identity.address().toInt() & 0xffffffff);
+
+ for(uint32_t k=ipRangeStart,l=0;(k<=ipRangeEnd)&&(l < 1000000);++k,++l) {
+ uint32_t ip = (ipRangeLen > 0) ? (ipRangeStart + (ipTrialCounter % ipRangeLen)) : ipRangeStart;
+ ++ipTrialCounter;
+ if ((ip & 0x000000ff) == 0x000000ff)
+ continue; // don't allow addresses that end in .255
+
+ for(std::vector< std::pair >::const_iterator r(routedNetworks.begin());r!=routedNetworks.end();++r) {
+ if ((ip & (0xffffffff << (32 - r->second))) == r->first) {
+ // IP is included in a routed network, so check if it's allocated
+
+ uint32_t ipBlob[4];
+ ipBlob[0] = 0; ipBlob[1] = 0; ipBlob[2] = 0; ipBlob[3] = Utils::hton(ip);
+
+ sqlite3_reset(_sCheckIfIpIsAllocated);
+ sqlite3_bind_text(_sCheckIfIpIsAllocated,1,network.id,16,SQLITE_STATIC);
+ sqlite3_bind_blob(_sCheckIfIpIsAllocated,2,(const void *)ipBlob,16,SQLITE_STATIC);
+ sqlite3_bind_int(_sCheckIfIpIsAllocated,3,4); // 4 == IPv4
+ sqlite3_bind_int(_sCheckIfIpIsAllocated,4,(int)ZT_IP_ASSIGNMENT_TYPE_ADDRESS);
+ if (sqlite3_step(_sCheckIfIpIsAllocated) != SQLITE_ROW) {
+ // No rows returned, so the IP is available
+ sqlite3_reset(_sAllocateIp);
+ sqlite3_bind_text(_sAllocateIp,1,network.id,16,SQLITE_STATIC);
+ sqlite3_bind_text(_sAllocateIp,2,member.nodeId,10,SQLITE_STATIC);
+ sqlite3_bind_int(_sAllocateIp,3,(int)ZT_IP_ASSIGNMENT_TYPE_ADDRESS);
+ sqlite3_bind_blob(_sAllocateIp,4,(const void *)ipBlob,16,SQLITE_STATIC);
+ sqlite3_bind_int(_sAllocateIp,5,r->second); // IP netmask bits from matching route
+ sqlite3_bind_int(_sAllocateIp,6,4); // 4 == IPv4
+ if (sqlite3_step(_sAllocateIp) == SQLITE_DONE) {
+ char ips[32];
+ Utils::snprintf(ips,sizeof(ips),"%d.%d.%d.%d/%d",(int)((ip >> 24) & 0xff),(int)((ip >> 16) & 0xff),(int)((ip >> 8) & 0xff),(int)(ip & 0xff),r->second);
+
+ if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) {
+ InetAddress tmp2(ips);
+ if (tmp2)
+ nc.staticIps[nc.staticIpCount++] = tmp2;
+ }
+
+ if (v4s.length())
+ v4s.push_back(',');
+ v4s.append(ips);
+
+ haveStaticIpAssignment = true; // break outer loop
+ }
+ }
+
+ break; // stop checking routed networks
+ }
+ }
+
+ if (haveStaticIpAssignment)
+ break;
+ }
+ }
+ }
+
+ if (v4s.length())
+ legacy[ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC] = v4s;
+ }
+
+ if ((network.v6AssignMode)&&(!strcmp(network.v6AssignMode,"rfc4193"))) {
+ InetAddress rfc4193Addr(InetAddress::makeIpv6rfc4193(nwid,identity.address().toInt()));
+ legacy[ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC] = rfc4193Addr.toString();
+ if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)
+ nc.staticIps[nc.staticIpCount++] = rfc4193Addr;
+ }
+
+ if (network.isPrivate) {
+ CertificateOfMembership com(now,ZT_NETWORK_COM_DEFAULT_REVISION_MAX_DELTA,nwid,identity.address());
+ if (com.sign(signingId)) {
+ legacy[ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP] = com.toString();
+ nc.com = com;
+ } else {
+ return NETCONF_QUERY_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ if (!legacy.sign(signingId,now)) {
+ return NETCONF_QUERY_INTERNAL_SERVER_ERROR;
+ }
+
+ // First append the legacy dictionary and a terminating NULL, then serialize the new-format one.
+ // Newer clients will use the new-format dictionary and older ones will use the old one.
+ std::string legacyStr(legacy.toString());
+ netconf.append((const void *)legacyStr.data(),(unsigned int)legacyStr.length());
+ netconf.append((uint8_t)0);
+ nc.serialize(netconf);
+
+ return NetworkController::NETCONF_QUERY_OK;
+}
+
+void SqliteNetworkController::_circuitTestCallback(ZT_Node *node,ZT_CircuitTest *test,const ZT_CircuitTestReport *report)
+{
+ char tmp[65535];
+ SqliteNetworkController *const self = reinterpret_cast(test->ptr);
+
+ if (!test)
+ return;
+ if (!report)
+ return;
+
+ Mutex::Lock _l(self->_lock);
+ std::map< uint64_t,_CircuitTestEntry >::iterator cte(self->_circuitTests.find(test->testId));
+
+ if (cte == self->_circuitTests.end()) { // sanity check: a circuit test we didn't launch?
+ self->_node->circuitTestEnd(test);
+ ::free((void *)test);
+ return;
+ }
+
+ Utils::snprintf(tmp,sizeof(tmp),
+ "%s{\n"
+ "\t\"timestamp\": %llu,"ZT_EOL_S
+ "\t\"testId\": \"%.16llx\","ZT_EOL_S
+ "\t\"upstream\": \"%.10llx\","ZT_EOL_S
+ "\t\"current\": \"%.10llx\","ZT_EOL_S
+ "\t\"receivedTimestamp\": %llu,"ZT_EOL_S
+ "\t\"remoteTimestamp\": %llu,"ZT_EOL_S
+ "\t\"sourcePacketId\": \"%.16llx\","ZT_EOL_S
+ "\t\"flags\": %llu,"ZT_EOL_S
+ "\t\"sourcePacketHopCount\": %u,"ZT_EOL_S
+ "\t\"errorCode\": %u,"ZT_EOL_S
+ "\t\"vendor\": %d,"ZT_EOL_S
+ "\t\"protocolVersion\": %u,"ZT_EOL_S
+ "\t\"majorVersion\": %u,"ZT_EOL_S
+ "\t\"minorVersion\": %u,"ZT_EOL_S
+ "\t\"revision\": %u,"ZT_EOL_S
+ "\t\"platform\": %d,"ZT_EOL_S
+ "\t\"architecture\": %d,"ZT_EOL_S
+ "\t\"receivedOnLocalAddress\": \"%s\","ZT_EOL_S
+ "\t\"receivedFromRemoteAddress\": \"%s\""ZT_EOL_S
+ "}",
+ ((cte->second.jsonResults.length() > 0) ? ",\n" : ""),
+ (unsigned long long)report->timestamp,
+ (unsigned long long)test->testId,
+ (unsigned long long)report->upstream,
+ (unsigned long long)report->current,
+ (unsigned long long)OSUtils::now(),
+ (unsigned long long)report->remoteTimestamp,
+ (unsigned long long)report->sourcePacketId,
+ (unsigned long long)report->flags,
+ report->sourcePacketHopCount,
+ report->errorCode,
+ (int)report->vendor,
+ report->protocolVersion,
+ report->majorVersion,
+ report->minorVersion,
+ report->revision,
+ (int)report->platform,
+ (int)report->architecture,
+ reinterpret_cast(&(report->receivedOnLocalAddress))->toString().c_str(),
+ reinterpret_cast(&(report->receivedFromRemoteAddress))->toString().c_str());
+
+ cte->second.jsonResults.append(tmp);
+}
+
+} // namespace ZeroTier
diff --git a/zerotierone/controller/SqliteNetworkController.hpp b/zerotierone/controller/SqliteNetworkController.hpp
new file mode 100644
index 0000000..05b46bf
--- /dev/null
+++ b/zerotierone/controller/SqliteNetworkController.hpp
@@ -0,0 +1,191 @@
+/*
+ * 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 .
+ *
+ * --
+ *
+ * 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_SQLITENETWORKCONTROLLER_HPP
+#define ZT_SQLITENETWORKCONTROLLER_HPP
+
+#include
+
+#include
+
+#include
+#include