summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniil Baturin <daniil@vyos.io>2024-01-03 15:12:13 -0500
committerDaniil Baturin <daniil@vyos.io>2024-01-03 15:12:13 -0500
commit346aedbcd2257512208195bb165cc857e2907922 (patch)
tree104b13bc55d97974d353fecd324084c23dd6d66e
parent1a92fc130e7915dd711d63ff448dae490b586ef4 (diff)
downloadipaddrcheck-346aedbcd2257512208195bb165cc857e2907922.tar.gz
ipaddrcheck-346aedbcd2257512208195bb165cc857e2907922.zip
Complete implementation of IPv4 and IPv6 address range checks
* Add IPv6 range checking and tests for it * Expose both options in the help message * Explain in comments how the range checking logic works
-rw-r--r--src/ipaddrcheck.c55
-rw-r--r--src/ipaddrcheck_functions.c141
-rw-r--r--src/ipaddrcheck_functions.h3
-rw-r--r--tests/check_ipaddrcheck.c10
-rwxr-xr-xtests/integration_tests.sh25
5 files changed, 198 insertions, 36 deletions
diff --git a/src/ipaddrcheck.c b/src/ipaddrcheck.c
index 8e9c782..230c6d6 100644
--- a/src/ipaddrcheck.c
+++ b/src/ipaddrcheck.c
@@ -2,7 +2,7 @@
* ipaddrcheck.c: an IPv4/IPv6 validator
*
* Copyright (C) 2013 Daniil Baturin
- * Copyright (C) 2018 VyOS maintainers and contributors
+ * Copyright (C) 2018-2024 VyOS maintainers and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 or later as
@@ -49,8 +49,15 @@
#define ALLOW_LOOPBACK 250
#define IS_ANY_HOST 260
#define IS_ANY_NET 270
+
+/* XXX: These options are handled outside of the main switch
+ * because they the main switch was design to handle
+ * only single addresses directly parseable by libcidr.
+ * Ideally, we should refactor that at some point in the future...
+ */
#define IS_IPV4_RANGE 280
#define IS_IPV6_RANGE 290
+
#define NO_ACTION 500
static const struct option options[] =
@@ -106,8 +113,11 @@ int main(int argc, char* argv[])
int no_action = 0; /* Indicates the option modifies program behaviour
but doesn't have its own action */
- int ipv4_range_check = 0; /* Disabled quick validity check for the argment string,
- since it needs to be split into components first. */
+ /* Range checks are handled separately now, without a quick sanity check,
+ * since their argument is not a single address and needs to be split first.
+ */
+ int ipv4_range_check = 0;
+ int ipv6_range_check = 0;
int verbose = 0;
@@ -207,7 +217,7 @@ int main(int argc, char* argv[])
ipv4_range_check = 1;
break;
case 'G':
- action = IS_IPV6_RANGE;
+ ipv6_range_check = 1;
break;
case 'V':
verbose = 1;
@@ -255,9 +265,10 @@ int main(int argc, char* argv[])
return(RESULT_INT_ERROR);
}
+ /* If the argument is a range, use special functions that can handle it. */
if( ipv4_range_check )
{
- int result = is_ipv4_range(address_str, verbose);
+ int result = is_ipv4_range(address_str, verbose);
if( result == RESULT_SUCCESS )
{
@@ -269,6 +280,24 @@ int main(int argc, char* argv[])
}
}
+ if( ipv6_range_check )
+ {
+ int result = is_ipv6_range(address_str, verbose);
+
+ if( result == RESULT_SUCCESS )
+ {
+ return(EXIT_SUCCESS);
+ }
+ else
+ {
+ return(EXIT_FAILURE);
+ }
+ }
+
+ /* If ipaddrcheck is called with options other than --is-ipv4-range or --is-ipv6-range,
+ * the argument is a single address that we can parse beforehand and pass to various checking functions.
+ */
+
CIDR *address;
address = cidr_from_str(address_str);
@@ -498,8 +527,7 @@ int main(int argc, char* argv[])
case NO_ACTION:
break;
case IS_ANY_HOST:
- /* Host vs. network address check only makes sense
- if prefix length is given */
+ /* Host vs. network address check only makes sense if prefix length is given */
if( !is_any_cidr(address_str) )
{
if( verbose )
@@ -522,8 +550,7 @@ int main(int argc, char* argv[])
}
break;
case IS_ANY_NET:
- /* Host vs. network address check only makes sense
- if prefix length is given */
+ /* Host vs. network address check only makes sense if prefix length is given */
if( !is_any_cidr(address_str) )
{
if( verbose )
@@ -574,7 +601,7 @@ void print_help(const char* program_name)
{
printf("Usage: %s <OPTIONS> [STRING]\n", program_name);
printf("\
-Options:\n\
+Address checking options:\n\
--is-valid Check if STRING is a valid IPv4 or IPv6 address\n\
with or without prefix length\n\
--is-any-cidr Check if STRING is a valid IPv4 or IPv6 address\n\
@@ -602,8 +629,14 @@ Options:\n\
--is-ipv6-link-local Check if STRING is an IPv6 link-local address \n\
--is-valid-intf-address Check if STRING is an IPv4 or IPv6 address that \n\
can be assigned to a network interface \n\
+ --is-ipv4-range Check if STRING is a valid IPv4 address range\n\
+ --is-ipv6-range Check if STRING is a valid IPv6 address range\n\
+ \n\
+Behavior options:\n\
--allow-loopback When used with --is-valid-intf-address,\n\
makes IPv4 loopback addresses pass the check\n\
+\n\
+Other options:\n\
--version Print version information and exit \n\
--help Print help message and exit\n\
\n\
@@ -619,7 +652,7 @@ Exit codes:\n\
void print_version(void)
{
printf("%s %s\n\n", PACKAGE_NAME, PACKAGE_VERSION);
- printf("Copyright (C) VyOS maintainers and contributors 2018.\n\
+ printf("Copyright (C) 2024 VyOS maintainers and contributors.\n\
License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html>\n\
This is free software: you are free to change and redistribute it.\n\
There is NO WARRANTY, to the extent permitted by law.\n");
diff --git a/src/ipaddrcheck_functions.c b/src/ipaddrcheck_functions.c
index fdbca77..e3e2c86 100644
--- a/src/ipaddrcheck_functions.c
+++ b/src/ipaddrcheck_functions.c
@@ -2,7 +2,7 @@
* ipaddrcheck_functions.c: IPv4/IPv6 validation functions for ipaddrcheck
*
* Copyright (C) 2013 Daniil Baturin
- * Copyright (C) 2018 VyOS maintainers and contributors
+ * Copyright (C) 2018-2024 VyOS maintainers and contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -456,6 +456,50 @@ int is_any_net(CIDR *address)
return(result);
}
+/* Split a hyphen-separated range into its left and right components.
+ * This function is patently unsafe,
+ * whether it's safe to do what it does should be determined by its callers.
+ */
+void split_range(char* range_str, char* left, char*right)
+{
+ char* ptr = left;
+ int length = strlen(range_str);
+ int pos = 0;
+ int index = 0;
+ while(pos < length)
+ {
+ if( range_str[pos] == '-' )
+ {
+ ptr[index] = '\0';
+ ptr = right;
+ index = 0;
+ }
+ else
+ {
+ ptr[index] = range_str[pos];
+ index++;
+ }
+
+ pos++;
+ }
+ ptr[index] = '\0';
+
+ return;
+}
+
+int compare_ipv6(struct in6_addr *left, struct in6_addr *right)
+{
+ int i = 0;
+ for( i = 0; i < 16; i++ )
+ {
+ if (left->s6_addr[i] < right->s6_addr[i])
+ return -1;
+ else if (left->s6_addr[i] > right->s6_addr[i])
+ return 1;
+ }
+ return 0;
+}
+
/* Is it a valid IPv4 address range? */
int is_ipv4_range(char* range_str, int verbose)
{
@@ -482,41 +526,93 @@ int is_ipv4_range(char* range_str, int verbose)
/* Split the string at the hyphen.
If the regex check succeeded, we know the hyphen is there. */
- char* ptr = left;
- int length = strlen(range_str);
- int pos = 0;
- int index = 0;
- while(pos < length)
+ split_range(range_str, left, right);
+
+ if( !is_ipv4_single(left) )
{
- if( range_str[pos] == '-' )
+ if( verbose )
{
- ptr[index] = '\0';
- ptr = right;
- index = 0;
+ fprintf(stderr, "Malformed range %s: %s is not a valid IPv4 address\n", range_str, left);
+ }
+ result = RESULT_FAILURE;
+ }
+ else if( !is_ipv4_single(right) )
+ {
+ if( verbose )
+ {
+ fprintf(stderr, "Malformed range %s: %s is not a valid IPv4 address\n", range_str, right);
+ }
+ result = RESULT_FAILURE;
+ }
+ else
+ {
+ CIDR* left_addr = cidr_from_str(left);
+ CIDR* right_addr = cidr_from_str(right);
+ struct in_addr* left_in_addr = cidr_to_inaddr(left_addr, NULL);
+ struct in_addr* right_in_addr = cidr_to_inaddr(right_addr, NULL);
+
+ if( left_in_addr->s_addr <= right_in_addr->s_addr )
+ {
+ result = RESULT_SUCCESS;
}
else
{
- ptr[index] = range_str[pos];
- index++;
+ if( verbose )
+ {
+ fprintf(stderr, "Malformed IPv4 range %s: its first address is greater than the last\n", range_str);
+ }
+ result = RESULT_FAILURE;
}
- pos++;
+ cidr_free(left_addr);
+ cidr_free(right_addr);
}
- ptr[index] = '\0';
+ }
- if( !is_ipv4_single(left) )
+ return(result);
+}
+
+/* Is it a valid IPv6 address range? */
+int is_ipv6_range(char* range_str, int verbose)
+{
+ int result = RESULT_SUCCESS;
+
+ int regex_check_res = regex_matches("^([0-9a-fA-F:]+\\-[0-9a-fA-F:]+)$", range_str);
+
+ if( !regex_check_res )
+ {
+ if( verbose )
+ {
+ fprintf(stderr, "Malformed range %s: must be a pair of hyphen-separated IPv6 addresses\n", range_str);
+ }
+ result = RESULT_FAILURE;
+ }
+ else
+ {
+ /* Extract sub-components from the range string. */
+
+ /* Allocate memory for the components of the range.
+ We need at most 39 characters for an IPv6 address, plus space for the terminating null byte. */
+ char left[40];
+ char right[40];
+
+ /* Split the string at the hyphen.
+ If the regex check succeeded, we know the hyphen is there. */
+ split_range(range_str, left, right);
+
+ if( !is_ipv6_single(left) )
{
if( verbose )
{
- fprintf(stderr, "Malformed range %s: %s is not a valid IPv4 address\n", range_str, left);
+ fprintf(stderr, "Malformed range %s: %s is not a valid IPv6 address\n", range_str, left);
}
result = RESULT_FAILURE;
}
- else if( !is_ipv4_single(right) )
+ else if( !is_ipv6_single(right) )
{
if( verbose )
{
- fprintf(stderr, "Malformed range %s: %s is not a valid IPv4 address\n", range_str, right);
+ fprintf(stderr, "Malformed range %s: %s is not a valid IPv6 address\n", range_str, right);
}
result = RESULT_FAILURE;
}
@@ -524,10 +620,10 @@ int is_ipv4_range(char* range_str, int verbose)
{
CIDR* left_addr = cidr_from_str(left);
CIDR* right_addr = cidr_from_str(right);
- struct in_addr* left_in_addr = cidr_to_inaddr(left_addr, NULL);
- struct in_addr* right_in_addr = cidr_to_inaddr(right_addr, NULL);
+ struct in6_addr* left_in6_addr = cidr_to_in6addr(left_addr, NULL);
+ struct in6_addr* right_in6_addr = cidr_to_in6addr(right_addr, NULL);
- if( left_in_addr->s_addr < right_in_addr->s_addr )
+ if( compare_ipv6(left_in6_addr, right_in6_addr) <= 0 )
{
result = RESULT_SUCCESS;
}
@@ -535,7 +631,7 @@ int is_ipv4_range(char* range_str, int verbose)
{
if( verbose )
{
- fprintf(stderr, "Malformed IPv4 range %s: its first address is greater than the last\n", range_str);
+ fprintf(stderr, "Malformed IPv6 range %s: its first address is greater than the last\n", range_str);
}
result = RESULT_FAILURE;
}
@@ -547,3 +643,4 @@ int is_ipv4_range(char* range_str, int verbose)
return(result);
}
+
diff --git a/src/ipaddrcheck_functions.h b/src/ipaddrcheck_functions.h
index 05a19c3..c0d8603 100644
--- a/src/ipaddrcheck_functions.h
+++ b/src/ipaddrcheck_functions.h
@@ -2,7 +2,7 @@
* ipaddrcheck_functions.h: macros and prototypes for ipaddrcheck
*
* Copyright (C) 2013 Daniil Baturin
- * Copyright (C) 2018 VyOS maintainers and contributors
+ * Copyright (C) 2018-2024 VyOS maintainers and contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -77,5 +77,6 @@ int is_valid_intf_address(CIDR *address, char* address_str, int allow_loopback);
int is_any_host(CIDR *address);
int is_any_net(CIDR *address);
int is_ipv4_range(char* range_str, int verbose);
+int is_ipv6_range(char* range_str, int verbose);
#endif /* IPADDRCHECK_FUNCTIONS_H */
diff --git a/tests/check_ipaddrcheck.c b/tests/check_ipaddrcheck.c
index feb5a41..3b260c2 100644
--- a/tests/check_ipaddrcheck.c
+++ b/tests/check_ipaddrcheck.c
@@ -2,7 +2,7 @@
* check_ipaddrcheck.c: ipaddrcheck unit tests
*
* Copyright (C) 2013 Daniil Baturin
- * Copyright (C) 2018 VyOS maintainers and contributors
+ * Copyright (C) 2018-2024 VyOS maintainers and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 or later as
@@ -411,6 +411,14 @@ START_TEST (test_is_ipv4_range)
}
END_TEST
+START_TEST (test_is_ipv6_range)
+{
+ ck_assert_int_eq(is_ipv6_range("2001:db8::1-2001:db8::20", 0), RESULT_SUCCESS);
+ ck_assert_int_eq(is_ipv6_range("2001:-", 0), RESULT_FAILURE);
+ ck_assert_int_eq(is_ipv6_range("2001:db8::99-2001:db8:1", 0), RESULT_FAILURE);
+}
+END_TEST
+
Suite *ipaddrcheck_suite(void)
{
diff --git a/tests/integration_tests.sh b/tests/integration_tests.sh
index a679ea0..8264960 100755
--- a/tests/integration_tests.sh
+++ b/tests/integration_tests.sh
@@ -3,7 +3,7 @@
# integration_tests.sh: ipaddrcheck integration tests
#
# Copyright (C) 2013 Daniil Baturin
-# Copyright (C) 2018 VyOS maintainers and contributors
+# Copyright (C) 2018-2024 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -58,6 +58,16 @@ ipv4_range_negative=(
192.0.2.200-192.0.2.100
)
+ipv6_range_positive=(
+ 2001:db8::1-2001:db8::99
+)
+
+ipv6_range_negative=(
+ 2001:db8:xx-2001:db8::99
+ 2001:db:-
+ 2001:db8::99-2001:db8::1
+)
+
ipv6_single_positive=(
2001:0db8:0000:0000:0000:ff00:0042:8329
2001:db8:0:0:0:ff00:42:8329
@@ -274,4 +284,17 @@ do
assert_raises "$IPADDRCHECK --is-ipv4-range $range" 1
done
+# --is-ipv6-range
+for range in \
+ ${ipv6_range_positive[*]}
+do
+ assert_raises "$IPADDRCHECK --is-ipv6-range $range" 0
+done
+
+for range in \
+ ${ipv6_range_negative[*]}
+do
+ assert_raises "$IPADDRCHECK --is-ipv6-range $range" 1
+done
+
assert_end ipaddrcheck_integration