From 3a7aa1583488d11b264014de5edfe097209de59e Mon Sep 17 00:00:00 2001 From: rofl0r Date: Sat, 21 Dec 2019 00:38:56 +0000 Subject: [PATCH 1/8] start work on 1.11.x --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 81c871d..1cac385 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.10.0 +1.11.0 From b935dc85c3fca51de8e131d6aa2047f8a0404f0c Mon Sep 17 00:00:00 2001 From: rofl0r Date: Mon, 17 Dec 2018 00:23:09 +0000 Subject: [PATCH 2/8] simplify codebase by using one thread/conn, instead of preforked procs the existing codebase used an elaborate and complex approach for its parallelism: 5 different config file options, namely - MaxClients - MinSpareServers - MaxSpareServers - StartServers - MaxRequestsPerChild were used to steer how (and how many) parallel processes tinyproxy would spin up at start, how many processes at each point needed to be idle, etc. it seems all preforked processes would listen on the server port and compete with each other about who would get assigned the new incoming connections. since some data needs to be shared across those processes, a half- baked "shared memory" implementation was provided for this purpose. that implementation used to use files in the filesystem, and since it had a big FIXME comment, the author was well aware of how hackish that approach was. this entire complexity is now removed. the main thread enters a loop which polls on the listening fds, then spins up a new thread per connection, until the maximum number of connections (MaxClients) is hit. this is the only of the 5 config options left after this cleanup. since threads share the same address space, the code necessary for shared memory access has been removed. this means that the other 4 mentioned config option will now produce a parse error, when encountered. currently each thread uses a hardcoded default of 256KB per thread for the thread stack size, which is quite lavish and should be sufficient for even the worst C libraries, but people may want to tweak this value to the bare minimum, thus we may provide a new config option for this purpose in the future. i suspect that on heavily optimized C libraries such a musl, a stack size of 8-16 KB per thread could be sufficient. since the existing list implementation in vector.c did not provide a way to remove a single item from an existing list, i added my own list implementation from my libulz library which offers this functionality, rather than trying to add an ad-hoc, and perhaps buggy implementation to the vector_t list code. the sblist code is contained in an 80 line C file and as simple as it can get, while offering good performance and is proven bugfree due to years of use in other projects. --- docs/man5/tinyproxy.conf.txt.in | 28 +- etc/tinyproxy.conf.in | 24 -- src/Makefile.am | 3 +- src/child.c | 503 ++++++++------------------------ src/conf.c | 38 +-- src/conf.h | 1 + src/heap.c | 58 ---- src/heap.h | 6 - src/main.c | 11 +- src/sblist.c | 80 +++++ src/sblist.h | 92 ++++++ src/stats.c | 27 +- tests/scripts/run_tests.sh | 4 - 13 files changed, 316 insertions(+), 559 deletions(-) create mode 100644 src/sblist.c create mode 100644 src/sblist.h diff --git a/docs/man5/tinyproxy.conf.txt.in b/docs/man5/tinyproxy.conf.txt.in index b3b94ec..afd3b6b 100644 --- a/docs/man5/tinyproxy.conf.txt.in +++ b/docs/man5/tinyproxy.conf.txt.in @@ -176,37 +176,11 @@ The possible keywords and their descriptions are as follows: *MaxClients*:: - Tinyproxy creates one child process for each connected client. + Tinyproxy creates one thread for each connected client. This options specifies the absolute highest number processes that will be created. With other words, only MaxClients clients can be connected to Tinyproxy simultaneously. -*MinSpareServers*:: -*MaxSpareServers*:: - - Tinyproxy always keeps a certain number of idle child processes - so that it can handle new incoming client requests quickly. - `MinSpareServer` and `MaxSpareServers` control the lower and upper - limits for the number of spare processes. I.e. when the number of - spare servers drops below `MinSpareServers` then Tinyproxy will - start forking new spare processes in the background and when the - number of spare processes exceeds `MaxSpareServers` then Tinyproxy - will kill off extra processes. - -*StartServers*:: - - The number of servers to start initially. This should usually be - set to a value between MinSpareServers and MaxSpareServers. - -*MaxRequestsPerChild*:: - - This limits the number of connections that a child process - will handle before it is killed. The default value is `0` - which disables this feature. This option is meant as an - emergency measure in the case of problems with memory leakage. - In that case, setting `MaxRequestsPerChild` to a value of e.g. - 1000, or 10000 can be useful. - *Allow*:: *Deny*:: diff --git a/etc/tinyproxy.conf.in b/etc/tinyproxy.conf.in index f1b8817..06f5480 100644 --- a/etc/tinyproxy.conf.in +++ b/etc/tinyproxy.conf.in @@ -189,30 +189,6 @@ LogLevel Info # MaxClients 100 -# -# MinSpareServers/MaxSpareServers: These settings set the upper and -# lower limit for the number of spare servers which should be available. -# -# If the number of spare servers falls below MinSpareServers then new -# server processes will be spawned. If the number of servers exceeds -# MaxSpareServers then the extras will be killed off. -# -MinSpareServers 5 -MaxSpareServers 20 - -# -# StartServers: The number of servers to start initially. -# -StartServers 10 - -# -# MaxRequestsPerChild: The number of connections a thread will handle -# before it is killed. In practise this should be set to 0, which -# disables thread reaping. If you do notice problems with memory -# leakage, then set this to something like 10000. -# -MaxRequestsPerChild 0 - # # Allow: Customization of authorization controls. If there are any # access control keywords then the default action is to DENY. Otherwise, diff --git a/src/Makefile.am b/src/Makefile.am index af2f621..3924909 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -48,10 +48,11 @@ tinyproxy_SOURCES = \ upstream.c upstream.h \ basicauth.c basicauth.h \ base64.c base64.h \ + sblist.c sblist.h \ connect-ports.c connect-ports.h EXTRA_tinyproxy_SOURCES = filter.c filter.h \ reverse-proxy.c reverse-proxy.h \ transparent-proxy.c transparent-proxy.h tinyproxy_DEPENDENCIES = @ADDITIONAL_OBJECTS@ -tinyproxy_LDADD = @ADDITIONAL_OBJECTS@ +tinyproxy_LDADD = @ADDITIONAL_OBJECTS@ -lpthread diff --git a/src/child.c b/src/child.c index 60f8ead..06a977e 100644 --- a/src/child.c +++ b/src/child.c @@ -31,176 +31,66 @@ #include "sock.h" #include "utils.h" #include "conf.h" +#include "sblist.h" +#include static vector_t listen_fds; -/* - * Stores the internal data needed for each child (connection) - */ -enum child_status_t { T_EMPTY, T_WAITING, T_CONNECTED }; -struct child_s { - pid_t tid; - unsigned int connects; - enum child_status_t status; +union sockaddr_union { + struct sockaddr_in v4; + struct sockaddr_in6 v6; }; -/* - * A pointer to an array of children. A certain number of children are - * created when the program is started. - */ -static struct child_s *child_ptr; +struct client { + union sockaddr_union addr; + int fd; +}; -static struct child_config_s { - unsigned int maxclients, maxrequestsperchild; - unsigned int maxspareservers, minspareservers, startservers; -} child_config; +struct child { + pthread_t thread; + struct client client; + volatile int done; +}; -static unsigned int *servers_waiting; /* servers waiting for a connection */ - -/* - * Lock/Unlock the "servers_waiting" variable so that two children cannot - * modify it at the same time. - */ -#define SERVER_COUNT_LOCK() _child_lock_wait() -#define SERVER_COUNT_UNLOCK() _child_lock_release() - -/* START OF LOCKING SECTION */ - -/* - * These variables are required for the locking mechanism. Also included - * are the "private" functions for locking/unlocking. - */ -static struct flock lock_it, unlock_it; -static int lock_fd = -1; - -static void _child_lock_init (void) +static void* child_thread(void* data) { - char lock_file[] = "/tmp/tinyproxy.servers.lock.XXXXXX"; - - /* Only allow u+rw bits. This may be required for some versions - * of glibc so that mkstemp() doesn't make us vulnerable. - */ - umask (0177); - - lock_fd = mkstemp (lock_file); - unlink (lock_file); - - lock_it.l_type = F_WRLCK; - lock_it.l_whence = SEEK_SET; - lock_it.l_start = 0; - lock_it.l_len = 0; - - unlock_it.l_type = F_UNLCK; - unlock_it.l_whence = SEEK_SET; - unlock_it.l_start = 0; - unlock_it.l_len = 0; + struct child *c = data; + handle_connection (c->client.fd); + c->done = 1; + return NULL; } -static void _child_lock_wait (void) +static sblist *childs; + +static void collect_threads(void) { - int rc; - - while ((rc = fcntl (lock_fd, F_SETLKW, &lock_it)) < 0) { - if (errno == EINTR) - continue; - else - return; - } -} - -static void _child_lock_release (void) -{ - if (fcntl (lock_fd, F_SETLKW, &unlock_it) < 0) - return; -} - -/* END OF LOCKING SECTION */ - -#define SERVER_INC() do { \ - SERVER_COUNT_LOCK(); \ - ++(*servers_waiting); \ - DEBUG2("INC: servers_waiting: %d", *servers_waiting); \ - SERVER_COUNT_UNLOCK(); \ -} while (0) - -#define SERVER_DEC() do { \ - SERVER_COUNT_LOCK(); \ - assert(*servers_waiting > 0); \ - --(*servers_waiting); \ - DEBUG2("DEC: servers_waiting: %d", *servers_waiting); \ - SERVER_COUNT_UNLOCK(); \ -} while (0) - -/* - * Set the configuration values for the various child related settings. - */ -short int child_configure (child_config_t type, unsigned int val) -{ - switch (type) { - case CHILD_MAXCLIENTS: - child_config.maxclients = val; - break; - case CHILD_MAXSPARESERVERS: - child_config.maxspareservers = val; - break; - case CHILD_MINSPARESERVERS: - child_config.minspareservers = val; - break; - case CHILD_STARTSERVERS: - child_config.startservers = val; - break; - case CHILD_MAXREQUESTSPERCHILD: - child_config.maxrequestsperchild = val; - break; - default: - DEBUG2 ("Invalid type (%d)", type); - return -1; - } - - return 0; -} - -/** - * child signal handler for sighup - */ -static void child_sighup_handler (int sig) -{ - if (sig == SIGHUP) { - /* - * Ignore the return value of reload_config for now. - * This should actually be handled somehow... - */ - reload_config (); - -#ifdef FILTER_ENABLE - filter_reload (); -#endif /* FILTER_ENABLE */ - } + size_t i; + for (i = 0; i < sblist_getsize(childs); ) { + struct child *c = *((struct child**)sblist_get(childs, i)); + if (c->done) { + pthread_join(c->thread, 0); + sblist_delete(childs, i); + safefree(c); + } else i++; + } } /* - * This is the main (per child) loop. + * This is the main loop accepting new connections. */ -static void child_main (struct child_s *ptr) +void child_main_loop (void) { int connfd; - struct sockaddr *cliaddr; - socklen_t clilen; + union sockaddr_union cliaddr_storage; + struct sockaddr *cliaddr = (void*) &cliaddr_storage; + socklen_t clilen = sizeof(cliaddr_storage); fd_set rfds; - int maxfd = 0; ssize_t i; - int ret; + int ret, listenfd, maxfd, was_full = 0; + pthread_attr_t *attrp, attr; + struct child *child; - cliaddr = (struct sockaddr *) - safemalloc (sizeof(struct sockaddr_storage)); - if (!cliaddr) { - log_message (LOG_CRIT, - "Could not allocate memory for child address."); - exit (0); - } - - ptr->connects = 0; - srand(time(NULL)); + childs = sblist_new(sizeof (struct child*), config.maxclients); /* * We have to wait for connections on multiple fds, @@ -208,7 +98,37 @@ static void child_main (struct child_s *ptr) */ while (!config.quit) { - int listenfd = -1; + collect_threads(); + + if (sblist_getsize(childs) >= config.maxclients) { + if (!was_full) + log_message (LOG_NOTICE, + "Maximum number of connections reached. " + "Refusing new connections."); + was_full = 1; + usleep(16); + continue; + } + + was_full = 0; + listenfd = -1; + maxfd = 0; + + /* Handle log rotation if it was requested */ + if (received_sighup) { + /* + * Ignore the return value of reload_config for now. + * This should actually be handled somehow... + */ + reload_config (); + +#ifdef FILTER_ENABLE + filter_reload (); +#endif /* FILTER_ENABLE */ + + received_sighup = FALSE; + } + FD_ZERO(&rfds); @@ -220,17 +140,13 @@ static void child_main (struct child_s *ptr) log_message(LOG_ERR, "Failed to set the listening " "socket %d to non-blocking: %s", fd, strerror(errno)); - exit(1); + continue; } FD_SET(*fd, &rfds); maxfd = max(maxfd, *fd); } - ptr->status = T_WAITING; - - clilen = sizeof(struct sockaddr_storage); - ret = select(maxfd + 1, &rfds, NULL, NULL, NULL); if (ret == -1) { if (errno == EINTR) { @@ -238,7 +154,7 @@ static void child_main (struct child_s *ptr) } log_message (LOG_ERR, "error calling select: %s", strerror(errno)); - exit(1); + continue; } else if (ret == 0) { log_message (LOG_WARNING, "Strange: select returned 0 " "but we did not specify a timeout..."); @@ -269,7 +185,7 @@ static void child_main (struct child_s *ptr) log_message(LOG_ERR, "Failed to set listening " "socket %d to blocking for accept: %s", listenfd, strerror(errno)); - exit(1); + continue; } /* @@ -279,21 +195,6 @@ static void child_main (struct child_s *ptr) connfd = accept (listenfd, cliaddr, &clilen); -#ifndef NDEBUG - /* - * Enable the TINYPROXY_DEBUG environment variable if you - * want to use the GDB debugger. - */ - if (getenv ("TINYPROXY_DEBUG")) { - /* Pause for 10 seconds to allow us to connect debugger */ - fprintf (stderr, - "Process has accepted connection: %ld\n", - (long int) ptr->tid); - sleep (10); - fprintf (stderr, "Continuing process: %ld\n", - (long int) ptr->tid); - } -#endif /* * Make sure no error occurred... @@ -305,224 +206,37 @@ static void child_main (struct child_s *ptr) continue; } - ptr->status = T_CONNECTED; - - SERVER_DEC (); - - handle_connection (connfd); - ptr->connects++; - - if (child_config.maxrequestsperchild != 0) { - DEBUG2 ("%u connections so far...", ptr->connects); - - if (ptr->connects == child_config.maxrequestsperchild) { - log_message (LOG_NOTICE, - "Child has reached MaxRequestsPerChild (%u). " - "Killing child.", ptr->connects); - break; - } + child = safemalloc(sizeof(struct child)); + if (!child) { +oom: + close(connfd); + log_message (LOG_CRIT, + "Could not allocate memory for child."); + usleep(16); /* prevent 100% CPU usage in OOM situation */ + continue; } - SERVER_COUNT_LOCK (); - if (*servers_waiting > child_config.maxspareservers) { - /* - * There are too many spare children, kill ourself - * off. - */ - log_message (LOG_NOTICE, - "Waiting servers (%d) exceeds MaxSpareServers (%d). " - "Killing child.", - *servers_waiting, - child_config.maxspareservers); - SERVER_COUNT_UNLOCK (); + child->done = 0; - break; - } else { - SERVER_COUNT_UNLOCK (); + if (!sblist_add(childs, &child)) { + free(child); + goto oom; } - SERVER_INC (); - } + child->client.fd = connfd; + memcpy(&child->client.addr, &cliaddr_storage, sizeof(cliaddr_storage)); - ptr->status = T_EMPTY; - - safefree (cliaddr); - exit (0); -} - -/* - * Fork a child "child" (or in our case a process) and then start up the - * child_main() function. - */ -static pid_t child_make (struct child_s *ptr) -{ - pid_t pid; - - if ((pid = fork ()) > 0) - return pid; /* parent */ - - /* - * Reset the SIGNALS so that the child can be reaped. - */ - set_signal_handler (SIGCHLD, SIG_DFL); - set_signal_handler (SIGTERM, SIG_DFL); - set_signal_handler (SIGHUP, child_sighup_handler); - - child_main (ptr); /* never returns */ - return -1; -} - -/* - * Create a pool of children to handle incoming connections - */ -short int child_pool_create (void) -{ - unsigned int i; - - /* - * Make sure the number of MaxClients is not zero, since this - * variable determines the size of the array created for children - * later on. - */ - if (child_config.maxclients == 0) { - log_message (LOG_ERR, - "child_pool_create: \"MaxClients\" must be " - "greater than zero."); - return -1; - } - if (child_config.startservers == 0) { - log_message (LOG_ERR, - "child_pool_create: \"StartServers\" must be " - "greater than zero."); - return -1; - } - - child_ptr = - (struct child_s *) calloc_shared_memory (child_config.maxclients, - sizeof (struct child_s)); - if (!child_ptr) { - log_message (LOG_ERR, - "Could not allocate memory for children."); - return -1; - } - - servers_waiting = - (unsigned int *) malloc_shared_memory (sizeof (unsigned int)); - if (servers_waiting == MAP_FAILED) { - log_message (LOG_ERR, - "Could not allocate memory for child counting."); - return -1; - } - *servers_waiting = 0; - - /* - * Create a "locking" file for use around the servers_waiting - * variable. - */ - _child_lock_init (); - - if (child_config.startservers > child_config.maxclients) { - log_message (LOG_WARNING, - "Can not start more than \"MaxClients\" servers. " - "Starting %u servers instead.", - child_config.maxclients); - child_config.startservers = child_config.maxclients; - } - - for (i = 0; i != child_config.maxclients; i++) { - child_ptr[i].status = T_EMPTY; - child_ptr[i].connects = 0; - } - - for (i = 0; i != child_config.startservers; i++) { - DEBUG2 ("Trying to create child %d of %d", i + 1, - child_config.startservers); - child_ptr[i].status = T_WAITING; - child_ptr[i].tid = child_make (&child_ptr[i]); - - if (child_ptr[i].tid < 0) { - log_message (LOG_WARNING, - "Could not create child number %d of %d", - i, child_config.startservers); - return -1; - } else { - log_message (LOG_INFO, - "Creating child number %d of %d ...", - i + 1, child_config.startservers); - - SERVER_INC (); - } - } - - log_message (LOG_INFO, "Finished creating all children."); - - return 0; -} - -/* - * Keep the proper number of servers running. This is the birth of the - * servers. It monitors this at least once a second. - */ -void child_main_loop (void) -{ - unsigned int i; - - while (1) { - if (config.quit) - return; - - /* If there are not enough spare servers, create more */ - SERVER_COUNT_LOCK (); - if (*servers_waiting < child_config.minspareservers) { - log_message (LOG_NOTICE, - "Waiting servers (%d) is less than MinSpareServers (%d). " - "Creating new child.", - *servers_waiting, - child_config.minspareservers); - - SERVER_COUNT_UNLOCK (); - - for (i = 0; i != child_config.maxclients; i++) { - if (child_ptr[i].status == T_EMPTY) { - child_ptr[i].status = T_WAITING; - child_ptr[i].tid = - child_make (&child_ptr[i]); - if (child_ptr[i].tid < 0) { - log_message (LOG_NOTICE, - "Could not create child"); - - child_ptr[i].status = T_EMPTY; - break; - } - - SERVER_INC (); - - break; - } - } - } else { - SERVER_COUNT_UNLOCK (); + attrp = 0; + if (pthread_attr_init(&attr) == 0) { + attrp = &attr; + pthread_attr_setstacksize(attrp, 256*1024); } - sleep (5); - - /* Handle log rotation if it was requested */ - if (received_sighup) { - /* - * Ignore the return value of reload_config for now. - * This should actually be handled somehow... - */ - reload_config (); - -#ifdef FILTER_ENABLE - filter_reload (); -#endif /* FILTER_ENABLE */ - - /* propagate filter reload to all children */ - child_kill_children (SIGHUP); - - received_sighup = FALSE; - } + if (pthread_create(&child->thread, attrp, child_thread, child) != 0) { + sblist_delete(childs, sblist_getsize(childs) -1); + free(child); + goto oom; + } } } @@ -531,12 +245,25 @@ void child_main_loop (void) */ void child_kill_children (int sig) { - unsigned int i; + size_t i; - for (i = 0; i != child_config.maxclients; i++) { - if (child_ptr[i].status != T_EMPTY) - kill (child_ptr[i].tid, sig); - } + if (sig != SIGTERM) return; + + for (i = 0; i < sblist_getsize(childs); i++) { + struct child *c = *((struct child**)sblist_get(childs, i)); + if (!c->done) { + /* interrupt blocking operations. + this should cause the threads to shutdown orderly. */ + close(c->client.fd); + } + } + usleep(16); + collect_threads(); + if (sblist_getsize(childs) != 0) + log_message (LOG_CRIT, + "child_kill_children: %zu threads still alive!", + sblist_getsize(childs) + ); } diff --git a/src/conf.c b/src/conf.c index 5ebf179..d7cf5b6 100644 --- a/src/conf.c +++ b/src/conf.c @@ -27,7 +27,6 @@ #include "acl.h" #include "anonymous.h" -#include "child.h" #include "filter.h" #include "heap.h" #include "html-error.h" @@ -140,9 +139,6 @@ static HANDLE_FUNC (handle_listen); static HANDLE_FUNC (handle_logfile); static HANDLE_FUNC (handle_loglevel); static HANDLE_FUNC (handle_maxclients); -static HANDLE_FUNC (handle_maxrequestsperchild); -static HANDLE_FUNC (handle_maxspareservers); -static HANDLE_FUNC (handle_minspareservers); static HANDLE_FUNC (handle_pidfile); static HANDLE_FUNC (handle_port); #ifdef REVERSE_SUPPORT @@ -151,7 +147,6 @@ static HANDLE_FUNC (handle_reversemagic); static HANDLE_FUNC (handle_reverseonly); static HANDLE_FUNC (handle_reversepath); #endif -static HANDLE_FUNC (handle_startservers); static HANDLE_FUNC (handle_statfile); static HANDLE_FUNC (handle_stathost); static HANDLE_FUNC (handle_syslog); @@ -217,10 +212,6 @@ struct { /* integer arguments */ STDCONF ("port", INT, handle_port), STDCONF ("maxclients", INT, handle_maxclients), - STDCONF ("maxspareservers", INT, handle_maxspareservers), - STDCONF ("minspareservers", INT, handle_minspareservers), - STDCONF ("startservers", INT, handle_startservers), - STDCONF ("maxrequestsperchild", INT, handle_maxrequestsperchild), STDCONF ("timeout", INT, handle_timeout), STDCONF ("connectport", INT, handle_connectport), /* alphanumeric arguments */ @@ -805,34 +796,7 @@ static HANDLE_FUNC (handle_port) static HANDLE_FUNC (handle_maxclients) { - child_configure (CHILD_MAXCLIENTS, get_long_arg (line, &match[2])); - return 0; -} - -static HANDLE_FUNC (handle_maxspareservers) -{ - child_configure (CHILD_MAXSPARESERVERS, - get_long_arg (line, &match[2])); - return 0; -} - -static HANDLE_FUNC (handle_minspareservers) -{ - child_configure (CHILD_MINSPARESERVERS, - get_long_arg (line, &match[2])); - return 0; -} - -static HANDLE_FUNC (handle_startservers) -{ - child_configure (CHILD_STARTSERVERS, get_long_arg (line, &match[2])); - return 0; -} - -static HANDLE_FUNC (handle_maxrequestsperchild) -{ - child_configure (CHILD_MAXREQUESTSPERCHILD, - get_long_arg (line, &match[2])); + set_int_arg (&conf->maxclients, line, &match[2]); return 0; } diff --git a/src/conf.h b/src/conf.h index beb2b01..44f12d7 100644 --- a/src/conf.h +++ b/src/conf.h @@ -45,6 +45,7 @@ struct config_s { char *stathost; unsigned int godaemon; /* boolean */ unsigned int quit; /* boolean */ + unsigned int maxclients; char *user; char *group; vector_t listen_addrs; diff --git a/src/heap.c b/src/heap.c index c7d8560..0611c39 100644 --- a/src/heap.c +++ b/src/heap.c @@ -97,61 +97,3 @@ char *debugging_strdup (const char *s, const char *file, unsigned long line) #endif /* !NDEBUG */ -/* - * Allocate a block of memory in the "shared" memory region. - * - * FIXME: This uses the most basic (and slowest) means of creating a - * shared memory location. It requires the use of a temporary file. We might - * want to look into something like MM (Shared Memory Library) for a better - * solution. - */ -void *malloc_shared_memory (size_t size) -{ - int fd; - void *ptr; - char buffer[32]; - - static const char *shared_file = "/tmp/tinyproxy.shared.XXXXXX"; - - assert (size > 0); - - strlcpy (buffer, shared_file, sizeof (buffer)); - - /* Only allow u+rw bits. This may be required for some versions - * of glibc so that mkstemp() doesn't make us vulnerable. - */ - umask (0177); - - if ((fd = mkstemp (buffer)) == -1) - return MAP_FAILED; - unlink (buffer); - - if (ftruncate (fd, size) == -1) - return MAP_FAILED; - ptr = mmap (NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - - return ptr; -} - -/* - * Allocate a block of memory from the "shared" region an initialize it to - * zero. - */ -void *calloc_shared_memory (size_t nmemb, size_t size) -{ - void *ptr; - long length; - - assert (nmemb > 0); - assert (size > 0); - - length = nmemb * size; - - ptr = malloc_shared_memory (length); - if (ptr == MAP_FAILED) - return ptr; - - memset (ptr, 0, length); - - return ptr; -} diff --git a/src/heap.h b/src/heap.h index f3cf671..da64461 100644 --- a/src/heap.h +++ b/src/heap.h @@ -52,10 +52,4 @@ extern char *debugging_strdup (const char *s, const char *file, #endif -/* - * Allocate memory from the "shared" region of memory. - */ -extern void *malloc_shared_memory (size_t size); -extern void *calloc_shared_memory (size_t nmemb, size_t size); - #endif diff --git a/src/main.c b/src/main.c index 43170c5..06465b1 100644 --- a/src/main.c +++ b/src/main.c @@ -65,6 +65,7 @@ takesig (int sig) received_sighup = TRUE; break; + case SIGINT: case SIGTERM: config.quit = TRUE; break; @@ -302,6 +303,7 @@ static void initialize_config_defaults (struct config_s *conf) conf->idletimeout = MAX_IDLE_TIME; conf->logf_name = NULL; conf->pidpath = NULL; + conf->maxclients = 100; } /** @@ -329,6 +331,8 @@ done: int main (int argc, char **argv) { + srand(time(NULL)); /* for hashmap seeds */ + /* Only allow u+rw bits. This may be required for some versions * of glibc so that mkstemp() doesn't make us vulnerable. */ @@ -407,13 +411,6 @@ main (int argc, char **argv) exit (EX_SOFTWARE); } - if (child_pool_create () < 0) { - fprintf (stderr, - "%s: Could not create the pool of children.\n", - argv[0]); - exit (EX_SOFTWARE); - } - /* These signals are only for the parent process. */ log_message (LOG_INFO, "Setting the various signals."); diff --git a/src/sblist.c b/src/sblist.c new file mode 100644 index 0000000..4ddc4aa --- /dev/null +++ b/src/sblist.c @@ -0,0 +1,80 @@ +#undef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#include "sblist.h" +#include +#include +#include +#define MY_PAGE_SIZE 4096 + +sblist* sblist_new(size_t itemsize, size_t blockitems) { + sblist* ret = (sblist*) malloc(sizeof(sblist)); + sblist_init(ret, itemsize, blockitems); + return ret; +} + +static void sblist_clear(sblist* l) { + l->items = NULL; + l->capa = 0; + l->count = 0; +} + +void sblist_init(sblist* l, size_t itemsize, size_t blockitems) { + if(l) { + l->blockitems = blockitems ? blockitems : MY_PAGE_SIZE / itemsize; + l->itemsize = itemsize; + sblist_clear(l); + } +} + +void sblist_free_items(sblist* l) { + if(l) { + if(l->items) free(l->items); + sblist_clear(l); + } +} + +void sblist_free(sblist* l) { + if(l) { + sblist_free_items(l); + free(l); + } +} + +char* sblist_item_from_index(sblist* l, size_t idx) { + return l->items + (idx * l->itemsize); +} + +void* sblist_get(sblist* l, size_t item) { + if(item < l->count) return (void*) sblist_item_from_index(l, item); + return NULL; +} + +int sblist_set(sblist* l, void* item, size_t pos) { + if(pos >= l->count) return 0; + memcpy(sblist_item_from_index(l, pos), item, l->itemsize); + return 1; +} + +int sblist_grow_if_needed(sblist* l) { + char* temp; + if(l->count == l->capa) { + temp = realloc(l->items, (l->capa + l->blockitems) * l->itemsize); + if(!temp) return 0; + l->capa += l->blockitems; + l->items = temp; + } + return 1; +} + +int sblist_add(sblist* l, void* item) { + if(!sblist_grow_if_needed(l)) return 0; + l->count++; + return sblist_set(l, item, l->count - 1); +} + +void sblist_delete(sblist* l, size_t item) { + if (l->count && item < l->count) { + memmove(sblist_item_from_index(l, item), sblist_item_from_index(l, item + 1), (sblist_getsize(l) - (item + 1)) * l->itemsize); + l->count--; + } +} diff --git a/src/sblist.h b/src/sblist.h new file mode 100644 index 0000000..02c33d7 --- /dev/null +++ b/src/sblist.h @@ -0,0 +1,92 @@ +#ifndef SBLIST_H +#define SBLIST_H + +/* this file is part of libulz, as of commit 8ab361a27743aaf025323ee43b8b8876dc054fdd + modified for direct inclusion in tinyproxy, and for this purpose released under + the license of tinyproxy. */ + + +#ifdef __cplusplus +extern "C" { +#endif + +#include +/* + * simple buffer list. + * + * this thing here is basically a generic dynamic array + * will realloc after every blockitems inserts + * can store items of any size. + * + * so think of it as a by-value list, as opposed to a typical by-ref list. + * you typically use it by having some struct on the stack, and pass a pointer + * to sblist_add, which will copy the contents into its internal memory. + * + */ + +typedef struct { + size_t itemsize; + size_t blockitems; + size_t count; + size_t capa; + char* items; +} sblist; + +#define sblist_getsize(X) ((X)->count) +#define sblist_get_count(X) ((X)->count) +#define sblist_empty(X) ((X)->count == 0) + +/* for dynamic style */ +sblist* sblist_new(size_t itemsize, size_t blockitems); +void sblist_free(sblist* l); + +/*for static style*/ +void sblist_init(sblist* l, size_t itemsize, size_t blockitems); +void sblist_free_items(sblist* l); + +/* accessors */ +void* sblist_get(sblist* l, size_t item); +/* returns 1 on success, 0 on OOM */ +int sblist_add(sblist* l, void* item); +int sblist_set(sblist* l, void* item, size_t pos); +void sblist_delete(sblist* l, size_t item); +char* sblist_item_from_index(sblist* l, size_t idx); +int sblist_grow_if_needed(sblist* l); +int sblist_insert(sblist* l, void* item, size_t pos); +/* same as sblist_add, but returns list index of new item, or -1 */ +size_t sblist_addi(sblist* l, void* item); +void sblist_sort(sblist *l, int (*compar)(const void *, const void *)); +/* insert element into presorted list, returns listindex of new entry or -1*/ +size_t sblist_insert_sorted(sblist* l, void* o, int (*compar)(const void *, const void *)); + +#ifndef __COUNTER__ +#define __COUNTER__ __LINE__ +#endif + +#define __sblist_concat_impl( x, y ) x##y +#define __sblist_macro_concat( x, y ) __sblist_concat_impl( x, y ) +#define __sblist_iterator_name __sblist_macro_concat(sblist_iterator, __COUNTER__) + +/* use with custom iterator variable */ +#define sblist_iter_counter(LIST, ITER, PTR) \ + for(size_t ITER = 0; (PTR = sblist_get(LIST, ITER)), ITER < sblist_getsize(LIST); ITER++) + +/* use with custom iterator variable, which is predeclared */ +#define sblist_iter_counter2(LIST, ITER, PTR) \ + for(ITER = 0; (PTR = sblist_get(LIST, ITER)), ITER < sblist_getsize(LIST); ITER++) + +/* use with custom iterator variable, which is predeclared and signed */ +/* useful for a loop which can delete items from the list, and then decrease the iterator var. */ +#define sblist_iter_counter2s(LIST, ITER, PTR) \ + for(ITER = 0; (PTR = sblist_get(LIST, ITER)), ITER < (ssize_t) sblist_getsize(LIST); ITER++) + + +/* uses "magic" iterator variable */ +#define sblist_iter(LIST, PTR) sblist_iter_counter(LIST, __sblist_iterator_name, PTR) + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/stats.c b/src/stats.c index c7b4423..93a30cf 100644 --- a/src/stats.c +++ b/src/stats.c @@ -33,6 +33,7 @@ #include "stats.h" #include "utils.h" #include "conf.h" +#include struct stat_s { unsigned long int num_reqs; @@ -43,14 +44,16 @@ struct stat_s { }; static struct stat_s *stats; +static pthread_mutex_t stats_update_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t stats_file_lock = PTHREAD_MUTEX_INITIALIZER; /* * Initialize the statistics information to zero. */ void init_stats (void) { - stats = (struct stat_s *) malloc_shared_memory (sizeof (struct stat_s)); - if (stats == MAP_FAILED) + stats = (struct stat_s *) safemalloc (sizeof (struct stat_s)); + if (!stats) return; memset (stats, 0, sizeof (struct stat_s)); @@ -72,10 +75,15 @@ showstats (struct conn_s *connptr) snprintf (denied, sizeof (denied), "%lu", stats->num_denied); snprintf (refused, sizeof (refused), "%lu", stats->num_refused); + pthread_mutex_lock(&stats_file_lock); + if (!config.statpage || (!(statfile = fopen (config.statpage, "r")))) { message_buffer = (char *) safemalloc (MAXBUFFSIZE); - if (!message_buffer) + if (!message_buffer) { +err_minus_one: + pthread_mutex_unlock(&stats_file_lock); return -1; + } snprintf (message_buffer, MAXBUFFSIZE, @@ -105,13 +113,13 @@ showstats (struct conn_s *connptr) if (send_http_message (connptr, 200, "OK", message_buffer) < 0) { safefree (message_buffer); - return -1; + goto err_minus_one; } safefree (message_buffer); + pthread_mutex_unlock(&stats_file_lock); return 0; } - add_error_variable (connptr, "opens", opens); add_error_variable (connptr, "reqs", reqs); add_error_variable (connptr, "badconns", badconns); @@ -121,6 +129,7 @@ showstats (struct conn_s *connptr) send_http_headers (connptr, 200, "Statistic requested"); send_html_file (statfile, connptr); fclose (statfile); + pthread_mutex_unlock(&stats_file_lock); return 0; } @@ -131,6 +140,9 @@ showstats (struct conn_s *connptr) */ int update_stats (status_t update_level) { + int ret = 0; + + pthread_mutex_lock(&stats_update_lock); switch (update_level) { case STAT_BADCONN: ++stats->num_badcons; @@ -149,8 +161,9 @@ int update_stats (status_t update_level) ++stats->num_denied; break; default: - return -1; + ret = -1; } + pthread_mutex_unlock(&stats_update_lock); - return 0; + return ret; } diff --git a/tests/scripts/run_tests.sh b/tests/scripts/run_tests.sh index dd95400..6f797ef 100755 --- a/tests/scripts/run_tests.sh +++ b/tests/scripts/run_tests.sh @@ -84,10 +84,6 @@ Logfile "$TINYPROXY_LOG_DIR/tinyproxy.log" PidFile "$TINYPROXY_PID_FILE" LogLevel Info MaxClients 100 -MinSpareServers 5 -MaxSpareServers 20 -StartServers 10 -MaxRequestsPerChild 0 Allow 127.0.0.0/8 ViaProxyName "tinyproxy" #DisableViaHeader Yes From 1186c297b4651d5c84ac7a387b07cbc12ec0c69a Mon Sep 17 00:00:00 2001 From: rofl0r Date: Tue, 18 Dec 2018 23:36:04 +0000 Subject: [PATCH 3/8] conf.c: pass lineno to handler funcs --- src/conf.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/conf.c b/src/conf.c index d7cf5b6..575fc6c 100644 --- a/src/conf.c +++ b/src/conf.c @@ -91,7 +91,8 @@ * All configuration handling functions are REQUIRED to be defined * with the same function template as below. */ -typedef int (*CONFFILE_HANDLER) (struct config_s *, const char *, regmatch_t[]); +typedef int (*CONFFILE_HANDLER) (struct config_s *, const char *, + unsigned long, regmatch_t[]); /* * Define the pattern used by any directive handling function. The @@ -106,7 +107,7 @@ typedef int (*CONFFILE_HANDLER) (struct config_s *, const char *, regmatch_t[]); */ #define HANDLE_FUNC(func) \ int func(struct config_s* conf, const char* line, \ - regmatch_t match[]) + unsigned long lineno, regmatch_t match[]) /* * List all the handling functions. These are defined later, but they need @@ -369,7 +370,8 @@ config_free_regex (void) * Returns 0 if a match was found and successfully processed; otherwise, * a negative number is returned. */ -static int check_match (struct config_s *conf, const char *line) +static int check_match (struct config_s *conf, const char *line, + unsigned long lineno) { regmatch_t match[RE_MAX_MATCHES]; unsigned int i; @@ -380,7 +382,7 @@ static int check_match (struct config_s *conf, const char *line) assert (directives[i].cre); if (!regexec (directives[i].cre, line, RE_MAX_MATCHES, match, 0)) - return (*directives[i].handler) (conf, line, match); + return (*directives[i].handler) (conf, line, lineno, match); } return -1; @@ -395,7 +397,7 @@ static int config_parse (struct config_s *conf, FILE * f) unsigned long lineno = 1; while (fgets (buffer, sizeof (buffer), f)) { - if (check_match (conf, buffer)) { + if (check_match (conf, buffer, lineno)) { printf ("Syntax error on line %ld\n", lineno); return 1; } From b09d8d927de61e5b4411f8e9f713bfcf10a04796 Mon Sep 17 00:00:00 2001 From: rofl0r Date: Tue, 18 Dec 2018 23:38:00 +0000 Subject: [PATCH 4/8] conf.c: merely warn on encountering recently obsoleted config items if we don't handle these gracefully, pretty much every existing config file will fail with an error, which is probably not very friendly. the obsoleted config items can be made hard errors after the next release. --- src/conf.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/conf.c b/src/conf.c index 575fc6c..eded11a 100644 --- a/src/conf.c +++ b/src/conf.c @@ -140,6 +140,7 @@ static HANDLE_FUNC (handle_listen); static HANDLE_FUNC (handle_logfile); static HANDLE_FUNC (handle_loglevel); static HANDLE_FUNC (handle_maxclients); +static HANDLE_FUNC (handle_obsolete); static HANDLE_FUNC (handle_pidfile); static HANDLE_FUNC (handle_port); #ifdef REVERSE_SUPPORT @@ -213,6 +214,10 @@ struct { /* integer arguments */ STDCONF ("port", INT, handle_port), STDCONF ("maxclients", INT, handle_maxclients), + STDCONF ("maxspareservers", INT, handle_obsolete), + STDCONF ("minspareservers", INT, handle_obsolete), + STDCONF ("startservers", INT, handle_obsolete), + STDCONF ("maxrequestsperchild", INT, handle_obsolete), STDCONF ("timeout", INT, handle_timeout), STDCONF ("connectport", INT, handle_connectport), /* alphanumeric arguments */ @@ -802,6 +807,13 @@ static HANDLE_FUNC (handle_maxclients) return 0; } +static HANDLE_FUNC (handle_obsolete) +{ + fprintf (stderr, "WARNING: obsolete config item on line %lu\n", + lineno); + return 0; +} + static HANDLE_FUNC (handle_timeout) { return set_int_arg (&conf->idletimeout, line, &match[2]); From fa2ad0cf9ac1bf24c7c4ddb188b5922132e38f73 Mon Sep 17 00:00:00 2001 From: rofl0r Date: Tue, 18 Dec 2018 23:48:57 +0000 Subject: [PATCH 5/8] log.c: protect logging facility with a mutex since the write syscall is used instead of stdio, accesses have been safe already, but it's better to use a mutex anyway to prevent out- of-order writes. --- src/log.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/log.c b/src/log.c index f85d29d..0ed686b 100644 --- a/src/log.c +++ b/src/log.c @@ -29,6 +29,7 @@ #include "utils.h" #include "vector.h" #include "conf.h" +#include static const char *syslog_level[] = { NULL, @@ -45,6 +46,8 @@ static const char *syslog_level[] = { #define TIME_LENGTH 16 #define STRING_LENGTH 800 +static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER; + /* * Global file descriptor for the log file */ @@ -165,12 +168,14 @@ void log_message (int level, const char *fmt, ...) goto out; if (config.syslog) { + pthread_mutex_lock(&log_mutex); #ifdef HAVE_VSYSLOG_H vsyslog (level, fmt, args); #else vsnprintf (str, STRING_LENGTH, fmt, args); syslog (level, "%s", str); #endif + pthread_mutex_unlock(&log_mutex); } else { char *p; @@ -196,7 +201,10 @@ void log_message (int level, const char *fmt, ...) assert (log_file_fd >= 0); + pthread_mutex_lock(&log_mutex); ret = write (log_file_fd, str, strlen (str)); + pthread_mutex_unlock(&log_mutex); + if (ret == -1) { config.syslog = TRUE; @@ -207,7 +215,10 @@ void log_message (int level, const char *fmt, ...) "Falling back to syslog logging"); } + pthread_mutex_lock(&log_mutex); fsync (log_file_fd); + pthread_mutex_unlock(&log_mutex); + } out: From 82e10935d2955923d419cb46ee97e0022a8dfdb0 Mon Sep 17 00:00:00 2001 From: rofl0r Date: Mon, 31 Dec 2018 14:24:17 +0000 Subject: [PATCH 6/8] move sockaddr_union to sock.h --- src/child.c | 5 ----- src/sock.h | 6 ++++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/child.c b/src/child.c index 06a977e..3ae3d82 100644 --- a/src/child.c +++ b/src/child.c @@ -36,11 +36,6 @@ static vector_t listen_fds; -union sockaddr_union { - struct sockaddr_in v4; - struct sockaddr_in6 v6; -}; - struct client { union sockaddr_union addr; int fd; diff --git a/src/sock.h b/src/sock.h index f1225ea..a516d4f 100644 --- a/src/sock.h +++ b/src/sock.h @@ -28,8 +28,14 @@ #define MAXLINE (1024 * 4) +#include "common.h" #include "vector.h" +union sockaddr_union { + struct sockaddr_in v4; + struct sockaddr_in6 v6; +}; + extern int opensock (const char *host, int port, const char *bind_to); extern int listen_sock (const char *addr, uint16_t port, vector_t listen_fds); From f6d4da5d81694721bf50b2275621e7ce84e6da30 Mon Sep 17 00:00:00 2001 From: rofl0r Date: Mon, 31 Dec 2018 15:47:40 +0000 Subject: [PATCH 7/8] do hostname resolution only when it is absolutely necessary for ACL check tinyproxy used to do a full hostname resolution whenever a new client connection happened, which could cause very long delays (as reported in #198). there's only a single place/scenario that actually requires a hostname, and that is when an Allow/Deny rule exists for a hostname or domain, rather than a raw IP address. since it is very likely this feature is not very widely used, it makes absolute sense to only do the costly resolution when it is unavoidable. --- docs/man5/tinyproxy.conf.txt.in | 3 +++ src/acl.c | 28 +++++++++++++++++++--------- src/acl.h | 3 ++- src/child.c | 2 +- src/conns.c | 4 ---- src/conns.h | 4 +--- src/html-error.c | 1 - src/reqs.c | 15 +++++++-------- src/reqs.h | 3 ++- src/sock.c | 26 ++++---------------------- src/sock.h | 2 +- 11 files changed, 40 insertions(+), 51 deletions(-) diff --git a/docs/man5/tinyproxy.conf.txt.in b/docs/man5/tinyproxy.conf.txt.in index afd3b6b..3e24852 100644 --- a/docs/man5/tinyproxy.conf.txt.in +++ b/docs/man5/tinyproxy.conf.txt.in @@ -196,6 +196,9 @@ The possible keywords and their descriptions are as follows: end of the client host name, i.e, this can be a full host name like `host.example.com` or a domain name like `.example.com` or even a top level domain name like `.com`. + Note that by adding a rule using a host or domain name, a costly name + lookup has to be done for every new connection, which could slow down + the service considerably. *AddHeader*:: diff --git a/src/acl.c b/src/acl.c index b7a334c..7918a07 100644 --- a/src/acl.c +++ b/src/acl.c @@ -221,8 +221,8 @@ insert_acl (char *location, acl_access_t access_type, vector_t *access_list) * -1 if no tests match, so skip */ static int -acl_string_processing (struct acl_s *acl, - const char *ip_address, const char *string_address) +acl_string_processing (struct acl_s *acl, const char *ip_address, + union sockaddr_union *addr, char *string_addr) { int match; struct addrinfo hints, *res, *ressave; @@ -231,7 +231,6 @@ acl_string_processing (struct acl_s *acl, assert (acl && acl->type == ACL_STRING); assert (ip_address && strlen (ip_address) > 0); - assert (string_address && strlen (string_address) > 0); /* * If the first character of the ACL string is a period, we need to @@ -267,7 +266,15 @@ acl_string_processing (struct acl_s *acl, } STRING_TEST: - test_length = strlen (string_address); + if(string_addr[0] == 0) { + /* only do costly hostname resolution when it is absolutely needed, + and only once */ + if(getnameinfo ((void *) addr, sizeof (*addr), + string_addr, HOSTNAME_LENGTH, NULL, 0, 0) != 0) + return -1; + } + + test_length = strlen (string_addr); match_length = strlen (acl->address.string); /* @@ -278,7 +285,7 @@ STRING_TEST: return -1; if (strcasecmp - (string_address + (test_length - match_length), + (string_addr + (test_length - match_length), acl->address.string) == 0) { if (acl->access == ACL_DENY) return 0; @@ -329,15 +336,18 @@ static int check_numeric_acl (const struct acl_s *acl, const char *ip) * 1 if allowed * 0 if denied */ -int check_acl (const char *ip, const char *host, vector_t access_list) +int check_acl (const char *ip, union sockaddr_union *addr, vector_t access_list) { struct acl_s *acl; int perm = 0; size_t i; + char string_addr[HOSTNAME_LENGTH]; assert (ip != NULL); assert (host != NULL); + string_addr[0] = 0; + /* * If there is no access list allow everything. */ @@ -348,7 +358,7 @@ int check_acl (const char *ip, const char *host, vector_t access_list) acl = (struct acl_s *) vector_getentry (access_list, i, NULL); switch (acl->type) { case ACL_STRING: - perm = acl_string_processing (acl, ip, host); + perm = acl_string_processing (acl, ip, addr, string_addr); break; case ACL_NUMERIC: @@ -371,8 +381,8 @@ int check_acl (const char *ip, const char *host, vector_t access_list) /* * Deny all connections by default. */ - log_message (LOG_NOTICE, "Unauthorized connection from \"%s\" [%s].", - host, ip); + log_message (LOG_NOTICE, "Unauthorized connection from \"%s\".", + ip); return 0; } diff --git a/src/acl.h b/src/acl.h index 2d11cef..ba0aebe 100644 --- a/src/acl.h +++ b/src/acl.h @@ -22,12 +22,13 @@ #define TINYPROXY_ACL_H #include "vector.h" +#include "sock.h" typedef enum { ACL_ALLOW, ACL_DENY } acl_access_t; extern int insert_acl (char *location, acl_access_t access_type, vector_t *access_list); -extern int check_acl (const char *ip_address, const char *string_address, +extern int check_acl (const char *ip_address, union sockaddr_union *addr, vector_t access_list); extern void flush_access_list (vector_t access_list); diff --git a/src/child.c b/src/child.c index 3ae3d82..8bd713d 100644 --- a/src/child.c +++ b/src/child.c @@ -50,7 +50,7 @@ struct child { static void* child_thread(void* data) { struct child *c = data; - handle_connection (c->client.fd); + handle_connection (c->client.fd, &c->client.addr); c->done = 1; return NULL; } diff --git a/src/conns.c b/src/conns.c index 94faeea..505b5c4 100644 --- a/src/conns.c +++ b/src/conns.c @@ -31,7 +31,6 @@ #include "stats.h" struct conn_s *initialize_conn (int client_fd, const char *ipaddr, - const char *string_addr, const char *sock_ipaddr) { struct conn_s *connptr; @@ -79,7 +78,6 @@ struct conn_s *initialize_conn (int client_fd, const char *ipaddr, connptr->server_ip_addr = (sock_ipaddr ? safestrdup (sock_ipaddr) : NULL); connptr->client_ip_addr = safestrdup (ipaddr); - connptr->client_string_addr = safestrdup (string_addr); connptr->upstream_proxy = NULL; @@ -134,8 +132,6 @@ void destroy_conn (struct conn_s *connptr) safefree (connptr->server_ip_addr); if (connptr->client_ip_addr) safefree (connptr->client_ip_addr); - if (connptr->client_string_addr) - safefree (connptr->client_string_addr); #ifdef REVERSE_SUPPORT if (connptr->reversepath) diff --git a/src/conns.h b/src/conns.h index b63d026..393e5d4 100644 --- a/src/conns.h +++ b/src/conns.h @@ -62,10 +62,9 @@ struct conn_s { char *server_ip_addr; /* - * Store the client's IP and hostname information + * Store the client's IP information */ char *client_ip_addr; - char *client_string_addr; /* * Store the incoming request's HTTP protocol. @@ -92,7 +91,6 @@ struct conn_s { * Functions for the creation and destruction of a connection structure. */ extern struct conn_s *initialize_conn (int client_fd, const char *ipaddr, - const char *string_addr, const char *sock_ipaddr); extern void destroy_conn (struct conn_s *connptr); diff --git a/src/html-error.c b/src/html-error.c index ee3c987..2b15c08 100644 --- a/src/html-error.c +++ b/src/html-error.c @@ -262,7 +262,6 @@ int add_standard_vars (struct conn_s *connptr) ADD_VAR_RET ("cause", connptr->error_string); ADD_VAR_RET ("request", connptr->request_line); ADD_VAR_RET ("clientip", connptr->client_ip_addr); - ADD_VAR_RET ("clienthost", connptr->client_string_addr); /* The following value parts are all non-NULL and will * trigger warnings in ADD_VAR_RET(), so we use diff --git a/src/reqs.c b/src/reqs.c index 8450cff..c576412 100644 --- a/src/reqs.c +++ b/src/reqs.c @@ -1533,7 +1533,7 @@ get_request_entity(struct conn_s *connptr) * tinyproxy code, which was confusing, redundant. Hail progress. * - rjkaes */ -void handle_connection (int fd) +void handle_connection (int fd, union sockaddr_union* addr) { ssize_t i; struct conn_s *connptr; @@ -1542,26 +1542,25 @@ void handle_connection (int fd) char sock_ipaddr[IP_LENGTH]; char peer_ipaddr[IP_LENGTH]; - char peer_string[HOSTNAME_LENGTH]; - getpeer_information (fd, peer_ipaddr, peer_string); + getpeer_information (addr, peer_ipaddr, sizeof(peer_ipaddr)); if (config.bindsame) getsock_ip (fd, sock_ipaddr); log_message (LOG_CONN, config.bindsame ? - "Connect (file descriptor %d): %s [%s] at [%s]" : - "Connect (file descriptor %d): %s [%s]", - fd, peer_string, peer_ipaddr, sock_ipaddr); + "Connect (file descriptor %d): %s at [%s]" : + "Connect (file descriptor %d): %s", + fd, peer_ipaddr, sock_ipaddr); - connptr = initialize_conn (fd, peer_ipaddr, peer_string, + connptr = initialize_conn (fd, peer_ipaddr, config.bindsame ? sock_ipaddr : NULL); if (!connptr) { close (fd); return; } - if (check_acl (peer_ipaddr, peer_string, config.access_list) <= 0) { + if (check_acl (peer_ipaddr, addr, config.access_list) <= 0) { update_stats (STAT_DENIED); indicate_http_error (connptr, 403, "Access denied", "detail", diff --git a/src/reqs.h b/src/reqs.h index 73dd030..c1c5100 100644 --- a/src/reqs.h +++ b/src/reqs.h @@ -23,6 +23,7 @@ #define _TINYPROXY_REQS_H_ #include "common.h" +#include "sock.h" /* * Port constants for HTTP (80) and SSL (443) @@ -43,6 +44,6 @@ struct request_s { char *path; }; -extern void handle_connection (int fd); +extern void handle_connection (int fd, union sockaddr_union* addr); #endif diff --git a/src/sock.c b/src/sock.c index 59c2fa8..f74a588 100644 --- a/src/sock.c +++ b/src/sock.c @@ -354,27 +354,9 @@ int getsock_ip (int fd, char *ipaddr) /* * Return the peer's socket information. */ -int getpeer_information (int fd, char *ipaddr, char *string_addr) +void getpeer_information (union sockaddr_union* addr, char *ipaddr, size_t ipaddr_len) { - struct sockaddr_storage sa; - socklen_t salen = sizeof sa; - - assert (fd >= 0); - assert (ipaddr != NULL); - assert (string_addr != NULL); - - /* Set the strings to default values */ - ipaddr[0] = '\0'; - strlcpy (string_addr, "[unknown]", HOSTNAME_LENGTH); - - /* Look up the IP address */ - if (getpeername (fd, (struct sockaddr *) &sa, &salen) != 0) - return -1; - - if (get_ip_string ((struct sockaddr *) &sa, ipaddr, IP_LENGTH) == NULL) - return -1; - - /* Get the full host name */ - return getnameinfo ((struct sockaddr *) &sa, salen, - string_addr, HOSTNAME_LENGTH, NULL, 0, 0); + int af = addr->v4.sin_family; + void *ipdata = af == AF_INET ? (void*)&addr->v4.sin_addr : (void*)&addr->v6.sin6_addr; + inet_ntop(af, ipdata, ipaddr, ipaddr_len); } diff --git a/src/sock.h b/src/sock.h index a516d4f..033e179 100644 --- a/src/sock.h +++ b/src/sock.h @@ -43,6 +43,6 @@ extern int socket_nonblocking (int sock); extern int socket_blocking (int sock); extern int getsock_ip (int fd, char *ipaddr); -extern int getpeer_information (int fd, char *ipaddr, char *string_addr); +extern void getpeer_information (union sockaddr_union *addr, char *ipaddr, size_t ipaddr_len); #endif From cd005a94cec38e73ca796f1d142c193f48aaa27f Mon Sep 17 00:00:00 2001 From: rofl0r Date: Mon, 31 Dec 2018 22:25:04 +0000 Subject: [PATCH 8/8] implement detection and denial of endless connection loops it is quite easy to bring down a proxy server by forcing it to make connections to one of its own ports, because this will result in an endless loop spawning more and more connections, until all available fds are exhausted. since there's a potentially infinite number of potential DNS/ip addresses resolving to the proxy, it is impossible to detect an endless loop by simply looking at the destination ip address and port. what *is* possible though is to record the ip/port tuples assigned to outgoing connections, and then compare them against new incoming connections. if they match, the sender was the proxy itself and therefore needs to reject that connection. fixes #199. --- src/Makefile.am | 1 + src/child.c | 3 ++ src/loop.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++++ src/loop.h | 11 +++++++ src/reqs.c | 15 ++++++++++ src/sock.c | 12 +++++++- 6 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 src/loop.c create mode 100644 src/loop.h diff --git a/src/Makefile.am b/src/Makefile.am index 3924909..50e645b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -49,6 +49,7 @@ tinyproxy_SOURCES = \ basicauth.c basicauth.h \ base64.c base64.h \ sblist.c sblist.h \ + loop.c loop.h \ connect-ports.c connect-ports.h EXTRA_tinyproxy_SOURCES = filter.c filter.h \ diff --git a/src/child.c b/src/child.c index 8bd713d..861606b 100644 --- a/src/child.c +++ b/src/child.c @@ -32,6 +32,7 @@ #include "utils.h" #include "conf.h" #include "sblist.h" +#include "loop.h" #include static vector_t listen_fds; @@ -87,6 +88,8 @@ void child_main_loop (void) childs = sblist_new(sizeof (struct child*), config.maxclients); + loop_records_init(); + /* * We have to wait for connections on multiple fds, * so use select. diff --git a/src/loop.c b/src/loop.c new file mode 100644 index 0000000..77b073d --- /dev/null +++ b/src/loop.c @@ -0,0 +1,76 @@ +#include +#include +#include "loop.h" +#include "conf.h" +#include "main.h" +#include "sblist.h" +#include "sock.h" + +struct loop_record { + union sockaddr_union addr; + time_t tstamp; +}; + +static sblist *loop_records; +static pthread_mutex_t loop_records_lock = PTHREAD_MUTEX_INITIALIZER; + +void loop_records_init(void) { + loop_records = sblist_new(sizeof (struct loop_record), 32); +} + +#if 0 +static void su_to_str(union sockaddr_union *addr, char *buf) { + int af = addr->v4.sin_family; + unsigned port = ntohs(af == AF_INET ? addr->v4.sin_port : addr->v6.sin6_port); + char portb[32]; + sprintf(portb, ":%u", port); + getpeer_information (addr, buf, 256); + strcat(buf, portb); +} +#endif + +void loop_records_add(union sockaddr_union *addr) { + time_t now =time(0); + struct loop_record rec; + pthread_mutex_lock(&loop_records_lock); + rec.tstamp = now; + rec.addr = *addr; + sblist_add(loop_records, &rec); + pthread_mutex_unlock(&loop_records_lock); +} + +#define TIMEOUT_SECS 15 + +int connection_loops (union sockaddr_union *addr) { + int ret = 0, af, our_af = addr->v4.sin_family; + void *ipdata, *our_ipdata = our_af == AF_INET ? (void*)&addr->v4.sin_addr.s_addr : (void*)&addr->v6.sin6_addr.s6_addr; + size_t i, cmp_len = our_af == AF_INET ? sizeof(addr->v4.sin_addr.s_addr) : sizeof(addr->v6.sin6_addr.s6_addr); + unsigned port, our_port = ntohs(our_af == AF_INET ? addr->v4.sin_port : addr->v6.sin6_port); + time_t now = time(0); + + pthread_mutex_lock(&loop_records_lock); + for (i = 0; i < sblist_getsize(loop_records); ) { + struct loop_record *rec = sblist_get(loop_records, i); + + if (rec->tstamp + TIMEOUT_SECS < now) { + sblist_delete(loop_records, i); + continue; + } + + if (!ret) { + af = rec->addr.v4.sin_family; + if (af != our_af) goto next; + port = ntohs(af == AF_INET ? rec->addr.v4.sin_port : rec->addr.v6.sin6_port); + if (port != our_port) goto next; + ipdata = af == AF_INET ? (void*)&rec->addr.v4.sin_addr.s_addr : (void*)&rec->addr.v6.sin6_addr.s6_addr; + if (!memcmp(ipdata, our_ipdata, cmp_len)) { + ret = 1; + } + } +next: + i++; + } + pthread_mutex_unlock(&loop_records_lock); + return ret; +} + diff --git a/src/loop.h b/src/loop.h new file mode 100644 index 0000000..19877d3 --- /dev/null +++ b/src/loop.h @@ -0,0 +1,11 @@ +#ifndef LOOP_H +#define LOOP_H + +#include "sock.h" + +void loop_records_init(void); +void loop_records_add(union sockaddr_union *addr); +int connection_loops (union sockaddr_union *addr); + +#endif + diff --git a/src/reqs.c b/src/reqs.c index c576412..3adc473 100644 --- a/src/reqs.c +++ b/src/reqs.c @@ -49,6 +49,7 @@ #include "connect-ports.h" #include "conf.h" #include "basicauth.h" +#include "loop.h" /* * Maximum length of a HTTP line @@ -1560,6 +1561,20 @@ void handle_connection (int fd, union sockaddr_union* addr) return; } + if (connection_loops (addr)) { + log_message (LOG_CONN, + "Prevented endless loop (file descriptor %d): %s", + fd, peer_ipaddr); + + indicate_http_error(connptr, 400, "Bad Request", + "detail", + "You tried to connect to the " + "machine the proxy is running on", + NULL); + goto fail; + } + + if (check_acl (peer_ipaddr, addr, config.access_list) <= 0) { update_stats (STAT_DENIED); indicate_http_error (connptr, 403, "Access denied", diff --git a/src/sock.c b/src/sock.c index f74a588..8513ba8 100644 --- a/src/sock.c +++ b/src/sock.c @@ -33,6 +33,7 @@ #include "sock.h" #include "text.h" #include "conf.h" +#include "loop.h" /* * Return a human readable error for getaddrinfo() and getnameinfo(). @@ -141,8 +142,17 @@ int opensock (const char *host, int port, const char *bind_to) } } - if (connect (sockfd, res->ai_addr, res->ai_addrlen) == 0) + if (connect (sockfd, res->ai_addr, res->ai_addrlen) == 0) { + union sockaddr_union *p = (void*) res->ai_addr, u; + int af = res->ai_addr->sa_family; + unsigned dport = ntohs(af == AF_INET ? p->v4.sin_port : p->v6.sin6_port); + socklen_t slen = sizeof u; + if (dport == config.port) { + getsockname(sockfd, (void*)&u, &slen); + loop_records_add(&u); + } break; /* success */ + } close (sockfd); } while ((res = res->ai_next) != NULL);