diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp
index 85c759e73..b57a37e81 100644
--- a/controller/EmbeddedNetworkController.cpp
+++ b/controller/EmbeddedNetworkController.cpp
@@ -76,19 +76,19 @@ static json _renderRule(ZT_VirtualNetworkRule &rule)
 			break;
 		case ZT_NETWORK_RULE_ACTION_TEE:
 			r["type"] = "ACTION_TEE";
-			r["address"] = Address(rule.v.fwd.address).toString();
+			r["address"] = Address(rule.v.fwd.address).toString(tmp);
 			r["flags"] = (unsigned int)rule.v.fwd.flags;
 			r["length"] = (unsigned int)rule.v.fwd.length;
 			break;
 		case ZT_NETWORK_RULE_ACTION_WATCH:
 			r["type"] = "ACTION_WATCH";
-			r["address"] = Address(rule.v.fwd.address).toString();
+			r["address"] = Address(rule.v.fwd.address).toString(tmp);
 			r["flags"] = (unsigned int)rule.v.fwd.flags;
 			r["length"] = (unsigned int)rule.v.fwd.length;
 			break;
 		case ZT_NETWORK_RULE_ACTION_REDIRECT:
 			r["type"] = "ACTION_REDIRECT";
-			r["address"] = Address(rule.v.fwd.address).toString();
+			r["address"] = Address(rule.v.fwd.address).toString(tmp);
 			r["flags"] = (unsigned int)rule.v.fwd.flags;
 			break;
 		case ZT_NETWORK_RULE_ACTION_BREAK:
@@ -102,11 +102,11 @@ static json _renderRule(ZT_VirtualNetworkRule &rule)
 		switch(rt) {
 			case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS:
 				r["type"] = "MATCH_SOURCE_ZEROTIER_ADDRESS";
-				r["zt"] = Address(rule.v.zt).toString();
+				r["zt"] = Address(rule.v.zt).toString(tmp);
 				break;
 			case ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS:
 				r["type"] = "MATCH_DEST_ZEROTIER_ADDRESS";
-				r["zt"] = Address(rule.v.zt).toString();
+				r["zt"] = Address(rule.v.zt).toString(tmp);
 				break;
 			case ZT_NETWORK_RULE_MATCH_VLAN_ID:
 				r["type"] = "MATCH_VLAN_ID";
@@ -122,29 +122,29 @@ static json _renderRule(ZT_VirtualNetworkRule &rule)
 				break;
 			case ZT_NETWORK_RULE_MATCH_MAC_SOURCE:
 				r["type"] = "MATCH_MAC_SOURCE";
-				Utils::ztsnprintf(tmp,sizeof(tmp),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(unsigned int)rule.v.mac[0],(unsigned int)rule.v.mac[1],(unsigned int)rule.v.mac[2],(unsigned int)rule.v.mac[3],(unsigned int)rule.v.mac[4],(unsigned int)rule.v.mac[5]);
+				OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(unsigned int)rule.v.mac[0],(unsigned int)rule.v.mac[1],(unsigned int)rule.v.mac[2],(unsigned int)rule.v.mac[3],(unsigned int)rule.v.mac[4],(unsigned int)rule.v.mac[5]);
 				r["mac"] = tmp;
 				break;
 			case ZT_NETWORK_RULE_MATCH_MAC_DEST:
 				r["type"] = "MATCH_MAC_DEST";
-				Utils::ztsnprintf(tmp,sizeof(tmp),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(unsigned int)rule.v.mac[0],(unsigned int)rule.v.mac[1],(unsigned int)rule.v.mac[2],(unsigned int)rule.v.mac[3],(unsigned int)rule.v.mac[4],(unsigned int)rule.v.mac[5]);
+				OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(unsigned int)rule.v.mac[0],(unsigned int)rule.v.mac[1],(unsigned int)rule.v.mac[2],(unsigned int)rule.v.mac[3],(unsigned int)rule.v.mac[4],(unsigned int)rule.v.mac[5]);
 				r["mac"] = tmp;
 				break;
 			case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE:
 				r["type"] = "MATCH_IPV4_SOURCE";
-				r["ip"] = InetAddress(&(rule.v.ipv4.ip),4,(unsigned int)rule.v.ipv4.mask).toString();
+				r["ip"] = InetAddress(&(rule.v.ipv4.ip),4,(unsigned int)rule.v.ipv4.mask).toString(tmp);
 				break;
 			case ZT_NETWORK_RULE_MATCH_IPV4_DEST:
 				r["type"] = "MATCH_IPV4_DEST";
-				r["ip"] = InetAddress(&(rule.v.ipv4.ip),4,(unsigned int)rule.v.ipv4.mask).toString();
+				r["ip"] = InetAddress(&(rule.v.ipv4.ip),4,(unsigned int)rule.v.ipv4.mask).toString(tmp);
 				break;
 			case ZT_NETWORK_RULE_MATCH_IPV6_SOURCE:
 				r["type"] = "MATCH_IPV6_SOURCE";
-				r["ip"] = InetAddress(rule.v.ipv6.ip,16,(unsigned int)rule.v.ipv6.mask).toString();
+				r["ip"] = InetAddress(rule.v.ipv6.ip,16,(unsigned int)rule.v.ipv6.mask).toString(tmp);
 				break;
 			case ZT_NETWORK_RULE_MATCH_IPV6_DEST:
 				r["type"] = "MATCH_IPV6_DEST";
-				r["ip"] = InetAddress(rule.v.ipv6.ip,16,(unsigned int)rule.v.ipv6.mask).toString();
+				r["ip"] = InetAddress(rule.v.ipv6.ip,16,(unsigned int)rule.v.ipv6.mask).toString(tmp);
 				break;
 			case ZT_NETWORK_RULE_MATCH_IP_TOS:
 				r["type"] = "MATCH_IP_TOS";
@@ -179,7 +179,7 @@ static json _renderRule(ZT_VirtualNetworkRule &rule)
 				break;
 			case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS:
 				r["type"] = "MATCH_CHARACTERISTICS";
-				Utils::ztsnprintf(tmp,sizeof(tmp),"%.16llx",rule.v.characteristics);
+				OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.16llx",rule.v.characteristics);
 				r["mask"] = tmp;
 				break;
 			case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE:
