From: Jay Satiro Date: Sun, 15 Dec 2024 08:43:08 +0000 (-0500) Subject: examples/block-ip: show how to block IP addresses X-Git-Tag: curl-8_12_0~292 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=a9d881c725e3f8ba926f6da0f05d5e14b9c671af;p=thirdparty%2Fcurl.git examples/block-ip: show how to block IP addresses - Show how CURLOPT_OPENSOCKETFUNCTION can be used to block IP addresses. This is a new example that demonstrates IP blocking. Ref: https://github.com/curl/curl/discussions/15710#discussioncomment-11534877 Closes https://github.com/curl/curl/pull/15748 --- diff --git a/docs/examples/.gitignore b/docs/examples/.gitignore index 68f96939aa..370170743e 100644 --- a/docs/examples/.gitignore +++ b/docs/examples/.gitignore @@ -6,6 +6,7 @@ address-scope altsvc anyauthput +block_ip certinfo chkspeed connect-to diff --git a/docs/examples/Makefile.inc b/docs/examples/Makefile.inc index 71dac0bf43..5c03ab865b 100644 --- a/docs/examples/Makefile.inc +++ b/docs/examples/Makefile.inc @@ -28,6 +28,7 @@ check_PROGRAMS = \ address-scope \ altsvc \ anyauthput \ + block_ip \ certinfo \ chkspeed \ connect-to \ diff --git a/docs/examples/block_ip.c b/docs/examples/block_ip.c new file mode 100644 index 0000000000..9e83f55053 --- /dev/null +++ b/docs/examples/block_ip.c @@ -0,0 +1,351 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* + * Show how CURLOPT_OPENSOCKETFUNCTION can be used to block IP addresses. + * + */ +/* This is an advanced example that defines a whitelist or a blacklist to + * filter IP addresses. + */ + +#ifdef __AMIGA__ +#include +int main(void) { printf("AmigaOS is not supported.\n"); return 1; } +#else + +#ifdef _WIN32 +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif +#ifndef _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_NONSTDC_NO_DEPRECATE +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 +#endif +#include +#include +#include +#else +#include +#include +#include +#include +#endif + +#include +#include +#include +#include + +#include + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +struct ip { + /* The user-provided IP address or network (use CIDR) to filter */ + char *str; + /* IP address family AF_INET (IPv4) or AF_INET6 (IPv6) */ + int family; + /* IP in network byte format */ + union netaddr { + struct in_addr ipv4; +#ifdef AF_INET6 + struct in6_addr ipv6; +#endif + } netaddr; + /* IP bits to match against. + * This is equal to the CIDR notation or max bits if no CIDR. + * For example if ip->str is 127.0.0.0/8 then ip->maskbits is 8. + */ + int maskbits; + struct ip *next; +}; + +enum connection_filter_t { + CONNECTION_FILTER_BLACKLIST, + CONNECTION_FILTER_WHITELIST +}; + +struct connection_filter { + struct ip *list; + enum connection_filter_t type; + int verbose; +#ifdef AF_INET6 + /* If the address being filtered is an IPv4-mapped IPv6 address then it is + * checked against IPv4 list entries as well, unless ipv6_v6only is set TRUE. + */ + int ipv6_v6only; +#endif +}; + +static struct ip *ip_list_append(struct ip *list, const char *data) +{ + struct ip *ip, *last; + char *cidr; + + ip = (struct ip *)calloc(1, sizeof(*ip)); + if(!ip) + return NULL; + + if(strchr(data, ':')) { +#ifdef AF_INET6 + ip->family = AF_INET6; +#else + free(ip); + return NULL; +#endif + } + else + ip->family = AF_INET; + + ip->str = strdup(data); + if(!ip->str) { + free(ip); + return NULL; + } + + /* determine the number of bits that this IP will match against */ + cidr = strchr(ip->str, '/'); + if(cidr) { + ip->maskbits = atoi(cidr + 1); + if(ip->maskbits <= 0 || +#ifdef AF_INET6 + (ip->family == AF_INET6 && ip->maskbits > 128) || +#endif + (ip->family == AF_INET && ip->maskbits > 32)) { + free(ip->str); + free(ip); + return NULL; + } + /* ignore the CIDR notation when converting ip->str to ip->netaddr */ + *cidr = '\0'; + } + else if(ip->family == AF_INET) + ip->maskbits = 32; +#ifdef AF_INET6 + else if(ip->family == AF_INET6) + ip->maskbits = 128; +#endif + + if(1 != inet_pton(ip->family, ip->str, &ip->netaddr)) { + free(ip->str); + free(ip); + return NULL; + } + + if(cidr) + *cidr = '/'; + + if(!list) + return ip; + for(last = list; last->next; last = last->next) + ; + last->next = ip; + return list; +} + +static void ip_list_free_all(struct ip *list) +{ + struct ip *next; + while(list) { + next = list->next; + free(list->str); + free(list); + list = next; + } +} + +static void free_connection_filter(struct connection_filter *filter) +{ + if(filter) { + ip_list_free_all(filter->list); + free(filter); + } +} + +static int ip_match(struct ip *ip, void *netaddr) +{ + int bytes, tailbits; + const unsigned char *x, *y; + + x = (unsigned char *)&ip->netaddr; + y = (unsigned char *)netaddr; + + for(bytes = ip->maskbits / 8; bytes; --bytes) { + if(*x++ != *y++) + return FALSE; + } + + tailbits = ip->maskbits % 8; + if(tailbits) { + unsigned char tailmask = (unsigned char)((0xFF << (8 - tailbits)) & 0xFF); + if((*x & tailmask) != (*y & tailmask)) + return FALSE; + } + + return TRUE; +} + +#ifdef AF_INET6 +static int is_ipv4_mapped_ipv6_address(int family, void *netaddr) +{ + if(family == AF_INET6) { + int i; + unsigned char *x = (unsigned char *)netaddr; + for(i = 0; i < 12; ++i) { + if(x[i]) + break; + } + /* support formats ::x.x.x.x (deprecated) and ::ffff:x.x.x.x */ + if((i == 12 && (x[i] || x[i + 1] || x[i + 2] || x[i + 3])) || + (i == 10 && (x[i] == 0xFF && x[i + 1] == 0xFF))) + return TRUE; + } + + return FALSE; +} +#endif /* AF_INET6 */ + +static curl_socket_t opensocket(void *clientp, + curlsocktype purpose, + struct curl_sockaddr *address) +{ + /* filter the address */ + if(purpose == CURLSOCKTYPE_IPCXN) { + void *cinaddr = NULL; + + if(address->family == AF_INET) + cinaddr = &((struct sockaddr_in *)(void *)&address->addr)->sin_addr; +#ifdef AF_INET6 + else if(address->family == AF_INET6) + cinaddr = &((struct sockaddr_in6 *)(void *)&address->addr)->sin6_addr; +#endif + + if(cinaddr) { + struct ip *ip; + struct connection_filter *filter = (struct connection_filter *)clientp; +#ifdef AF_INET6 + int mapped = !filter->ipv6_v6only && + is_ipv4_mapped_ipv6_address(address->family, cinaddr); +#endif + + for(ip = filter->list; ip; ip = ip->next) { + if(ip->family == address->family && ip_match(ip, cinaddr)) + break; +#ifdef AF_INET6 + if(mapped && ip->family == AF_INET && address->family == AF_INET6 && + ip_match(ip, (unsigned char *)cinaddr + 12)) + break; +#endif + } + + if(ip && filter->type == CONNECTION_FILTER_BLACKLIST) { + if(filter->verbose) { + char buf[128] = {0}; + inet_ntop(address->family, cinaddr, buf, sizeof(buf)); + fprintf(stderr, "* Rejecting IP %s due to blacklist entry %s.\n", + buf, ip->str); + } + return CURL_SOCKET_BAD; + } + else if(!ip && filter->type == CONNECTION_FILTER_WHITELIST) { + if(filter->verbose) { + char buf[128] = {0}; + inet_ntop(address->family, cinaddr, buf, sizeof(buf)); + fprintf(stderr, + "* Rejecting IP %s due to missing whitelist entry.\n", buf); + } + return CURL_SOCKET_BAD; + } + } + } + + return socket(address->family, address->socktype, address->protocol); +} + +int main(void) +{ + CURL *curl; + CURLcode res; + struct connection_filter *filter; + + filter = (struct connection_filter *)calloc(1, sizeof(*filter)); + if(!filter) + exit(1); + + if(curl_global_init(CURL_GLOBAL_DEFAULT)) + exit(1); + + curl = curl_easy_init(); + if(!curl) + exit(1); + + /* Set the target URL */ + curl_easy_setopt(curl, CURLOPT_URL, "http://localhost"); + + /* Define an IP connection filter. + * If an address has CIDR notation then it matches the network. + * For example 74.6.143.25/24 matches 74.6.143.0 - 74.6.143.255. + */ + filter->type = CONNECTION_FILTER_BLACKLIST; + filter->list = ip_list_append(filter->list, "98.137.11.164"); + filter->list = ip_list_append(filter->list, "127.0.0.0/8"); +#ifdef AF_INET6 + filter->list = ip_list_append(filter->list, "::1"); +#endif + + /* Set the socket function which does the filtering */ + curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, opensocket); + curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, filter); + + /* Verbose mode */ + filter->verbose = TRUE; + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + + /* Perform the request */ + res = curl_easy_perform(curl); + + /* Check for errors */ + if(res != CURLE_OK) { + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(res)); + } + + /* Clean up */ + curl_easy_cleanup(curl); + free_connection_filter(filter); + + /* Clean up libcurl */ + curl_global_cleanup(); + + return 0; +} +#endif diff --git a/docs/libcurl/opts/CURLOPT_OPENSOCKETFUNCTION.md b/docs/libcurl/opts/CURLOPT_OPENSOCKETFUNCTION.md index 47bb7e132e..d3c569e717 100644 --- a/docs/libcurl/opts/CURLOPT_OPENSOCKETFUNCTION.md +++ b/docs/libcurl/opts/CURLOPT_OPENSOCKETFUNCTION.md @@ -60,14 +60,21 @@ is allowed to modify the address or refuse to connect completely. The callback function should return the newly created socket or *CURL_SOCKET_BAD* in case no connection could be established or another error was detected. Any additional *setsockopt(2)* calls can of course be done on the socket at -the user's discretion. A *CURL_SOCKET_BAD* return value from the callback -function signals an unrecoverable error to libcurl and it returns -*CURLE_COULDNT_CONNECT* from the function that triggered this callback. -This return code can be used for IP address block listing. +the user's discretion. + +If *CURL_SOCKET_BAD* is returned by the callback then libcurl treats it as a +failed connection and tries to open a socket to connect to a different IP +address associated with the transfer. If there are no more addresses to try +then libcurl fails the transfer with error code *CURLE_COULDNT_CONNECT*. + +You can get the IP address that curl is opening the socket for by casting +*address-\>addr* to `sockaddr_in` if *address-\>family* is `AF_INET`, or to +`sockaddr_in6` if *address-\>family* is `AF_INET6`. For an example of how that +data can be compared against refer to *docs/examples/block_ip.c*. If you want to pass in a socket with an already established connection, pass -the socket back with this callback and then use -CURLOPT_SOCKOPTFUNCTION(3) to signal that it already is connected. +the socket back with this callback and then use CURLOPT_SOCKOPTFUNCTION(3) to +signal that it already is connected. # DEFAULT