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;
}
