net: Add function to parse IP address string

The net_ipaddr_parse() will take a string with optional port
number and convert its information into struct sockaddr.
The format of the IP string can be:
     192.0.2.1:80
     192.0.2.42
     [2001:db8::1]:8080
     [2001:db8::2]
     2001:db::42

Signed-off-by: Jukka Rissanen <jukka.rissanen@linux.intel.com>
diff --git a/include/net/net_ip.h b/include/net/net_ip.h
index 0db5140..e68b9e9 100644
--- a/include/net/net_ip.h
+++ b/include/net/net_ip.h
@@ -870,6 +870,30 @@
 		    char *dst, size_t size);
 
 /**
+ * @brief Parse a string that contains either IPv4 or IPv6 address
+ * and optional port, and store the information in user supplied
+ * sockaddr struct.
+ *
+ * @details Syntax of the IP address string:
+ *   192.0.2.1:80
+ *   192.0.2.42
+ *   [2001:db8::1]:8080
+ *   [2001:db8::2]
+ *   2001:db::42
+ * Note that the str_len parameter is used to restrict the amount of
+ * characters that are checked. If the string does not contain port
+ * number, then the port number in sockaddr is not modified.
+ *
+ * @param str String that contains the IP address.
+ * @param str_len Length of the string to be parsed.
+ * @param addr Pointer to user supplied struct sockaddr.
+ *
+ * @return True if parsing could be done, false otherwise.
+ */
+bool net_ipaddr_parse(const char *str, size_t str_len,
+		      struct sockaddr *addr);
+
+/**
  * @brief Compare TCP sequence numbers.
  *
  * @details This function compares TCP sequence numbers,
diff --git a/subsys/net/ip/utils.c b/subsys/net/ip/utils.c
index 6741d7b..e4ad0ec 100644
--- a/subsys/net/ip/utils.c
+++ b/subsys/net/ip/utils.c
@@ -518,3 +518,206 @@
 
 	return false;
 }
+
+#if defined(CONFIG_NET_IPV6) || defined(CONFIG_NET_IPV4)
+static bool convert_port(const char *buf, u16_t *port)
+{
+	unsigned long tmp;
+	char *endptr;
+
+	tmp = strtoul(buf, &endptr, 10);
+	if ((endptr == buf && tmp == 0) ||
+	    !(*buf != '\0' && *endptr == '\0') ||
+	    ((unsigned long)(unsigned short)tmp != tmp)) {
+		return false;
+	}
+
+	*port = tmp;
+
+	return true;
+}
+#endif /* CONFIG_NET_IPV6 || CONFIG_NET_IPV4 */
+
+#if defined(CONFIG_NET_IPV6)
+static bool parse_ipv6(const char *str, size_t str_len,
+		       struct sockaddr *addr, bool has_port)
+{
+	char *ptr = NULL;
+	struct in6_addr *addr6;
+	char ipaddr[INET6_ADDRSTRLEN + 1];
+	int end, len, ret, i;
+	u16_t port;
+
+	len = min(INET6_ADDRSTRLEN, str_len);
+
+	for (i = 0; i < len; i++) {
+		if (!str[i]) {
+			len = i;
+			break;
+		}
+	}
+
+	if (has_port) {
+		/* IPv6 address with port number */
+		ptr = memchr(str, ']', len);
+		if (!ptr) {
+			return false;
+		}
+
+		end = min(len, ptr - (str + 1));
+		memcpy(ipaddr, str + 1, end);
+	} else {
+		end = len;
+		memcpy(ipaddr, str, end);
+	}
+
+	ipaddr[end] = '\0';
+
+	addr6 = &net_sin6(addr)->sin6_addr;
+
+	ret = net_addr_pton(AF_INET6, ipaddr, addr6);
+	if (ret < 0) {
+		return false;
+	}
+
+	net_sin6(addr)->sin6_family = AF_INET6;
+
+	if (!has_port) {
+		return true;
+	}
+
+	if ((ptr + 1) < (str + str_len) && *(ptr + 1) == ':') {
+		len = str_len - end;
+
+		/* Re-use the ipaddr buf for port conversion */
+		memcpy(ipaddr, ptr + 2, len);
+		ipaddr[len] = '\0';
+
+		ret = convert_port(ipaddr, &port);
+		if (!ret) {
+			return false;
+		}
+
+		net_sin6(addr)->sin6_port = htons(port);
+
+		NET_DBG("IPv6 host %s port %d",
+			net_addr_ntop(AF_INET6, addr6,
+				      ipaddr, sizeof(ipaddr) - 1),
+			port);
+	} else {
+		NET_DBG("IPv6 host %s",
+			net_addr_ntop(AF_INET6, addr6,
+				      ipaddr, sizeof(ipaddr) - 1));
+	}
+
+	return true;
+}
+#endif /* CONFIG_NET_IPV6 */
+
+#if defined(CONFIG_NET_IPV4)
+static bool parse_ipv4(const char *str, size_t str_len,
+		       struct sockaddr *addr, bool has_port)
+{
+	char *ptr = NULL;
+	char ipaddr[NET_IPV4_ADDR_LEN + 1];
+	struct in_addr *addr4;
+	int end, len, ret, i;
+	u16_t port;
+
+	len = min(NET_IPV4_ADDR_LEN, str_len);
+
+	for (i = 0; i < len; i++) {
+		if (!str[i]) {
+			len = i;
+			break;
+		}
+	}
+
+	if (has_port) {
+		/* IPv4 address with port number */
+		ptr = memchr(str, ':', len);
+		if (!ptr) {
+			return false;
+		}
+
+		end = min(len, ptr - str);
+	} else {
+		end = len;
+	}
+
+	memcpy(ipaddr, str, end);
+	ipaddr[end] = '\0';
+
+	addr4 = &net_sin(addr)->sin_addr;
+
+	ret = net_addr_pton(AF_INET, ipaddr, addr4);
+	if (ret < 0) {
+		return false;
+	}
+
+	net_sin(addr)->sin_family = AF_INET;
+
+	if (!has_port) {
+		return true;
+	}
+
+	memcpy(ipaddr, ptr + 1, str_len - end);
+	ipaddr[str_len - end] = '\0';
+
+	ret = convert_port(ipaddr, &port);
+	if (!ret) {
+		return false;
+	}
+
+	net_sin(addr)->sin_port = htons(port);
+
+	NET_DBG("IPv4 host %s port %d",
+		net_addr_ntop(AF_INET, addr4,
+			      ipaddr, sizeof(ipaddr) - 1),
+		port);
+	return true;
+}
+#endif /* CONFIG_NET_IPV4 */
+
+bool net_ipaddr_parse(const char *str, size_t str_len, struct sockaddr *addr)
+{
+	int i, count;
+
+	if (*str == '[') {
+#if defined(CONFIG_NET_IPV6)
+		return parse_ipv6(str, str_len, addr, true);
+#else
+		return false;
+#endif /* CONFIG_NET_IPV6 */
+	}
+
+	for (count = i = 0; str[i] && i < str_len; i++) {
+		if (str[i] == ':') {
+			count++;
+		}
+	}
+
+	if (count == 1) {
+#if defined(CONFIG_NET_IPV4)
+		return parse_ipv4(str, str_len, addr, true);
+#else
+		return false;
+#endif /* CONFIG_NET_IPV4 */
+	}
+
+#if defined(CONFIG_NET_IPV4) && defined(CONFIG_NET_IPV6)
+	if (!parse_ipv4(str, str_len, addr, false)) {
+		return parse_ipv6(str, str_len, addr, false);
+	}
+
+	return true;
+#endif
+
+#if defined(CONFIG_NET_IPV4) && !defined(CONFIG_NET_IPV6)
+	return parse_ipv4(str, str_len, addr, false);
+#endif
+
+#if defined(CONFIG_NET_IPV6) && !defined(CONFIG_NET_IPV4)
+	return parse_ipv6(str, str_len, addr, false);
+#endif
+}