diff options
-rw-r--r-- | src/ipaddrcheck.c | 55 | ||||
-rw-r--r-- | src/ipaddrcheck_functions.c | 141 | ||||
-rw-r--r-- | src/ipaddrcheck_functions.h | 3 | ||||
-rw-r--r-- | tests/check_ipaddrcheck.c | 10 | ||||
-rwxr-xr-x | tests/integration_tests.sh | 25 |
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 |