Attached is a patch that seems to work on my system, against 2.0.48.
I am not completely clear on apache memory management, and I think it may leak the char* device memory allocated in the url-parsing code. I am also not sure that the url parsing code handles all cases correctly. It does handle this at least: eth4:192.168.1.5:80
There is printf debugging still in the patch.
Please consider this patch, or some other method of achieving the SO_BINDTODEVICE call in a configurable manner.
One question: I tried make distclean before making a recursive diff, but not all of the auto-generated files were cleaned up. Is there a better way to make diffs?
Thanks, Ben
-- Ben Greear <[EMAIL PROTECTED]> Candela Technologies Inc http://www.candelatech.com
diff -u -r -N httpd-2.0.48/include/ap_listen.h httpd-2.0.48.ben/include/ap_listen.h --- httpd-2.0.48/include/ap_listen.h 2003-02-03 09:31:29.000000000 -0800 +++ httpd-2.0.48.ben/include/ap_listen.h 2004-02-11 20:24:23.000000000 -0800 @@ -91,6 +91,10 @@ * Is this socket currently active */ int active; + + /** The device to bind to, empty string for no binding. + */ + char bind_device[16]; /* more stuff here, like which protocol is bound to the port */ }; diff -u -r -N httpd-2.0.48/include/httpd.h httpd-2.0.48.ben/include/httpd.h --- httpd-2.0.48/include/httpd.h 2003-10-24 09:19:31.000000000 -0700 +++ httpd-2.0.48.ben/include/httpd.h 2004-02-11 19:51:43.000000000 -0800 @@ -1043,6 +1043,8 @@ apr_port_t host_port; /** The name given in <VirtualHost> */ char *virthost; + /** The device name to bind this server to */ + char *host_device; }; /** A structure to store information for each virtual server */ diff -u -r -N httpd-2.0.48/server/listen.c httpd-2.0.48.ben/server/listen.c --- httpd-2.0.48/server/listen.c 2003-03-30 20:30:52.000000000 -0800 +++ httpd-2.0.48.ben/server/listen.c 2004-02-12 22:31:49.000000000 -0800 @@ -165,7 +165,7 @@ ap_sock_disable_nagle(s); #endif - if ((stat = apr_bind(s, server->bind_addr)) != APR_SUCCESS) { + if ((stat = apr_bind(s, server->bind_addr, server->bind_device)) != APR_SUCCESS) { ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, stat, p, "make_sock: could not bind to address %pI", server->bind_addr); @@ -253,7 +253,7 @@ if ((sock_rv = apr_socket_create(&tmp_sock, APR_INET6, SOCK_STREAM, p)) == APR_SUCCESS && apr_sockaddr_info_get(&sa, NULL, APR_INET6, 0, 0, p) == APR_SUCCESS && - apr_bind(tmp_sock, sa) == APR_SUCCESS) { + apr_bind(tmp_sock, sa, NULL) == APR_SUCCESS) { default_family = APR_INET6; } else { @@ -267,7 +267,8 @@ } -static const char *alloc_listener(process_rec *process, char *addr, apr_port_t port) +static const char *alloc_listener(process_rec *process, char *addr, apr_port_t port, + const char* device) { ap_listen_rec **walk; ap_listen_rec *new; @@ -275,6 +276,14 @@ apr_port_t oldport; apr_sockaddr_t *sa; + printf("addr: %p port: %d device: %p\n", addr, port, device); + if (addr) { + printf("addr -:%s:-\n", addr); + } + if (device) { + printf("device -:%s:-\n", device); + } + if (!addr) { /* don't bind to specific interface */ find_default_family(process->pool); switch(default_family) { @@ -313,6 +322,15 @@ /* this has to survive restarts */ new = apr_palloc(process->pool, sizeof(ap_listen_rec)); new->active = 0; + + if (device) { + strncpy(new->bind_device, device, sizeof(new->bind_device)); + new->bind_device[sizeof(new->bind_device) - 1] = 0; + } + else { + new->bind_device[0] = '\0'; + } + if ((status = apr_sockaddr_info_get(&new->bind_addr, addr, APR_UNSPEC, port, 0, process->pool)) != APR_SUCCESS) { @@ -413,7 +431,7 @@ const char *ap_set_listener(cmd_parms *cmd, void *dummy, const char *ips) { - char *host, *scope_id; + char *host, *scope_id, *device; apr_port_t port; apr_status_t rv; const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); @@ -422,7 +440,7 @@ return err; } - rv = apr_parse_addr_port(&host, &scope_id, &port, ips, cmd->pool); + rv = apr_parse_addr_port(&device, &host, &scope_id, &port, ips, cmd->pool); if (rv != APR_SUCCESS) { return "Invalid address or port"; } @@ -440,7 +458,7 @@ return "Port must be specified"; } - return alloc_listener(cmd->server->process, host, port); + return alloc_listener(cmd->server->process, host, port, device); } const char *ap_set_listenbacklog(cmd_parms *cmd, void *dummy, const char *arg) diff -u -r -N httpd-2.0.48/server/rfc1413.c httpd-2.0.48.ben/server/rfc1413.c --- httpd-2.0.48/server/rfc1413.c 2003-02-03 09:32:01.000000000 -0800 +++ httpd-2.0.48.ben/server/rfc1413.c 2004-02-11 20:15:40.000000000 -0800 @@ -164,7 +164,7 @@ * addresses from the query socket. */ - if ((rv = apr_bind(*newsock, localsa)) != APR_SUCCESS) { + if ((rv = apr_bind(*newsock, localsa, NULL)) != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_CRIT, rv, srv, "rfc1413: Error binding query socket to local port"); apr_socket_close(*newsock); diff -u -r -N httpd-2.0.48/server/vhost.c httpd-2.0.48.ben/server/vhost.c --- httpd-2.0.48/server/vhost.c 2003-02-03 09:32:01.000000000 -0800 +++ httpd-2.0.48.ben/server/vhost.c 2004-02-11 19:47:03.000000000 -0800 @@ -183,7 +183,7 @@ { apr_sockaddr_t *my_addr; server_addr_rec *sar; - char *w, *host, *scope_id; + char *w, *host, *scope_id, *device; int wild_port; apr_size_t wlen; apr_port_t port; @@ -205,7 +205,7 @@ wild_port = 1; } } - rv = apr_parse_addr_port(&host, &scope_id, &port, w, p); + rv = apr_parse_addr_port(&device, &host, &scope_id, &port, w, p); /* If the string is "80", apr_parse_addr_port() will be happy and set * host to NULL and port to 80, so watch out for that. */ @@ -246,6 +246,7 @@ sar = apr_pcalloc(p, sizeof(server_addr_rec)); **paddr = sar; *paddr = &sar->next; + sar->host_device = device; sar->host_addr = my_addr; sar->host_port = port; sar->virthost = host; @@ -740,7 +741,7 @@ */ static void fix_hostname(request_rec *r) { - char *host, *scope_id; + char *host, *scope_id, *device; char *dst; apr_port_t port; apr_status_t rv; @@ -750,7 +751,7 @@ return; } - rv = apr_parse_addr_port(&host, &scope_id, &port, r->hostname, r->pool); + rv = apr_parse_addr_port(&device, &host, &scope_id, &port, r->hostname, r->pool); if (rv != APR_SUCCESS || scope_id) { goto bad; } diff -u -r -N httpd-2.0.48/srclib/apr/include/apr_network_io.h httpd-2.0.48.ben/srclib/apr/include/apr_network_io.h --- httpd-2.0.48/srclib/apr/include/apr_network_io.h 2003-10-16 15:13:02.000000000 -0700 +++ httpd-2.0.48.ben/srclib/apr/include/apr_network_io.h 2004-02-11 20:04:56.000000000 -0800 @@ -349,14 +349,16 @@ * Bind the socket to its associated port * @param sock The socket to bind * @param sa The socket address to bind to + * @param device The device (ie, eth2) to bind to, or NULL for no binding. * @remark This may be where we will find out if there is any other process * using the selected port. */ APR_DECLARE(apr_status_t) apr_socket_bind(apr_socket_t *sock, - apr_sockaddr_t *sa); + apr_sockaddr_t *sa, + const char* device); /** @deprecated @see apr_socket_bind */ -APR_DECLARE(apr_status_t) apr_bind(apr_socket_t *sock, apr_sockaddr_t *sa); +APR_DECLARE(apr_status_t) apr_bind(apr_socket_t *sock, apr_sockaddr_t *sa, const char* device); /** * Listen to a bound socket for connections. @@ -451,12 +453,17 @@ * www.apache.org:8080 (hostname and port number) * [fe80::1]:80 (IPv6 numeric address string only) * [fe80::1%eth0] (IPv6 numeric address string and scope id) + * eth0:192.168.1.1:8080 (Specify device to bind to as well) + * eth0::8080 (Specify device to bind to, use first IP on device) + * * * Invalid strings: * (empty string) * [abc] (not valid IPv6 numeric address string) * abc:65536 (invalid port number) * + * @param device The new buffer containing just the device name. On output, + * *device will be NULL if no device was specified. * @param addr The new buffer containing just the hostname. On output, *addr * will be NULL if no hostname/IP address was specfied. * @param scope_id The new buffer containing just the scope id. On output, @@ -472,7 +479,8 @@ * required, check for addr == NULL in addition to checking the * return code. */ -APR_DECLARE(apr_status_t) apr_parse_addr_port(char **addr, +APR_DECLARE(apr_status_t) apr_parse_addr_port(char **device, + char **addr, char **scope_id, apr_port_t *port, const char *str, diff -u -r -N httpd-2.0.48/srclib/apr/network_io/unix/sockaddr.c httpd-2.0.48.ben/srclib/apr/network_io/unix/sockaddr.c --- httpd-2.0.48/srclib/apr/network_io/unix/sockaddr.c 2003-09-29 12:08:15.000000000 -0700 +++ httpd-2.0.48.ben/srclib/apr/network_io/unix/sockaddr.c 2004-02-13 00:21:45.000000000 -0800 @@ -257,16 +257,19 @@ return APR_SUCCESS; } -APR_DECLARE(apr_status_t) apr_parse_addr_port(char **addr, + +APR_DECLARE(apr_status_t) apr_parse_addr_port(char **device, + char **addr, char **scope_id, apr_port_t *port, const char *str, apr_pool_t *p) { - const char *ch, *lastchar; + const char *ch, *lastchar, *addr_start; int big_port; apr_size_t addrlen; + *device = NULL; /* assume not specified */ *addr = NULL; /* assume not specified */ *scope_id = NULL; /* assume not specified */ *port = 0; /* assume not specified */ @@ -298,18 +301,66 @@ } *port = big_port; lastchar = ch - 1; + --ch; /* back over the ':' separator */ + } + + /* I think IP-v6 addrs can have ':' in them?? */ + printf("str: %p(%s) lastchar: %p(%s) ch: %p(%c)\n", + str, str, lastchar, lastchar, ch, *ch); + addrlen = 0; + if (*lastchar == ']') { + while (ch >= str && (*ch != '[')) { + --ch; + addrlen++; + } + if (*ch != '[') { + /* BAD, need matching bracket. */ + return APR_EINVAL; + } + --ch; + addrlen++; + } + else { + if (*lastchar == ':') { + /* Was something like eth0::80, we have no address specified */ + } + else { + while (ch >= str && (*ch != ':')) { + --ch; + addrlen++; + } + } + } + + printf("str: %p(%s) lastchar: %p(%s) ch: %p(%c) addrlen: %d\n", + str, str, lastchar, lastchar, ch, *ch, addrlen); + + /* *ch points to the ':' at the end of eth0 if we are using that format */ + + addr_start = ch + 1; + + if (addr_start > (str + 1)) { + apr_size_t devlen = (addr_start - 1) - str; + if (devlen > 0) { + *device = apr_palloc(p, devlen + 1); + memcpy(*device, str, devlen); + (*device)[devlen] = '\0'; + } } + if (addrlen == 0) { + goto out; + } + /* now handle the hostname */ - addrlen = lastchar - str + 1; /* XXX we don't really have to require APR_HAVE_IPV6 for this; * just pass char[] for ipaddr (so we don't depend on struct in6_addr) * and always define APR_INET6 */ #if APR_HAVE_IPV6 - if (*str == '[') { - const char *end_bracket = memchr(str, ']', addrlen); + if (*addr_start == '[') { + const char *end_bracket = memchr(addr_start, ']', addrlen); struct in6_addr ipaddr; const char *scope_delim; @@ -319,13 +370,13 @@ } /* handle scope id; this is the only context where it is allowed */ - scope_delim = memchr(str, '%', addrlen); + scope_delim = memchr(addr_start, '%', addrlen); if (scope_delim) { if (scope_delim == end_bracket - 1) { /* '%' without scope id */ *port = 0; return APR_EINVAL; } - addrlen = scope_delim - str - 1; + addrlen = scope_delim - addr_start - 1; *scope_id = apr_palloc(p, end_bracket - scope_delim); memcpy(*scope_id, scope_delim + 1, end_bracket - scope_delim - 1); (*scope_id)[end_bracket - scope_delim - 1] = '\0'; @@ -336,7 +387,7 @@ *addr = apr_palloc(p, addrlen + 1); memcpy(*addr, - str + 1, + addr_start + 1, addrlen); (*addr)[addrlen] = '\0'; if (apr_inet_pton(AF_INET6, *addr, &ipaddr) != 1) { @@ -353,9 +404,11 @@ * for bogus scope ids first. */ *addr = apr_palloc(p, addrlen + 1); - memcpy(*addr, str, addrlen); + memcpy(*addr, addr_start, addrlen); (*addr)[addrlen] = '\0'; } + + out: return APR_SUCCESS; } diff -u -r -N httpd-2.0.48/srclib/apr/network_io/unix/sockets.c httpd-2.0.48.ben/srclib/apr/network_io/unix/sockets.c --- httpd-2.0.48/srclib/apr/network_io/unix/sockets.c 2003-07-08 05:53:11.000000000 -0700 +++ httpd-2.0.48.ben/srclib/apr/network_io/unix/sockets.c 2004-02-11 20:41:18.000000000 -0800 @@ -164,8 +164,22 @@ return apr_pool_cleanup_run(thesocket->cntxt, thesocket, socket_cleanup); } -apr_status_t apr_socket_bind(apr_socket_t *sock, apr_sockaddr_t *sa) +apr_status_t apr_socket_bind(apr_socket_t *sock, apr_sockaddr_t *sa, const char* device) { + +#ifdef __linux__ + if (device && strlen(device)) { + // Bind to specific device. + apr_status_t status = 0; + if (setsockopt(sock->socketdes, SOL_SOCKET, SO_BINDTODEVICE, + device, strlen(device) + 1)) { + /*printf("Could not BINDTODEVICE, device: %s error: %s\n", + device, strerror(errno)); */ + /* Soldier on, this should not be fatal. */ + } + } +#endif + if (bind(sock->socketdes, (struct sockaddr *)&sa->sa, sa->salen) == -1) { return errno; @@ -428,9 +442,9 @@ } /* deprecated */ -apr_status_t apr_bind(apr_socket_t *sock, apr_sockaddr_t *sa) +apr_status_t apr_bind(apr_socket_t *sock, apr_sockaddr_t *sa, const char* device) { - return apr_socket_bind(sock, sa); + return apr_socket_bind(sock, sa, device); } /* deprecated */ diff -u -r -N httpd-2.0.48/support/ab.c httpd-2.0.48.ben/support/ab.c --- httpd-2.0.48/support/ab.c 2003-09-15 08:40:06.000000000 -0700 +++ httpd-2.0.48.ben/support/ab.c 2004-02-11 20:11:13.000000000 -0800 @@ -1841,6 +1841,7 @@ char *cp; char *h; char *scope_id; + char *device; apr_status_t rv; /* Save a copy for the proxy */ @@ -1870,7 +1871,7 @@ h = apr_palloc(cntxt, cp - url + 1); memcpy(h, url, cp - url); h[cp - url] = '\0'; - rv = apr_parse_addr_port(&hostname, &scope_id, &port, h, cntxt); + rv = apr_parse_addr_port(&device, &hostname, &scope_id, &port, h, cntxt); if (rv != APR_SUCCESS || !hostname || scope_id) { return 1; }