From 90f18f7ee741aca66579165b7807f27ed6a6d685 Mon Sep 17 00:00:00 2001
From: Adam Ierymenko <adam.ierymenko@zerotier.com>
Date: Fri, 6 Nov 2020 11:01:45 -0500
Subject: [PATCH] Fix for ZTO-33 (Jira), only assign routes if there is a
 viable source IP.

---
 node/InetAddress.hpp   | 46 ++++++++++++++++++++++++++++++++++++++++++
 node/Utils.hpp         | 16 +++++++++++++++
 service/OneService.cpp | 22 ++++++++++----------
 3 files changed, 73 insertions(+), 11 deletions(-)

diff --git a/node/InetAddress.hpp b/node/InetAddress.hpp
index 67f70d2a9..a9a35dd20 100644
--- a/node/InetAddress.hpp
+++ b/node/InetAddress.hpp
@@ -453,6 +453,52 @@ struct InetAddress : public sockaddr_storage
 	 */
 	bool isNetwork() const;
 
+	/**
+	 * Find the total number of prefix bits that match between this IP and another
+	 * 
+	 * @param b Second IP to compare with
+	 * @return Number of matching prefix bits or 0 if none match or IPs are of different families (e.g. v4 and v6)
+	 */
+	inline unsigned int matchingPrefixBits(const InetAddress &b) const
+	{
+		unsigned int c = 0;
+		if (ss_family == b.ss_family) {
+			switch(ss_family) {
+				case AF_INET: {
+					uint32_t ip0 = Utils::ntoh((uint32_t)reinterpret_cast<const struct sockaddr_in *>(this)->sin_addr.s_addr);
+					uint32_t ip1 = Utils::ntoh((uint32_t)reinterpret_cast<const struct sockaddr_in *>(&b)->sin_addr.s_addr);
+					while ((ip0 >> 31) == (ip1 >> 31)) {
+						ip0 <<= 1;
+						ip1 <<= 1;
+						if (++c == 32)
+							break;
+					}
+				}	break;
+				case AF_INET6: {
+					const uint8_t *ip0 = reinterpret_cast<const uint8_t *>(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr);
+					const uint8_t *ip1 = reinterpret_cast<const uint8_t *>(reinterpret_cast<const struct sockaddr_in6 *>(&b)->sin6_addr.s6_addr);
+					for(unsigned int i=0;i<16;++i) {
+						if (ip0[i] == ip1[i]) {
+							c += 8;
+						} else {
+							uint8_t ip0b = ip0[i];
+							uint8_t ip1b = ip1[i];
+							uint8_t bit = 0x80;
+							while (bit != 0) {
+								if ((ip0b & bit) != (ip1b & bit))
+									break;
+								++c;
+								bit >>= 1;
+							}
+							break;
+						}
+					}
+				}	break;
+			}
+		}
+		return c;
+	}
+
 	/**
 	 * @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
 	 */
diff --git a/node/Utils.hpp b/node/Utils.hpp
index ec898fc0a..685fdf591 100644
--- a/node/Utils.hpp
+++ b/node/Utils.hpp
@@ -94,6 +94,22 @@ public:
 	static const CPUIDRegisters CPUID;
 #endif
 
+	/**
+	 * Compute the log2 (most significant bit set) of a 32-bit integer
+	 * 
+	 * @param v Integer to compute
+	 * @return log2 or 0 if v is 0
+	 */
+	static inline unsigned int log2(uint32_t v)
+	{
+		uint32_t r = (v > 0xffff) << 4; v >>= r;
+		uint32_t shift = (v > 0xff) << 3; v >>= shift; r |= shift;
+		shift = (v > 0xf) << 2; v >>= shift; r |= shift;
+		shift = (v > 0x3) << 1; v >>= shift; r |= shift;
+		r |= (v >> 1);
+		return (unsigned int)r;
+	}
+
 	/**
 	 * Perform a time-invariant binary comparison
 	 *
diff --git a/service/OneService.cpp b/service/OneService.cpp
index 7cf28d3fd..30f241196 100644
--- a/service/OneService.cpp
+++ b/service/OneService.cpp
@@ -1949,21 +1949,21 @@ public:
 				const InetAddress *const target = reinterpret_cast<const InetAddress *>(&(n.config.routes[i].target));
 				const InetAddress *const via = reinterpret_cast<const InetAddress *>(&(n.config.routes[i].via));
 
+				// Make sure we are allowed to set this managed route.
+				if ( (!checkIfManagedIsAllowed(n,*target)) || ((via->ss_family == target->ss_family)&&(matchIpOnly(myIps,*via))) )
+					continue;
+
+				// Find an IP on the interface that can be a source IP, abort if no IPs assigned.
 				const InetAddress *src = NULL;
-				for (unsigned int j=0; j<n.config.assignedAddressCount; ++j) {
-					const InetAddress *const tmp = reinterpret_cast<const InetAddress *>(&(n.config.assignedAddresses[j]));
-					if (target->isV4() && tmp->isV4()) {
-						src = reinterpret_cast<InetAddress *>(&(n.config.assignedAddresses[j]));
-						break;
-					} else if (target->isV6() && tmp->isV6()) {
-						src = reinterpret_cast<InetAddress *>(&(n.config.assignedAddresses[j]));
-						break;
+				unsigned int mostMatchingPrefixBits = 0;
+				for(std::vector<InetAddress>::const_iterator i(myIps.begin());i!=myIps.end();++i) {
+					const unsigned int matchingPrefixBits = i->matchingPrefixBits(*target);
+					if (matchingPrefixBits >= mostMatchingPrefixBits) {
+						mostMatchingPrefixBits = matchingPrefixBits;
+						src = &(*i);
 					}
 				}
 				if (!src)
-					src = &NULL_INET_ADDR;
-
-				if ( (!checkIfManagedIsAllowed(n,*target)) || ((via->ss_family == target->ss_family)&&(matchIpOnly(myIps,*via))) )
 					continue;
 
 				// Ignore routes implied by local managed IPs since adding the IP adds the route