From: Fabian Keil Date: Sun, 17 Jul 2011 13:34:36 +0000 (+0000) Subject: Allow to bind to multiple separate addresses. X-Git-Tag: v_3_0_18~167 X-Git-Url: http://www.privoxy.org/gitweb/?p=privoxy.git;a=commitdiff_plain;h=dde30f5e8bb12c63688330c97fde75493f92c09c Allow to bind to multiple separate addresses. Patch set submitted by Petr Písař in #3354485. --- diff --git a/cgi.c b/cgi.c index d4a1f22f..a55805b4 100644 --- a/cgi.c +++ b/cgi.c @@ -1,4 +1,4 @@ -const char cgi_rcs[] = "$Id: cgi.c,v 1.139 2011/07/08 13:27:56 fabiankeil Exp $"; +const char cgi_rcs[] = "$Id: cgi.c,v 1.140 2011/07/08 13:28:11 fabiankeil Exp $"; /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/cgi.c,v $ @@ -2143,6 +2143,7 @@ struct map *default_exports(const struct client_state *csp, const char *caller) struct map * exports; int local_help_exists = 0; char *ip_address = NULL; + char *port = NULL; char *hostname = NULL; assert(csp); @@ -2155,12 +2156,12 @@ struct map *default_exports(const struct client_state *csp, const char *caller) if (csp->config->hostname) { - get_host_information(csp->cfd, &ip_address, NULL); + get_host_information(csp->cfd, &ip_address, &port, NULL); hostname = strdup(csp->config->hostname); } else { - get_host_information(csp->cfd, &ip_address, &hostname); + get_host_information(csp->cfd, &ip_address, &port, &hostname); } err = map(exports, "version", 1, html_encode(VERSION), 0); @@ -2168,6 +2169,8 @@ struct map *default_exports(const struct client_state *csp, const char *caller) if (!err) err = map(exports, "time", 1, html_encode(buf), 0); if (!err) err = map(exports, "my-ip-address", 1, html_encode(ip_address ? ip_address : "unknown"), 0); freez(ip_address); + if (!err) err = map(exports, "my-port", 1, html_encode(port ? port : "unkown"), 0); + freez(port); if (!err) err = map(exports, "my-hostname", 1, html_encode(hostname ? hostname : "unknown"), 0); freez(hostname); if (!err) err = map(exports, "homepage", 1, html_encode(HOME_PAGE_URL), 0); @@ -2192,9 +2195,6 @@ struct map *default_exports(const struct client_state *csp, const char *caller) if (!err) err = map_block_killer(exports, "can-toggle"); #endif - snprintf(buf, sizeof(buf), "%d", csp->config->hport); - if (!err) err = map(exports, "my-port", 1, buf, 1); - if(!strcmp(CODE_STATUS, "stable")) { if (!err) err = map_block_killer(exports, "unstable"); diff --git a/doc/source/p-config.sgml b/doc/source/p-config.sgml index 4a4a6dbe..2b46e9ca 100644 --- a/doc/source/p-config.sgml +++ b/doc/source/p-config.sgml @@ -3,7 +3,7 @@ Purpose : Used with other docs and files only. - $Id: p-config.sgml,v 2.74 2011/07/08 13:31:17 fabiankeil Exp $ + $Id: p-config.sgml,v 2.75 2011/07/08 13:31:40 fabiankeil Exp $ Copyright (C) 2001-2010 Privoxy Developers http://www.privoxy.org/ See LICENSE. @@ -97,7 +97,7 @@ Sample Configuration File for Privoxy v&p-version; - $Id: p-config.sgml,v 2.74 2011/07/08 13:31:17 fabiankeil Exp $ + $Id: p-config.sgml,v 2.75 2011/07/08 13:31:40 fabiankeil Exp $ Copyright (C) 2001-2010 Privoxy Developers http://www.privoxy.org/ @@ -1234,6 +1234,13 @@ actionsfile serve requests from other machines (e.g. on your local network) as well, you will need to override the default. + + You can use this statement multiple times to make + Privoxy listen on more ports or more + IP addresses. Suitable if your operating system does not + support sharing IPv6 and IPv4 protocols + on the same socket. + If a hostname is used instead of an IP address, Privoxy will try to resolve it to an IP address and if there are multiple, use the first diff --git a/jbsockets.c b/jbsockets.c index b00d5144..0ce7507c 100644 --- a/jbsockets.c +++ b/jbsockets.c @@ -1,4 +1,4 @@ -const char jbsockets_rcs[] = "$Id: jbsockets.c,v 1.103 2011/06/23 13:58:22 fabiankeil Exp $"; +const char jbsockets_rcs[] = "$Id: jbsockets.c,v 1.104 2011/07/04 17:47:29 fabiankeil Exp $"; /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/jbsockets.c,v $ @@ -923,14 +923,17 @@ int bind_port(const char *hostnam, int portnum, jb_socket *pfd) * 1 : afd = File descriptor returned from accept(). * 2 : ip_address = Pointer to return the pointer to * the ip address string. - * 3 : hostname = Pointer to return the pointer to + * 3 : port = Pointer to return the pointer to + * the TCP port string. + * 4 : hostname = Pointer to return the pointer to * the hostname or NULL if the caller * isn't interested in it. * * Returns : void. * *********************************************************************/ -void get_host_information(jb_socket afd, char **ip_address, char **hostname) +void get_host_information(jb_socket afd, char **ip_address, char **port, + char **hostname) { #ifdef HAVE_RFC2553 struct sockaddr_storage server; @@ -963,6 +966,7 @@ void get_host_information(jb_socket afd, char **ip_address, char **hostname) *hostname = NULL; } *ip_address = NULL; + *port = NULL; if (!getsockname(afd, (struct sockaddr *) &server, &s_length)) { @@ -971,25 +975,37 @@ void get_host_information(jb_socket afd, char **ip_address, char **hostname) log_error(LOG_LEVEL_ERROR, "getsockname() truncated server address"); return; } + *port = malloc(NI_MAXSERV); + if (NULL == *port) + { + log_error(LOG_LEVEL_ERROR, + "Out of memory while getting the client's port."); + return; + } #ifdef HAVE_RFC2553 *ip_address = malloc(NI_MAXHOST); if (NULL == *ip_address) { log_error(LOG_LEVEL_ERROR, "Out of memory while getting the client's IP address."); + freez(*port); return; } retval = getnameinfo((struct sockaddr *) &server, s_length, - *ip_address, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); + *ip_address, NI_MAXHOST, *port, NI_MAXSERV, + NI_NUMERICHOST|NI_NUMERICSERV); if (retval) { log_error(LOG_LEVEL_ERROR, "Unable to print my own IP address: %s", gai_strerror(retval)); freez(*ip_address); + freez(*port); return; } #else *ip_address = strdup(inet_ntoa(server.sin_addr)); + snprintf(*port, NI_MAXSERV, "%hu", ntohs(server.sin_port)); + *port[NI_MAXSERV - 1] = '\0'; #endif /* HAVE_RFC2553 */ if (NULL == hostname) { @@ -1065,24 +1081,23 @@ void get_host_information(jb_socket afd, char **ip_address, char **hostname) * * Function : accept_connection * - * Description : Accepts a connection on a socket. Socket must have - * been created using bind_port(). + * Description : Accepts a connection on one of more socket. Sockets + * must have been created using bind_port(). * * Parameters : * 1 : csp = Client state, cfd, ip_addr_str, and * ip_addr_long will be set by this routine. - * 2 : fd = file descriptor returned from bind_port + * 2 : fds = File descriptors returned from bind_port * * Returns : when a connection is accepted, it returns 1 (TRUE). * On an error it returns 0 (FALSE). * *********************************************************************/ -int accept_connection(struct client_state * csp, jb_socket fd) +int accept_connection(struct client_state * csp, jb_socket fds[]) { #ifdef HAVE_RFC2553 /* XXX: client is stored directly into csp->tcp_addr */ #define client (csp->tcp_addr) - int retval; #else struct sockaddr_in client; #endif @@ -1093,9 +1108,66 @@ int accept_connection(struct client_state * csp, jb_socket fd) #else socklen_t c_length; #endif + int retval; + int i; + int max_selected_socket; + fd_set selected_fds; + jb_socket fd; c_length = sizeof(client); + /* Wait for a connection on any socket. Return immediately if no socket is + * listening. */ + FD_ZERO(&selected_fds); + max_selected_socket = 0; + for (i = 0; i < MAX_LISTENING_SOCKETS; i++) + { + if (JB_INVALID_SOCKET != fds[i]) + { + FD_SET(fds[i], &selected_fds); + if (max_selected_socket < fds[i] + 1) + { + max_selected_socket = fds[i] + 1; + } + } + } + if (0 == max_selected_socket) + { + return 0; + } + do + { + retval = select(max_selected_socket, &selected_fds, NULL, NULL, NULL); + } while (retval < 0 && errno == EINTR); + if (retval <= 0) + { + if (0 == retval) + { + log_error(LOG_LEVEL_ERROR, + "Waiting on new client failed because select(2) returned 0." + " This should not happen."); + } + else + { + log_error(LOG_LEVEL_ERROR, + "Waiting on new client failed because of problems in select(2): " + "%s.", strerror(errno)); + } + return 0; + } + for (i = 0; i < MAX_LISTENING_SOCKETS && !FD_ISSET(fds[i], &selected_fds); + i++); + if (i >= MAX_LISTENING_SOCKETS) + { + log_error(LOG_LEVEL_ERROR, + "select(2) reported connected clients (number = %u, " + "descriptor boundary = %u), but none found.", + retval, max_selected_socket); + return 0; + } + fd = fds[i]; + + /* Accept selected connection */ #ifdef _WIN32 afd = accept (fd, (struct sockaddr *) &client, &c_length); if (afd == JB_INVALID_SOCKET) diff --git a/jbsockets.h b/jbsockets.h index f19968c8..6b74b1d2 100644 --- a/jbsockets.h +++ b/jbsockets.h @@ -1,6 +1,6 @@ #ifndef JBSOCKETS_H_INCLUDED #define JBSOCKETS_H_INCLUDED -#define JBSOCKETS_H_VERSION "$Id: jbsockets.h,v 1.16 2009/05/16 13:27:20 fabiankeil Exp $" +#define JBSOCKETS_H_VERSION "$Id: jbsockets.h,v 1.17 2010/04/23 11:53:48 fabiankeil Exp $" /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/jbsockets.h,v $ @@ -57,8 +57,8 @@ extern int data_is_available(jb_socket fd, int seconds_to_wait); extern void close_socket(jb_socket fd); extern int bind_port(const char *hostnam, int portnum, jb_socket *pfd); -extern int accept_connection(struct client_state * csp, jb_socket fd); -extern void get_host_information(jb_socket afd, char **ip_address, char **hostname); +extern int accept_connection(struct client_state * csp, jb_socket fds[]); +extern void get_host_information(jb_socket afd, char **ip_address, char **port, char **hostname); extern unsigned long resolve_hostname_to_ip(const char *host); diff --git a/jcc.c b/jcc.c index 91b2397b..271b1d17 100644 --- a/jcc.c +++ b/jcc.c @@ -1,4 +1,4 @@ -const char jcc_rcs[] = "$Id: jcc.c,v 1.356 2011/07/17 13:30:24 fabiankeil Exp $"; +const char jcc_rcs[] = "$Id: jcc.c,v 1.357 2011/07/17 13:31:02 fabiankeil Exp $"; /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/jcc.c,v $ @@ -151,7 +151,9 @@ static void serve(struct client_state *csp); static void usage(const char *myname); #endif static void initialize_mutexes(void); -static jb_socket bind_port_helper(struct configuration_spec *config); +static jb_socket bind_port_helper(const char *haddr, int hport); +static void bind_ports_helper(struct configuration_spec *config, jb_socket sockets[]); +static void close_ports_helper(jb_socket sockets[]); static void listen_loop(void); #ifdef AMIGA @@ -3325,29 +3327,30 @@ int main(int argc, char **argv) * on failure. * * Parameters : - * 1 : config = Privoxy configuration. Specifies port - * to bind to. + * 1 : haddr = Host addres to bind to. Use NULL to bind to + * INADDR_ANY. + * 2 : hport = Specifies port to bind to. * * Returns : Port that was opened. * *********************************************************************/ -static jb_socket bind_port_helper(struct configuration_spec * config) +static jb_socket bind_port_helper(const char *haddr, int hport) { int result; jb_socket bfd; - if (config->haddr == NULL) + if (haddr == NULL) { log_error(LOG_LEVEL_INFO, "Listening on port %d on all IP addresses", - config->hport); + hport); } else { log_error(LOG_LEVEL_INFO, "Listening on port %d on IP address %s", - config->hport, config->haddr); + hport, haddr); } - result = bind_port(config->haddr, config->hport, &bfd); + result = bind_port(haddr, hport, &bfd); if (result < 0) { @@ -3357,29 +3360,96 @@ static jb_socket bind_port_helper(struct configuration_spec * config) log_error(LOG_LEVEL_FATAL, "can't bind to %s:%d: " "There may be another Privoxy or some other " "proxy running on port %d", - (NULL != config->haddr) ? config->haddr : "INADDR_ANY", - config->hport, config->hport); + (NULL != haddr) ? haddr : "INADDR_ANY", hport, hport); case -2 : log_error(LOG_LEVEL_FATAL, "can't bind to %s:%d: " "The hostname is not resolvable", - (NULL != config->haddr) ? config->haddr : "INADDR_ANY", config->hport); + (NULL != haddr) ? haddr : "INADDR_ANY", hport); default : log_error(LOG_LEVEL_FATAL, "can't bind to %s:%d: %E", - (NULL != config->haddr) ? config->haddr : "INADDR_ANY", config->hport); + (NULL != haddr) ? haddr : "INADDR_ANY", hport); } /* shouldn't get here */ return JB_INVALID_SOCKET; } - config->need_bind = 0; - return bfd; } +/********************************************************************* + * + * Function : bind_ports_helper + * + * Description : Bind the listen ports. Handles logging, and aborts + * on failure. + * + * Parameters : + * 1 : config = Privoxy configuration. Specifies ports + * to bind to. + * 2 : sockets = Preallocated array of opened sockets + * corresponding to specification in config. + * All non-opened sockets will be set to + * JB_INVALID_SOCKET. + * + * Returns : Nothing. Inspect sockets argument. + * + *********************************************************************/ +static void bind_ports_helper(struct configuration_spec * config, + jb_socket sockets[]) +{ + int i; + + config->need_bind = 1; + + for (i = 0; i < MAX_LISTENING_SOCKETS; i++) + { + sockets[i] = JB_INVALID_SOCKET; + + if (config->hport[i]) + { + sockets[i] = bind_port_helper(config->haddr[i], config->hport[i]); + if (JB_INVALID_SOCKET != sockets[i]) + { + config->need_bind = 0; + } + } + } +} + + +/********************************************************************* + * + * Function : close_ports_helper + * + * Description : Close listenings ports. + * + * Parameters : + * 1 : sockets = Array of opened and non-opened sockets to + * close. All sockets will be set to + * JB_INVALID_SOCKET. + * + * Returns : Nothing. + * + *********************************************************************/ +static void close_ports_helper(jb_socket sockets[]) +{ + int i; + + for (i = 0; i < MAX_LISTENING_SOCKETS; i++) + { + if (JB_INVALID_SOCKET != sockets[i]) + { + close_socket(sockets[i]); + } + sockets[i] = JB_INVALID_SOCKET; + } +} + + #ifdef _WIN32 /* Without this simple workaround we get this compiler warning from _beginthread * warning C4028: formal parameter 1 different from declaration @@ -3406,7 +3476,7 @@ static void listen_loop(void) { struct client_states *csp_list = NULL; struct client_state *csp = NULL; - jb_socket bfd; + jb_socket bfds[MAX_LISTENING_SOCKETS]; struct configuration_spec *config; unsigned int active_threads = 0; @@ -3420,7 +3490,7 @@ static void listen_loop(void) initialize_reusable_connections(); #endif /* def FEATURE_CONNECTION_SHARING */ - bfd = bind_port_helper(config); + bind_ports_helper(config, bfds); #ifdef FEATURE_GRACEFUL_TERMINATION while (!g_terminate) @@ -3465,7 +3535,7 @@ static void listen_loop(void) log_error(LOG_LEVEL_CONNECT, "Listening for new connections ... "); - if (!accept_connection(csp, bfd)) + if (!accept_connection(csp, bfds)) { log_error(LOG_LEVEL_CONNECT, "accept failed: %E"); @@ -3505,9 +3575,9 @@ static void listen_loop(void) * that this will hurt people's feelings. */ - close_socket(bfd); + close_ports_helper(bfds); - bfd = bind_port_helper(config); + bind_ports_helper(config, bfds); } #ifdef FEATURE_TOGGLE diff --git a/loadcfg.c b/loadcfg.c index ac868483..82ef8d2c 100644 --- a/loadcfg.c +++ b/loadcfg.c @@ -1,4 +1,4 @@ -const char loadcfg_rcs[] = "$Id: loadcfg.c,v 1.116 2011/07/08 13:29:39 fabiankeil Exp $"; +const char loadcfg_rcs[] = "$Id: loadcfg.c,v 1.117 2011/07/08 13:30:08 fabiankeil Exp $"; /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/loadcfg.c,v $ @@ -229,7 +229,10 @@ static void unload_configfile (void * data) freez(config->templdir); freez(config->hostname); - freez(config->haddr); + for (i = 0; i < MAX_LISTENING_SOCKETS; i++) + { + freez(config->haddr[i]); + } freez(config->logfile); for (i = 0; i < MAX_AF_FILES; i++) @@ -1066,8 +1069,23 @@ struct configuration_spec * load_config(void) * listen-address [ip][:port] * *************************************************************************/ case hash_listen_address : - freez(config->haddr); - config->haddr = strdup(arg); + i = 0; + while ((i < MAX_LISTENING_SOCKETS) && (NULL != config->haddr[i])) + { + i++; + } + + if (i >= MAX_LISTENING_SOCKETS) + { + log_error(LOG_LEVEL_FATAL, "Too many 'listen-address' directives in config file - limit is %d.\n" + "(You can increase this limit by changing MAX_LISTENING_SOCKETS in project.h and recompiling).", + MAX_LISTENING_SOCKETS); + } + config->haddr[i] = strdup(arg); + if (NULL == config->haddr[i]) + { + log_error(LOG_LEVEL_FATAL, "Out of memory while copying listening address"); + } break; /* ************************************************************************* @@ -1524,39 +1542,45 @@ struct configuration_spec * load_config(void) } #endif /* def FEATURE_TRUST */ - if ( NULL == config->haddr ) + if ( NULL == config->haddr[0] ) { - config->haddr = strdup( HADDR_DEFAULT ); + config->haddr[0] = strdup( HADDR_DEFAULT ); + if (NULL == config->haddr[0]) + { + log_error(LOG_LEVEL_FATAL, "Out of memory while copying default listening address"); + } } - if ( NULL != config->haddr ) + for (i = 0; i < MAX_LISTENING_SOCKETS && NULL != config->haddr[i]; i++ ) { - if ((*config->haddr == '[') - && (NULL != (p = strchr(config->haddr, ']'))) + if ((*config->haddr[i] == '[') + && (NULL != (p = strchr(config->haddr[i], ']'))) && (p[1] == ':') - && (0 < (config->hport = atoi(p + 2)))) + && (0 < (config->hport[i] = atoi(p + 2)))) { *p = '\0'; - memmove((void *)config->haddr, config->haddr + 1, - (size_t)(p - config->haddr)); + memmove((void *)config->haddr[i], config->haddr[i] + 1, + (size_t)(p - config->haddr[i])); } - else if (NULL != (p = strchr(config->haddr, ':')) - && (0 < (config->hport = atoi(p + 1)))) + else if (NULL != (p = strchr(config->haddr[i], ':')) + && (0 < (config->hport[i] = atoi(p + 1)))) { *p = '\0'; } else { - log_error(LOG_LEVEL_FATAL, "invalid bind port spec %s", config->haddr); + log_error(LOG_LEVEL_FATAL, "invalid bind port spec %s", config->haddr[i]); /* Never get here - LOG_LEVEL_FATAL causes program exit */ } - if (*config->haddr == '\0') + if (*config->haddr[i] == '\0') { /* - * Only the port specified. We stored it in config->hport + * Only the port specified. We stored it in config->hport[i] * and don't need its text representation anymore. + * Use config->hport[i] == 0 to iterate listening addresses since + * now. */ - freez(config->haddr); + freez(config->haddr[i]); } } @@ -1600,30 +1624,34 @@ struct configuration_spec * load_config(void) struct configuration_spec * oldcfg = (struct configuration_spec *) current_configfile->f; /* - * Check if config->haddr,hport == oldcfg->haddr,hport + * Check if config->haddr[i],hport[i] == oldcfg->haddr[i],hport[i] * * The following could be written more compactly as a single, * (unreadably long) if statement. */ config->need_bind = 0; - if (config->hport != oldcfg->hport) - { - config->need_bind = 1; - } - else if (config->haddr == NULL) + + for (i = 0; i < MAX_LISTENING_SOCKETS; i++) { - if (oldcfg->haddr != NULL) + if (config->hport[i] != oldcfg->hport[i]) + { + config->need_bind = 1; + } + else if (config->haddr[i] == NULL) + { + if (oldcfg->haddr[i] != NULL) + { + config->need_bind = 1; + } + } + else if (oldcfg->haddr[i] == NULL) + { + config->need_bind = 1; + } + else if (0 != strcmp(config->haddr[i], oldcfg->haddr[i])) { config->need_bind = 1; } - } - else if (oldcfg->haddr == NULL) - { - config->need_bind = 1; - } - else if (0 != strcmp(config->haddr, oldcfg->haddr)) - { - config->need_bind = 1; } current_configfile->unloader = unload_configfile; diff --git a/project.h b/project.h index 37cfc41c..d44eed46 100644 --- a/project.h +++ b/project.h @@ -1,7 +1,7 @@ #ifndef PROJECT_H_INCLUDED #define PROJECT_H_INCLUDED /** Version string. */ -#define PROJECT_H_VERSION "$Id: project.h,v 1.168 2011/07/08 13:27:31 fabiankeil Exp $" +#define PROJECT_H_VERSION "$Id: project.h,v 1.169 2011/07/08 13:30:08 fabiankeil Exp $" /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/project.h,v $ @@ -843,6 +843,12 @@ struct reusable_connection */ #define MAX_AF_FILES 10 +/** + * Maximum number of sockets to listen to. This limit is arbitrary - it's just used + * to size an array. + */ +#define MAX_LISTENING_SOCKETS 10 + /** * The state of a Privoxy processing thread. */ @@ -1259,11 +1265,11 @@ struct configuration_spec /** The hostname to show on CGI pages, or NULL to use the real one. */ const char *hostname; - /** IP address to bind to. Defaults to HADDR_DEFAULT == 127.0.0.1. */ - const char *haddr; + /** IP addresses to bind to. Defaults to HADDR_DEFAULT == 127.0.0.1. */ + const char *haddr[MAX_LISTENING_SOCKETS]; - /** Port to bind to. Defaults to HADDR_PORT == 8118. */ - int hport; + /** Ports to bind to. Defaults to HADDR_PORT == 8118. */ + int hport[MAX_LISTENING_SOCKETS]; /** Size limit for IOB */ size_t buffer_limit;