]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
examples/block-ip: show how to block IP addresses
authorJay Satiro <raysatiro@yahoo.com>
Sun, 15 Dec 2024 08:43:08 +0000 (03:43 -0500)
committerJay Satiro <raysatiro@yahoo.com>
Sun, 22 Dec 2024 08:11:19 +0000 (03:11 -0500)
- 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

docs/examples/.gitignore
docs/examples/Makefile.inc
docs/examples/block_ip.c [new file with mode: 0644]
docs/libcurl/opts/CURLOPT_OPENSOCKETFUNCTION.md

index 68f96939aa993aff51fd8088490d36a56abd7751..370170743e585d5807103b923912e7b26a6a7afe 100644 (file)
@@ -6,6 +6,7 @@
 address-scope
 altsvc
 anyauthput
+block_ip
 certinfo
 chkspeed
 connect-to
index 71dac0bf43656f9fba383febf6ecba1c3e2d7f74..5c03ab865b98b9c7f263f7ce9252208f53d99443 100644 (file)
@@ -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 (file)
index 0000000..9e83f55
--- /dev/null
@@ -0,0 +1,351 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, 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
+ *
+ ***************************************************************************/
+/* <DESC>
+ * Show how CURLOPT_OPENSOCKETFUNCTION can be used to block IP addresses.
+ * </DESC>
+ */
+/* This is an advanced example that defines a whitelist or a blacklist to
+ * filter IP addresses.
+ */
+
+#ifdef __AMIGA__
+#include <stdio.h>
+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 <winsock2.h>
+#include <ws2tcpip.h>
+#include <windows.h>
+#else
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <memory.h>
+
+#include <curl/curl.h>
+
+#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
index 47bb7e132e483f01dd2ee3f568c19cb2f22b44ba..d3c569e717c70cb9d62f8ed22871658b8d5b13de 100644 (file)
@@ -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