blob: 91fc06ea5422ed55fdcfdff15029c2b35c3b42e8 [file] [log] [blame]
/*
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 */