From f6e29e4756a35a43e074e1d0fb6fd427ded7e1ad Mon Sep 17 00:00:00 2001 From: 0x1eef <0x1eef@protonmail.com> Date: Sun, 3 Sep 2023 04:49:33 -0300 Subject: [PATCH 1/2] Add isinetaddr6 This change introduces `isinetaddr6`, a function that validates IPv6 addresses. The interface is identical to isinetaddr, where 0 is returned for an invalid address and 1 is returned for a valid address. --- .gitignore | 1 + Makefile | 11 ++++- include/isinetaddr.h | 1 + src/isinetaddr6.c | 90 +++++++++++++++++++++++++++++++++++++++++ test/isinetaddr6_test.c | 72 +++++++++++++++++++++++++++++++++ 5 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 src/isinetaddr6.c create mode 100644 test/isinetaddr6_test.c diff --git a/.gitignore b/.gitignore index d4aa077..21c4153 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ test/isinetaddr test/iscidraddr +test/isinetaddr6 diff --git a/Makefile b/Makefile index 54fb911..c64f5e1 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ SRCDIR = src -SRCFILES = $(SRCDIR)/isinetaddr.c $(SRCDIR)/iscidraddr.c +SRCFILES = $(SRCDIR)/isinetaddr.c $(SRCDIR)/iscidraddr.c $(SRCDIR)/isinetaddr6.c INCDIR = include TESTDIR = test @@ -7,6 +7,10 @@ CC = cc CFLAGS = -fstack-protector-all -I$(INCDIR) -Wall -Wextra -pedantic test: + @make test4 + @make test6 + +test4: @$(CC) $(CFLAGS) $(SRCFILES) $(TESTDIR)/isinetaddr_test.c -o $(TESTDIR)/isinetaddr @echo -n test/isinetaddr: '' @$(TESTDIR)/isinetaddr @@ -14,4 +18,9 @@ test: @echo -n test/iscidraddr: '' @$(TESTDIR)/iscidraddr +test6: + @$(CC) $(CFLAGS) $(SRCFILES) $(TESTDIR)/isinetaddr6_test.c -o $(TESTDIR)/isinetaddr6 + @echo -n test/isinetaddr6: '' + @$(TESTDIR)/isinetaddr6 + .PHONY: test diff --git a/include/isinetaddr.h b/include/isinetaddr.h index 80e7696..798b6d8 100644 --- a/include/isinetaddr.h +++ b/include/isinetaddr.h @@ -1,3 +1,4 @@ #pragma once int isinetaddr(const char *str); int iscidraddr(const char *str); +int isinetaddr6(const char *str); diff --git a/src/isinetaddr6.c b/src/isinetaddr6.c new file mode 100644 index 0000000..7ef79a2 --- /dev/null +++ b/src/isinetaddr6.c @@ -0,0 +1,90 @@ +#include +#include +#include +#include + +static const int MAX_DIGITLEN = 4; +static const int MAX_HEXTETS = 8; +static const int MAX_HEXDIGITS = 32; +static const int MAX_STRLEN = 40; +static const char SEP = ':'; + +static int has_consecutive_chars(const char *str, char c, int n); +static char* expand(const char *str, size_t strlen, char *new_str, size_t headlen); + +int +isinetaddr6(const char *str) +{ + int hextets = 1, digitlen = 0, hexdigits = 0; + size_t len = (str == NULL ? 0 : strnlen(str, MAX_STRLEN)); + + if (len == 0) { + return 0; + } else if (strncasecmp(str, "::ffff", 6) == 0) { + return isinetaddr(&str[7]); + } + for (size_t i = 0; i < len; i++) { + if (has_consecutive_chars(&str[i], SEP, 3)) { + return 0; + } else if (has_consecutive_chars(&str[i], SEP, 2)) { + char new_str[MAX_STRLEN]; + return isinetaddr6(expand(str, len, new_str, i)); + } else if (str[i] == SEP) { + if (digitlen < MAX_DIGITLEN) { + return 0; + } else { + digitlen = 0; + hextets++; + } + } else if (isxdigit(str[i])) { + if (digitlen == MAX_DIGITLEN) { + return 0; + } else { + digitlen++; + hexdigits++; + } + } else { + return 0; + } + } + if (hextets == MAX_HEXTETS) { + return hexdigits <= MAX_HEXDIGITS && digitlen == MAX_DIGITLEN; + } else { + return 0; + } +} + +static int +has_consecutive_chars(const char *str, char c, int n) +{ + for (int i = 0; i < n; i++) { + if (*str != c) { + return 0; + } + str++; + } + return 1; +} + +static char* +expand(const char *str, size_t strlen, char *new_str, size_t headlen) +{ + char *ptr = new_str; + size_t taillen = (strlen - headlen) - 2; + size_t bodylen = MAX_STRLEN - taillen; + size_t i = headlen + 2; + size_t j = headlen; + + while (i++ < strlen) { + if (has_consecutive_chars(&str[i], SEP, 2)) { + return NULL; + } + } + memcpy(ptr, &str[0], headlen); + ptr += headlen; + while (++j < bodylen) { + *ptr++ = j % 5 == 0 ? ':' : '0'; + } + memcpy(ptr, &str[headlen + 2], taillen); + return new_str; +} diff --git a/test/isinetaddr6_test.c b/test/isinetaddr6_test.c new file mode 100644 index 0000000..7e4fe11 --- /dev/null +++ b/test/isinetaddr6_test.c @@ -0,0 +1,72 @@ +#include +#include +#include +#include + +const char *valid[] = { + /* valid IPv6 (single colon)*/ + "0000:0000:0000:0000:0000:0000:0000:0001", + "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "fe80:0000:0000:0000:0202:b3ff:fe1e:8329", + "abcd:abcd:abcd:abcd:abcd:abcd:abcd:abcd", + "1234:5678:9abc:def0:face:face:b00c:0ffe", + "2001:19f0:5401:0000:0000:ffff:1e61:face", + "dead:beef:cafe:babe:affe:8a2e:0370:7334", + + /* valid IPv6 (double colon) */ + "fe80::c001:a0ff:fe12:3456", + "2001:0db8::1", + "2001:abcd:ef01:2345::", + "1234::5678", + "0000:00::1111", + "000::1111:1111", + "000::1111:1111:1111", + "0000:00::1111", + "0000:0000:0000:0000:0000:0000:0000:00::", + "2001:0db8:85a3:0000:0000::8a2e:0370:7334", + "2001::5", + "::ffff:192.168.2.1", + "::FFFF:192.168.2.1", + "::", + "::1", +}; + +const char *invalid[] = { + /* invalid IPv6 (single colon) */ + "0000:0000:0000:0000:0000:0000:0000:000Z", + "0000:0000:0000:0000:0000:0000:0000:0", + "0000:0000:0000:0000:0000:0000:0000:", + + /* invalid IPv6 (double colon) */ + ":::", "2001:::1", "2001:::1::", + "2001::1::", "::1::", + "2001:db8:85a3::8a2e:3700:7334", + "::ffff", "::ffff:", "::ffff:00", + + /* edge cases */ + NULL +}; + +int +main(void) { + size_t len; + /* IPv6: valid */ + len = sizeof(valid) / sizeof(valid[0]); + for (size_t i = 0; i < len; i++) { + if (isinetaddr6(valid[i]) != 1) { + fprintf(stderr, "assertion failed: '%s' should be valid\n", valid[i]); + abort(); + } + } + /* IPv6: invalid */ + len = sizeof(invalid) / sizeof(invalid[0]); + for (size_t i = 0; i < len; i++) { + if (isinetaddr6(invalid[i]) != 0) { + fprintf(stderr, "assertion failed: '%s' should NOT be valid\n", invalid[i]); + abort(); + } + } + /* Done */ + printf("OK\n"); + return EXIT_SUCCESS; +} From a0fed4159d226b5dd2ff9c6235f334023ac7276e Mon Sep 17 00:00:00 2001 From: 0x1eef <0x1eef@protonmail.com> Date: Mon, 11 Sep 2023 22:37:25 -0300 Subject: [PATCH 2/2] README: cover isinetaddr6 --- README.md | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9b5db27..ee3a510 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ ## About isinetaddr is a simple C library that provides an interface that can -be used to validate one or more IPv4 addresses (with optional support -for CIDR notation as well). The library is guided by easy to extend -[testcases](test/) that help verify safety and correctness. +be used to validate one or more IPv(4|6) addresses +(with optional support for CIDR notation as well). The library is +guided by easy to extend [testcases](test/) that help verify safety +and correctness. ## Examples @@ -122,6 +123,61 @@ foobar is an invalid IPv4 address 127.0.0.1/64 is an invalid IPv4 address ``` +### IPv6 + +The following example demonstrates the `isinetaddr6` function with +both valid and invalid inputs. The `isinetaddr6` function returns 1 +when the input given is valid, and otherwise returns 0. + +```C +#include +#include +#include + +const char *strings[] = { + /* valid */ + "::", + "::1", + "0000:0000:0000:0000:0000:0000:0000:0000", + + /* invalid */ + "foobar", + NULL, + "00:::0", +}; + +int +main(void) +{ + const char *str; + const int i = sizeof(strings) / sizeof(strings[0]); + for (int j = 0; j < i; j++) { + str = strings[j]; + if (isinetaddr6(str)) { + printf("%s is a valid IPv6 address\n", str); + } else { + printf("%s is an invalid IPv6 address\n", str); + } + } + return EXIT_SUCCESS; +} +``` + +When the above source code is compiled and run the output is +expected to be as follows: + +``` +$ cc -Iinclude src/*.c share/isinetaddr/examples/isinetaddr6.c -o example +$ ./example +0x1eef [isinetaddr] % ./example +:: is a valid IPv6 address +::1 is a valid IPv6 address +0000:0000:0000:0000:0000:0000:0000:0000 is a valid IPv6 address +foobar is an invalid IPv6 address +(null) is an invalid IPv6 address +00:::0 is an invalid IPv6 address +``` + ## Sources * [Source code (GitHub)](https://github.com/0x1eef/isinetaddr#readme)