socket.h: Introduce API osmo_sockaddr_netmask_to_prefixlen()

Implementation is imported from osmo-ggsn.git
97f60e3dca581797007524e0006ca9fafad59713 in46a_netmasklen() and adapter
to work with an osmo_sockaddr.

This will be used by osmocom-bb's "modem" app.

Change-Id: I75e75e251c6776801fffdde745aebedf21c68799
diff --git a/TODO-RELEASE b/TODO-RELEASE
index 6ac8db8..86363e1 100644
--- a/TODO-RELEASE
+++ b/TODO-RELEASE
@@ -7,5 +7,5 @@
 # If any interfaces have been added since the last public release: c:r:a + 1.
 # If any interfaces have been removed or changed since the last public release: c:r:0.
 #library	what			description / commit summary line
-libosmocore new API osmo_sockaddr_is_any()
+libosmocore new API osmo_sockaddr_is_any(), osmo_sockaddr_netmask_to_prefixlen()
 libosmocore	ABI breakage		OSMO_NUM_DLIB change affecting internal_cat[]
diff --git a/include/osmocom/core/socket.h b/include/osmocom/core/socket.h
index 06db5d0..3ed9cc6 100644
--- a/include/osmocom/core/socket.h
+++ b/include/osmocom/core/socket.h
@@ -50,6 +50,8 @@
 int osmo_sockaddr_to_octets(uint8_t *dst, size_t dst_maxlen, const struct osmo_sockaddr *os);
 int osmo_sockaddr_from_octets(struct osmo_sockaddr *os, const void *src, size_t src_len);
 
+int osmo_sockaddr_netmask_to_prefixlen(const struct osmo_sockaddr *addr);
+
 const char *osmo_sockaddr_to_str(const struct osmo_sockaddr *sockaddr);
 char *osmo_sockaddr_to_str_buf(char *buf, size_t buf_len,
 			       const struct osmo_sockaddr *sockaddr);
diff --git a/src/socket.c b/src/socket.c
index 3d945e7..ee49c27 100644
--- a/src/socket.c
+++ b/src/socket.c
@@ -1291,6 +1291,63 @@
 	}
 }
 
