net: ipv6: Add string_to_ip6 converter
authorViacheslav Mitrofanov <v.v.mitrofanov@yadro.com>
Fri, 2 Dec 2022 09:18:02 +0000 (12:18 +0300)
committerTom Rini <trini@konsulko.com>
Mon, 5 Dec 2022 17:47:16 +0000 (12:47 -0500)
This functions is used as a converter from IPv6 address string notation
to struct ip6_addr that is used everywhere in IPv6 implementation. For
example it is used to parse and convert IPv6 address from tftpboot
command. Conversion algorithm uses two passes, first to verify syntax and
locate colons and second pass to read the address. In case of valid IPv6
address it returns 0.

Examples of valid strings:
2001:db8::0:1234:1
2001:0db8:0000:0000:0000:0000:1234:0001
::1
::ffff:192.168.1.1

Examples of invalid strings
2001:db8::0::0          (:: can only appear once)
2001:db8:192.168.1.1::1 (v4 part can only appear at the end)
192.168.1.1             (we don't implicity map v4)

Series-changes: 3
- Added function description
- Added length parameter to string_to_ip6()

Series-changes: 4
- Fixed function description style

Signed-off-by: Viacheslav Mitrofanov <v.v.mitrofanov@yadro.com>
Reviewed-by: Ramon Fried <rfried.dev@gmail.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
include/net6.h
lib/net_utils.c

index 78981877ce371404bdb6dea16faff4ee882c8782..50b7120ca3460b74a2d325cf1e8a7dacfda626ee 100644 (file)
@@ -174,6 +174,7 @@ extern u32 net_prefix_length;       /* Our prefixlength (0 = unknown) */
 extern struct in6_addr net_server_ip6; /* Server IPv6 addr (0 = unknown) */
 extern bool use_ip6;
 
+#if IS_ENABLED(CONFIG_IPV6)
 /**
  * string_to_ip6() - Convert IPv6 string addr to inner IPV6 addr format
  *
@@ -193,11 +194,14 @@ extern bool use_ip6;
  * @addr:      converted IPv6 addr
  * Return: 0 if conversion successful, -EINVAL if fail
  */
+int string_to_ip6(const char *s, size_t len, struct in6_addr *addr);
+#else
 static inline int
 string_to_ip6(const char *s, size_t len, struct in6_addr *addr)
 {
        return -EINVAL;
 }
+#endif
 
 /**
  * ip6_is_unspecified_addr() - Check if IPv6 addr is not set i.e. is zero
index 72a3b098a70518b84a53bb2e776a582763d4d4bf..4283c13a31d9394db20ce759512000428caeba90 100644 (file)
@@ -11,6 +11,7 @@
 
 #include <common.h>
 #include <net.h>
+#include <net6.h>
 
 struct in_addr string_to_ip(const char *s)
 {
@@ -43,6 +44,114 @@ struct in_addr string_to_ip(const char *s)
        return addr;
 }
 
+#if IS_ENABLED(CONFIG_IPV6)
+int string_to_ip6(const char *str, size_t len, struct in6_addr *addr)
+{
+       int colon_count = 0;
+       int found_double_colon = 0;
+       int xstart = 0;         /* first zero (double colon) */
+       int section_num = 7;    /* num words the double colon represents */
+       int i;
+       const char *s = str;
+       const char *const e = s + len;
+       struct in_addr zero_ip = {.s_addr = 0};
+
+       if (!str)
+               return -1;
+
+       /* First pass, verify the syntax and locate the double colon */
+       while (s < e) {
+               while (s < e && isxdigit((int)*s))
+                       s++;
+               if (*s == '\0')
+                       break;
+               if (*s != ':') {
+                       if (*s == '.' && section_num >= 2) {
+                               struct in_addr v4;
+
+                               while (s != str && *(s - 1) != ':')
+                                       --s;
+                               v4 = string_to_ip(s);
+                               if (memcmp(&zero_ip, &v4,
+                                          sizeof(struct in_addr)) != 0) {
+                                       section_num -= 2;
+                                       break;
+                               }
+                       }
+                       /* This could be a valid address */
+                       break;
+               }
+               if (s == str) {
+                       /* The address begins with a colon */
+                       if (*++s != ':')
+                               /* Must start with a double colon or a number */
+                               goto out_err;
+               } else {
+                       s++;
+                       if (found_double_colon)
+                               section_num--;
+                       else
+                               xstart++;
+               }
+
+               if (*s == ':') {
+                       if (found_double_colon)
+                               /* Two double colons are not allowed */
+                               goto out_err;
+                       found_double_colon = 1;
+                       section_num -= xstart;
+                       s++;
+               }
+
+               if (++colon_count == 7)
+                       /* Found all colons */
+                       break;
+               ++s;
+       }
+
+       if (colon_count == 0)
+               goto out_err;
+       if (*--s == ':')
+               section_num++;
+
+       /* Second pass, read the address */
+       s = str;
+       for (i = 0; i < 8; i++) {
+               int val = 0;
+               char *end;
+
+               if (found_double_colon &&
+                   i >= xstart && i < xstart + section_num) {
+                       addr->s6_addr16[i] = 0;
+                       continue;
+               }
+               while (*s == ':')
+                       s++;
+
+               if (i == 6 && isdigit((int)*s)) {
+                       struct in_addr v4 = string_to_ip(s);
+
+                       if (memcmp(&zero_ip, &v4,
+                                  sizeof(struct in_addr)) != 0) {
+                               /* Ending with :IPv4-address */
+                               addr->s6_addr32[3] = v4.s_addr;
+                               break;
+                       }
+               }
+
+               val = simple_strtoul(s, &end, 16);
+               if (end != e && *end != '\0' && *end != ':')
+                       goto out_err;
+               addr->s6_addr16[i] = htons(val);
+               s = end;
+       }
+       return 0;
+
+out_err:
+       return -1;
+}
+#endif
+
 void string_to_enetaddr(const char *addr, uint8_t *enetaddr)
 {
        char *end;