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;

Reply via email to