Skip to content

Commit

Permalink
Support IPv6 link-local address scope/zone mapping.
Browse files Browse the repository at this point in the history
* network_io/unix/sockaddr.c (apr_sockaddr_zone_set,
  apr_sockaddr_zone_get): New functions.
  (apr_sockaddr_ip_getbuf): Append %scope for link-local address.
  (apr_sockaddr_equal): Compare link-local address with different
  scopes as not equal.

* include/apr_network_io.h: Add function declarations.

* configure.in: Test for if_indextoname and if_nametoindex.

* test/testsock.c (test_zone): New test case.

* include/arch/win32/apr_private.h: Assume Windows supports
  if_nametoindex and if_indextoname.


git-svn-id: https://svn.apache.org/repos/asf/apr/apr/trunk@1816527 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
notroj committed Nov 28, 2017
1 parent 008ce98 commit 30cab59
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 5 deletions.
9 changes: 9 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
-*- coding: utf-8 -*-
Changes for APR 2.0.0

*) Add apr_sockaddr_zone_set, apr_sockaddr_zone_set to set and retrieve
the zone for link-local IPv6 addresses. [Joe Orton]

*) apr_sockaddr_equal: Compare link-local IPv6 addresses with different
zones as not equal. [Joe Orton]

*) apr_sockaddr_ip_getbuf, apr_sockaddr_ip_get: Append "%zone" for
IPv6 link-local addresses. [Joe Orton]

*) Don't seek to the end when opening files with APR_FOPEN_APPEND on Windows.
[Evgeny Kotkov <evgeny.kotkov visualsvn.com>]

Expand Down
6 changes: 4 additions & 2 deletions configure.in
Original file line number Diff line number Diff line change
Expand Up @@ -1069,7 +1069,9 @@ case $host in
#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])

Expand Down Expand Up @@ -2755,7 +2757,7 @@ esac
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
Expand Down
23 changes: 23 additions & 0 deletions include/apr_network_io.h
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,29 @@ APR_DECLARE(apr_status_t) apr_sockaddr_info_copy(apr_sockaddr_t **dst,
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.
Expand Down
2 changes: 2 additions & 0 deletions include/arch/win32/apr_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ APR_DECLARE_DATA int errno;
#if APR_HAVE_IPV6
#define HAVE_GETADDRINFO 1
#define HAVE_GETNAMEINFO 1
#define HAVE_IF_INDEXTONAME 1
#define HAVE_IF_NAMETOINDEX 1
#endif

/* MSVC 7.0 introduced _strtoi64 */
Expand Down
100 changes: 97 additions & 3 deletions network_io/unix/sockaddr.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -125,9 +129,31 @@ APR_DECLARE(apr_status_t) apr_sockaddr_ip_getbuf(char *buf, apr_size_t buflen,
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
/* Append scope name for link-local addresses. */
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 >= (p-buf) + strlen(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;
}

Expand Down Expand Up @@ -899,11 +925,19 @@ APR_DECLARE(apr_status_t) apr_getservbyname(apr_sockaddr_t *sockaddr,
&((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
Expand Down Expand Up @@ -1182,3 +1216,63 @@ APR_DECLARE(int) apr_ipsubnet_test(apr_ipsubnet_t *ipsub, apr_sockaddr_t *sa)
#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
}
93 changes: 93 additions & 0 deletions test/testsock.c
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,98 @@ static void test_freebind(abts_case *tc, void *data)
#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)
Expand All @@ -657,6 +749,7 @@ abts_suite *testsock(abts_suite *suite)
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;
Expand Down

0 comments on commit 30cab59

Please sign in to comment.