| /* |
| This file is part of libmicrohttpd |
| Copyright (C) 2007-2018 Daniel Pittman and Christian Grothoff |
| |
| This library is free software; you can redistribute it and/or |
| modify it under the terms of the GNU Lesser General Public |
| License as published by the Free Software Foundation; either |
| version 2.1 of the License, or (at your option) any later version. |
| |
| This library is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| Lesser General Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public |
| License along with this library; if not, write to the Free Software |
| Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| /** |
| * @file lib/daemon_ip_limit.c |
| * @brief counting of connections per IP |
| * @author Christian Grothoff |
| */ |
| #include "internal.h" |
| #include "daemon_ip_limit.h" |
| #ifdef HAVE_SEARCH_H |
| #include <search.h> |
| #else |
| #include "tsearch.h" |
| #endif |
| |
| |
| /** |
| * Maintain connection count for single address. |
| */ |
| struct MHD_IPCount |
| { |
| /** |
| * Address family. AF_INET or AF_INET6 for now. |
| */ |
| int family; |
| |
| /** |
| * Actual address. |
| */ |
| union |
| { |
| /** |
| * IPv4 address. |
| */ |
| struct in_addr ipv4; |
| #ifdef HAVE_INET6 |
| /** |
| * IPv6 address. |
| */ |
| struct in6_addr ipv6; |
| #endif |
| } addr; |
| |
| /** |
| * Counter. |
| */ |
| unsigned int count; |
| }; |
| |
| |
| /** |
| * Trace up to and return master daemon. If the supplied daemon |
| * is a master, then return the daemon itself. |
| * |
| * @param daemon handle to a daemon |
| * @return master daemon handle |
| */ |
| static struct MHD_Daemon * |
| get_master (struct MHD_Daemon *daemon) |
| { |
| while (NULL != daemon->master) |
| daemon = daemon->master; |
| return daemon; |
| } |
| |
| |
| /** |
| * Lock shared structure for IP connection counts and connection DLLs. |
| * |
| * @param daemon handle to daemon where lock is |
| */ |
| static void |
| MHD_ip_count_lock (struct MHD_Daemon *daemon) |
| { |
| MHD_mutex_lock_chk_ (&daemon->per_ip_connection_mutex); |
| } |
| |
| |
| /** |
| * Unlock shared structure for IP connection counts and connection DLLs. |
| * |
| * @param daemon handle to daemon where lock is |
| */ |
| static void |
| MHD_ip_count_unlock (struct MHD_Daemon *daemon) |
| { |
| MHD_mutex_unlock_chk_ (&daemon->per_ip_connection_mutex); |
| } |
| |
| |
| /** |
| * Tree comparison function for IP addresses (supplied to tsearch() family). |
| * We compare everything in the struct up through the beginning of the |
| * 'count' field. |
| * |
| * @param a1 first address to compare |
| * @param a2 second address to compare |
| * @return -1, 0 or 1 depending on result of compare |
| */ |
| static int |
| MHD_ip_addr_compare (const void *a1, |
| const void *a2) |
| { |
| return memcmp (a1, |
| a2, |
| offsetof (struct MHD_IPCount, |
| count)); |
| } |
| |
| |
| /** |
| * Parse address and initialize @a key using the address. |
| * |
| * @param addr address to parse |
| * @param addrlen number of bytes in @a addr |
| * @param key where to store the parsed address |
| * @return #MHD_YES on success and #MHD_NO otherwise (e.g., invalid address type) |
| */ |
| static int |
| MHD_ip_addr_to_key (const struct sockaddr *addr, |
| socklen_t addrlen, |
| struct MHD_IPCount *key) |
| { |
| memset (key, |
| 0, |
| sizeof(*key)); |
| |
| /* IPv4 addresses */ |
| if (sizeof (struct sockaddr_in) == addrlen) |
| { |
| const struct sockaddr_in *addr4 = (const struct sockaddr_in *) addr; |
| |
| key->family = AF_INET; |
| memcpy (&key->addr.ipv4, |
| &addr4->sin_addr, |
| sizeof(addr4->sin_addr)); |
| return MHD_YES; |
| } |
| |
| #ifdef HAVE_INET6 |
| /* IPv6 addresses */ |
| if (sizeof (struct sockaddr_in6) == addrlen) |
| { |
| const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *) addr; |
| |
| key->family = AF_INET6; |
| memcpy (&key->addr.ipv6, |
| &addr6->sin6_addr, |
| sizeof(addr6->sin6_addr)); |
| return MHD_YES; |
| } |
| #endif |
| |
| /* Some other address */ |
| return MHD_NO; |
| } |
| |
| |
| /** |
| * Check if IP address is over its limit in terms of the number |
| * of allowed concurrent connections. If the IP is still allowed, |
| * increments the connection counter. |
| * |
| * @param daemon handle to daemon where connection counts are tracked |
| * @param addr address to add (or increment counter) |
| * @param addrlen number of bytes in @a addr |
| * @return Return #MHD_YES if IP below limit, #MHD_NO if IP has surpassed limit. |
| * Also returns #MHD_NO if fails to allocate memory. |
| */ |
| int |
| MHD_ip_limit_add (struct MHD_Daemon *daemon, |
| const struct sockaddr *addr, |
| socklen_t addrlen) |
| { |
| struct MHD_IPCount *key; |
| void **nodep; |
| void *node; |
| int result; |
| |
| daemon = get_master (daemon); |
| /* Ignore if no connection limit assigned */ |
| if (0 == daemon->ip_connection_limit) |
| return MHD_YES; |
| |
| if (NULL == (key = malloc (sizeof(*key)))) |
| return MHD_NO; |
| |
| /* Initialize key */ |
| if (MHD_NO == MHD_ip_addr_to_key (addr, |
| addrlen, |
| key)) |
| { |
| /* Allow unhandled address types through */ |
| free (key); |
| return MHD_YES; |
| } |
| MHD_ip_count_lock (daemon); |
| |
| /* Search for the IP address */ |
| if (NULL == (nodep = tsearch (key, |
| &daemon->per_ip_connection_count, |
| &MHD_ip_addr_compare))) |
| { |
| #ifdef HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| MHD_SC_IP_COUNTER_FAILURE, |
| _ ("Failed to add IP connection count node.\n")); |
| #endif |
| MHD_ip_count_unlock (daemon); |
| free (key); |
| return MHD_NO; |
| } |
| node = *nodep; |
| /* If we got an existing node back, free the one we created */ |
| if (node != key) |
| free (key); |
| key = (struct MHD_IPCount *) node; |
| /* Test if there is room for another connection; if so, |
| * increment count */ |
| result = (key->count < daemon->ip_connection_limit) ? MHD_YES : MHD_NO; |
| if (MHD_YES == result) |
| ++key->count; |
| |
| MHD_ip_count_unlock (daemon); |
| return result; |
| } |
| |
| |
| /** |
| * Decrement connection count for IP address, removing from table |
| * count reaches 0. |
| * |
| * @param daemon handle to daemon where connection counts are tracked |
| * @param addr address to remove (or decrement counter) |
| * @param addrlen number of bytes in @a addr |
| */ |
| void |
| MHD_ip_limit_del (struct MHD_Daemon *daemon, |
| const struct sockaddr *addr, |
| socklen_t addrlen) |
| { |
| struct MHD_IPCount search_key; |
| struct MHD_IPCount *found_key; |
| void **nodep; |
| |
| daemon = get_master (daemon); |
| /* Ignore if no connection limit assigned */ |
| if (0 == daemon->ip_connection_limit) |
| return; |
| /* Initialize search key */ |
| if (MHD_NO == MHD_ip_addr_to_key (addr, |
| addrlen, |
| &search_key)) |
| return; |
| |
| MHD_ip_count_lock (daemon); |
| |
| /* Search for the IP address */ |
| if (NULL == (nodep = tfind (&search_key, |
| &daemon->per_ip_connection_count, |
| &MHD_ip_addr_compare))) |
| { |
| /* Something's wrong if we couldn't find an IP address |
| * that was previously added */ |
| MHD_PANIC (_ ("Failed to find previously-added IP address.\n")); |
| } |
| found_key = (struct MHD_IPCount *) *nodep; |
| /* Validate existing count for IP address */ |
| if (0 == found_key->count) |
| { |
| MHD_PANIC (_ ("Previously-added IP address had counter of zero.\n")); |
| } |
| /* Remove the node entirely if count reduces to 0 */ |
| if (0 == --found_key->count) |
| { |
| tdelete (found_key, |
| &daemon->per_ip_connection_count, |
| &MHD_ip_addr_compare); |
| free (found_key); |
| } |
| |
| MHD_ip_count_unlock (daemon); |
| } |
| |
| |
| /* end of daemon_ip_limit.c */ |