Version #2. Adds both apr_sockaddr_zone_set() and apr_sockaddr_zone_get() to set and retrieve the zone id/name.
Also now changes apr_sockaddr_equal() to compare different scopes as not equal, which I think is the only sensible way to do it, although that function is a bit odd in ignoring port differences already. IMO this is a bug fix, and is not a problem in a minor or patch version bump; the way to uniquely identify a link-local address is the (ipv6 addr, scope) pair so these should be compared appropriately. Also now enhances apr_sockaddr_ip_get*() to append the scope ID when converting an IP address to text format. Again I think this makes sense, e.g. if you are going to log a link-local address it only makes sense to log the addr with the scope. Regards, Joe
Index: configure.in =================================================================== --- configure.in (revision 1816144) +++ configure.in (working copy) @@ -1069,7 +1069,9 @@ #endif";; esac -AC_CHECK_HEADERS([sys/types.h sys/mman.h sys/ipc.h sys/mutex.h sys/shm.h sys/file.h kernel/OS.h os2.h windows.h]) +AC_CHECK_HEADERS([sys/types.h sys/mman.h sys/ipc.h sys/mutex.h \ + sys/shm.h sys/file.h kernel/OS.h os2.h windows.h \ + net/if.h]) AC_CHECK_FUNCS([mmap munmap shm_open shm_unlink shmget shmat shmdt shmctl \ create_area mprotect]) @@ -2755,7 +2757,7 @@ AC_SEARCH_LIBS(getaddrinfo, socket inet6) AC_SEARCH_LIBS(gai_strerror, socket inet6) AC_SEARCH_LIBS(getnameinfo, socket inet6) -AC_CHECK_FUNCS(gai_strerror) +AC_CHECK_FUNCS(gai_strerror if_nametoindex if_indextoname) APR_CHECK_WORKING_GETADDRINFO APR_CHECK_NEGATIVE_EAI APR_CHECK_WORKING_GETNAMEINFO Index: include/apr_network_io.h =================================================================== --- include/apr_network_io.h (revision 1816144) +++ include/apr_network_io.h (working copy) @@ -441,6 +441,29 @@ const apr_sockaddr_t *src, apr_pool_t *p); +/* Set the zone of an IPv6 link-local address object. + * @param sa Socket address object + * @param zone_id Zone ID (textual "eth0" or numeric "3"). + */ +APR_DECLARE(apr_status_t) apr_sockaddr_zone_set(apr_sockaddr_t *sa, + const char *zone_id); + + +/* Retrieve the zone of an IPv6 link-local address object. + * @param sa Socket address object + * @param name If non-NULL, set to the textual representation of the zone id + * @param id If non-NULL, set to the integer zone id + * @param p Pool from which *name is allocated if used. + * @return Returns APR_EBADIP for non-IPv6 socket or socket without any zone id + * set, or other error if the interface could not be mapped to a name. + * @remark Both name and id may be NULL, neither are modified if + * non-NULL in error cases. + */ +APR_DECLARE(apr_status_t) apr_sockaddr_zone_get(const apr_sockaddr_t *sa, + const char **name, + apr_uint32_t *id, + apr_pool_t *p); + /** * Look up the host name from an apr_sockaddr_t. * @param hostname The hostname. Index: network_io/unix/sockaddr.c =================================================================== --- network_io/unix/sockaddr.c (revision 1816144) +++ network_io/unix/sockaddr.c (working copy) @@ -25,6 +25,10 @@ #include <stdlib.h> #endif +#ifdef HAVE_NET_IF_H +#include <net/if.h> +#endif + #define APR_WANT_STRFUNC #include "apr_want.h" @@ -125,9 +129,29 @@ memmove(buf, buf + strlen("::ffff:"), strlen(buf + strlen("::ffff:"))+1); } -#endif + /* ensure NUL termination if the buffer is too short */ buf[buflen-1] = '\0'; + +#ifdef HAVE_IF_INDEXTONAME + if (sockaddr->family == AF_INET6 + && IN6_IS_ADDR_LINKLOCAL((struct in6_addr *)sockaddr->ipaddr_ptr)) { + char scbuf[IF_NAMESIZE], *p = buf + strlen(buf); + + if (if_indextoname(sockaddr->sa.sin6.sin6_scope_id, scbuf) == scbuf) { + /* Space check, need room for buf + '%' + scope + '\0'. + * Assert: buflen >= strlen(buf) + strlen(scbuf) + 2 + * Equiv: buflen >= strlen(scbuf) + (p-buf) + 2 + * Thus, fail in inverse condition: */ + if (buflen < strlen(scbuf) + (p - buf) + 2) { + return APR_ENOSPC; + } + *p++ = '%'; + memcpy(p, scbuf, strlen(scbuf) + 1); + } + } +#endif /* HAVE_IF_INDEXTONAME */ +#endif /* APR_HAVE_IPV6 */ return APR_SUCCESS; } @@ -899,11 +923,19 @@ &((struct in6_addr *)(b)->ipaddr_ptr)->s6_addr[12], \ (a)->ipaddr_len)) +#if APR_HAVE_IPV6 +#define SCOPE_OR_ZERO(sa_) ((sa_)->family != AF_INET6 ? 0 : \ + ((sa_)->sa.sin6.sin6_scope_id)) +#else +#define SCOPE_OR_ZERO(sa_) (0) +#endif + APR_DECLARE(int) apr_sockaddr_equal(const apr_sockaddr_t *addr1, const apr_sockaddr_t *addr2) { - if (addr1->ipaddr_len == addr2->ipaddr_len && - !memcmp(addr1->ipaddr_ptr, addr2->ipaddr_ptr, addr1->ipaddr_len)) { + if (addr1->ipaddr_len == addr2->ipaddr_len + && !memcmp(addr1->ipaddr_ptr, addr2->ipaddr_ptr, addr1->ipaddr_len) + && SCOPE_OR_ZERO(addr1) == SCOPE_OR_ZERO(addr2)) { return 1; } #if APR_HAVE_IPV6 @@ -1182,3 +1214,63 @@ #endif /* APR_HAVE_IPV6 */ return 0; /* no match */ } + +APR_DECLARE(apr_status_t) apr_sockaddr_zone_set(apr_sockaddr_t *sa, + const char *zone_id) +{ +#if !APR_HAVE_IPV6 || !defined(HAVE_IF_NAMETOINDEX) + return APR_ENOTIMPL; +#else + unsigned int idx; + + if (sa->family != APR_INET6) { + return APR_EBADIP; + } + + idx = if_nametoindex(zone_id); + if (idx) { + sa->sa.sin6.sin6_scope_id = idx; + return APR_SUCCESS; + } + + if (errno != ENODEV) { + return errno; + } + else { + char *endptr; + apr_int64_t i = apr_strtoi64(zone_id, &endptr, 10); + + if (*endptr != '\0' || errno || i < 1 || i > APR_INT16_MAX) { + return APR_EGENERAL; + } + + sa->sa.sin6.sin6_scope_id = i; + return APR_SUCCESS; + } +#endif +} + +APR_DECLARE(apr_status_t) apr_sockaddr_zone_get(const apr_sockaddr_t *sa, + const char **name, + apr_uint32_t *id, + apr_pool_t *p) +{ +#if !APR_HAVE_IPV6 || !defined(HAVE_IF_INDEXTONAME) + return APR_ENOTIMPL; +#else + if (sa->family != APR_INET6 || !sa->sa.sin6.sin6_scope_id) { + return APR_EBADIP; + } + + if (name) { + char *buf = apr_palloc(p, IF_NAMESIZE); + if (if_indextoname(sa->sa.sin6.sin6_scope_id, buf) == NULL) + return errno; + *name = buf; + } + + if (id) *id = sa->sa.sin6.sin6_scope_id; + + return APR_SUCCESS; +#endif +} Index: test/testsock.c =================================================================== --- test/testsock.c (revision 1816144) +++ test/testsock.c (working copy) @@ -640,6 +640,98 @@ #endif } +#define TEST_ZONE_ADDR "fe80::1" + +#ifdef __linux__ +/* Reasonable bet that "lo" will exist. */ +#define TEST_ZONE_NAME "lo" +/* ... fill in other platforms here */ +#endif + +#ifdef TEST_ZONE_NAME +#define TEST_ZONE_FULLADDR TEST_ZONE_ADDR "%" TEST_ZONE_NAME +#endif + +static void test_zone(abts_case *tc, void *data) +{ +#if APR_HAVE_IPV6 + apr_sockaddr_t *sa; + apr_status_t rv; + const char *name = NULL; + apr_uint32_t id = 0; + + /* RFC 5737 address */ + rv = apr_sockaddr_info_get(&sa, "127.0.0.1", APR_INET, 8080, 0, p); + APR_ASSERT_SUCCESS(tc, "Problem generating sockaddr", rv); + + /* Fail for an IPv4 address! */ + ABTS_INT_EQUAL(tc, APR_EBADIP, + apr_sockaddr_zone_set(sa, "1")); + ABTS_INT_EQUAL(tc, APR_EBADIP, + apr_sockaddr_zone_get(sa, &name, &id, p)); + + rv = apr_sockaddr_info_get(&sa, TEST_ZONE_ADDR, APR_INET6, 8080, 0, p); + APR_ASSERT_SUCCESS(tc, "Problem generating sockaddr", rv); + + rv = apr_sockaddr_info_get(&sa, TEST_ZONE_ADDR, APR_INET6, 8080, 0, p); + APR_ASSERT_SUCCESS(tc, "Problem generating sockaddr", rv); + + ABTS_INT_EQUAL(tc, APR_EBADIP, apr_sockaddr_zone_get(sa, &name, &id, p)); + +#ifdef TEST_ZONE_NAME + { + apr_sockaddr_t *sa2; + char buf[50]; + + APR_ASSERT_SUCCESS(tc, "Set zone to " TEST_ZONE_NAME, + apr_sockaddr_zone_set(sa, TEST_ZONE_NAME)); + + APR_ASSERT_SUCCESS(tc, "Get zone", + apr_sockaddr_zone_get(sa, NULL, NULL, p)); + + APR_ASSERT_SUCCESS(tc, "Get zone", + apr_sockaddr_zone_get(sa, &name, &id, p)); + ABTS_STR_EQUAL(tc, TEST_ZONE_NAME, name); + ABTS_INT_NEQUAL(tc, 0, id); /* Only guarantee is that it should be non-zero */ + + /* Check string translation. */ + APR_ASSERT_SUCCESS(tc, "get IP address", + apr_sockaddr_ip_getbuf(buf, 50, sa)); + ABTS_STR_EQUAL(tc, TEST_ZONE_FULLADDR, buf); + + memset(buf, 'A', sizeof buf); + ABTS_INT_EQUAL(tc, APR_ENOSPC, apr_sockaddr_ip_getbuf(buf, strlen(TEST_ZONE_ADDR), sa)); + ABTS_INT_EQUAL(tc, APR_ENOSPC, apr_sockaddr_ip_getbuf(buf, strlen(TEST_ZONE_FULLADDR), sa)); + + APR_ASSERT_SUCCESS(tc, "get IP address", + apr_sockaddr_ip_getbuf(buf, strlen(TEST_ZONE_FULLADDR) + 1, sa)); + /* Check for overflow. */ + ABTS_INT_EQUAL(tc, 'A', buf[strlen(buf) + 1]); + + rv = apr_sockaddr_info_copy(&sa2, sa, p); + APR_ASSERT_SUCCESS(tc, "Problem copying sockaddr", rv); + + /* Copy copied zone matches */ + APR_ASSERT_SUCCESS(tc, "Get zone", + apr_sockaddr_zone_get(sa2, &name, &id, p)); + ABTS_STR_EQUAL(tc, TEST_ZONE_NAME, name); + ABTS_INT_NEQUAL(tc, 0, id); /* Only guarantee is that it should be non-zero */ + + /* Should match self and copy */ + ABTS_INT_NEQUAL(tc, 0, apr_sockaddr_equal(sa, sa)); + ABTS_INT_NEQUAL(tc, 0, apr_sockaddr_equal(sa2, sa2)); + ABTS_INT_NEQUAL(tc, 0, apr_sockaddr_equal(sa2, sa)); + + /* Should not match against copy without zone set. */ + rv = apr_sockaddr_info_get(&sa2, TEST_ZONE_ADDR, APR_INET6, 8080, 0, p); + APR_ASSERT_SUCCESS(tc, "Problem generating sockaddr", rv); + + ABTS_INT_EQUAL(tc, 0, apr_sockaddr_equal(sa2, sa)); + } +#endif /* TEST_ZONE_NAME */ +#endif /* APR_HAVE_IPV6 */ +} + abts_suite *testsock(abts_suite *suite) { suite = ADD_SUITE(suite) @@ -657,6 +749,7 @@ abts_run_test(suite, test_wait, NULL); abts_run_test(suite, test_nonblock_inheritance, NULL); abts_run_test(suite, test_freebind, NULL); + abts_run_test(suite, test_zone, NULL); #if APR_HAVE_SOCKADDR_UN socket_name = UNIX_SOCKET_NAME; socket_type = APR_UNIX;