@@ -312,28 +312,28 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule)
 		return true;
 	} else if (t == "MATCH_IPV4_SOURCE") {
 		rule.t |= ZT_NETWORK_RULE_MATCH_IPV4_SOURCE;
-		InetAddress ip(OSUtils::jsonString(r["ip"],"0.0.0.0"));
+		InetAddress ip(OSUtils::jsonString(r["ip"],"0.0.0.0").c_str());
 		rule.v.ipv4.ip = reinterpret_cast<struct sockaddr_in *>(&ip)->sin_addr.s_addr;
 		rule.v.ipv4.mask = Utils::ntoh(reinterpret_cast<struct sockaddr_in *>(&ip)->sin_port) & 0xff;
 		if (rule.v.ipv4.mask > 32) rule.v.ipv4.mask = 32;
 		return true;
 	} else if (t == "MATCH_IPV4_DEST") {
 		rule.t |= ZT_NETWORK_RULE_MATCH_IPV4_DEST;
-		InetAddress ip(OSUtils::jsonString(r["ip"],"0.0.0.0"));
+		InetAddress ip(OSUtils::jsonString(r["ip"],"0.0.0.0").c_str());
 		rule.v.ipv4.ip = reinterpret_cast<struct sockaddr_in *>(&ip)->sin_addr.s_addr;
 		rule.v.ipv4.mask = Utils::ntoh(reinterpret_cast<struct sockaddr_in *>(&ip)->sin_port) & 0xff;
 		if (rule.v.ipv4.mask > 32) rule.v.ipv4.mask = 32;
 		return true;
 	} else if (t == "MATCH_IPV6_SOURCE") {
 		rule.t |= ZT_NETWORK_RULE_MATCH_IPV6_SOURCE;
-		InetAddress ip(OSUtils::jsonString(r["ip"],"::0"));
+		InetAddress ip(OSUtils::jsonString(r["ip"],"::0").c_str());
 		memcpy(rule.v.ipv6.ip,reinterpret_cast<struct sockaddr_in6 *>(&ip)->sin6_addr.s6_addr,16);
 		rule.v.ipv6.mask = Utils::ntoh(reinterpret_cast<struct sockaddr_in6 *>(&ip)->sin6_port) & 0xff;
 		if (rule.v.ipv6.mask > 128) rule.v.ipv6.mask = 128;
 		return true;
 	} else if (t == "MATCH_IPV6_DEST") {
 		rule.t |= ZT_NETWORK_RULE_MATCH_IPV6_DEST;
-		InetAddress ip(OSUtils::jsonString(r["ip"],"::0"));
+		InetAddress ip(OSUtils::jsonString(r["ip"],"::0").c_str());
 		memcpy(rule.v.ipv6.ip,reinterpret_cast<struct sockaddr_in6 *>(&ip)->sin6_addr.s6_addr,16);
 		rule.v.ipv6.mask = Utils::ntoh(reinterpret_cast<struct sockaddr_in6 *>(&ip)->sin6_port) & 0xff;
 		if (rule.v.ipv6.mask > 128) rule.v.ipv6.mask = 128;
@@ -514,7 +514,7 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpGET(
 						_db.eachMember(nwid,[&responseBody](uint64_t networkId,uint64_t nodeId,const json &member) {
 							if ((member.is_object())&&(member.size() > 0)) {
 								char tmp[128];
-								Utils::ztsnprintf(tmp,sizeof(tmp),"%s%.10llx\":%llu",(responseBody.length() > 1) ? ",\"" : "\"",(unsigned long long)nodeId,(unsigned long long)OSUtils::jsonInt(member["revision"],0));
+								OSUtils::ztsnprintf(tmp,sizeof(tmp),"%s%.10llx\":%llu",(responseBody.length() > 1) ? ",\"" : "\"",(unsigned long long)nodeId,(unsigned long long)OSUtils::jsonInt(member["revision"],0));
 								responseBody.append(tmp);
 							}
 						});
@@ -548,7 +548,7 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpGET(
 			for(std::vector<uint64_t>::const_iterator i(networkIds.begin());i!=networkIds.end();++i) {
 				if (responseBody.length() > 1)
 					responseBody.push_back(',');
-				Utils::ztsnprintf(tmp,sizeof(tmp),"\"%.16llx\"",(unsigned long long)*i);
+				OSUtils::ztsnprintf(tmp,sizeof(tmp),"\"%.16llx\"",(unsigned long long)*i);
 				responseBody.append(tmp);
 			}
 			responseBody.push_back(']');
@@ -562,7 +562,7 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpGET(
 		// Controller status
 
 		char tmp[4096];
-		Utils::ztsnprintf(tmp,sizeof(tmp),"{\n\t\"controller\": true,\n\t\"apiVersion\": %d,\n\t\"clock\": %llu\n}\n",ZT_NETCONF_CONTROLLER_API_VERSION,(unsigned long long)OSUtils::now());
+		OSUtils::ztsnprintf(tmp,sizeof(tmp),"{\n\t\"controller\": true,\n\t\"apiVersion\": %d,\n\t\"clock\": %llu\n}\n",ZT_NETCONF_CONTROLLER_API_VERSION,(unsigned long long)OSUtils::now());
 		responseBody = tmp;
 		responseContentType = "application/json";
 		return 200;
@@ -603,14 +603,14 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
 		if ((path.size() >= 2)&&(path[1].length() == 16)) {
 			uint64_t nwid = Utils::hexStrToU64(path[1].c_str());
 			char nwids[24];
-			Utils::ztsnprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
+			OSUtils::ztsnprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
 
 			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::ztsnprintf(addrs,sizeof(addrs),"%.10llx",(unsigned long long)address);
+					OSUtils::ztsnprintf(addrs,sizeof(addrs),"%.10llx",(unsigned long long)address);
 
 					json member;
 					_db.getNetworkMember(nwid,address,member);
@@ -655,9 +655,10 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
 								json mipa(json::array());
 								for(unsigned long i=0;i<ipa.size();++i) {
 									std::string ips = ipa[i];
-									InetAddress ip(ips);
+									InetAddress ip(ips.c_str());
 									if ((ip.ss_family == AF_INET)||(ip.ss_family == AF_INET6)) {
-										mipa.push_back(ip.toIpString());
+										char tmpip[64];
+										mipa.push_back(ip.toIpString(tmpip));
 									}
 								}
 								member["ipAssignments"] = mipa;
@@ -748,7 +749,7 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
 					if (!nwid)
 						return 503;
 				}
-				Utils::ztsnprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
+				OSUtils::ztsnprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
 
 				json network;
 				_db.getNetwork(nwid,network);
@@ -815,14 +816,15 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
 									json &target = rt["target"];
 									json &via = rt["via"];
 									if (target.is_string()) {
-										InetAddress t(target.get<std::string>());
+										InetAddress t(target.get<std::string>().c_str());
 										InetAddress v;
-										if (via.is_string()) v.fromString(via.get<std::string>());
+										if (via.is_string()) v.fromString(via.get<std::string>().c_str());
 										if ( ((t.ss_family == AF_INET)||(t.ss_family == AF_INET6)) && (t.netmaskBitsValid()) ) {
 											json tmp;
-											tmp["target"] = t.toString();
+											char tmp2[64];
+											tmp["target"] = t.toString(tmp2);
 											if (v.ss_family == t.ss_family)
-												tmp["via"] = v.toIpString();
+												tmp["via"] = v.toIpString(tmp2);
 											else tmp["via"] = json();
 											nrts.push_back(tmp);
 										}
@@ -840,12 +842,13 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
 							for(unsigned long i=0;i<ipp.size();++i) {
 								json &ip = ipp[i];
 								if ((ip.is_object())&&(ip.count("ipRangeStart"))&&(ip.count("ipRangeEnd"))) {
-									InetAddress f(OSUtils::jsonString(ip["ipRangeStart"],""));
-									InetAddress t(OSUtils::jsonString(ip["ipRangeEnd"],""));
+									InetAddress f(OSUtils::jsonString(ip["ipRangeStart"],"").c_str());
+									InetAddress t(OSUtils::jsonString(ip["ipRangeEnd"],"").c_str());
 									if ( ((f.ss_family == AF_INET)||(f.ss_family == AF_INET6)) && (f.ss_family == t.ss_family) ) {
 										json tmp = json::object();
-										tmp["ipRangeStart"] = f.toIpString();
-										tmp["ipRangeEnd"] = t.toIpString();
+										char tmp2[64];
+										tmp["ipRangeStart"] = f.toIpString(tmp2);
+										tmp["ipRangeEnd"] = t.toIpString(tmp2);
 										nipp.push_back(tmp);
 									}
 								}
@@ -995,7 +998,7 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
 		_queue.post(qe);
 
 		char tmp[64];
-		Utils::ztsnprintf(tmp,sizeof(tmp),"{\"clock\":%llu,\"ping\":%s}",(unsigned long long)now,OSUtils::jsonDump(b).c_str());
+		OSUtils::ztsnprintf(tmp,sizeof(tmp),"{\"clock\":%llu,\"ping\":%s}",(unsigned long long)now,OSUtils::jsonDump(b).c_str());
 		responseBody = tmp;
 		responseContentType = "application/json";
 
@@ -1083,7 +1086,7 @@ void EmbeddedNetworkController::threadMain()
 						auto ms = this->_memberStatus.find(_MemberStatusKey(networkId,nodeId));
 						if (ms != _memberStatus.end())
 							lrt = ms->second.lastRequestTime;
-						Utils::ztsnprintf(tmp,sizeof(tmp),"%s\"%.16llx-%.10llx\":%llu",
+						OSUtils::ztsnprintf(tmp,sizeof(tmp),"%s\"%.16llx-%.10llx\":%llu",
 							(first) ? "" : ",",
 							(unsigned long long)networkId,
 							(unsigned long long)nodeId,
@@ -1093,7 +1096,7 @@ void EmbeddedNetworkController::threadMain()
 					});
 				}
 				char tmp2[256];
-				Utils::ztsnprintf(tmp2,sizeof(tmp2),"},\"clock\":%llu,\"startTime\":%llu}",(unsigned long long)now,(unsigned long long)_startTime);
+				OSUtils::ztsnprintf(tmp2,sizeof(tmp2),"},\"clock\":%llu,\"startTime\":%llu}",(unsigned long long)now,(unsigned long long)_startTime);
 				pong.append(tmp2);
 				_db.writeRaw("pong",pong);
 			}
@@ -1126,7 +1129,7 @@ void EmbeddedNetworkController::_request(
 		ms.lastRequestTime = now;
 	}
 
-	Utils::ztsnprintf(nwids,sizeof(nwids),"%.16llx",nwid);
+	OSUtils::ztsnprintf(nwids,sizeof(nwids),"%.16llx",nwid);
 	if (!_db.getNetworkAndMember(nwid,identity.address().toInt(),network,member,ns)) {
 		_sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_OBJECT_NOT_FOUND);
 		return;
@@ -1152,13 +1155,15 @@ void EmbeddedNetworkController::_request(
 			}
 		} else {
 			// If we do not yet know this member's identity, learn it.
-			member["identity"] = identity.toString(false);
+			char idtmp[1024];
+			member["identity"] = identity.toString(false,idtmp);
 		}
 	}
 
 	// These are always the same, but make sure they are set
 	{
-		const std::string addrs(identity.address().toString());
+		char tmpid[128];
+		const std::string addrs(identity.address().toString(tmpid));
 		member["id"] = addrs;
 		member["address"] = addrs;
 		member["nwid"] = nwids;
@@ -1264,8 +1269,9 @@ void EmbeddedNetworkController::_request(
 
 				if (fromAddr)
 					ms.physicalAddr = fromAddr;
+				char tmpip[64];
 				if (ms.physicalAddr)
-					member["physicalAddr"] = ms.physicalAddr.toString();
+					member["physicalAddr"] = ms.physicalAddr.toString(tmpip);
 			}
 		}
 	} else {
@@ -1427,9 +1433,9 @@ void EmbeddedNetworkController::_request(
 			json &target = route["target"];
 			json &via = route["via"];
 			if (target.is_string()) {
-				const InetAddress t(target.get<std::string>());
+				const InetAddress t(target.get<std::string>().c_str());
 				InetAddress v;
-				if (via.is_string()) v.fromString(via.get<std::string>());
+				if (via.is_string()) v.fromString(via.get<std::string>().c_str());
 				if ((t.ss_family == AF_INET)||(t.ss_family == AF_INET6)) {
 					ZT_VirtualNetworkRoute *r = &(nc->routes[nc->routeCount]);
 					*(reinterpret_cast<InetAddress *>(&(r->target))) = t;
@@ -1462,7 +1468,7 @@ void EmbeddedNetworkController::_request(
 			if (!ipAssignments[i].is_string())
 				continue;
 			std::string ips = ipAssignments[i];
-			InetAddress ip(ips);
+			InetAddress ip(ips.c_str());
 
 			// IP assignments are only pushed if there is a corresponding local route. We also now get the netmask bits from
 			// this route, ignoring the netmask bits field of the assigned IP itself. Using that was worthless and a source
@@ -1492,8 +1498,8 @@ void EmbeddedNetworkController::_request(
 		for(unsigned long p=0;((p<ipAssignmentPools.size())&&(!haveManagedIpv6AutoAssignment));++p) {
 			json &pool = ipAssignmentPools[p];
 			if (pool.is_object()) {
-				InetAddress ipRangeStart(OSUtils::jsonString(pool["ipRangeStart"],""));
-				InetAddress ipRangeEnd(OSUtils::jsonString(pool["ipRangeEnd"],""));
+				InetAddress ipRangeStart(OSUtils::jsonString(pool["ipRangeStart"],"").c_str());
+				InetAddress ipRangeEnd(OSUtils::jsonString(pool["ipRangeEnd"],"").c_str());
 				if ( (ipRangeStart.ss_family == AF_INET6) && (ipRangeEnd.ss_family == AF_INET6) ) {
 					uint64_t s[2],e[2],x[2],xx[2];
 					memcpy(s,ipRangeStart.rawIpData(),16);
@@ -1534,7 +1540,8 @@ void EmbeddedNetworkController::_request(
 
 						// If it's routed, then try to claim and assign it and if successful end loop
 						if ( (routedNetmaskBits > 0) && (!std::binary_search(ns.allocatedIps.begin(),ns.allocatedIps.end(),ip6)) ) {
-							ipAssignments.push_back(ip6.toIpString());
+							char tmpip[64];
+							ipAssignments.push_back(ip6.toIpString(tmpip));
 							member["ipAssignments"] = ipAssignments;
 							ip6.setPort((unsigned int)routedNetmaskBits);
 							if (nc->staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)
@@ -1552,8 +1559,8 @@ void EmbeddedNetworkController::_request(
 		for(unsigned long p=0;((p<ipAssignmentPools.size())&&(!haveManagedIpv4AutoAssignment));++p) {
 			json &pool = ipAssignmentPools[p];
 			if (pool.is_object()) {
-				InetAddress ipRangeStartIA(OSUtils::jsonString(pool["ipRangeStart"],""));
-				InetAddress ipRangeEndIA(OSUtils::jsonString(pool["ipRangeEnd"],""));
+				InetAddress ipRangeStartIA(OSUtils::jsonString(pool["ipRangeStart"],"").c_str());
+				InetAddress ipRangeEndIA(OSUtils::jsonString(pool["ipRangeEnd"],"").c_str());
 				if ( (ipRangeStartIA.ss_family == AF_INET) && (ipRangeEndIA.ss_family == AF_INET) ) {
 					uint32_t ipRangeStart = Utils::ntoh((uint32_t)(reinterpret_cast<struct sockaddr_in *>(&ipRangeStartIA)->sin_addr.s_addr));
 					uint32_t ipRangeEnd = Utils::ntoh((uint32_t)(reinterpret_cast<struct sockaddr_in *>(&ipRangeEndIA)->sin_addr.s_addr));
@@ -1586,7 +1593,8 @@ void EmbeddedNetworkController::_request(
 						// If it's routed, then try to claim and assign it and if successful end loop
 						const InetAddress ip4(Utils::hton(ip),0);
 						if ( (routedNetmaskBits > 0) && (!std::binary_search(ns.allocatedIps.begin(),ns.allocatedIps.end(),ip4)) ) {
-							ipAssignments.push_back(ip4.toIpString());
+							char tmpip[64];
+							ipAssignments.push_back(ip4.toIpString(tmpip));
 							member["ipAssignments"] = ipAssignments;
 							if (nc->staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) {
 								struct sockaddr_in *const v4ip = reinterpret_cast<struct sockaddr_in *>(&(nc->staticIps[nc->staticIpCount++]));
diff --git a/controller/JSONDB.cpp b/controller/JSONDB.cpp
index acf23700d..97a217a11 100644
--- a/controller/JSONDB.cpp
+++ b/controller/JSONDB.cpp
@@ -39,7 +39,7 @@ JSONDB::JSONDB(const std::string &basePath) :
 		std::size_t hnsep = hn.find_last_of(':');
 		if (hnsep != std::string::npos)
 			hn[hnsep] = '/';
-		_httpAddr.fromString(hn);
+		_httpAddr.fromString(hn.c_str());
 		if (hnend != std::string::npos)
 			_basePath = _basePath.substr(7 + hnend);
 		if (_basePath.length() == 0)
@@ -94,7 +94,7 @@ bool JSONDB::writeRaw(const std::string &n,const std::string &obj)
 		std::string body;
 		std::map<std::string,std::string> reqHeaders;
 		char tmp[64];
-		Utils::ztsnprintf(tmp,sizeof(tmp),"%lu",(unsigned long)obj.length());
+		OSUtils::ztsnprintf(tmp,sizeof(tmp),"%lu",(unsigned long)obj.length());
 		reqHeaders["Content-Length"] = tmp;
 		reqHeaders["Content-Type"] = "application/json";
 		const unsigned int sc = Http::PUT(0,ZT_JSONDB_HTTP_TIMEOUT,reinterpret_cast<const struct sockaddr *>(&_httpAddr),(_basePath+"/"+n).c_str(),reqHeaders,obj.data(),(unsigned long)obj.length(),headers,body);
@@ -164,7 +164,7 @@ bool JSONDB::getNetworkMember(const uint64_t networkId,const uint64_t nodeId,nlo
 void JSONDB::saveNetwork(const uint64_t networkId,const nlohmann::json &networkConfig)
 {
 	char n[64];
-	Utils::ztsnprintf(n,sizeof(n),"network/%.16llx",(unsigned long long)networkId);
+	OSUtils::ztsnprintf(n,sizeof(n),"network/%.16llx",(unsigned long long)networkId);
 	writeRaw(n,OSUtils::jsonDump(networkConfig));
 	{
 		Mutex::Lock _l(_networks_m);
@@ -176,7 +176,7 @@ void JSONDB::saveNetwork(const uint64_t networkId,const nlohmann::json &networkC
 void JSONDB::saveNetworkMember(const uint64_t networkId,const uint64_t nodeId,const nlohmann::json &memberConfig)
 {
 	char n[256];
-	Utils::ztsnprintf(n,sizeof(n),"network/%.16llx/member/%.10llx",(unsigned long long)networkId,(unsigned long long)nodeId);
+	OSUtils::ztsnprintf(n,sizeof(n),"network/%.16llx/member/%.10llx",(unsigned long long)networkId,(unsigned long long)nodeId);
 	writeRaw(n,OSUtils::jsonDump(memberConfig));
 	{
 		Mutex::Lock _l(_networks_m);
@@ -202,7 +202,7 @@ nlohmann::json JSONDB::eraseNetwork(const uint64_t networkId)
 	}
 
 	char n[256];
-	Utils::ztsnprintf(n,sizeof(n),"network/%.16llx",(unsigned long long)networkId);
+	OSUtils::ztsnprintf(n,sizeof(n),"network/%.16llx",(unsigned long long)networkId);
 
 	if (_httpAddr) {
 		// Deletion is currently done by Central in harnessed mode
@@ -229,7 +229,7 @@ nlohmann::json JSONDB::eraseNetwork(const uint64_t networkId)
 nlohmann::json JSONDB::eraseNetworkMember(const uint64_t networkId,const uint64_t nodeId,bool recomputeSummaryInfo)
 {
 	char n[256];
-	Utils::ztsnprintf(n,sizeof(n),"network/%.16llx/member/%.10llx",(unsigned long long)networkId,(unsigned long long)nodeId);
+	OSUtils::ztsnprintf(n,sizeof(n),"network/%.16llx/member/%.10llx",(unsigned long long)networkId,(unsigned long long)nodeId);
 
 	if (_httpAddr) {
 		// Deletion is currently done by the caller in Central harnessed mode
@@ -314,7 +314,7 @@ void JSONDB::threadMain()
 								const nlohmann::json &mips = member["ipAssignments"];
 								if (mips.is_array()) {
 									for(unsigned long i=0;i<mips.size();++i) {
-										InetAddress mip(OSUtils::jsonString(mips[i],""));
+										InetAddress mip(OSUtils::jsonString(mips[i],"").c_str());
 										if ((mip.ss_family == AF_INET)||(mip.ss_family == AF_INET6))
 											ns.allocatedIps.push_back(mip);
 									}
diff --git a/node/Address.hpp b/node/Address.hpp
index 98e328585..12c52a3ff 100644
--- a/node/Address.hpp
+++ b/node/Address.hpp
@@ -141,20 +141,9 @@ public:
 	/**
 	 * @return Hexadecimal string
 	 */
-	inline std::string toString() const
+	inline char *toString(char buf[11]) const
 	{
-		char buf[16];
-		Utils::ztsnprintf(buf,sizeof(buf),"%.10llx",(unsigned long long)_a);
-		return std::string(buf);
-	};
-
-	/**
-	 * @param buf Buffer to fill
-	 * @param len Length of buffer
-	 */
-	inline void toString(char *buf,unsigned int len) const
-	{
-		Utils::ztsnprintf(buf,len,"%.10llx",(unsigned long long)_a);
+		return Utils::hex10(_a,buf);
 	}
 
 	/**
diff --git a/node/CertificateOfMembership.cpp b/node/CertificateOfMembership.cpp
index a5445e423..100253e10 100644
--- a/node/CertificateOfMembership.cpp
+++ b/node/CertificateOfMembership.cpp
@@ -57,6 +57,7 @@ void CertificateOfMembership::setQualifier(uint64_t id,uint64_t value,uint64_t m
 
 std::string CertificateOfMembership::toString() const
 {
+	char tmp[ZT_NETWORK_COM_MAX_QUALIFIERS * 32];
 	std::string s;
 
 	s.append("1:"); // COM_UINT64_ED25519
@@ -69,7 +70,7 @@ std::string CertificateOfMembership::toString() const
 			buf[ptr++] = Utils::hton(_qualifiers[i].value);
 			buf[ptr++] = Utils::hton(_qualifiers[i].maxDelta);
 		}
-		s.append(Utils::hex(buf,ptr * sizeof(uint64_t)));
+		s.append(Utils::hex(buf,ptr * sizeof(uint64_t),tmp));
 		delete [] buf;
 	} catch ( ... ) {
 		delete [] buf;
@@ -78,11 +79,11 @@ std::string CertificateOfMembership::toString() const
 
 	s.push_back(':');
 
-	s.append(_signedBy.toString());
+	s.append(_signedBy.toString(tmp));
 
 	if (_signedBy) {
 		s.push_back(':');
-		s.append(Utils::hex(_signature.data,(unsigned int)_signature.size()));
+		s.append(Utils::hex(_signature.data,(unsigned int)_signature.size(),tmp));
 	}
 
 	return s;
diff --git a/node/Dictionary.hpp b/node/Dictionary.hpp
index 0b000f136..6cbbfc0ec 100644
--- a/node/Dictionary.hpp
+++ b/node/Dictionary.hpp
@@ -391,8 +391,7 @@ public:
 	inline bool add(const char *key,uint64_t value)
 	{
 		char tmp[32];
-		Utils::ztsnprintf(tmp,sizeof(tmp),"%llx",(unsigned long long)value);
-		return this->add(key,tmp,-1);
+		return this->add(key,Utils::hex(value,tmp),-1);
 	}
 
 	/** 
@@ -401,8 +400,7 @@ public:
 	inline bool add(const char *key,const Address &a)
 	{
 		char tmp[32];
-		Utils::ztsnprintf(tmp,sizeof(tmp),"%.10llx",(unsigned long long)a.toInt());
-		return this->add(key,tmp,-1);
+		return this->add(key,Utils::hex(a.toInt(),tmp),-1);
 	}
 
 	/**
diff --git a/node/Identity.cpp b/node/Identity.cpp
index ba77aa475..3b00b4c0c 100644
--- a/node/Identity.cpp
+++ b/node/Identity.cpp
@@ -136,19 +136,23 @@ bool Identity::locallyValidate() const
 		(digest[63] == addrb[4]));
 }
 
-std::string Identity::toString(bool includePrivate) const
+char *Identity::toString(bool includePrivate,char buf[ZT_IDENTITY_STRING_BUFFER_LENGTH]) const
 {
-	std::string r;
-
-	r.append(_address.toString());
-	r.append(":0:"); // 0 == ZT_OBJECT_TYPE_IDENTITY
-	r.append(Utils::hex(_publicKey.data,(unsigned int)_publicKey.size()));
+	char *p = buf;
+	Utils::hex10(_address.toInt(),p);
+	p += 10;
+	*(p++) = ':';
+	*(p++) = '0';
+	*(p++) = ':';
+	Utils::hex(_publicKey.data,ZT_C25519_PUBLIC_KEY_LEN,p);
+	p += ZT_C25519_PUBLIC_KEY_LEN * 2;
 	if ((_privateKey)&&(includePrivate)) {
-		r.push_back(':');
-		r.append(Utils::hex(_privateKey->data,(unsigned int)_privateKey->size()));
+		*(p++) = ':';
+		Utils::hex(_privateKey->data,ZT_C25519_PRIVATE_KEY_LEN,p);
+		p += ZT_C25519_PRIVATE_KEY_LEN * 2;
 	}
-
-	return r;
+	*(p++) = (char)0;
+	return buf;
 }
 
 bool Identity::fromString(const char *str)
@@ -157,7 +161,7 @@ bool Identity::fromString(const char *str)
 		return false;
 
 	char *saveptr = (char *)0;
-	char tmp[1024];
+	char tmp[ZT_IDENTITY_STRING_BUFFER_LENGTH];
 	if (!Utils::scopy(tmp,sizeof(tmp),str))
 		return false;
 
diff --git a/node/Identity.hpp b/node/Identity.hpp
index 79e17f4d8..5804b9f8a 100644
--- a/node/Identity.hpp
+++ b/node/Identity.hpp
@@ -29,7 +29,6 @@
 
 #include <stdio.h>
 #include <stdlib.h>
-#include <string>
 
 #include "Constants.hpp"
 #include "Array.hpp"
@@ -39,6 +38,8 @@
 #include "Buffer.hpp"
 #include "SHA512.hpp"
 
+#define ZT_IDENTITY_STRING_BUFFER_LENGTH 384
+
 namespace ZeroTier {
 
 /**
@@ -66,16 +67,7 @@ public:
 	{
 	}
 
-	Identity(const char *str)
-		throw(std::invalid_argument) :
-		_privateKey((C25519::Private *)0)
-	{
-		if (!fromString(str))
-			throw std::invalid_argument(std::string("invalid string-serialized identity: ") + str);
-	}
-
-	Identity(const std::string &str)
-		throw(std::invalid_argument) :
+	Identity(const char *str) :
 		_privateKey((C25519::Private *)0)
 	{
 		if (!fromString(str))
@@ -277,9 +269,10 @@ public:
 	 * Serialize to a more human-friendly string
 	 *
 	 * @param includePrivate If true, include private key (if it exists)
+	 * @param buf Buffer to store string
 	 * @return ASCII string representation of identity
 	 */
-	std::string toString(bool includePrivate) const;
+	char *toString(bool includePrivate,char buf[ZT_IDENTITY_STRING_BUFFER_LENGTH]) const;
 
 	/**
 	 * Deserialize a human-friendly string
@@ -291,7 +284,6 @@ public:
 	 * @return True if deserialization appears successful
 	 */
 	bool fromString(const char *str);
-	inline bool fromString(const std::string &str) { return fromString(str.c_str()); }
 
 	/**
 	 * @return C25519 public key
diff --git a/node/InetAddress.cpp b/node/InetAddress.cpp
index 17d7c72e6..f7585bdba 100644
--- a/node/InetAddress.cpp
+++ b/node/InetAddress.cpp
@@ -5,7 +5,7 @@
  * 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.
+ * (at your oion) 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
@@ -40,7 +40,6 @@ const InetAddress InetAddress::LO4((const void *)("\x7f\x00\x00\x01"),4,0);
 const InetAddress InetAddress::LO6((const void *)("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"),16,0);
 
 InetAddress::IpScope InetAddress::ipScope() const
-	throw()
 {
 	switch(ss_family) {
 
@@ -111,27 +110,7 @@ InetAddress::IpScope InetAddress::ipScope() const
 	return IP_SCOPE_NONE;
 }
 
-void InetAddress::set(const std::string &ip,unsigned int port)
-	throw()
-{
-	memset(this,0,sizeof(InetAddress));
-	if (ip.find(':') != std::string::npos) {
-		struct sockaddr_in6 *sin6 = reinterpret_cast<struct sockaddr_in6 *>(this);
-		ss_family = AF_INET6;
-		sin6->sin6_port = Utils::hton((uint16_t)port);
-		if (inet_pton(AF_INET6,ip.c_str(),(void *)&(sin6->sin6_addr.s6_addr)) <= 0)
-			memset(this,0,sizeof(InetAddress));
-	} else if (ip.find('.') != std::string::npos) {
-		struct sockaddr_in *sin = reinterpret_cast<struct sockaddr_in *>(this);
-		ss_family = AF_INET;
-		sin->sin_port = Utils::hton((uint16_t)port);
-		if (inet_pton(AF_INET,ip.c_str(),(void *)&(sin->sin_addr.s_addr)) <= 0)
-			memset(this,0,sizeof(InetAddress));
-	}
-}
-
 void InetAddress::set(const void *ipBytes,unsigned int ipLen,unsigned int port)
-	throw()
 {
 	memset(this,0,sizeof(InetAddress));
 	if (ipLen == 4) {
@@ -147,90 +126,98 @@ void InetAddress::set(const void *ipBytes,unsigned int ipLen,unsigned int port)
 	}
 }
 
-std::string InetAddress::toString() const
+char *InetAddress::toString(char buf[64]) const
 {
-	char buf[128];
-	switch(ss_family) {
-		case AF_INET:
-			Utils::ztsnprintf(buf,sizeof(buf),"%d.%d.%d.%d/%d",
-					(int)(reinterpret_cast<const unsigned char *>(&(reinterpret_cast<const struct sockaddr_in *>(this)->sin_addr.s_addr)))[0],
-					(int)(reinterpret_cast<const unsigned char *>(&(reinterpret_cast<const struct sockaddr_in *>(this)->sin_addr.s_addr)))[1],
-					(int)(reinterpret_cast<const unsigned char *>(&(reinterpret_cast<const struct sockaddr_in *>(this)->sin_addr.s_addr)))[2],
-					(int)(reinterpret_cast<const unsigned char *>(&(reinterpret_cast<const struct sockaddr_in *>(this)->sin_addr.s_addr)))[3],
-					(int)Utils::ntoh((uint16_t)(reinterpret_cast<const struct sockaddr_in *>(this)->sin_port))
-				);
-			return std::string(buf);
-		case AF_INET6:
-			Utils::ztsnprintf(buf,sizeof(buf),"%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x/%d",
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[0]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[1]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[2]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[3]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[4]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[5]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[6]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[7]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[8]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[9]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[10]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[11]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[12]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[13]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[14]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[15]),
-					(int)Utils::ntoh((uint16_t)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_port))
-				);
-			return std::string(buf);
+	char *p = toIpString(buf);
+	if (*p) {
+		while (*p) ++p;
+		*(p++) = '/';
+		Utils::decimal(port(),p);
 	}
-	return std::string();
+	return buf;
 }
 
-std::string InetAddress::toIpString() const
+char *InetAddress::toIpString(char buf[64]) const
 {
-	char buf[128];
 	switch(ss_family) {
-		case AF_INET:
-			Utils::ztsnprintf(buf,sizeof(buf),"%d.%d.%d.%d",
-					(int)(reinterpret_cast<const unsigned char *>(&(reinterpret_cast<const struct sockaddr_in *>(this)->sin_addr.s_addr)))[0],
-					(int)(reinterpret_cast<const unsigned char *>(&(reinterpret_cast<const struct sockaddr_in *>(this)->sin_addr.s_addr)))[1],
-					(int)(reinterpret_cast<const unsigned char *>(&(reinterpret_cast<const struct sockaddr_in *>(this)->sin_addr.s_addr)))[2],
-					(int)(reinterpret_cast<const unsigned char *>(&(reinterpret_cast<const struct sockaddr_in *>(this)->sin_addr.s_addr)))[3]
-				);
-			return std::string(buf);
-		case AF_INET6:
-			Utils::ztsnprintf(buf,sizeof(buf),"%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x",
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[0]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[1]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[2]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[3]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[4]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[5]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[6]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[7]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[8]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[9]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[10]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[11]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[12]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[13]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[14]),
-					(int)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr[15])
-				);
-			return std::string(buf);
+		case AF_INET: {
+			const uint8_t *a = reinterpret_cast<const uint8_t *>(&(reinterpret_cast<const struct sockaddr_in *>(this)->sin_addr.s_addr));
+			char *p = buf;
+			for(int i=0;;++i) {
+				Utils::decimal((unsigned long)a[i],p);
+				if (i != 3) {
+					while (*p) ++p;
+					*(p++) = '.';
+				} else break;
+			}
+		}	break;
+
+		case AF_INET6: {
+			uint16_t a[8];
+			memcpy(a,reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr,16);
+			char *p = buf;
+			for(int i=0;i<8;++i) {
+				Utils::hex(Utils::ntoh(a[i]),p);
+				p[4] = (i == 7) ? (char)0 : ':';
+				p += 5;
+			}
+		}	break;
+
+		default:
+			buf[0] = (char)0;
+			break;
 	}
-	return std::string();
+	return buf;
 }
 
-void InetAddress::fromString(const std::string &ipSlashPort)
+bool InetAddress::fromString(const char *ipSlashPort)
 {
-	const std::size_t slashAt = ipSlashPort.find('/');
-	if (slashAt == std::string::npos) {
-		set(ipSlashPort,0);
+	char buf[64];
+
+	memset(this,0,sizeof(InetAddress));
+
+	if (!*ipSlashPort)
+		return true;
+	if (!Utils::scopy(buf,sizeof(buf),ipSlashPort))
+		return false;
+
+	char *portAt = buf;
+	while ((*portAt)&&(*portAt != '/'))
+		++portAt;
+	unsigned int port = 0;
+	if (*portAt) {
+		*(portAt++) = (char)0;
+		port = Utils::strToUInt(portAt) & 0xffff;
+	}
+
+	if (strchr(buf,':')) {
+		uint16_t a[8];
+		unsigned int b = 0;
+		char *saveptr = (char *)0;
+		for(char *s=Utils::stok(buf,":",&saveptr);((s)&&(b<8));s=Utils::stok((char *)0,":",&saveptr))
+			a[b++] = Utils::hton((uint16_t)(Utils::hexStrToUInt(s) & 0xffff));
+
+		struct sockaddr_in6 *const in6 = reinterpret_cast<struct sockaddr_in6 *>(this);
+		in6->sin6_family = AF_INET6;
+		memcpy(in6->sin6_addr.s6_addr,a,16);
+		in6->sin6_port = Utils::hton((uint16_t)port);
+
+		return true;
+	} else if (strchr(buf,'.')) {
+		uint8_t a[4];
+		unsigned int b = 0;
+		char *saveptr = (char *)0;
+		for(char *s=Utils::stok(buf,".",&saveptr);((s)&&(b<4));s=Utils::stok((char *)0,".",&saveptr))
+			a[b++] = (uint8_t)(Utils::strToUInt(s) & 0xff);
+
+		struct sockaddr_in *const in = reinterpret_cast<struct sockaddr_in *>(this);
+		in->sin_family = AF_INET;
+		memcpy(&(in->sin_addr.s_addr),a,4);
+		in->sin_port = Utils::hton((uint16_t)port);
+
+		return true;
 	} else {
-		long p = strtol(ipSlashPort.substr(slashAt+1).c_str(),(char **)0,10);
-		if ((p > 0)&&(p <= 0xffff))
-			set(ipSlashPort.substr(0,slashAt),(unsigned int)p);
-		else set(ipSlashPort.substr(0,slashAt),0);
+		return false;
 	}
 }
 
@@ -244,14 +231,13 @@ InetAddress InetAddress::netmask() const
 		case AF_INET6: {
 			uint64_t nm[2];
 			const unsigned int bits = netmaskBits();
-            if(bits) {
-                nm[0] = Utils::hton((uint64_t)((bits >= 64) ? 0xffffffffffffffffULL : (0xffffffffffffffffULL << (64 - bits))));
-                nm[1] = Utils::hton((uint64_t)((bits <= 64) ? 0ULL : (0xffffffffffffffffULL << (128 - bits))));
-            }
-            else {
-                nm[0] = 0;
-                nm[1] = 0;
-            }
+			if(bits) {
+				nm[0] = Utils::hton((uint64_t)((bits >= 64) ? 0xffffffffffffffffULL : (0xffffffffffffffffULL << (64 - bits))));
+				nm[1] = Utils::hton((uint64_t)((bits <= 64) ? 0ULL : (0xffffffffffffffffULL << (128 - bits))));
+			} else {
+				nm[0] = 0;
+				nm[1] = 0;
+			}
 			memcpy(reinterpret_cast<struct sockaddr_in6 *>(&r)->sin6_addr.s6_addr,nm,16);
 		}	break;
 	}
@@ -338,7 +324,6 @@ bool InetAddress::containsAddress(const InetAddress &addr) const
 }
 
 bool InetAddress::isNetwork() const
-	throw()
 {
 	switch(ss_family) {
 		case AF_INET: {
@@ -371,7 +356,6 @@ bool InetAddress::isNetwork() const
 }
 
 bool InetAddress::operator==(const InetAddress &a) const
-	throw()
 {
 	if (ss_family == a.ss_family) {
 		switch(ss_family) {
@@ -395,7 +379,6 @@ bool InetAddress::operator==(const InetAddress &a) const
 }
 
 bool InetAddress::operator<(const InetAddress &a) const
-	throw()
 {
 	if (ss_family < a.ss_family)
 		return true;
diff --git a/node/InetAddress.hpp b/node/InetAddress.hpp
index 4cb9a4dc9..dd0550841 100644
--- a/node/InetAddress.hpp
+++ b/node/InetAddress.hpp
@@ -31,8 +31,6 @@
 #include <string.h>
 #include <stdint.h>
 
-#include <string>
-
 #include "Constants.hpp"
 #include "../include/ZeroTierOne.h"
 #include "Utils.hpp"
@@ -85,25 +83,22 @@ struct InetAddress : public sockaddr_storage
 		IP_SCOPE_PRIVATE = 7        // 10.x.x.x, 192.168.x.x, etc.
 	};
 
-	InetAddress() throw() { memset(this,0,sizeof(InetAddress)); }
-	InetAddress(const InetAddress &a) throw() { memcpy(this,&a,sizeof(InetAddress)); }
-	InetAddress(const InetAddress *a) throw() { memcpy(this,a,sizeof(InetAddress)); }
-	InetAddress(const struct sockaddr_storage &ss) throw() { *this = ss; }
-	InetAddress(const struct sockaddr_storage *ss) throw() { *this = ss; }
-	InetAddress(const struct sockaddr &sa) throw() { *this = sa; }
-	InetAddress(const struct sockaddr *sa) throw() { *this = sa; }
-	InetAddress(const struct sockaddr_in &sa) throw() { *this = sa; }
-	InetAddress(const struct sockaddr_in *sa) throw() { *this = sa; }
-	InetAddress(const struct sockaddr_in6 &sa) throw() { *this = sa; }
-	InetAddress(const struct sockaddr_in6 *sa) throw() { *this = sa; }
-	InetAddress(const void *ipBytes,unsigned int ipLen,unsigned int port) throw() { this->set(ipBytes,ipLen,port); }
-	InetAddress(const uint32_t ipv4,unsigned int port) throw() { this->set(&ipv4,4,port); }
-	InetAddress(const std::string &ip,unsigned int port) throw() { this->set(ip,port); }
-	InetAddress(const std::string &ipSlashPort) throw() { this->fromString(ipSlashPort); }
-	InetAddress(const char *ipSlashPort) throw() { this->fromString(std::string(ipSlashPort)); }
+	InetAddress() { memset(this,0,sizeof(InetAddress)); }
+	InetAddress(const InetAddress &a) { memcpy(this,&a,sizeof(InetAddress)); }
+	InetAddress(const InetAddress *a) { memcpy(this,a,sizeof(InetAddress)); }
+	InetAddress(const struct sockaddr_storage &ss) { *this = ss; }
+	InetAddress(const struct sockaddr_storage *ss) { *this = ss; }
+	InetAddress(const struct sockaddr &sa) { *this = sa; }
+	InetAddress(const struct sockaddr *sa) { *this = sa; }
+	InetAddress(const struct sockaddr_in &sa) { *this = sa; }
+	InetAddress(const struct sockaddr_in *sa) { *this = sa; }
+	InetAddress(const struct sockaddr_in6 &sa) { *this = sa; }
+	InetAddress(const struct sockaddr_in6 *sa) { *this = sa; }
+	InetAddress(const void *ipBytes,unsigned int ipLen,unsigned int port) { this->set(ipBytes,ipLen,port); }
+	InetAddress(const uint32_t ipv4,unsigned int port) { this->set(&ipv4,4,port); }
+	InetAddress(const char *ipSlashPort) { this->fromString(ipSlashPort); }
 
 	inline InetAddress &operator=(const InetAddress &a)
-		throw()
 	{
 		if (&a != this)
 			memcpy(this,&a,sizeof(InetAddress));
@@ -111,7 +106,6 @@ struct InetAddress : public sockaddr_storage
 	}
 
 	inline InetAddress &operator=(const InetAddress *a)
-		throw()
 	{
 		if (a != this)
 			memcpy(this,a,sizeof(InetAddress));
@@ -119,7 +113,6 @@ struct InetAddress : public sockaddr_storage
 	}
 
 	inline InetAddress &operator=(const struct sockaddr_storage &ss)
-		throw()
 	{
 		if (reinterpret_cast<const InetAddress *>(&ss) != this)
 			memcpy(this,&ss,sizeof(InetAddress));
@@ -127,7 +120,6 @@ struct InetAddress : public sockaddr_storage
 	}
 
 	inline InetAddress &operator=(const struct sockaddr_storage *ss)
-		throw()
 	{
 		if (reinterpret_cast<const InetAddress *>(ss) != this)
 			memcpy(this,ss,sizeof(InetAddress));
@@ -135,7 +127,6 @@ struct InetAddress : public sockaddr_storage
 	}
 
 	inline InetAddress &operator=(const struct sockaddr_in &sa)
-		throw()
 	{
 		if (reinterpret_cast<const InetAddress *>(&sa) != this) {
 			memset(this,0,sizeof(InetAddress));
@@ -145,7 +136,6 @@ struct InetAddress : public sockaddr_storage
 	}
 
 	inline InetAddress &operator=(const struct sockaddr_in *sa)
-		throw()
 	{
 		if (reinterpret_cast<const InetAddress *>(sa) != this) {
 			memset(this,0,sizeof(InetAddress));
@@ -155,7 +145,6 @@ struct InetAddress : public sockaddr_storage
 	}
 
 	inline InetAddress &operator=(const struct sockaddr_in6 &sa)
-		throw()
 	{
 		if (reinterpret_cast<const InetAddress *>(&sa) != this) {
 			memset(this,0,sizeof(InetAddress));
@@ -165,7 +154,6 @@ struct InetAddress : public sockaddr_storage
 	}
 
 	inline InetAddress &operator=(const struct sockaddr_in6 *sa)
-		throw()
 	{
 		if (reinterpret_cast<const InetAddress *>(sa) != this) {
 			memset(this,0,sizeof(InetAddress));
@@ -175,7 +163,6 @@ struct InetAddress : public sockaddr_storage
 	}
 
 	inline InetAddress &operator=(const struct sockaddr &sa)
-		throw()
 	{
 		if (reinterpret_cast<const InetAddress *>(&sa) != this) {
 			memset(this,0,sizeof(InetAddress));
@@ -192,7 +179,6 @@ struct InetAddress : public sockaddr_storage
 	}
 
 	inline InetAddress &operator=(const struct sockaddr *sa)
-		throw()
 	{
 		if (reinterpret_cast<const InetAddress *>(sa) != this) {
 			memset(this,0,sizeof(InetAddress));
@@ -211,17 +197,7 @@ struct InetAddress : public sockaddr_storage
 	/**
 	 * @return IP scope classification (e.g. loopback, link-local, private, global)
 	 */
-	IpScope ipScope() const
-		throw();
-
-	/**
-	 * Set from a string-format IP and a port
-	 *
-	 * @param ip IP address in V4 or V6 ASCII notation
-	 * @param port Port or 0 for none
-	 */
-	void set(const std::string &ip,unsigned int port)
-		throw();
+	IpScope ipScope() const;
 
 	/**
 	 * Set from a raw IP and port number
@@ -230,8 +206,7 @@ struct InetAddress : public sockaddr_storage
 	 * @param ipLen Length of IP address: 4 or 16
 	 * @param port Port number or 0 for none
 	 */
-	void set(const void *ipBytes,unsigned int ipLen,unsigned int port)
-		throw();
+	void set(const void *ipBytes,unsigned int ipLen,unsigned int port);
 
 	/**
 	 * Set the port component
@@ -272,23 +247,23 @@ struct InetAddress : public sockaddr_storage
 	/**
 	 * @return ASCII IP/port format representation
 	 */
-	std::string toString() const;
+	char *toString(char buf[64]) const;
 
 	/**
 	 * @return IP portion only, in ASCII string format
 	 */
-	std::string toIpString() const;
+	char *toIpString(char buf[64]) const;
 
 	/**
-	 * @param ipSlashPort ASCII IP/port format notation
+	 * @param ipSlashPort IP/port (port is optional, will be 0 if not included)
+	 * @return True if address appeared to be valid
 	 */
-	void fromString(const std::string &ipSlashPort);
+	bool fromString(const char *ipSlashPort);
 
 	/**
 	 * @return Port or 0 if no port component defined
 	 */
 	inline unsigned int port() const
-		throw()
 	{
 		switch(ss_family) {
 			case AF_INET: return Utils::ntoh((uint16_t)(reinterpret_cast<const struct sockaddr_in *>(this)->sin_port));
@@ -306,7 +281,7 @@ struct InetAddress : public sockaddr_storage
 	 *
 	 * @return Netmask bits
 	 */
-	inline unsigned int netmaskBits() const throw() { return port(); }
+	inline unsigned int netmaskBits() const { return port(); }
 
 	/**
 	 * @return True if netmask bits is valid for the address type
@@ -329,7 +304,7 @@ struct InetAddress : public sockaddr_storage
 	 *
 	 * @return Gateway metric
 	 */
-	inline unsigned int metric() const throw() { return port(); }
+	inline unsigned int metric() const { return port(); }
 
 	/**
 	 * Construct a full netmask as an InetAddress
@@ -376,12 +351,12 @@ struct InetAddress : public sockaddr_storage
 	/**
 	 * @return True if this is an IPv4 address
 	 */
-	inline bool isV4() const throw() { return (ss_family == AF_INET); }
+	inline bool isV4() const { return (ss_family == AF_INET); }
 
 	/**
 	 * @return True if this is an IPv6 address
 	 */
-	inline bool isV6() const throw() { return (ss_family == AF_INET6); }
+	inline bool isV6() const { return (ss_family == AF_INET6); }
 
 	/**
 	 * @return pointer to raw address bytes or NULL if not available
@@ -454,7 +429,7 @@ struct InetAddress : public sockaddr_storage
 	/**
 	 * Set to null/zero
 	 */
-	inline void zero() throw() { memset(this,0,sizeof(InetAddress)); }
+	inline void zero() { memset(this,0,sizeof(InetAddress)); }
 
 	/**
 	 * Check whether this is a network/route rather than an IP assignment
@@ -464,8 +439,7 @@ struct InetAddress : public sockaddr_storage
 	 *
 	 * @return True if everything after netmask bits is zero
 	 */
-	bool isNetwork() const
-		throw();
+	bool isNetwork() const;
 
 	/**
 	 * @return 14-bit (0-16383) hash of this IP's first 24 or 48 bits (for V4 or V6) for rate limiting code, or 0 if non-IP
@@ -494,7 +468,7 @@ struct InetAddress : public sockaddr_storage
 	/**
 	 * @return True if address family is non-zero
 	 */
-	inline operator bool() const throw() { return (ss_family != 0); }
+	inline operator bool() const { return (ss_family != 0); }
 
 	template<unsigned int C>
 	inline void serialize(Buffer<C> &b) const
@@ -552,12 +526,12 @@ struct InetAddress : public sockaddr_storage
 		return (p - startAt);
 	}
 
-	bool operator==(const InetAddress &a) const throw();
-	bool operator<(const InetAddress &a) const throw();
-	inline bool operator!=(const InetAddress &a) const throw() { return !(*this == a); }
-	inline bool operator>(const InetAddress &a) const throw() { return (a < *this); }
-	inline bool operator<=(const InetAddress &a) const throw() { return !(a < *this); }
-	inline bool operator>=(const InetAddress &a) const throw() { return !(*this < a); }
+	bool operator==(const InetAddress &a) const;
+	bool operator<(const InetAddress &a) const;
+	inline bool operator!=(const InetAddress &a) const { return !(*this == a); }
+	inline bool operator>(const InetAddress &a) const { return (a < *this); }
+	inline bool operator<=(const InetAddress &a) const { return !(a < *this); }
+	inline bool operator>=(const InetAddress &a) const { return !(*this < a); }
 
 	/**
 	 * @param mac MAC address seed
diff --git a/node/MAC.hpp b/node/MAC.hpp
index db50aeb13..52388d592 100644
--- a/node/MAC.hpp
+++ b/node/MAC.hpp
@@ -44,30 +44,24 @@ namespace ZeroTier {
 class MAC
 {
 public:
-	MAC() throw() : _m(0ULL) {}
-	MAC(const MAC &m) throw() : _m(m._m) {}
+	MAC() : _m(0ULL) {}
+	MAC(const MAC &m) : _m(m._m) {}
 
-	MAC(const unsigned char a,const unsigned char b,const unsigned char c,const unsigned char d,const unsigned char e,const unsigned char f) throw() :
+	MAC(const unsigned char a,const unsigned char b,const unsigned char c,const unsigned char d,const unsigned char e,const unsigned char f) :
 		_m( ((((uint64_t)a) & 0xffULL) << 40) |
 		    ((((uint64_t)b) & 0xffULL) << 32) |
 		    ((((uint64_t)c) & 0xffULL) << 24) |
 		    ((((uint64_t)d) & 0xffULL) << 16) |
 		    ((((uint64_t)e) & 0xffULL) << 8) |
 		    (((uint64_t)f) & 0xffULL) ) {}
-
-	MAC(const char *s) throw() { fromString(s); }
-	MAC(const std::string &s) throw() { fromString(s.c_str()); }
-
-	MAC(const void *bits,unsigned int len) throw() { setTo(bits,len); }
-
-	MAC(const Address &ztaddr,uint64_t nwid) throw() { fromAddress(ztaddr,nwid); }
-
-	MAC(const uint64_t m) throw() : _m(m & 0xffffffffffffULL) {}
+	MAC(const void *bits,unsigned int len) { setTo(bits,len); }
+	MAC(const Address &ztaddr,uint64_t nwid) { fromAddress(ztaddr,nwid); }
+	MAC(const uint64_t m) : _m(m & 0xffffffffffffULL) {}
 
 	/**
 	 * @return MAC in 64-bit integer
 	 */
-	inline uint64_t toInt() const throw() { return _m; }
+	inline uint64_t toInt() const { return _m; }
 
 	/**
 	 * Set MAC to zero
@@ -77,14 +71,13 @@ public:
 	/**
 	 * @return True if MAC is non-zero
 	 */
-	inline operator bool() const throw() { return (_m != 0ULL); }
+	inline operator bool() const { return (_m != 0ULL); }
 
 	/**
 	 * @param bits Raw MAC in big-endian byte order
 	 * @param len Length, must be >= 6 or result is zero
 	 */
 	inline void setTo(const void *bits,unsigned int len)
-		throw()
 	{
 		if (len < 6) {
 			_m = 0ULL;
@@ -104,7 +97,6 @@ public:
 	 * @param len Length of buffer, must be >= 6 or nothing is copied
 	 */
 	inline void copyTo(void *buf,unsigned int len) const
-		throw()
 	{
 		if (len < 6)
 			return;
@@ -124,7 +116,6 @@ public:
 	 */
 	template<unsigned int C>
 	inline void appendTo(Buffer<C> &b) const
-		throw(std::out_of_range)
 	{
 		unsigned char *p = (unsigned char *)b.appendField(6);
 		*(p++) = (unsigned char)((_m >> 40) & 0xff);
@@ -138,48 +129,17 @@ public:
 	/**
 	 * @return True if this is broadcast (all 0xff)
 	 */
-	inline bool isBroadcast() const throw() { return (_m == 0xffffffffffffULL); }
+	inline bool isBroadcast() const { return (_m == 0xffffffffffffULL); }
 
 	/**
 	 * @return True if this is a multicast MAC
 	 */
-	inline bool isMulticast() const throw() { return ((_m & 0x010000000000ULL) != 0ULL); }
+	inline bool isMulticast() const { return ((_m & 0x010000000000ULL) != 0ULL); }
 
 	/**
 	 * @param True if this is a locally-administered MAC
 	 */
-	inline bool isLocallyAdministered() const throw() { return ((_m & 0x020000000000ULL) != 0ULL); }
-
-	/**
-	 * @param s Hex MAC, with or without : delimiters
-	 */
-	inline void fromString(const char *s)
-	{
-		char tmp[8];
-		for(int i=0;i<6;++i)
-			tmp[i] = (char)0;
-		Utils::unhex(s,tmp,6);
-		setTo(tmp,6);
-	}
-
-	/**
-	 * @return MAC address in standard :-delimited hex format
-	 */
-	inline std::string toString() const
-	{
-		char tmp[24];
-		toString(tmp,sizeof(tmp));
-		return std::string(tmp);
-	}
-
-	/**
-	 * @param buf Buffer to contain human-readable MAC
-	 * @param len Length of buffer
-	 */
-	inline void toString(char *buf,unsigned int len) const
-	{
-		Utils::ztsnprintf(buf,len,"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(int)(*this)[0],(int)(*this)[1],(int)(*this)[2],(int)(*this)[3],(int)(*this)[4],(int)(*this)[5]);
-	}
+	inline bool isLocallyAdministered() const { return ((_m & 0x020000000000ULL) != 0ULL); }
 
 	/**
 	 * Set this MAC to a MAC derived from an address and a network ID
@@ -188,7 +148,6 @@ public:
 	 * @param nwid 64-bit network ID
 	 */
 	inline void fromAddress(const Address &ztaddr,uint64_t nwid)
-		throw()
 	{
 		uint64_t m = ((uint64_t)firstOctetForNetwork(nwid)) << 40;
 		m |= ztaddr.toInt(); // a is 40 bits
@@ -208,7 +167,6 @@ public:
 	 * @param nwid Network ID
 	 */
 	inline Address toAddress(uint64_t nwid) const
-		throw()
 	{
 		uint64_t a = _m & 0xffffffffffULL; // least significant 40 bits of MAC are formed from address
 		a ^= ((nwid >> 8) & 0xff) << 32; // ... XORed with bits 8-48 of the nwid in little-endian byte order, so unmask it
@@ -224,7 +182,6 @@ public:
 	 * @return First octet of MAC for this network
 	 */
 	static inline unsigned char firstOctetForNetwork(uint64_t nwid)
-		throw()
 	{
 		unsigned char a = ((unsigned char)(nwid & 0xfe) | 0x02); // locally administered, not multicast, from LSB of network ID
 		return ((a == 0x52) ? 0x32 : a); // blacklist 0x52 since it's used by KVM, libvirt, and other popular virtualization engines... seems de-facto standard on Linux
@@ -239,29 +196,27 @@ public:
 	/**
 	 * @return 6, which is the number of bytes in a MAC, for container compliance
 	 */
-	inline unsigned int size() const throw() { return 6; }
+	inline unsigned int size() const { return 6; }
 
-	inline unsigned long hashCode() const throw() { return (unsigned long)_m; }
+	inline unsigned long hashCode() const { return (unsigned long)_m; }
 
 	inline MAC &operator=(const MAC &m)
-		throw()
 	{
 		_m = m._m;
 		return *this;
 	}
 	inline MAC &operator=(const uint64_t m)
-		throw()
 	{
 		_m = m;
 		return *this;
 	}
 
-	inline bool operator==(const MAC &m) const throw() { return (_m == m._m); }
-	inline bool operator!=(const MAC &m) const throw() { return (_m != m._m); }
-	inline bool operator<(const MAC &m) const throw() { return (_m < m._m); }
-	inline bool operator<=(const MAC &m) const throw() { return (_m <= m._m); }
-	inline bool operator>(const MAC &m) const throw() { return (_m > m._m); }
-	inline bool operator>=(const MAC &m) const throw() { return (_m >= m._m); }
+	inline bool operator==(const MAC &m) const { return (_m == m._m); }
+	inline bool operator!=(const MAC &m) const { return (_m != m._m); }
+	inline bool operator<(const MAC &m) const { return (_m < m._m); }
+	inline bool operator<=(const MAC &m) const { return (_m <= m._m); }
+	inline bool operator>(const MAC &m) const { return (_m > m._m); }
+	inline bool operator>=(const MAC &m) const { return (_m >= m._m); }
 
 private:
 	uint64_t _m;
diff --git a/node/MulticastGroup.hpp b/node/MulticastGroup.hpp
index 7cbec2e02..f56c675b9 100644
--- a/node/MulticastGroup.hpp
+++ b/node/MulticastGroup.hpp
@@ -29,8 +29,6 @@
 
 #include <stdint.h>
 
-#include <string>
-
 #include "MAC.hpp"
 #include "InetAddress.hpp"
 
@@ -94,16 +92,6 @@ public:
 		return MulticastGroup();
 	}
 
-	/**
-	 * @return Human readable string representing this group (MAC/ADI in hex)
-	 */
-	inline std::string toString() const
-	{
-		char buf[64];
-		Utils::ztsnprintf(buf,sizeof(buf),"%.2x%.2x%.2x%.2x%.2x%.2x/%.8lx",(unsigned int)_mac[0],(unsigned int)_mac[1],(unsigned int)_mac[2],(unsigned int)_mac[3],(unsigned int)_mac[4],(unsigned int)_mac[5],(unsigned long)_adi);
-		return std::string(buf);
-	}
-
 	/**
 	 * @return Multicast address
 	 */
diff --git a/node/Network.cpp b/node/Network.cpp
index bccc0397e..f2b6771b2 100644
--- a/node/Network.cpp
+++ b/node/Network.cpp
@@ -51,7 +51,7 @@ namespace ZeroTier {
 namespace {
 
 #ifdef ZT_RULES_ENGINE_DEBUGGING
-#define FILTER_TRACE(f,...) { Utils::ztsnprintf(dpbuf,sizeof(dpbuf),f,##__VA_ARGS__); dlog.push_back(std::string(dpbuf)); }
+#define FILTER_TRACE(f,...) { snprintf(dpbuf,sizeof(dpbuf),f,##__VA_ARGS__); dlog.push_back(std::string(dpbuf)); }
 static const char *_rtn(const ZT_VirtualNetworkRuleType rt)
 {
 	switch(rt) {
@@ -1257,7 +1257,17 @@ void Network::requestConfiguration(void *tPtr)
 			nconf->rules[13].t = (uint8_t)ZT_NETWORK_RULE_ACTION_DROP;
 
 			nconf->type = ZT_NETWORK_TYPE_PUBLIC;
-			Utils::ztsnprintf(nconf->name,sizeof(nconf->name),"adhoc-%.04x-%.04x",(int)startPortRange,(int)endPortRange);
+
+			nconf->name[0] = 'a';
+			nconf->name[1] = 'd';
+			nconf->name[2] = 'h';
+			nconf->name[3] = 'o';
+			nconf->name[4] = 'c';
+			nconf->name[5] = '-';
+			Utils::hex((uint16_t)startPortRange,nconf->name + 6);
+			nconf->name[10] = '-';
+			Utils::hex((uint16_t)endPortRange,nconf->name + 11);
+			nconf->name[15] = (char)0;
 
 			this->setConfiguration(tPtr,*nconf,false);
 			delete nconf;
diff --git a/node/NetworkConfig.cpp b/node/NetworkConfig.cpp
index 65101c3ad..e59299230 100644
--- a/node/NetworkConfig.cpp
+++ b/node/NetworkConfig.cpp
@@ -64,7 +64,8 @@ bool NetworkConfig::toDictionary(Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> &d,b
 				if (this->staticIps[i].ss_family == AF_INET) {
 					if (v4s.length() > 0)
 						v4s.push_back(',');
-					v4s.append(this->staticIps[i].toString());
+					char buf[64];
+					v4s.append(this->staticIps[i].toString(buf));
 				}
 			}
 			if (v4s.length() > 0) {
@@ -75,7 +76,8 @@ bool NetworkConfig::toDictionary(Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> &d,b
 				if (this->staticIps[i].ss_family == AF_INET6) {
 					if (v6s.length() > 0)
 						v6s.push_back(',');
-					v6s.append(this->staticIps[i].toString());
+					char buf[64];
+					v6s.append(this->staticIps[i].toString(buf));
 				}
 			}
 			if (v6s.length() > 0) {
@@ -94,8 +96,7 @@ bool NetworkConfig::toDictionary(Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> &d,b
 						if (ets.length() > 0)
 							ets.push_back(',');
 						char tmp2[16];
-						Utils::ztsnprintf(tmp2,sizeof(tmp2),"%x",et);
-						ets.append(tmp2);
+						ets.append(Utils::hex((uint16_t)et,tmp2));
 					}
 					et = 0;
 				}
@@ -114,7 +115,8 @@ bool NetworkConfig::toDictionary(Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> &d,b
 				if ((this->specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE) != 0) {
 					if (ab.length() > 0)
 						ab.push_back(',');
-					ab.append(Address(this->specialists[i]).toString().c_str());
+					char tmp2[16];
+					ab.append(Address(this->specialists[i]).toString(tmp2));
 				}
 			}
 			if (ab.length() > 0) {
diff --git a/node/Node.cpp b/node/Node.cpp
index 4b598f615..e28accee3 100644
--- a/node/Node.cpp
+++ b/node/Node.cpp
@@ -82,8 +82,8 @@ Node::Node(void *uptr,void *tptr,const struct ZT_Node_Callbacks *callbacks,uint6
 	if (n > 0) {
 		tmp[n] = (char)0;
 		if (RR->identity.fromString(tmp)) {
-			RR->publicIdentityStr = RR->identity.toString(false);
-			RR->secretIdentityStr = RR->identity.toString(true);
+			RR->identity.toString(false,RR->publicIdentityStr);
+			RR->identity.toString(true,RR->secretIdentityStr);
 		} else {
 			n = -1;
 		}
@@ -92,10 +92,10 @@ Node::Node(void *uptr,void *tptr,const struct ZT_Node_Callbacks *callbacks,uint6
 	idtmp[0] = RR->identity.address().toInt(); idtmp[1] = 0;
 	if (n <= 0) {
 		RR->identity.generate();
-		RR->publicIdentityStr = RR->identity.toString(false);
-		RR->secretIdentityStr = RR->identity.toString(true);
-		stateObjectPut(tptr,ZT_STATE_OBJECT_IDENTITY_SECRET,idtmp,RR->secretIdentityStr.data(),(unsigned int)RR->secretIdentityStr.length());
-		stateObjectPut(tptr,ZT_STATE_OBJECT_IDENTITY_PUBLIC,idtmp,RR->publicIdentityStr.data(),(unsigned int)RR->publicIdentityStr.length());
+		RR->identity.toString(false,RR->publicIdentityStr);
+		RR->identity.toString(true,RR->secretIdentityStr);
+		stateObjectPut(tptr,ZT_STATE_OBJECT_IDENTITY_SECRET,idtmp,RR->secretIdentityStr,(unsigned int)strlen(RR->secretIdentityStr));
+		stateObjectPut(tptr,ZT_STATE_OBJECT_IDENTITY_PUBLIC,idtmp,RR->publicIdentityStr,(unsigned int)strlen(RR->publicIdentityStr));
 	} else {
 		n = stateObjectGet(tptr,ZT_STATE_OBJECT_IDENTITY_PUBLIC,idtmp,tmp,sizeof(tmp) - 1);
 		if (n > 0) {
@@ -104,7 +104,7 @@ Node::Node(void *uptr,void *tptr,const struct ZT_Node_Callbacks *callbacks,uint6
 				n = -1;
 		}
 		if (n <= 0)
-			stateObjectPut(tptr,ZT_STATE_OBJECT_IDENTITY_PUBLIC,idtmp,RR->publicIdentityStr.data(),(unsigned int)RR->publicIdentityStr.length());
+			stateObjectPut(tptr,ZT_STATE_OBJECT_IDENTITY_PUBLIC,idtmp,RR->publicIdentityStr,(unsigned int)strlen(RR->publicIdentityStr));
 	}
 
 	try {
@@ -386,8 +386,8 @@ uint64_t Node::address() const
 void Node::status(ZT_NodeStatus *status) const
 {
 	status->address = RR->identity.address().toInt();
-	status->publicIdentity = RR->publicIdentityStr.c_str();
-	status->secretIdentity = RR->secretIdentityStr.c_str();
+	status->publicIdentity = RR->publicIdentityStr;
+	status->secretIdentity = RR->secretIdentityStr;
 	status->online = _online ? 1 : 0;
 }
 
@@ -544,39 +544,6 @@ bool Node::shouldUsePathForZeroTierTraffic(void *tPtr,const Address &ztaddr,cons
 	return ( (_cb.pathCheckFunction) ? (_cb.pathCheckFunction(reinterpret_cast<ZT_Node *>(this),_uPtr,tPtr,ztaddr.toInt(),localSocket,reinterpret_cast<const struct sockaddr_storage *>(&remoteAddress)) != 0) : true);
 }
 
-#ifdef ZT_TRACE
-void Node::postTrace(const char *module,unsigned int line,const char *fmt,...)
-{
-	static Mutex traceLock;
-
-	va_list ap;
-	char tmp1[1024],tmp2[1024],tmp3[256];
-
-	Mutex::Lock _l(traceLock);
-
-	time_t now = (time_t)(_now / 1000ULL);
-#ifdef __WINDOWS__
-	ctime_s(tmp3,sizeof(tmp3),&now);
-	char *nowstr = tmp3;
-#else
-	char *nowstr = ctime_r(&now,tmp3);
-#endif
-	unsigned long nowstrlen = (unsigned long)strlen(nowstr);
-	if (nowstr[nowstrlen-1] == '\n')
-		nowstr[--nowstrlen] = (char)0;
-	if (nowstr[nowstrlen-1] == '\r')
-		nowstr[--nowstrlen] = (char)0;
-
-	va_start(ap,fmt);
-	vsnprintf(tmp2,sizeof(tmp2),fmt,ap);
-	va_end(ap);
-	tmp2[sizeof(tmp2)-1] = (char)0;
-
-	Utils::ztsnprintf(tmp1,sizeof(tmp1),"[%s] %s:%u %s",nowstr,module,line,tmp2);
-	postEvent((void *)0,ZT_EVENT_TRACE,tmp1);
-}
-#endif // ZT_TRACE
-
 uint64_t Node::prng()
 {
 	// https://en.wikipedia.org/wiki/Xorshift#xorshift.2B
diff --git a/node/Node.hpp b/node/Node.hpp
index 55491b064..40903f7c1 100644
--- a/node/Node.hpp
+++ b/node/Node.hpp
@@ -195,10 +195,6 @@ public:
 	inline void stateObjectPut(void *const tPtr,ZT_StateObjectType type,const uint64_t id[2],const void *const data,const unsigned int len) { _cb.statePutFunction(reinterpret_cast<ZT_Node *>(this),_uPtr,tPtr,type,id,data,(int)len); }
 	inline void stateObjectDelete(void *const tPtr,ZT_StateObjectType type,const uint64_t id[2]) { _cb.statePutFunction(reinterpret_cast<ZT_Node *>(this),_uPtr,tPtr,type,id,(const void *)0,-1); }
 
-#ifdef ZT_TRACE
-	void postTrace(const char *module,unsigned int line,const char *fmt,...);
-#endif
-
 	bool shouldUsePathForZeroTierTraffic(void *tPtr,const Address &ztaddr,const int64_t localSocket,const InetAddress &remoteAddress);
 	inline bool externalPathLookup(void *tPtr,const Address &ztaddr,int family,InetAddress &addr) { return ( (_cb.pathLookupFunction) ? (_cb.pathLookupFunction(reinterpret_cast<ZT_Node *>(this),_uPtr,tPtr,ztaddr.toInt(),family,reinterpret_cast<struct sockaddr_storage *>(&addr)) != 0) : false ); }
 
diff --git a/node/RuntimeEnvironment.hpp b/node/RuntimeEnvironment.hpp
index 99afe25db..94b96d348 100644
--- a/node/RuntimeEnvironment.hpp
+++ b/node/RuntimeEnvironment.hpp
@@ -27,7 +27,7 @@
 #ifndef ZT_RUNTIMEENVIRONMENT_HPP
 #define ZT_RUNTIMEENVIRONMENT_HPP
 
-#include <string>
+#include <string.h>
 
 #include "Constants.hpp"
 #include "Utils.hpp"
@@ -60,11 +60,13 @@ public:
 		,sa((SelfAwareness *)0)
 	{
 		Utils::getSecureRandom(&instanceId,sizeof(instanceId));
+		memset(publicIdentityStr,0,sizeof(publicIdentityStr));
+		memset(secretIdentityStr,0,sizeof(secretIdentityStr));
 	}
 
 	~RuntimeEnvironment()
 	{
-		Utils::burn(reinterpret_cast<void *>(const_cast<char *>(secretIdentityStr.data())),(unsigned int)secretIdentityStr.length());
+		Utils::burn(secretIdentityStr,sizeof(secretIdentityStr));
 	}
 
 	/**
@@ -77,8 +79,8 @@ public:
 
 	// This node's identity
 	Identity identity;
-	std::string publicIdentityStr;
-	std::string secretIdentityStr;
+	char publicIdentityStr[ZT_IDENTITY_STRING_BUFFER_LENGTH];
+	char secretIdentityStr[ZT_IDENTITY_STRING_BUFFER_LENGTH];
 
 	// This is set externally to an instance of this base class
 	NetworkController *localNetworkController;
diff --git a/node/Topology.cpp b/node/Topology.cpp
index 809bc7e78..e7bbdfae1 100644
--- a/node/Topology.cpp
+++ b/node/Topology.cpp
@@ -91,12 +91,8 @@ Topology::Topology(const RuntimeEnvironment *renv,void *tPtr) :
 SharedPtr<Peer> Topology::addPeer(void *tPtr,const SharedPtr<Peer> &peer)
 {
 #ifdef ZT_TRACE
-	if ((!peer)||(peer->address() == RR->identity.address())) {
-		if (!peer)
-			fprintf(stderr,"FATAL BUG: addPeer() caught attempt to add NULL peer" ZT_EOL_S);
-		else fprintf(stderr,"FATAL BUG: addPeer() caught attempt to add peer for self" ZT_EOL_S);
-		abort();
-	}
+	if ((!peer)||(peer->address() == RR->identity.address()))
+		return SharedPtr<Peer>();
 #endif
 
 	SharedPtr<Peer> np;
diff --git a/node/Utils.cpp b/node/Utils.cpp
index d2321e169..a3a4c3c38 100644
--- a/node/Utils.cpp
+++ b/node/Utils.cpp
@@ -55,6 +55,27 @@ namespace ZeroTier {
 
 const char Utils::HEXCHARS[16] = { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' };
 
+static unsigned long _Utils_itoa(unsigned long n,char *s)
+{
+	if (n == 0)
+		return 0;
+	unsigned long pos = _Utils_itoa(n / 10,s);
+	if (pos >= 22) // sanity check, should be impossible
+		pos = 22;
+	s[pos] = '0' + (char)(n % 10);
+	return pos + 1;
+}
+char *Utils::decimal(unsigned long n,char s[24])
+{
+	if (n == 0) {
+		s[0] = '0';
+		s[1] = (char)0;
+		return s;
+	}
+	s[_Utils_itoa(n,s)] = (char)0;
+	return s;
+}
+
 // Crazy hack to force memory to be securely zeroed in spite of the best efforts of optimizing compilers.
 static void _Utils_doBurn(volatile uint8_t *ptr,unsigned int len)
 {
@@ -64,82 +85,6 @@ static void _Utils_doBurn(volatile uint8_t *ptr,unsigned int len)
 static void (*volatile _Utils_doBurn_ptr)(volatile uint8_t *,unsigned int) = _Utils_doBurn;
 void Utils::burn(void *ptr,unsigned int len) { (_Utils_doBurn_ptr)((volatile uint8_t *)ptr,len); }
 
-std::string Utils::hex(const void *data,unsigned int len)
-{
-	std::string r;
-	r.reserve(len * 2);
-	for(unsigned int i=0;i<len;++i) {
-		r.push_back(HEXCHARS[(((const unsigned char *)data)[i] & 0xf0) >> 4]);
-		r.push_back(HEXCHARS[((const unsigned char *)data)[i] & 0x0f]);
-	}
-	return r;
-}
-
-std::string Utils::unhex(const char *hex,unsigned int maxlen)
-{
-	int n = 1;
-	unsigned char c,b = 0;
-	const char *eof = hex + maxlen;
-	std::string r;
-
-	if (!maxlen)
-		return r;
-
-	while ((c = (unsigned char)*(hex++))) {
-		if ((c >= 48)&&(c <= 57)) { // 0..9
-			if ((n ^= 1))
-				r.push_back((char)(b | (c - 48)));
-			else b = (c - 48) << 4;
-		} else if ((c >= 65)&&(c <= 70)) { // A..F
-			if ((n ^= 1))
-				r.push_back((char)(b | (c - (65 - 10))));
-			else b = (c - (65 - 10)) << 4;
-		} else if ((c >= 97)&&(c <= 102)) { // a..f
-			if ((n ^= 1))
-				r.push_back((char)(b | (c - (97 - 10))));
-			else b = (c - (97 - 10)) << 4;
-		}
-		if (hex == eof)
-			break;
-	}
-
-	return r;
-}
-
-unsigned int Utils::unhex(const char *hex,unsigned int maxlen,void *buf,unsigned int len)
-{
-	int n = 1;
-	unsigned char c,b = 0;
-	unsigned int l = 0;
-	const char *eof = hex + maxlen;
-
-	if (!maxlen)
-		return 0;
-
-	while ((c = (unsigned char)*(hex++))) {
-		if ((c >= 48)&&(c <= 57)) { // 0..9
-			if ((n ^= 1)) {
-				if (l >= len) break;
-				((unsigned char *)buf)[l++] = (b | (c - 48));
-			} else b = (c - 48) << 4;
-		} else if ((c >= 65)&&(c <= 70)) { // A..F
-			if ((n ^= 1)) {
-				if (l >= len) break;
-				((unsigned char *)buf)[l++] = (b | (c - (65 - 10)));
-			} else b = (c - (65 - 10)) << 4;
-		} else if ((c >= 97)&&(c <= 102)) { // a..f
-			if ((n ^= 1)) {
-				if (l >= len) break;
-				((unsigned char *)buf)[l++] = (b | (c - (97 - 10)));
-			} else b = (c - (97 - 10)) << 4;
-		}
-		if (hex == eof)
-			break;
-	}
-
-	return l;
-}
-
 void Utils::getSecureRandom(void *buf,unsigned int bytes)
 {
 	static Mutex globalLock;
@@ -244,21 +189,4 @@ bool Utils::scopy(char *dest,unsigned int len,const char *src)
 	return true;
 }
 
-unsigned int Utils::ztsnprintf(char *buf,unsigned int len,const char *fmt,...)
-{
-	va_list ap;
-
-	va_start(ap,fmt);
-	int n = (int)vsnprintf(buf,len,fmt,ap);
-	va_end(ap);
-
-	if ((n >= (int)len)||(n < 0)) {
-		if (len)
-			buf[len - 1] = (char)0;
-		throw std::length_error("buf[] overflow");
-	}
-
-	return (unsigned int)n;
-}
-
 } // namespace ZeroTier
diff --git a/node/Utils.hpp b/node/Utils.hpp
index 212ef247e..5a5e9f396 100644
--- a/node/Utils.hpp
+++ b/node/Utils.hpp
@@ -70,42 +70,144 @@ public:
 	static void burn(void *ptr,unsigned int len);
 
 	/**
-	 * Convert binary data to hexadecimal
-	 *
-	 * @param data Data to convert to hex
-	 * @param len Length of data
-	 * @return Hexadecimal string
+	 * @param n Number to convert
+	 * @param s Buffer, at least 24 bytes in size
+	 * @return String containing 'n' in base 10 form
 	 */
-	static std::string hex(const void *data,unsigned int len);
-	static inline std::string hex(const std::string &data) { return hex(data.data(),(unsigned int)data.length()); }
+	static char *decimal(unsigned long n,char s[24]);
 
-	/**
-	 * Convert hexadecimal to binary data
-	 *
-	 * This ignores all non-hex characters, just stepping over them and
-	 * continuing. Upper and lower case are supported for letters a-f.
-	 *
-	 * @param hex Hexadecimal ASCII code (non-hex chars are ignored, stops at zero or maxlen)
-	 * @param maxlen Maximum length of hex string buffer
-	 * @return Binary data
-	 */
-	static std::string unhex(const char *hex,unsigned int maxlen);
-	static inline std::string unhex(const std::string &hex) { return unhex(hex.c_str(),(unsigned int)hex.length()); }
+	static inline char *hex(uint64_t i,char *const s)
+	{
+		s[0] = HEXCHARS[(i >> 60) & 0xf];
+		s[1] = HEXCHARS[(i >> 56) & 0xf];
+		s[2] = HEXCHARS[(i >> 52) & 0xf];
+		s[3] = HEXCHARS[(i >> 48) & 0xf];
+		s[4] = HEXCHARS[(i >> 44) & 0xf];
+		s[5] = HEXCHARS[(i >> 40) & 0xf];
+		s[6] = HEXCHARS[(i >> 36) & 0xf];
+		s[7] = HEXCHARS[(i >> 32) & 0xf];
+		s[8] = HEXCHARS[(i >> 28) & 0xf];
+		s[9] = HEXCHARS[(i >> 24) & 0xf];
+		s[10] = HEXCHARS[(i >> 20) & 0xf];
+		s[11] = HEXCHARS[(i >> 16) & 0xf];
+		s[12] = HEXCHARS[(i >> 12) & 0xf];
+		s[13] = HEXCHARS[(i >> 8) & 0xf];
+		s[14] = HEXCHARS[(i >> 4) & 0xf];
+		s[15] = HEXCHARS[i & 0xf];
+		s[16] = (char)0;
+		return s;
+	}
 
-	/**
-	 * Convert hexadecimal to binary data
-	 *
-	 * This ignores all non-hex characters, just stepping over them and
-	 * continuing. Upper and lower case are supported for letters a-f.
-	 *
-	 * @param hex Hexadecimal ASCII
-	 * @param maxlen Maximum length of hex string buffer
-	 * @param buf Buffer to fill
-	 * @param len Length of buffer
-	 * @return Number of characters actually written
-	 */
-	static unsigned int unhex(const char *hex,unsigned int maxlen,void *buf,unsigned int len);
-	static inline unsigned int unhex(const std::string &hex,void *buf,unsigned int len) { return unhex(hex.c_str(),(unsigned int)hex.length(),buf,len); }
+	static inline char *hex10(uint64_t i,char *const s)
+	{
+		s[0] = HEXCHARS[(i >> 36) & 0xf];
+		s[1] = HEXCHARS[(i >> 32) & 0xf];
+		s[2] = HEXCHARS[(i >> 28) & 0xf];
+		s[3] = HEXCHARS[(i >> 24) & 0xf];
+		s[4] = HEXCHARS[(i >> 20) & 0xf];
+		s[5] = HEXCHARS[(i >> 16) & 0xf];
+		s[6] = HEXCHARS[(i >> 12) & 0xf];
+		s[7] = HEXCHARS[(i >> 8) & 0xf];
+		s[8] = HEXCHARS[(i >> 4) & 0xf];
+		s[9] = HEXCHARS[i & 0xf];
+		s[10] = (char)0;
+		return s;
+	}
+
+	static inline char *hex(uint16_t i,char *const s)
+	{
+		s[0] = HEXCHARS[(i >> 12) & 0xf];
+		s[1] = HEXCHARS[(i >> 8) & 0xf];
+		s[2] = HEXCHARS[(i >> 4) & 0xf];
+		s[3] = HEXCHARS[i & 0xf];
+		s[4] = (char)0;
+		return s;
+	}
+
+	static inline char *hex(uint8_t i,char *const s)
+	{
+		s[0] = HEXCHARS[(i >> 4) & 0xf];
+		s[1] = HEXCHARS[i & 0xf];
+		s[2] = (char)0;
+		return s;
+	}
+
+	static inline char *hex(const void *d,unsigned int l,char *s)
+	{
+		char *save = s;
+		for(unsigned int i=0;i<l;++i) {
+			unsigned int b = reinterpret_cast<const uint8_t *>(d)[i];
+			*(s++) = HEXCHARS[(b >> 4) & 0xf];
+			*(s++) = HEXCHARS[b & 0xf];
+		}
+		*s = (char)0;
+		return save;
+	}
+
+	static inline unsigned int unhex(const char *h,void *buf,unsigned int buflen)
+	{
+		unsigned int l = 0;
+		while (l < buflen) {
+			uint8_t hc = (uint8_t)*(h++);
+			if (!hc) break;
+
+			uint8_t c = 0;
+			if ((hc >= 48)&&(hc <= 57))
+				c = hc - 48;
+			else if ((hc >= 97)&&(hc <= 102))
+				c = hc - 87;
+			else if ((hc >= 65)&&(hc <= 70))
+				c = hc - 55;
+
+			hc = (uint8_t)*(h++);
+			if (!hc) break;
+
+			c <<= 4;
+			if ((hc >= 48)&&(hc <= 57))
+				c |= hc - 48;
+			else if ((hc >= 97)&&(hc <= 102))
+				c |= hc - 87;
+			else if ((hc >= 65)&&(hc <= 70))
+				c |= hc - 55;
+
+			reinterpret_cast<uint8_t *>(buf)[l++] = c;
+		}
+		return l;
+	}
+
+	static inline unsigned int unhex(const char *h,unsigned int hlen,void *buf,unsigned int buflen)
+	{
+		unsigned int l = 0;
+		const char *hend = h + hlen;
+		while (l < buflen) {
+			if (h == hend) break;
+			uint8_t hc = (uint8_t)*(h++);
+			if (!hc) break;
+
+			uint8_t c = 0;
+			if ((hc >= 48)&&(hc <= 57))
+				c = hc - 48;
+			else if ((hc >= 97)&&(hc <= 102))
+				c = hc - 87;
+			else if ((hc >= 65)&&(hc <= 70))
+				c = hc - 55;
+
+			if (h == hend) break;
+			hc = (uint8_t)*(h++);
+			if (!hc) break;
+
+			c <<= 4;
+			if ((hc >= 48)&&(hc <= 57))
+				c |= hc - 48;
+			else if ((hc >= 97)&&(hc <= 102))
+				c |= hc - 87;
+			else if ((hc >= 65)&&(hc <= 70))
+				c |= hc - 55;
+
+			reinterpret_cast<uint8_t *>(buf)[l++] = c;
+		}
+		return l;
+	}
 
 	/**
 	 * Generate secure random bytes
@@ -232,20 +334,6 @@ public:
 	 */
 	static bool scopy(char *dest,unsigned int len,const char *src);
 
-	/**
-	 * Variant of snprintf that is portable and throws an exception
-	 *
-	 * This just wraps the local implementation whatever it's called, while
-	 * performing a few other checks and adding exceptions for overflow.
-	 *
-	 * @param buf Buffer to write to
-	 * @param len Length of buffer in bytes
-	 * @param fmt Format string
-	 * @param ... Format arguments
-	 * @throws std::length_error buf[] too short (buf[] will still be left null-terminated)
-	 */
-	static unsigned int ztsnprintf(char *buf,unsigned int len,const char *fmt,...);
-
 	/**
 	 * Count the number of bits set in an integer
 	 *
diff --git a/one.cpp b/one.cpp
index cbf091214..b1a19e8c0 100644
--- a/one.cpp
+++ b/one.cpp
@@ -260,9 +260,9 @@ static int cli(int argc,char **argv)
 				if (hd) {
 					char p[4096];
 #ifdef __APPLE__
-					Utils::ztsnprintf(p,sizeof(p),"%s/Library/Application Support/ZeroTier/One/authtoken.secret",hd);
+					OSUtils::ztsnprintf(p,sizeof(p),"%s/Library/Application Support/ZeroTier/One/authtoken.secret",hd);
 #else
-					Utils::ztsnprintf(p,sizeof(p),"%s/.zeroTierOneAuthToken",hd);
+					OSUtils::ztsnprintf(p,sizeof(p),"%s/.zeroTierOneAuthToken",hd);
 #endif
 					OSUtils::readFile(p,authToken);
 				}
@@ -278,7 +278,7 @@ static int cli(int argc,char **argv)
 	InetAddress addr;
 	{
 		char addrtmp[256];
-		Utils::ztsnprintf(addrtmp,sizeof(addrtmp),"%s/%u",ip.c_str(),port);
+		OSUtils::ztsnprintf(addrtmp,sizeof(addrtmp),"%s/%u",ip.c_str(),port);
 		addr = InetAddress(addrtmp);
 	}
 
@@ -366,7 +366,7 @@ static int cli(int argc,char **argv)
 									std::string addr = path["address"];
 									const uint64_t now = OSUtils::now();
 									const double lq = (path.count("linkQuality")) ? (double)path["linkQuality"] : -1.0;
-									Utils::ztsnprintf(tmp,sizeof(tmp),"%s;%llu;%llu;%1.2f",addr.c_str(),now - (uint64_t)path["lastSend"],now - (uint64_t)path["lastReceive"],lq);
+									OSUtils::ztsnprintf(tmp,sizeof(tmp),"%s;%llu;%llu;%1.2f",addr.c_str(),now - (uint64_t)path["lastSend"],now - (uint64_t)path["lastReceive"],lq);
 									bestPath = tmp;
 									break;
 								}
@@ -378,7 +378,7 @@ static int cli(int argc,char **argv)
 						int64_t vmin = p["versionMinor"];
 						int64_t vrev = p["versionRev"];
 						if (vmaj >= 0) {
-							Utils::ztsnprintf(ver,sizeof(ver),"%lld.%lld.%lld",vmaj,vmin,vrev);
+							OSUtils::ztsnprintf(ver,sizeof(ver),"%lld.%lld.%lld",vmaj,vmin,vrev);
 						} else {
 							ver[0] = '-';
 							ver[1] = (char)0;
@@ -527,9 +527,9 @@ static int cli(int argc,char **argv)
 		const uint64_t seed = Utils::hexStrToU64(arg2.c_str());
 		if ((worldId)&&(seed)) {
 			char jsons[1024];
-			Utils::ztsnprintf(jsons,sizeof(jsons),"{\"seed\":\"%s\"}",arg2.c_str());
+			OSUtils::ztsnprintf(jsons,sizeof(jsons),"{\"seed\":\"%s\"}",arg2.c_str());
 			char cl[128];
-			Utils::ztsnprintf(cl,sizeof(cl),"%u",(unsigned int)strlen(jsons));
+			OSUtils::ztsnprintf(cl,sizeof(cl),"%u",(unsigned int)strlen(jsons));
 			requestHeaders["Content-Type"] = "application/json";
 			requestHeaders["Content-Length"] = cl;
 			unsigned int scode = Http::POST(
@@ -579,11 +579,11 @@ static int cli(int argc,char **argv)
 		if (eqidx != std::string::npos) {
 			if ((arg2.substr(0,eqidx) == "allowManaged")||(arg2.substr(0,eqidx) == "allowGlobal")||(arg2.substr(0,eqidx) == "allowDefault")) {
 				char jsons[1024];
-				Utils::ztsnprintf(jsons,sizeof(jsons),"{\"%s\":%s}",
+				OSUtils::ztsnprintf(jsons,sizeof(jsons),"{\"%s\":%s}",
 					arg2.substr(0,eqidx).c_str(),
 					(((arg2.substr(eqidx,2) == "=t")||(arg2.substr(eqidx,2) == "=1")) ? "true" : "false"));
 				char cl[128];
-				Utils::ztsnprintf(cl,sizeof(cl),"%u",(unsigned int)strlen(jsons));
+				OSUtils::ztsnprintf(cl,sizeof(cl),"%u",(unsigned int)strlen(jsons));
 				requestHeaders["Content-Type"] = "application/json";
 				requestHeaders["Content-Length"] = cl;
 				unsigned int scode = Http::POST(
@@ -648,7 +648,7 @@ static Identity getIdFromArg(char *arg)
 	} else { // identity is to be read from a file
 		std::string idser;
 		if (OSUtils::readFile(arg,idser)) {
-			if (id.fromString(idser))
+			if (id.fromString(idser.c_str()))
 				return id;
 		}
 	}
@@ -689,14 +689,15 @@ static int idtool(int argc,char **argv)
 			}
 		}
 
-		std::string idser = id.toString(true);
+		char idtmp[1024];
+		std::string idser = id.toString(true,idtmp);
 		if (argc >= 3) {
 			if (!OSUtils::writeFile(argv[2],idser)) {
 				fprintf(stderr,"Error writing to %s" ZT_EOL_S,argv[2]);
 				return 1;
 			} else printf("%s written" ZT_EOL_S,argv[2]);
 			if (argc >= 4) {
-				idser = id.toString(false);
+				idser = id.toString(false,idtmp);
 				if (!OSUtils::writeFile(argv[3],idser)) {
 					fprintf(stderr,"Error writing to %s" ZT_EOL_S,argv[3]);
 					return 1;
@@ -731,7 +732,8 @@ static int idtool(int argc,char **argv)
 			return 1;
 		}
 
-		printf("%s",id.toString(false).c_str());
+		char idtmp[1024];
+		printf("%s",id.toString(false,idtmp));
 	} else if (!strcmp(argv[1],"sign")) {
 		if (argc < 4) {
 			idtoolPrintHelp(stdout,argv[0]);
@@ -755,7 +757,8 @@ static int idtool(int argc,char **argv)
 			return 1;
 		}
 		C25519::Signature signature = id.sign(inf.data(),(unsigned int)inf.length());
-		printf("%s",Utils::hex(signature.data,(unsigned int)signature.size()).c_str());
+		char hexbuf[1024];
+		printf("%s",Utils::hex(signature.data,(unsigned int)signature.size(),hexbuf));
 	} else if (!strcmp(argv[1],"verify")) {
 		if (argc < 4) {
 			idtoolPrintHelp(stdout,argv[0]);
@@ -774,7 +777,8 @@ static int idtool(int argc,char **argv)
 			return 1;
 		}
 
-		std::string signature(Utils::unhex(argv[4]));
+		char buf[4096];
+		std::string signature(buf,Utils::unhex(argv[4],buf,(unsigned int)sizeof(buf)));
 		if ((signature.length() > ZT_ADDRESS_LENGTH)&&(id.verify(inf.data(),(unsigned int)inf.length(),signature.data(),(unsigned int)signature.length()))) {
 			printf("%s signature valid" ZT_EOL_S,argv[3]);
 		} else {
@@ -793,14 +797,15 @@ static int idtool(int argc,char **argv)
 
 			C25519::Pair kp(C25519::generate());
 
+			char idtmp[4096];
 			nlohmann::json mj;
 			mj["objtype"] = "world";
 			mj["worldType"] = "moon";
-			mj["updatesMustBeSignedBy"] = mj["signingKey"] = Utils::hex(kp.pub.data,(unsigned int)kp.pub.size());
-			mj["signingKey_SECRET"] = Utils::hex(kp.priv.data,(unsigned int)kp.priv.size());
-			mj["id"] = id.address().toString();
+			mj["updatesMustBeSignedBy"] = mj["signingKey"] = Utils::hex(kp.pub.data,(unsigned int)kp.pub.size(),idtmp);
+			mj["signingKey_SECRET"] = Utils::hex(kp.priv.data,(unsigned int)kp.priv.size(),idtmp);
+			mj["id"] = id.address().toString(idtmp);
 			nlohmann::json seedj;
-			seedj["identity"] = id.toString(false);
+			seedj["identity"] = id.toString(false,idtmp);
 			seedj["stableEndpoints"] = nlohmann::json::array();
 			(mj["roots"] = nlohmann::json::array()).push_back(seedj);
 			std::string mjd(OSUtils::jsonDump(mj));
@@ -836,9 +841,9 @@ static int idtool(int argc,char **argv)
 
 			C25519::Pair signingKey;
 			C25519::Public updatesMustBeSignedBy;
-			Utils::unhex(OSUtils::jsonString(mj["signingKey"],""),signingKey.pub.data,(unsigned int)signingKey.pub.size());
-			Utils::unhex(OSUtils::jsonString(mj["signingKey_SECRET"],""),signingKey.priv.data,(unsigned int)signingKey.priv.size());
-			Utils::unhex(OSUtils::jsonString(mj["updatesMustBeSignedBy"],""),updatesMustBeSignedBy.data,(unsigned int)updatesMustBeSignedBy.size());
+			Utils::unhex(OSUtils::jsonString(mj["signingKey"],"").c_str(),signingKey.pub.data,(unsigned int)signingKey.pub.size());
+			Utils::unhex(OSUtils::jsonString(mj["signingKey_SECRET"],"").c_str(),signingKey.priv.data,(unsigned int)signingKey.priv.size());
+			Utils::unhex(OSUtils::jsonString(mj["updatesMustBeSignedBy"],"").c_str(),updatesMustBeSignedBy.data,(unsigned int)updatesMustBeSignedBy.size());
 
 			std::vector<World::Root> roots;
 			nlohmann::json &rootsj = mj["roots"];
@@ -847,11 +852,11 @@ static int idtool(int argc,char **argv)
 					nlohmann::json &r = rootsj[i];
 					if (r.is_object()) {
 						roots.push_back(World::Root());
-						roots.back().identity = Identity(OSUtils::jsonString(r["identity"],""));
+						roots.back().identity = Identity(OSUtils::jsonString(r["identity"],"").c_str());
 						nlohmann::json &stableEndpointsj = r["stableEndpoints"];
 						if (stableEndpointsj.is_array()) {
 							for(unsigned long k=0;k<(unsigned long)stableEndpointsj.size();++k)
-								roots.back().stableEndpoints.push_back(InetAddress(OSUtils::jsonString(stableEndpointsj[k],"")));
+								roots.back().stableEndpoints.push_back(InetAddress(OSUtils::jsonString(stableEndpointsj[k],"").c_str()));
 							std::sort(roots.back().stableEndpoints.begin(),roots.back().stableEndpoints.end());
 						}
 					}
@@ -864,7 +869,7 @@ static int idtool(int argc,char **argv)
 			Buffer<ZT_WORLD_MAX_SERIALIZED_LENGTH> wbuf;
 			w.serialize(wbuf);
 			char fn[128];
-			Utils::ztsnprintf(fn,sizeof(fn),"%.16llx.moon",w.id());
+			OSUtils::ztsnprintf(fn,sizeof(fn),"%.16llx.moon",w.id());
 			OSUtils::writeFile(fn,wbuf.data(),wbuf.size());
 			printf("wrote %s (signed world with timestamp %llu)" ZT_EOL_S,fn,(unsigned long long)now);
 		}
diff --git a/osdep/BSDEthernetTap.cpp b/osdep/BSDEthernetTap.cpp
index f07f9e5a0..8e57d6057 100644
--- a/osdep/BSDEthernetTap.cpp
+++ b/osdep/BSDEthernetTap.cpp
@@ -114,8 +114,8 @@ BSDEthernetTap::BSDEthernetTap(
 
 	std::vector<std::string> devFiles(OSUtils::listDirectory("/dev"));
 	for(int i=9993;i<(9993+128);++i) {
-		Utils::ztsnprintf(tmpdevname,sizeof(tmpdevname),"tap%d",i);
-		Utils::ztsnprintf(devpath,sizeof(devpath),"/dev/%s",tmpdevname);
+		OSUtils::ztsnprintf(tmpdevname,sizeof(tmpdevname),"tap%d",i);
+		OSUtils::ztsnprintf(devpath,sizeof(devpath),"/dev/%s",tmpdevname);
 		if (std::find(devFiles.begin(),devFiles.end(),std::string(tmpdevname)) == devFiles.end()) {
 			long cpid = (long)vfork();
 			if (cpid == 0) {
@@ -152,8 +152,8 @@ BSDEthernetTap::BSDEthernetTap(
 	/* Other BSDs like OpenBSD only have a limited number of tap devices that cannot be renamed */
 
 	for(int i=0;i<64;++i) {
-		Utils::ztsnprintf(tmpdevname,sizeof(tmpdevname),"tap%d",i);
-		Utils::ztsnprintf(devpath,sizeof(devpath),"/dev/%s",tmpdevname);
+		OSUtils::ztsnprintf(tmpdevname,sizeof(tmpdevname),"tap%d",i);
+		OSUtils::ztsnprintf(devpath,sizeof(devpath),"/dev/%s",tmpdevname);
 		_fd = ::open(devpath,O_RDWR);
 		if (_fd > 0) {
 			_dev = tmpdevname;
@@ -171,9 +171,9 @@ BSDEthernetTap::BSDEthernetTap(
 	}
 
 	// Configure MAC address and MTU, bring interface up
-	Utils::ztsnprintf(ethaddr,sizeof(ethaddr),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(int)mac[0],(int)mac[1],(int)mac[2],(int)mac[3],(int)mac[4],(int)mac[5]);
-	Utils::ztsnprintf(mtustr,sizeof(mtustr),"%u",_mtu);
-	Utils::ztsnprintf(metstr,sizeof(metstr),"%u",_metric);
+	OSUtils::ztsnprintf(ethaddr,sizeof(ethaddr),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(int)mac[0],(int)mac[1],(int)mac[2],(int)mac[3],(int)mac[4],(int)mac[5]);
+	OSUtils::ztsnprintf(mtustr,sizeof(mtustr),"%u",_mtu);
+	OSUtils::ztsnprintf(metstr,sizeof(metstr),"%u",_metric);
 	long cpid = (long)vfork();
 	if (cpid == 0) {
 		::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"lladdr",ethaddr,"mtu",mtustr,"metric",metstr,"up",(const char *)0);
@@ -256,7 +256,8 @@ bool BSDEthernetTap::addIp(const InetAddress &ip)
 
 	long cpid = (long)vfork();
 	if (cpid == 0) {
-		::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),ip.isV4() ? "inet" : "inet6",ip.toString().c_str(),"alias",(const char *)0);
+		char tmp[128];
+		::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),ip.isV4() ? "inet" : "inet6",ip.toString(tmp),"alias",(const char *)0);
 		::_exit(-1);
 	} else if (cpid > 0) {
 		int exitcode = -1;
@@ -385,7 +386,7 @@ void BSDEthernetTap::setMtu(unsigned int mtu)
 		long cpid = (long)vfork();
 		if (cpid == 0) {
 			char tmp[64];
-			Utils::ztsnprintf(tmp,sizeof(tmp),"%u",mtu);
+			OSUtils::ztsnprintf(tmp,sizeof(tmp),"%u",mtu);
 			execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"mtu",tmp,(const char *)0);
 			_exit(-1);
 		} else if (cpid > 0) {
diff --git a/osdep/Http.cpp b/osdep/Http.cpp
index 3c556f44a..d6d0238c5 100644
--- a/osdep/Http.cpp
+++ b/osdep/Http.cpp
@@ -244,10 +244,10 @@ unsigned int Http::_do(
 
 		try {
 			char tmp[1024];
-			Utils::ztsnprintf(tmp,sizeof(tmp),"%s %s HTTP/1.1\r\n",method,path);
+			OSUtils::ztsnprintf(tmp,sizeof(tmp),"%s %s HTTP/1.1\r\n",method,path);
 			handler.writeBuf.append(tmp);
 			for(std::map<std::string,std::string>::const_iterator h(requestHeaders.begin());h!=requestHeaders.end();++h) {
-				Utils::ztsnprintf(tmp,sizeof(tmp),"%s: %s\r\n",h->first.c_str(),h->second.c_str());
+				OSUtils::ztsnprintf(tmp,sizeof(tmp),"%s: %s\r\n",h->first.c_str(),h->second.c_str());
 				handler.writeBuf.append(tmp);
 			}
 			handler.writeBuf.append("\r\n");
diff --git a/osdep/LinuxEthernetTap.cpp b/osdep/LinuxEthernetTap.cpp
index fc5199f1c..c8f9ef9d1 100644
--- a/osdep/LinuxEthernetTap.cpp
+++ b/osdep/LinuxEthernetTap.cpp
@@ -97,7 +97,7 @@ LinuxEthernetTap::LinuxEthernetTap(
 	char procpath[128],nwids[32];
 	struct stat sbuf;
 
-	Utils::ztsnprintf(nwids,sizeof(nwids),"%.16llx",nwid);
+	OSUtils::ztsnprintf(nwids,sizeof(nwids),"%.16llx",nwid);
 
 	Mutex::Lock _l(__tapCreateLock); // create only one tap at a time, globally
 
@@ -134,7 +134,7 @@ LinuxEthernetTap::LinuxEthernetTap(
 	std::map<std::string,std::string>::const_iterator gdmEntry = globalDeviceMap.find(nwids);
 	if (gdmEntry != globalDeviceMap.end()) {
 		Utils::scopy(ifr.ifr_name,sizeof(ifr.ifr_name),gdmEntry->second.c_str());
-		Utils::ztsnprintf(procpath,sizeof(procpath),"/proc/sys/net/ipv4/conf/%s",ifr.ifr_name);
+		OSUtils::ztsnprintf(procpath,sizeof(procpath),"/proc/sys/net/ipv4/conf/%s",ifr.ifr_name);
 		recalledDevice = (stat(procpath,&sbuf) != 0);
 	}
 
@@ -142,8 +142,8 @@ LinuxEthernetTap::LinuxEthernetTap(
 #ifdef __SYNOLOGY__
 		int devno = 50;
 		do {
-			Utils::ztsnprintf(ifr.ifr_name,sizeof(ifr.ifr_name),"eth%d",devno++);
-			Utils::ztsnprintf(procpath,sizeof(procpath),"/proc/sys/net/ipv4/conf/%s",ifr.ifr_name);
+			OSUtils::ztsnprintf(ifr.ifr_name,sizeof(ifr.ifr_name),"eth%d",devno++);
+			OSUtils::ztsnprintf(procpath,sizeof(procpath),"/proc/sys/net/ipv4/conf/%s",ifr.ifr_name);
 		} while (stat(procpath,&sbuf) == 0); // try zt#++ until we find one that does not exist
 #else
 		char devno = 0;
@@ -158,7 +158,7 @@ LinuxEthernetTap::LinuxEthernetTap(
 			_base32_5_to_8(reinterpret_cast<const uint8_t *>(tmp2) + 5,tmp3 + 10);
 			tmp3[15] = (char)0;
 			memcpy(ifr.ifr_name,tmp3,16);
-			Utils::ztsnprintf(procpath,sizeof(procpath),"/proc/sys/net/ipv4/conf/%s",ifr.ifr_name);
+			OSUtils::ztsnprintf(procpath,sizeof(procpath),"/proc/sys/net/ipv4/conf/%s",ifr.ifr_name);
 		} while (stat(procpath,&sbuf) == 0);
 #endif
 	}
@@ -264,7 +264,8 @@ static bool ___removeIp(const std::string &_dev,const InetAddress &ip)
 	if (cpid == 0) {
 		OSUtils::redirectUnixOutputs("/dev/null",(const char *)0);
 		setenv("PATH", "/sbin:/bin:/usr/sbin:/usr/bin", 1);
-		::execlp("ip","ip","addr","del",ip.toString().c_str(),"dev",_dev.c_str(),(const char *)0);
+		char iptmp[128];
+		::execlp("ip","ip","addr","del",ip.toString(iptmp),"dev",_dev.c_str(),(const char *)0);
 		::_exit(-1);
 	} else {
 		int exitcode = -1;
@@ -296,25 +297,28 @@ bool LinuxEthernetTap::addIpSyn(std::vector<InetAddress> ips)
 		// Assemble and write contents of ifcfg-dev file
 		for(int i=0; i<(int)ips.size(); i++) {
 			if (ips[i].isV4()) {
+				char iptmp[64],iptmp2[64];
 				std::string numstr4 = ip4_tot > 1 ? std::to_string(ip4) : "";
-				cfg_contents += "\nIPADDR"+numstr4+"="+ips[i].toIpString()
-					+ "\nNETMASK"+numstr4+"="+ips[i].netmask().toIpString()+"\n";
+				cfg_contents += "\nIPADDR"+numstr4+"="+ips[i].toIpString(iptmp)
+					+ "\nNETMASK"+numstr4+"="+ips[i].netmask().toIpString(iptmp2)+"\n";
 				ip4++;
 			}
 			else {
+				char iptmp[64],iptmp2[64];
 				std::string numstr6 = ip6_tot > 1 ? std::to_string(ip6) : "";
-				cfg_contents += "\nIPV6ADDR"+numstr6+"="+ips[i].toIpString()
-					+ "\nNETMASK"+numstr6+"="+ips[i].netmask().toIpString()+"\n";
+				cfg_contents += "\nIPV6ADDR"+numstr6+"="+ips[i].toIpString(iptmp)
+					+ "\nNETMASK"+numstr6+"="+ips[i].netmask().toIpString(iptmp2)+"\n";
 				ip6++;
 			}
 		}
 		OSUtils::writeFile(filepath.c_str(), cfg_contents.c_str(), cfg_contents.length());
 		// Finaly, add IPs
 		for(int i=0; i<(int)ips.size(); i++){
+			char iptmp[128],iptmp2[128[;
 			if (ips[i].isV4())
-				::execlp("ip","ip","addr","add",ips[i].toString().c_str(),"broadcast",ips[i].broadcast().toIpString().c_str(),"dev",_dev.c_str(),(const char *)0);
+				::execlp("ip","ip","addr","add",ips[i].toString(iptmp),"broadcast",ips[i].broadcast().toIpString(iptmp2),"dev",_dev.c_str(),(const char *)0);
 			else
-				::execlp("ip","ip","addr","add",ips[i].toString().c_str(),"dev",_dev.c_str(),(const char *)0);			
+				::execlp("ip","ip","addr","add",ips[i].toString(iptmp),"dev",_dev.c_str(),(const char *)0);			
 		}
 		::_exit(-1);
 	} else if (cpid > 0) {
@@ -345,10 +349,11 @@ bool LinuxEthernetTap::addIp(const InetAddress &ip)
 	if (cpid == 0) {
 		OSUtils::redirectUnixOutputs("/dev/null",(const char *)0);
 		setenv("PATH", "/sbin:/bin:/usr/sbin:/usr/bin", 1);
+		char iptmp[128],iptmp2[128];
 		if (ip.isV4()) {
-			::execlp("ip","ip","addr","add",ip.toString().c_str(),"broadcast",ip.broadcast().toIpString().c_str(),"dev",_dev.c_str(),(const char *)0);
+			::execlp("ip","ip","addr","add",ip.toString(iptmp),"broadcast",ip.broadcast().toIpString(iptmp2),"dev",_dev.c_str(),(const char *)0);
 		} else {
-			::execlp("ip","ip","addr","add",ip.toString().c_str(),"dev",_dev.c_str(),(const char *)0);
+			::execlp("ip","ip","addr","add",ip.toString(iptmp),"dev",_dev.c_str(),(const char *)0);
 		}
 		::_exit(-1);
 	} else if (cpid > 0) {
diff --git a/osdep/ManagedRoute.cpp b/osdep/ManagedRoute.cpp
index fca1c290d..3a0b8a7e8 100644
--- a/osdep/ManagedRoute.cpp
+++ b/osdep/ManagedRoute.cpp
@@ -246,7 +246,6 @@ static std::vector<_RTE> _getRTEs(const InetAddress &target,bool contains)
 
 static void _routeCmd(const char *op,const InetAddress &target,const InetAddress &via,const char *ifscope,const char *localInterface)
 {
-	//printf("route %s %s %s %s %s\n",op,target.toString().c_str(),(via) ? via.toString().c_str() : "(null)",(ifscope) ? ifscope : "(null)",(localInterface) ? localInterface : "(null)");
 	long p = (long)fork();
 	if (p > 0) {
 		int exitcode = -1;
@@ -254,17 +253,19 @@ static void _routeCmd(const char *op,const InetAddress &target,const InetAddress
 	} else if (p == 0) {
 		::close(STDOUT_FILENO);
 		::close(STDERR_FILENO);
+		char ttmp[64];
+		char iptmp[64];
 		if (via) {
 			if ((ifscope)&&(ifscope[0])) {
-				::execl(ZT_BSD_ROUTE_CMD,ZT_BSD_ROUTE_CMD,op,"-ifscope",ifscope,((target.ss_family == AF_INET6) ? "-inet6" : "-inet"),target.toString().c_str(),via.toIpString().c_str(),(const char *)0);
+				::execl(ZT_BSD_ROUTE_CMD,ZT_BSD_ROUTE_CMD,op,"-ifscope",ifscope,((target.ss_family == AF_INET6) ? "-inet6" : "-inet"),target.toString(ttmp),via.toIpString(iptmp),(const char *)0);
 			} else {
-				::execl(ZT_BSD_ROUTE_CMD,ZT_BSD_ROUTE_CMD,op,((target.ss_family == AF_INET6) ? "-inet6" : "-inet"),target.toString().c_str(),via.toIpString().c_str(),(const char *)0);
+				::execl(ZT_BSD_ROUTE_CMD,ZT_BSD_ROUTE_CMD,op,((target.ss_family == AF_INET6) ? "-inet6" : "-inet"),target.toString(ttmp),via.toIpString(iptmp),(const char *)0);
 			}
 		} else if ((localInterface)&&(localInterface[0])) {
 			if ((ifscope)&&(ifscope[0])) {
-				::execl(ZT_BSD_ROUTE_CMD,ZT_BSD_ROUTE_CMD,op,"-ifscope",ifscope,((target.ss_family == AF_INET6) ? "-inet6" : "-inet"),target.toString().c_str(),"-interface",localInterface,(const char *)0);
+				::execl(ZT_BSD_ROUTE_CMD,ZT_BSD_ROUTE_CMD,op,"-ifscope",ifscope,((target.ss_family == AF_INET6) ? "-inet6" : "-inet"),target.toString(ttmp),"-interface",localInterface,(const char *)0);
 			} else {
-				::execl(ZT_BSD_ROUTE_CMD,ZT_BSD_ROUTE_CMD,op,((target.ss_family == AF_INET6) ? "-inet6" : "-inet"),target.toString().c_str(),"-interface",localInterface,(const char *)0);
+				::execl(ZT_BSD_ROUTE_CMD,ZT_BSD_ROUTE_CMD,op,((target.ss_family == AF_INET6) ? "-inet6" : "-inet"),target.toString(ttmp),"-interface",localInterface,(const char *)0);
 			}
 		}
 		::_exit(-1);
diff --git a/osdep/OSUtils.cpp b/osdep/OSUtils.cpp
index 06508e773..882b82550 100644
--- a/osdep/OSUtils.cpp
+++ b/osdep/OSUtils.cpp
@@ -57,6 +57,23 @@
 
 namespace ZeroTier {
 
+unsigned int OSUtils::ztsnprintf(char *buf,unsigned int len,const char *fmt,...)
+{
+	va_list ap;
+
+	va_start(ap,fmt);
+	int n = (int)vsnprintf(buf,len,fmt,ap);
+	va_end(ap);
+
+	if ((n >= (int)len)||(n < 0)) {
+		if (len)
+			buf[len - 1] = (char)0;
+		throw std::length_error("buf[] overflow");
+	}
+
+	return (unsigned int)n;
+}
+
 #ifdef __UNIX_LIKE__
 bool OSUtils::redirectUnixOutputs(const char *stdoutPath,const char *stderrPath)
 	throw()
@@ -134,7 +151,7 @@ long OSUtils::cleanDirectory(const char *path,const uint64_t olderThan)
 					if (date.QuadPart > 0) {
 							date.QuadPart -= adjust.QuadPart;
 							if ((uint64_t)((date.QuadPart / 10000000) * 1000) < olderThan) {
-									Utils::ztsnprintf(tmp, sizeof(tmp), "%s\\%s", path, ffd.cFileName);
+									ztsnprintf(tmp, sizeof(tmp), "%s\\%s", path, ffd.cFileName);
 									if (DeleteFileA(tmp))
 											++cleaned;
 							}
@@ -157,7 +174,7 @@ long OSUtils::cleanDirectory(const char *path,const uint64_t olderThan)
 			break;
 		if (dptr) {
 			if ((strcmp(dptr->d_name,"."))&&(strcmp(dptr->d_name,".."))&&(dptr->d_type == DT_REG)) {
-				Utils::ztsnprintf(tmp,sizeof(tmp),"%s/%s",path,dptr->d_name);
+				ztsnprintf(tmp,sizeof(tmp),"%s/%s",path,dptr->d_name);
 				if (stat(tmp,&st) == 0) {
 					uint64_t mt = (uint64_t)(st.st_mtime);
 					if ((mt > 0)&&((mt * 1000) < olderThan)) {
@@ -464,7 +481,7 @@ std::string OSUtils::jsonString(const nlohmann::json &jv,const char *dfl)
 			return jv;
 		} else if (jv.is_number()) {
 			char tmp[64];
-			Utils::ztsnprintf(tmp,sizeof(tmp),"%llu",(uint64_t)jv);
+			ztsnprintf(tmp,sizeof(tmp),"%llu",(uint64_t)jv);
 			return tmp;
 		} else if (jv.is_boolean()) {
 			return ((bool)jv ? std::string("1") : std::string("0"));
@@ -477,9 +494,10 @@ std::string OSUtils::jsonBinFromHex(const nlohmann::json &jv)
 {
 	std::string s(jsonString(jv,""));
 	if (s.length() > 0) {
-		char *buf = new char[(s.length() / 2) + 1];
+		unsigned int buflen = (s.length() / 2) + 1;
+		char *buf = new char[buflen];
 		try {
-			unsigned int l = Utils::unhex(s,buf,(unsigned int)s.length());
+			unsigned int l = Utils::unhex(s.c_str(),buf,buflen);
 			std::string b(buf,l);
 			delete [] buf;
 			return b;
diff --git a/osdep/OSUtils.hpp b/osdep/OSUtils.hpp
index dff7df868..d6f328228 100644
--- a/osdep/OSUtils.hpp
+++ b/osdep/OSUtils.hpp
@@ -33,7 +33,6 @@
 #include <string.h>
 #include <time.h>
 
-#include <string>
 #include <stdexcept>
 #include <vector>
 #include <map>
@@ -66,6 +65,20 @@ namespace ZeroTier {
 class OSUtils
 {
 public:
+	/**
+	 * Variant of snprintf that is portable and throws an exception
+	 *
+	 * This just wraps the local implementation whatever it's called, while
+	 * performing a few other checks and adding exceptions for overflow.
+	 *
+	 * @param buf Buffer to write to
+	 * @param len Length of buffer in bytes
+	 * @param fmt Format string
+	 * @param ... Format arguments
+	 * @throws std::length_error buf[] too short (buf[] will still be left null-terminated)
+	 */
+	static unsigned int ztsnprintf(char *buf,unsigned int len,const char *fmt,...);
+
 #ifdef __UNIX_LIKE__
 	/**
 	 * Close STDOUT_FILENO and STDERR_FILENO and replace them with output to given path
diff --git a/osdep/OSXEthernetTap.cpp b/osdep/OSXEthernetTap.cpp
index e082408e3..b43d34c08 100644
--- a/osdep/OSXEthernetTap.cpp
+++ b/osdep/OSXEthernetTap.cpp
@@ -336,7 +336,7 @@ OSXEthernetTap::OSXEthernetTap(
 	char devpath[64],ethaddr[64],mtustr[32],metstr[32],nwids[32];
 	struct stat stattmp;
 
-	Utils::ztsnprintf(nwids,sizeof(nwids),"%.16llx",nwid);
+	OSUtils::ztsnprintf(nwids,sizeof(nwids),"%.16llx",nwid);
 
 	Mutex::Lock _gl(globalTapCreateLock);
 
@@ -391,13 +391,13 @@ OSXEthernetTap::OSXEthernetTap(
 	// Open the first unused tap device if we didn't recall a previous one.
 	if (!recalledDevice) {
 		for(int i=0;i<64;++i) {
-			Utils::ztsnprintf(devpath,sizeof(devpath),"/dev/zt%d",i);
+			OSUtils::ztsnprintf(devpath,sizeof(devpath),"/dev/zt%d",i);
 			if (stat(devpath,&stattmp))
 				throw std::runtime_error("no more TAP devices available");
 			_fd = ::open(devpath,O_RDWR);
 			if (_fd > 0) {
 				char foo[16];
-				Utils::ztsnprintf(foo,sizeof(foo),"zt%d",i);
+				OSUtils::ztsnprintf(foo,sizeof(foo),"zt%d",i);
 				_dev = foo;
 				break;
 			}
@@ -413,9 +413,9 @@ OSXEthernetTap::OSXEthernetTap(
 	}
 
 	// Configure MAC address and MTU, bring interface up
-	Utils::ztsnprintf(ethaddr,sizeof(ethaddr),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(int)mac[0],(int)mac[1],(int)mac[2],(int)mac[3],(int)mac[4],(int)mac[5]);
-	Utils::ztsnprintf(mtustr,sizeof(mtustr),"%u",_mtu);
-	Utils::ztsnprintf(metstr,sizeof(metstr),"%u",_metric);
+	OSUtils::ztsnprintf(ethaddr,sizeof(ethaddr),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(int)mac[0],(int)mac[1],(int)mac[2],(int)mac[3],(int)mac[4],(int)mac[5]);
+	OSUtils::ztsnprintf(mtustr,sizeof(mtustr),"%u",_mtu);
+	OSUtils::ztsnprintf(metstr,sizeof(metstr),"%u",_metric);
 	long cpid = (long)vfork();
 	if (cpid == 0) {
 		::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"lladdr",ethaddr,"mtu",mtustr,"metric",metstr,"up",(const char *)0);
@@ -499,7 +499,8 @@ bool OSXEthernetTap::addIp(const InetAddress &ip)
 
 	long cpid = (long)vfork();
 	if (cpid == 0) {
-		::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),(ip.ss_family == AF_INET6) ? "inet6" : "inet",ip.toString().c_str(),"alias",(const char *)0);
+		char tmp[128];
+		::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),(ip.ss_family == AF_INET6) ? "inet6" : "inet",ip.toString(tmp),"alias",(const char *)0);
 		::_exit(-1);
 	} else if (cpid > 0) {
 		int exitcode = -1;
@@ -519,7 +520,8 @@ bool OSXEthernetTap::removeIp(const InetAddress &ip)
 		if (*i == ip) {
 			long cpid = (long)vfork();
 			if (cpid == 0) {
-				execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),(ip.ss_family == AF_INET6) ? "inet6" : "inet",ip.toIpString().c_str(),"-alias",(const char *)0);
+				char tmp[128];
+				execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),(ip.ss_family == AF_INET6) ? "inet6" : "inet",ip.toIpString(tmp),"-alias",(const char *)0);
 				_exit(-1);
 			} else if (cpid > 0) {
 				int exitcode = -1;
@@ -636,7 +638,7 @@ void OSXEthernetTap::setMtu(unsigned int mtu)
 		long cpid = (long)vfork();
 		if (cpid == 0) {
 			char tmp[64];
-			Utils::ztsnprintf(tmp,sizeof(tmp),"%u",mtu);
+			OSUtils::ztsnprintf(tmp,sizeof(tmp),"%u",mtu);
 			execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"mtu",tmp,(const char *)0);
 			_exit(-1);
 		} else if (cpid > 0) {
diff --git a/osdep/PortMapper.cpp b/osdep/PortMapper.cpp
index df868e7a6..b1990486d 100644
--- a/osdep/PortMapper.cpp
+++ b/osdep/PortMapper.cpp
@@ -205,7 +205,7 @@ public:
 					memset(externalip,0,sizeof(externalip));
 					memset(&urls,0,sizeof(urls));
 					memset(&data,0,sizeof(data));
-					Utils::ztsnprintf(inport,sizeof(inport),"%d",localPort);
+					OSUtils::ztsnprintf(inport,sizeof(inport),"%d",localPort);
 
 					if ((UPNP_GetValidIGD(devlist,&urls,&data,lanaddr,sizeof(lanaddr)))&&(lanaddr[0])) {
 #ifdef ZT_PORTMAPPER_TRACE
@@ -220,7 +220,7 @@ public:
 								int tryPort = (int)localPort + tries;
 								if (tryPort >= 65535)
 									tryPort = (tryPort - 65535) + 1025;
-								Utils::ztsnprintf(outport,sizeof(outport),"%u",tryPort);
+								OSUtils::ztsnprintf(outport,sizeof(outport),"%u",tryPort);
 
 								// First check and see if this port is already mapped to the
 								// same unique name. If so, keep this mapping and don't try
diff --git a/osdep/WindowsEthernetTap.cpp b/osdep/WindowsEthernetTap.cpp
index b96ad791a..5344268f3 100644
--- a/osdep/WindowsEthernetTap.cpp
+++ b/osdep/WindowsEthernetTap.cpp
@@ -484,7 +484,7 @@ WindowsEthernetTap::WindowsEthernetTap(
 	char tag[24];
 
 	// We "tag" registry entries with the network ID to identify persistent devices
-	Utils::ztsnprintf(tag,sizeof(tag),"%.16llx",(unsigned long long)nwid);
+	OSUtils::ztsnprintf(tag,sizeof(tag),"%.16llx",(unsigned long long)nwid);
 
 	Mutex::Lock _l(_systemTapInitLock);
 
@@ -601,10 +601,10 @@ WindowsEthernetTap::WindowsEthernetTap(
 
 	if (_netCfgInstanceId.length() > 0) {
 		char tmps[64];
-		unsigned int tmpsl = Utils::ztsnprintf(tmps,sizeof(tmps),"%.2X-%.2X-%.2X-%.2X-%.2X-%.2X",(unsigned int)mac[0],(unsigned int)mac[1],(unsigned int)mac[2],(unsigned int)mac[3],(unsigned int)mac[4],(unsigned int)mac[5]) + 1;
+		unsigned int tmpsl = OSUtils::ztsnprintf(tmps,sizeof(tmps),"%.2X-%.2X-%.2X-%.2X-%.2X-%.2X",(unsigned int)mac[0],(unsigned int)mac[1],(unsigned int)mac[2],(unsigned int)mac[3],(unsigned int)mac[4],(unsigned int)mac[5]) + 1;
 		RegSetKeyValueA(nwAdapters,_mySubkeyName.c_str(),"NetworkAddress",REG_SZ,tmps,tmpsl);
 		RegSetKeyValueA(nwAdapters,_mySubkeyName.c_str(),"MAC",REG_SZ,tmps,tmpsl);
-		tmpsl = Utils::ztsnprintf(tmps, sizeof(tmps), "%d", mtu);
+		tmpsl = OSUtils::ztsnprintf(tmps, sizeof(tmps), "%d", mtu);
 		RegSetKeyValueA(nwAdapters,_mySubkeyName.c_str(),"MTU",REG_SZ,tmps,tmpsl);
 
 		DWORD tmp = 0;
@@ -879,7 +879,7 @@ void WindowsEthernetTap::setMtu(unsigned int mtu)
 		HKEY nwAdapters;
 		if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}", 0, KEY_READ | KEY_WRITE, &nwAdapters) == ERROR_SUCCESS) {
 			char tmps[64];
-			unsigned int tmpsl = Utils::ztsnprintf(tmps, sizeof(tmps), "%d", mtu);
+			unsigned int tmpsl = OSUtils::ztsnprintf(tmps, sizeof(tmps), "%d", mtu);
 			RegSetKeyValueA(nwAdapters, _mySubkeyName.c_str(), "MTU", REG_SZ, tmps, tmpsl);
 			RegCloseKey(nwAdapters);
 		}
@@ -902,7 +902,7 @@ void WindowsEthernetTap::threadMain()
 	HANDLE wait4[3];
 	OVERLAPPED tapOvlRead,tapOvlWrite;
 
-	Utils::ztsnprintf(tapPath,sizeof(tapPath),"\\\\.\\Global\\%s.tap",_netCfgInstanceId.c_str());
+	OSUtils::ztsnprintf(tapPath,sizeof(tapPath),"\\\\.\\Global\\%s.tap",_netCfgInstanceId.c_str());
 
 	try {
 		while (_run) {
diff --git a/selftest.cpp b/selftest.cpp
index ff171aa3e..e67057000 100644
--- a/selftest.cpp
+++ b/selftest.cpp
@@ -153,10 +153,11 @@ static int testCrypto()
 {
 	static unsigned char buf1[16384];
 	static unsigned char buf2[sizeof(buf1)],buf3[sizeof(buf1)];
+	static char hexbuf[1024];
 
 	for(int i=0;i<3;++i) {
 		Utils::getSecureRandom(buf1,64);
-		std::cout << "[crypto] getSecureRandom: " << Utils::hex(buf1,64) << std::endl;
+		std::cout << "[crypto] getSecureRandom: " << Utils::hex(buf1,64,hexbuf) << std::endl;
 	}
 
 	std::cout << "[crypto] Testing Salsa20... "; std::cout.flush();
@@ -213,7 +214,7 @@ static int testCrypto()
 		}
 		uint64_t end = OSUtils::now();
 		SHA512::hash(buf1,bb,1234567);
-		std::cout << ((bytes / 1048576.0) / ((long double)(end - start) / 1024.0)) << " MiB/second (" << Utils::hex(buf1,16) << ')' << std::endl;
+		std::cout << ((bytes / 1048576.0) / ((long double)(end - start) / 1024.0)) << " MiB/second (" << Utils::hex(buf1,16,hexbuf) << ')' << std::endl;
 		::free((void *)bb);
 	}
 
@@ -265,7 +266,7 @@ static int testCrypto()
 		}
 		uint64_t end = OSUtils::now();
 		SHA512::hash(buf1,bb,1234567);
-		std::cout << ((bytes / 1048576.0) / ((long double)(end - start) / 1024.0)) << " MiB/second (" << Utils::hex(buf1,16) << ')' << std::endl;
+		std::cout << ((bytes / 1048576.0) / ((long double)(end - start) / 1024.0)) << " MiB/second (" << Utils::hex(buf1,16,hexbuf) << ')' << std::endl;
 		::free((void *)bb);
 	}
 
@@ -427,6 +428,7 @@ static int testIdentity()
 {
 	Identity id;
 	Buffer<512> buf;
+	char buf2[1024];
 
 	std::cout << "[identity] Validate known-good identity... "; std::cout.flush();
 	if (!id.fromString(KNOWN_GOOD_IDENTITY)) {
@@ -459,7 +461,7 @@ static int testIdentity()
 		uint64_t genstart = OSUtils::now();
 		id.generate();
 		uint64_t genend = OSUtils::now();
-		std::cout << "(took " << (genend - genstart) << "ms): " << id.toString(true) << std::endl;
+		std::cout << "(took " << (genend - genstart) << "ms): " << id.toString(true,buf2) << std::endl;
 		std::cout << "[identity] Locally validate identity: ";
 		if (id.locallyValidate()) {
 			std::cout << "PASS" << std::endl;
@@ -499,7 +501,7 @@ static int testIdentity()
 
 	{
 		Identity id2;
-		id2.fromString(id.toString(true).c_str());
+		id2.fromString(id.toString(true,buf2));
 		std::cout << "[identity] Serialize and deserialize (ASCII w/private): ";
 		if ((id == id2)&&(id2.locallyValidate())) {
 			std::cout << "PASS" << std::endl;
@@ -511,7 +513,7 @@ static int testIdentity()
 
 	{
 		Identity id2;
-		id2.fromString(id.toString(false).c_str());
+		id2.fromString(id.toString(false,buf2));
 		std::cout << "[identity] Serialize and deserialize (ASCII no private): ";
 		if ((id == id2)&&(id2.locallyValidate())) {
 			std::cout << "PASS" << std::endl;
@@ -526,16 +528,18 @@ static int testIdentity()
 
 static int testCertificate()
 {
+	char buf[4096];
+
 	Identity authority;
 	std::cout << "[certificate] Generating identity to act as authority... "; std::cout.flush();
 	authority.generate();
-	std::cout << authority.address().toString() << std::endl;
+	std::cout << authority.address().toString(buf) << std::endl;
 
 	Identity idA,idB;
 	std::cout << "[certificate] Generating identities A and B... "; std::cout.flush();
 	idA.generate();
 	idB.generate();
-	std::cout << idA.address().toString() << ", " << idB.address().toString() << std::endl;
+	std::cout << idA.address().toString(buf) << ", " << idB.address().toString(buf) << std::endl;
 
 	std::cout << "[certificate] Generating certificates A and B...";
 	CertificateOfMembership cA(10000,100,1,idA.address());
@@ -641,6 +645,8 @@ static void _testExcept(int &depth)
 
 static int testOther()
 {
+	char buf[1024];
+
 	std::cout << "[other] Testing C++ exceptions... "; std::cout.flush();
 	int depth = 0;
 	try {
@@ -657,6 +663,13 @@ static int testOther()
 		return -1;
 	}
 
+	std::cout << "[other] Testing InetAddress encode/decode..."; std::cout.flush();
+	std::cout << " " << InetAddress("127.0.0.1/9993").toString(buf);
+	std::cout << " " << InetAddress("feed:dead:babe:dead:beef:f00d:1234:5678/12345").toString(buf);
+	std::cout << " " << InetAddress("0/9993").toString(buf);
+	std::cout << " " << InetAddress("").toString(buf);
+	std::cout << std::endl;
+
 #if 0
 	std::cout << "[other] Testing Hashtable... "; std::cout.flush();
 	{
@@ -831,7 +844,7 @@ static int testOther()
 		memset(key, 0, sizeof(key));
 		memset(value, 0, sizeof(value));
 		for(unsigned int q=0;q<32;++q) {
-			Utils::ztsnprintf(key[q],16,"%.8lx",(unsigned long)(rand() % 1000) + (q * 1000));
+			OSUtils::ztsnprintf(key[q],16,"%.8lx",(unsigned long)(rand() % 1000) + (q * 1000));
 			int r = rand() % 128;
 			for(int x=0;x<r;++x)
 				value[q][x] = ("0123456789\0\t\r\n= ")[rand() % 16];
diff --git a/service/OneService.cpp b/service/OneService.cpp
index 6c2c9a8b9..d6e3e4dff 100644
--- a/service/OneService.cpp
+++ b/service/OneService.cpp
@@ -202,10 +202,10 @@ static void _networkToJson(nlohmann::json &nj,const ZT_VirtualNetworkConfig *nc,
 		case ZT_NETWORK_TYPE_PUBLIC:                     ntype = "PUBLIC"; break;
 	}
 
-	Utils::ztsnprintf(tmp,sizeof(tmp),"%.16llx",nc->nwid);
+	OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.16llx",nc->nwid);
 	nj["id"] = tmp;
 	nj["nwid"] = tmp;
-	Utils::ztsnprintf(tmp,sizeof(tmp),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(unsigned int)((nc->mac >> 40) & 0xff),(unsigned int)((nc->mac >> 32) & 0xff),(unsigned int)((nc->mac >> 24) & 0xff),(unsigned int)((nc->mac >> 16) & 0xff),(unsigned int)((nc->mac >> 8) & 0xff),(unsigned int)(nc->mac & 0xff));
+	OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(unsigned int)((nc->mac >> 40) & 0xff),(unsigned int)((nc->mac >> 32) & 0xff),(unsigned int)((nc->mac >> 24) & 0xff),(unsigned int)((nc->mac >> 16) & 0xff),(unsigned int)((nc->mac >> 8) & 0xff),(unsigned int)(nc->mac & 0xff));
 	nj["mac"] = tmp;
 	nj["name"] = nc->name;
 	nj["status"] = nstatus;
@@ -223,16 +223,16 @@ static void _networkToJson(nlohmann::json &nj,const ZT_VirtualNetworkConfig *nc,
 
 	nlohmann::json aa = nlohmann::json::array();
 	for(unsigned int i=0;i<nc->assignedAddressCount;++i) {
-		aa.push_back(reinterpret_cast<const InetAddress *>(&(nc->assignedAddresses[i]))->toString());
+		aa.push_back(reinterpret_cast<const InetAddress *>(&(nc->assignedAddresses[i]))->toString(tmp));
 	}
 	nj["assignedAddresses"] = aa;
 
 	nlohmann::json ra = nlohmann::json::array();
 	for(unsigned int i=0;i<nc->routeCount;++i) {
 		nlohmann::json rj;
-		rj["target"] = reinterpret_cast<const InetAddress *>(&(nc->routes[i].target))->toString();
+		rj["target"] = reinterpret_cast<const InetAddress *>(&(nc->routes[i].target))->toString(tmp);
 		if (nc->routes[i].via.ss_family == nc->routes[i].target.ss_family)
-			rj["via"] = reinterpret_cast<const InetAddress *>(&(nc->routes[i].via))->toIpString();
+			rj["via"] = reinterpret_cast<const InetAddress *>(&(nc->routes[i].via))->toIpString(tmp);
 		else rj["via"] = nlohmann::json();
 		rj["flags"] = (int)nc->routes[i].flags;
 		rj["metric"] = (int)nc->routes[i].metric;
@@ -252,12 +252,12 @@ static void _peerToJson(nlohmann::json &pj,const ZT_Peer *peer)
 		case ZT_PEER_ROLE_PLANET: prole = "PLANET"; break;
 	}
 
-	Utils::ztsnprintf(tmp,sizeof(tmp),"%.10llx",peer->address);
+	OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.10llx",peer->address);
 	pj["address"] = tmp;
 	pj["versionMajor"] = peer->versionMajor;
 	pj["versionMinor"] = peer->versionMinor;
 	pj["versionRev"] = peer->versionRev;
-	Utils::ztsnprintf(tmp,sizeof(tmp),"%d.%d.%d",peer->versionMajor,peer->versionMinor,peer->versionRev);
+	OSUtils::ztsnprintf(tmp,sizeof(tmp),"%d.%d.%d",peer->versionMajor,peer->versionMinor,peer->versionRev);
 	pj["version"] = tmp;
 	pj["latency"] = peer->latency;
 	pj["role"] = prole;
@@ -265,7 +265,7 @@ static void _peerToJson(nlohmann::json &pj,const ZT_Peer *peer)
 	nlohmann::json pa = nlohmann::json::array();
 	for(unsigned int i=0;i<peer->pathCount;++i) {
 		nlohmann::json j;
-		j["address"] = reinterpret_cast<const InetAddress *>(&(peer->paths[i].address))->toString();
+		j["address"] = reinterpret_cast<const InetAddress *>(&(peer->paths[i].address))->toString(tmp);
 		j["lastSend"] = peer->paths[i].lastSend;
 		j["lastReceive"] = peer->paths[i].lastReceive;
 		j["trustedPathId"] = peer->paths[i].trustedPathId;
@@ -280,19 +280,19 @@ static void _peerToJson(nlohmann::json &pj,const ZT_Peer *peer)
 
 static void _moonToJson(nlohmann::json &mj,const World &world)
 {
-	char tmp[64];
-	Utils::ztsnprintf(tmp,sizeof(tmp),"%.16llx",world.id());
+	char tmp[4096];
+	OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.16llx",world.id());
 	mj["id"] = tmp;
 	mj["timestamp"] = world.timestamp();
-	mj["signature"] = Utils::hex(world.signature().data,(unsigned int)world.signature().size());
-	mj["updatesMustBeSignedBy"] = Utils::hex(world.updatesMustBeSignedBy().data,(unsigned int)world.updatesMustBeSignedBy().size());
+	mj["signature"] = Utils::hex(world.signature().data,(unsigned int)world.signature().size(),tmp);
+	mj["updatesMustBeSignedBy"] = Utils::hex(world.updatesMustBeSignedBy().data,(unsigned int)world.updatesMustBeSignedBy().size(),tmp);
 	nlohmann::json ra = nlohmann::json::array();
 	for(std::vector<World::Root>::const_iterator r(world.roots().begin());r!=world.roots().end();++r) {
 		nlohmann::json rj;
-		rj["identity"] = r->identity.toString(false);
+		rj["identity"] = r->identity.toString(false,tmp);
 		nlohmann::json eps = nlohmann::json::array();
 		for(std::vector<InetAddress>::const_iterator a(r->stableEndpoints.begin());a!=r->stableEndpoints.end();++a)
-			eps.push_back(a->toString());
+			eps.push_back(a->toString(tmp));
 		rj["stableEndpoints"] = eps;
 		ra.push_back(rj);
 	}
@@ -613,7 +613,7 @@ public:
 				json &physical = _localConfig["physical"];
 				if (physical.is_object()) {
 					for(json::iterator phy(physical.begin());phy!=physical.end();++phy) {
-						InetAddress net(OSUtils::jsonString(phy.key(),""));
+						InetAddress net(OSUtils::jsonString(phy.key(),"").c_str());
 						if (net) {
 							if (phy.value().is_object()) {
 								uint64_t tpid;
@@ -674,7 +674,7 @@ public:
 
 			// Save primary port to a file so CLIs and GUIs can learn it easily
 			char portstr[64];
-			Utils::ztsnprintf(portstr,sizeof(portstr),"%u",_ports[0]);
+			OSUtils::ztsnprintf(portstr,sizeof(portstr),"%u",_ports[0]);
 			OSUtils::writeFile((_homePath + ZT_PATH_SEPARATOR_S "zerotier-one.port").c_str(),std::string(portstr));
 
 			// Attempt to bind to a secondary port chosen from our ZeroTier address.
@@ -712,7 +712,7 @@ public:
 					}
 					if (_ports[2]) {
 						char uniqueName[64];
-						Utils::ztsnprintf(uniqueName,sizeof(uniqueName),"ZeroTier/%.10llx@%u",_node->address(),_ports[2]);
+						OSUtils::ztsnprintf(uniqueName,sizeof(uniqueName),"ZeroTier/%.10llx@%u",_node->address(),_ports[2]);
 						_portMapper = new PortMapper(_ports[2],uniqueName);
 					}
 				}
@@ -982,7 +982,7 @@ public:
 		n->second.settings = settings;
 
 		char nlcpath[4096];
-		Utils::ztsnprintf(nlcpath,sizeof(nlcpath),"%s" ZT_PATH_SEPARATOR_S "%.16llx.local.conf",_networksPath.c_str(),nwid);
+		OSUtils::ztsnprintf(nlcpath,sizeof(nlcpath),"%s" ZT_PATH_SEPARATOR_S "%.16llx.local.conf",_networksPath.c_str(),nwid);
 		FILE *out = fopen(nlcpath,"w");
 		if (out) {
 			fprintf(out,"allowManaged=%d\n",(int)n->second.settings.allowManaged);
@@ -1101,7 +1101,7 @@ public:
 					ZT_NodeStatus status;
 					_node->status(&status);
 
-					Utils::ztsnprintf(tmp,sizeof(tmp),"%.10llx",status.address);
+					OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.10llx",status.address);
 					res["address"] = tmp;
 					res["publicIdentity"] = status.publicIdentity;
 					res["online"] = (bool)(status.online != 0);
@@ -1110,7 +1110,7 @@ public:
 					res["versionMinor"] = ZEROTIER_ONE_VERSION_MINOR;
 					res["versionRev"] = ZEROTIER_ONE_VERSION_REVISION;
 					res["versionBuild"] = ZEROTIER_ONE_VERSION_BUILD;
-					Utils::ztsnprintf(tmp,sizeof(tmp),"%d.%d.%d",ZEROTIER_ONE_VERSION_MAJOR,ZEROTIER_ONE_VERSION_MINOR,ZEROTIER_ONE_VERSION_REVISION);
+					OSUtils::ztsnprintf(tmp,sizeof(tmp),"%d.%d.%d",ZEROTIER_ONE_VERSION_MAJOR,ZEROTIER_ONE_VERSION_MINOR,ZEROTIER_ONE_VERSION_REVISION);
 					res["version"] = tmp;
 					res["clock"] = OSUtils::now();
 
@@ -1257,7 +1257,7 @@ public:
 
 						if ((scode != 200)&&(seed != 0)) {
 							char tmp[64];
-							Utils::ztsnprintf(tmp,sizeof(tmp),"%.16llx",id);
+							OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.16llx",id);
 							res["id"] = tmp;
 							res["roots"] = json::array();
 							res["timestamp"] = 0;
@@ -1395,7 +1395,7 @@ public:
 						json &tryAddrs = v.value()["try"];
 						if (tryAddrs.is_array()) {
 							for(unsigned long i=0;i<tryAddrs.size();++i) {
-								const InetAddress ip(OSUtils::jsonString(tryAddrs[i],""));
+								const InetAddress ip(OSUtils::jsonString(tryAddrs[i],"").c_str());
 								if (ip.ss_family == AF_INET)
 									v4h.push_back(ip);
 								else if (ip.ss_family == AF_INET6)
@@ -1405,7 +1405,7 @@ public:
 						json &blAddrs = v.value()["blacklist"];
 						if (blAddrs.is_array()) {
 							for(unsigned long i=0;i<blAddrs.size();++i) {
-								const InetAddress ip(OSUtils::jsonString(tryAddrs[i],""));
+								const InetAddress ip(OSUtils::jsonString(tryAddrs[i],"").c_str());
 								if (ip.ss_family == AF_INET)
 									v4b.push_back(ip);
 								else if (ip.ss_family == AF_INET6)
@@ -1427,7 +1427,7 @@ public:
 		json &physical = lc["physical"];
 		if (physical.is_object()) {
 			for(json::iterator phy(physical.begin());phy!=physical.end();++phy) {
-				const InetAddress net(OSUtils::jsonString(phy.key(),""));
+				const InetAddress net(OSUtils::jsonString(phy.key(),"").c_str());
 				if ((net)&&(net.netmaskBits() > 0)) {
 					if (phy.value().is_object()) {
 						if (OSUtils::jsonBool(phy.value()["blacklist"],false)) {
@@ -1477,7 +1477,7 @@ public:
 		json &amf = settings["allowManagementFrom"];
 		if (amf.is_array()) {
 			for(unsigned long i=0;i<amf.size();++i) {
-				const InetAddress nw(OSUtils::jsonString(amf[i],""));
+				const InetAddress nw(OSUtils::jsonString(amf[i],"").c_str());
 				if (nw)
 					_allowManagementFrom.push_back(nw);
 			}
@@ -1491,7 +1491,7 @@ public:
 			std::string h = controllerDbHttpHost;
 			_controllerDbPath.append(h);
 			char dbp[128];
-			Utils::ztsnprintf(dbp,sizeof(dbp),"%d",(int)controllerDbHttpPort);
+			OSUtils::ztsnprintf(dbp,sizeof(dbp),"%d",(int)controllerDbHttpPort);
 			_controllerDbPath.push_back(':');
 			_controllerDbPath.append(dbp);
 			if (controllerDbHttpPath.is_string()) {
@@ -1550,6 +1550,8 @@ public:
 	// Apply or update managed IPs for a configured network (be sure n.tap exists)
 	void syncManagedStuff(NetworkState &n,bool syncIps,bool syncRoutes)
 	{
+		char ipbuf[64];
+
 		// assumes _nets_m is locked
 		if (syncIps) {
 			std::vector<InetAddress> newManagedIps;
@@ -1565,7 +1567,7 @@ public:
 			for(std::vector<InetAddress>::iterator ip(n.managedIps.begin());ip!=n.managedIps.end();++ip) {
 				if (std::find(newManagedIps.begin(),newManagedIps.end(),*ip) == newManagedIps.end()) {
 					if (!n.tap->removeIp(*ip))
-						fprintf(stderr,"ERROR: unable to remove ip address %s" ZT_EOL_S, ip->toString().c_str());
+						fprintf(stderr,"ERROR: unable to remove ip address %s" ZT_EOL_S, ip->toString(ipbuf));
 				}
 			}
 #ifdef __SYNOLOGY__
@@ -1575,7 +1577,7 @@ public:
 			for(std::vector<InetAddress>::iterator ip(newManagedIps.begin());ip!=newManagedIps.end();++ip) {
 				if (std::find(n.managedIps.begin(),n.managedIps.end(),*ip) == n.managedIps.end()) {
 					if (!n.tap->addIp(*ip))
-						fprintf(stderr,"ERROR: unable to add ip address %s" ZT_EOL_S, ip->toString().c_str());
+						fprintf(stderr,"ERROR: unable to add ip address %s" ZT_EOL_S, ip->toString(ipbuf));
 				}			
 			}
 #endif
@@ -1585,7 +1587,7 @@ public:
 		if (syncRoutes) {
 			char tapdev[64];
 #ifdef __WINDOWS__
-			Utils::ztsnprintf(tapdev,sizeof(tapdev),"%.16llx",(unsigned long long)n.tap->luid().Value);
+			OSUtils::ztsnprintf(tapdev,sizeof(tapdev),"%.16llx",(unsigned long long)n.tap->luid().Value);
 #else
 			Utils::scopy(tapdev,sizeof(tapdev),n.tap->deviceName().c_str());
 #endif
@@ -1670,7 +1672,7 @@ public:
 			&_nextBackgroundTaskDeadline);
 		if (ZT_ResultCode_isFatal(rc)) {
 			char tmp[256];
-			Utils::ztsnprintf(tmp,sizeof(tmp),"fatal error code from processWirePacket: %d",(int)rc);
+			OSUtils::ztsnprintf(tmp,sizeof(tmp),"fatal error code from processWirePacket: %d",(int)rc);
 			Mutex::Lock _l(_termReason_m);
 			_termReason = ONE_UNRECOVERABLE_ERROR;
 			_fatalErrorMessage = tmp;
@@ -1851,7 +1853,7 @@ public:
 										&_nextBackgroundTaskDeadline);
 									if (ZT_ResultCode_isFatal(rc)) {
 										char tmp[256];
-										Utils::ztsnprintf(tmp,sizeof(tmp),"fatal error code from processWirePacket: %d",(int)rc);
+										OSUtils::ztsnprintf(tmp,sizeof(tmp),"fatal error code from processWirePacket: %d",(int)rc);
 										Mutex::Lock _l(_termReason_m);
 										_termReason = ONE_UNRECOVERABLE_ERROR;
 										_fatalErrorMessage = tmp;
@@ -1919,7 +1921,7 @@ public:
 				if (!n.tap) {
 					try {
 						char friendlyName[128];
-						Utils::ztsnprintf(friendlyName,sizeof(friendlyName),"ZeroTier One [%.16llx]",nwid);
+						OSUtils::ztsnprintf(friendlyName,sizeof(friendlyName),"ZeroTier One [%.16llx]",nwid);
 
 						n.tap = new EthernetTap(
 							_homePath.c_str(),
@@ -1933,7 +1935,7 @@ public:
 						*nuptr = (void *)&n;
 
 						char nlcpath[256];
-						Utils::ztsnprintf(nlcpath,sizeof(nlcpath),"%s" ZT_PATH_SEPARATOR_S "networks.d" ZT_PATH_SEPARATOR_S "%.16llx.local.conf",_homePath.c_str(),nwid);
+						OSUtils::ztsnprintf(nlcpath,sizeof(nlcpath),"%s" ZT_PATH_SEPARATOR_S "networks.d" ZT_PATH_SEPARATOR_S "%.16llx.local.conf",_homePath.c_str(),nwid);
 						std::string nlcbuf;
 						if (OSUtils::readFile(nlcpath,nlcbuf)) {
 							Dictionary<4096> nc;
@@ -1954,7 +1956,7 @@ public:
 									while (true) {
 										size_t nextPos = addresses.find(',', pos);
 										std::string address = addresses.substr(pos, (nextPos == std::string::npos ? addresses.size() : nextPos) - pos);
-										n.settings.allowManagedWhitelist.push_back(InetAddress(address));
+										n.settings.allowManagedWhitelist.push_back(InetAddress(address.c_str()));
 										if (nextPos == std::string::npos) break;
 										pos = nextPos + 1;
 									}
@@ -2019,7 +2021,7 @@ public:
 #endif
 					if (op == ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY) {
 						char nlcpath[256];
-						Utils::ztsnprintf(nlcpath,sizeof(nlcpath),"%s" ZT_PATH_SEPARATOR_S "networks.d" ZT_PATH_SEPARATOR_S "%.16llx.local.conf",_homePath.c_str(),nwid);
+						OSUtils::ztsnprintf(nlcpath,sizeof(nlcpath),"%s" ZT_PATH_SEPARATOR_S "networks.d" ZT_PATH_SEPARATOR_S "%.16llx.local.conf",_homePath.c_str(),nwid);
 						OSUtils::rm(nlcpath);
 					}
 				} else {
@@ -2068,20 +2070,20 @@ public:
 
 		switch(type) {
 			case ZT_STATE_OBJECT_IDENTITY_PUBLIC:
-				Utils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "identity.public",_homePath.c_str());
+				OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "identity.public",_homePath.c_str());
 				break;
 			case ZT_STATE_OBJECT_IDENTITY_SECRET:
-				Utils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "identity.secret",_homePath.c_str());
+				OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "identity.secret",_homePath.c_str());
 				secure = true;
 				break;
 			case ZT_STATE_OBJECT_PLANET:
-				Utils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "planet",_homePath.c_str());
+				OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "planet",_homePath.c_str());
 				break;
 			case ZT_STATE_OBJECT_MOON:
-				Utils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "moons.d/%.16llx.moon",_homePath.c_str(),(unsigned long long)id[0]);
+				OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "moons.d/%.16llx.moon",_homePath.c_str(),(unsigned long long)id[0]);
 				break;
 			case ZT_STATE_OBJECT_NETWORK_CONFIG:
-				Utils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "networks.d/%.16llx.conf",_homePath.c_str(),(unsigned long long)id[0]);
+				OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "networks.d/%.16llx.conf",_homePath.c_str(),(unsigned long long)id[0]);
 				secure = true;
 				break;
 			default:
@@ -2121,19 +2123,19 @@ public:
 		char p[4096];
 		switch(type) {
 			case ZT_STATE_OBJECT_IDENTITY_PUBLIC:
-				Utils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "identity.public",_homePath.c_str());
+				OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "identity.public",_homePath.c_str());
 				break;
 			case ZT_STATE_OBJECT_IDENTITY_SECRET:
-				Utils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "identity.secret",_homePath.c_str());
+				OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "identity.secret",_homePath.c_str());
 				break;
 			case ZT_STATE_OBJECT_NETWORK_CONFIG:
-				Utils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "networks.d/%.16llx.conf",_homePath.c_str(),(unsigned long long)id);
+				OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "networks.d/%.16llx.conf",_homePath.c_str(),(unsigned long long)id);
 				break;
 			case ZT_STATE_OBJECT_PLANET:
-				Utils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "planet",_homePath.c_str());
+				OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "planet",_homePath.c_str());
 				break;
 			case ZT_STATE_OBJECT_MOON:
-				Utils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "moons.d/%.16llx.moon",_homePath.c_str(),(unsigned long long)id);
+				OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "moons.d/%.16llx.moon",_homePath.c_str(),(unsigned long long)id);
 				break;
 			default:
 				return -1;
@@ -2322,7 +2324,7 @@ public:
 			default: scodestr = "Error"; break;
 		}
 
-		Utils::ztsnprintf(tmpn,sizeof(tmpn),"HTTP/1.1 %.3u %s\r\nCache-Control: no-cache\r\nPragma: no-cache\r\nContent-Type: %s\r\nContent-Length: %lu\r\nConnection: close\r\n\r\n",
+		OSUtils::ztsnprintf(tmpn,sizeof(tmpn),"HTTP/1.1 %.3u %s\r\nCache-Control: no-cache\r\nPragma: no-cache\r\nContent-Type: %s\r\nContent-Length: %lu\r\nConnection: close\r\n\r\n",
 			scode,
 			scodestr,
 			contentType.c_str(),
diff --git a/service/SoftwareUpdater.cpp b/service/SoftwareUpdater.cpp
index e05198270..b4bf03ec3 100644
--- a/service/SoftwareUpdater.cpp
+++ b/service/SoftwareUpdater.cpp
@@ -284,7 +284,7 @@ bool SoftwareUpdater::check(const uint64_t now)
 	if ((now - _lastCheckTime) >= ZT_SOFTWARE_UPDATE_CHECK_PERIOD) {
 		_lastCheckTime = now;
 		char tmp[512];
-		const unsigned int len = Utils::ztsnprintf(tmp,sizeof(tmp),
+		const unsigned int len = OSUtils::ztsnprintf(tmp,sizeof(tmp),
 			"%c{\"" ZT_SOFTWARE_UPDATE_JSON_VERSION_MAJOR "\":%d,"
 			"\"" ZT_SOFTWARE_UPDATE_JSON_VERSION_MINOR "\":%d,"
 			"\"" ZT_SOFTWARE_UPDATE_JSON_VERSION_REVISION "\":%d,"
@@ -321,7 +321,8 @@ bool SoftwareUpdater::check(const uint64_t now)
 				// (1) Check the hash itself to make sure the image is basically okay
 				uint8_t sha512[ZT_SHA512_DIGEST_LEN];
 				SHA512::hash(sha512,_download.data(),(unsigned int)_download.length());
-				if (Utils::hex(sha512,ZT_SHA512_DIGEST_LEN) == OSUtils::jsonString(_latestMeta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_HASH],"")) {
+				char hexbuf[(ZT_SHA512_DIGEST_LEN * 2) + 2];
+				if (OSUtils::jsonString(_latestMeta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_HASH],"") == Utils::hex(sha512,ZT_SHA512_DIGEST_LEN,hexbuf)) {
 					// (2) Check signature by signing authority
 					const std::string sig(OSUtils::jsonBinFromHex(_latestMeta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIGNATURE]));
 					if (Identity(ZT_SOFTWARE_UPDATE_SIGNING_AUTHORITY).verify(_download.data(),(unsigned int)_download.length(),sig.data(),(unsigned int)sig.length())) {