diff options
author | Daniil Baturin <daniil@vyos.io> | 2024-01-03 15:12:13 -0500 |
---|---|---|
committer | Daniil Baturin <daniil@vyos.io> | 2024-01-03 15:12:13 -0500 |
commit | 346aedbcd2257512208195bb165cc857e2907922 (patch) | |
tree | 104b13bc55d97974d353fecd324084c23dd6d66e /src | |
parent | 1a92fc130e7915dd711d63ff448dae490b586ef4 (diff) | |
download | ipaddrcheck-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
Diffstat (limited to 'src')
-rw-r--r-- | src/ipaddrcheck.c | 55 | ||||
-rw-r--r-- | src/ipaddrcheck_functions.c | 141 | ||||
-rw-r--r-- | src/ipaddrcheck_functions.h | 3 |
3 files changed, 165 insertions, 34 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 */ |