+static unsigned int in6_addr_netmask_to_prefixlen(const struct in6_addr *netmask)
+{
+	#if defined(__linux__)
+		#define ADDRFIELD(i) s6_addr32[i]
+	#else
+		#define ADDRFIELD(i) __u6_addr.__u6_addr32[i]
+	#endif
+
+	unsigned int i, j, prefix = 0;
+
+	for (j = 0; j < 4; j++) {
+		uint32_t bits = netmask->ADDRFIELD(j);
+		uint8_t *b = (uint8_t *)&bits;
+		for (i = 0; i < 4; i++) {
+			while (b[i] & 0x80) {
+				prefix++;
+				b[i] = b[i] << 1;
+			}
+		}
+	}
+
+	#undef ADDRFIELD
+
+	return prefix;
+}
+
+static unsigned int in_addr_netmask_to_prefixlen(const struct in_addr *netmask)
+{
+	uint32_t bits = netmask->s_addr;
+	uint8_t *b = (uint8_t *)&bits;
+	unsigned int i, prefix = 0;
+
+	for (i = 0; i < 4; i++) {
+		while (b[i] & 0x80) {
+			prefix++;
+			b[i] = b[i] << 1;
+		}
+	}
+	return prefix;
+}
+
+/*! Convert netmask to prefix length representation
+ *  \param[in] netmask sockaddr containing a netmask (consecutive list of 1-bit followed by consecutive list of 0-bit)
+ *  \returns prefix length representation of the netmask (count of 1-bit from the start of the netmask), negative on error.
+ */
+int osmo_sockaddr_netmask_to_prefixlen(const struct osmo_sockaddr *netmask)
+{
+	switch (netmask->u.sa.sa_family) {
+	case AF_INET6:
+		return in6_addr_netmask_to_prefixlen(&netmask->u.sin6.sin6_addr);
+	case AF_INET:
+		return in_addr_netmask_to_prefixlen(&netmask->u.sin.sin_addr);
+	default:
+		return -ENOTSUP;
+	}
+}
+
 /*! Initialize a unix domain socket (including bind/connect)
  *  \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM
  *  \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP
diff --git a/tests/socket/socket_test.c b/tests/socket/socket_test.c
index d090fba..b3b9584 100644
--- a/tests/socket/socket_test.c
+++ b/tests/socket/socket_test.c
@@ -385,6 +385,63 @@
 	OSMO_ASSERT(!strncmp("[2003:1234:5678:90ab:cdef:1234:4321:4321]:23420", result, sizeof(buf)));
 }
 
+static void test_osa_netmask_prefixlen(void)
+{
+	struct osmo_sockaddr ipv4;
+	struct osmo_sockaddr ipv6;
+	int rc;
+
+	ipv4.u.sin = (struct sockaddr_in){
+		.sin_family = AF_INET,
+	};
+
+	ipv4.u.sin.sin_addr.s_addr = inet_addr("0.0.0.0");
+	rc = osmo_sockaddr_netmask_to_prefixlen(&ipv4);
+	OSMO_ASSERT(rc == 0);
+
+	ipv4.u.sin.sin_addr.s_addr = inet_addr("255.0.0.0");
+	rc = osmo_sockaddr_netmask_to_prefixlen(&ipv4);
+	OSMO_ASSERT(rc == 8);
+
+	ipv4.u.sin.sin_addr.s_addr = inet_addr("255.255.0.0");
+	rc = osmo_sockaddr_netmask_to_prefixlen(&ipv4);
+	OSMO_ASSERT(rc == 16);
+
+	ipv4.u.sin.sin_addr.s_addr = inet_addr("255.255.255.0");
+	rc = osmo_sockaddr_netmask_to_prefixlen(&ipv4);
+	OSMO_ASSERT(rc == 24);
+
+	ipv4.u.sin.sin_addr.s_addr = inet_addr("255.255.255.255");
+	rc = osmo_sockaddr_netmask_to_prefixlen(&ipv4);
+	OSMO_ASSERT(rc == 32);
+
+	ipv4.u.sin.sin_addr.s_addr = inet_addr("0.255.0.0");
+	rc = osmo_sockaddr_netmask_to_prefixlen(&ipv4);
+	/* FIXME: This shows the implementation is not that robust checking validity of input netmask: */
+	OSMO_ASSERT(rc == 8);
+
+	ipv6.u.sin6 = (struct sockaddr_in6){
+		.sin6_family = AF_INET6,
+	};
+
+	inet_pton(AF_INET6, "fe::", &ipv6.u.sin6.sin6_addr);
+	rc = osmo_sockaddr_netmask_to_prefixlen(&ipv6);
+	OSMO_ASSERT(rc == 7);
+
+	inet_pton(AF_INET6, "ff::", &ipv6.u.sin6.sin6_addr);
+	rc = osmo_sockaddr_netmask_to_prefixlen(&ipv6);
+	OSMO_ASSERT(rc == 8);
+
+	inet_pton(AF_INET6, "ff:ff::", &ipv6.u.sin6.sin6_addr);
+	rc = osmo_sockaddr_netmask_to_prefixlen(&ipv6);
+	OSMO_ASSERT(rc == 16);
+
+	inet_pton(AF_INET6, "ff:ff::ff", &ipv6.u.sin6.sin6_addr);
+	rc = osmo_sockaddr_netmask_to_prefixlen(&ipv6);
+	/* FIXME: This shows the implementation is not that robust checking validity of input netmask: */
+	OSMO_ASSERT(rc == 24);
+}
+
 const struct log_info_cat default_categories[] = {
 };
 
@@ -407,6 +464,7 @@
 	test_get_ip_and_port();
 	test_sockinit_osa();
 	test_osa_str();
+	test_osa_netmask_prefixlen();
 
 	return EXIT_SUCCESS;
 }