Skip to content

Commit 30cab59

Browse files
committed
Support IPv6 link-local address scope/zone mapping.
* 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
1 parent 008ce98 commit 30cab59

File tree

6 files changed

+228
-5
lines changed

6 files changed

+228
-5
lines changed

CHANGES

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
-*- coding: utf-8 -*-
22
Changes for APR 2.0.0
33

4+
*) Add apr_sockaddr_zone_set, apr_sockaddr_zone_set to set and retrieve
5+
the zone for link-local IPv6 addresses. [Joe Orton]
6+
7+
*) apr_sockaddr_equal: Compare link-local IPv6 addresses with different
8+
zones as not equal. [Joe Orton]
9+
10+
*) apr_sockaddr_ip_getbuf, apr_sockaddr_ip_get: Append "%zone" for
11+
IPv6 link-local addresses. [Joe Orton]
12+
413
*) Don't seek to the end when opening files with APR_FOPEN_APPEND on Windows.
514
[Evgeny Kotkov <evgeny.kotkov visualsvn.com>]
615

configure.in

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,7 +1069,9 @@ case $host in
10691069
#endif";;
10701070
esac
10711071

1072-
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])
1072+
AC_CHECK_HEADERS([sys/types.h sys/mman.h sys/ipc.h sys/mutex.h \
1073+
sys/shm.h sys/file.h kernel/OS.h os2.h windows.h \
1074+
net/if.h])
10731075
AC_CHECK_FUNCS([mmap munmap shm_open shm_unlink shmget shmat shmdt shmctl \
10741076
create_area mprotect])
10751077

@@ -2755,7 +2757,7 @@ esac
27552757
AC_SEARCH_LIBS(getaddrinfo, socket inet6)
27562758
AC_SEARCH_LIBS(gai_strerror, socket inet6)
27572759
AC_SEARCH_LIBS(getnameinfo, socket inet6)
2758-
AC_CHECK_FUNCS(gai_strerror)
2760+
AC_CHECK_FUNCS(gai_strerror if_nametoindex if_indextoname)
27592761
APR_CHECK_WORKING_GETADDRINFO
27602762
APR_CHECK_NEGATIVE_EAI
27612763
APR_CHECK_WORKING_GETNAMEINFO

include/apr_network_io.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,29 @@ APR_DECLARE(apr_status_t) apr_sockaddr_info_copy(apr_sockaddr_t **dst,
441441
const apr_sockaddr_t *src,
442442
apr_pool_t *p);
443443

444+
/* Set the zone of an IPv6 link-local address object.
445+
* @param sa Socket address object
446+
* @param zone_id Zone ID (textual "eth0" or numeric "3").
447+
*/
448+
APR_DECLARE(apr_status_t) apr_sockaddr_zone_set(apr_sockaddr_t *sa,
449+
const char *zone_id);
450+
451+
452+
/* Retrieve the zone of an IPv6 link-local address object.
453+
* @param sa Socket address object
454+
* @param name If non-NULL, set to the textual representation of the zone id
455+
* @param id If non-NULL, set to the integer zone id
456+
* @param p Pool from which *name is allocated if used.
457+
* @return Returns APR_EBADIP for non-IPv6 socket or socket without any zone id
458+
* set, or other error if the interface could not be mapped to a name.
459+
* @remark Both name and id may be NULL, neither are modified if
460+
* non-NULL in error cases.
461+
*/
462+
APR_DECLARE(apr_status_t) apr_sockaddr_zone_get(const apr_sockaddr_t *sa,
463+
const char **name,
464+
apr_uint32_t *id,
465+
apr_pool_t *p);
466+
444467
/**
445468
* Look up the host name from an apr_sockaddr_t.
446469
* @param hostname The hostname.

include/arch/win32/apr_private.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ APR_DECLARE_DATA int errno;
134134
#if APR_HAVE_IPV6
135135
#define HAVE_GETADDRINFO 1
136136
#define HAVE_GETNAMEINFO 1
137+
#define HAVE_IF_INDEXTONAME 1
138+
#define HAVE_IF_NAMETOINDEX 1
137139
#endif
138140

139141
/* MSVC 7.0 introduced _strtoi64 */

network_io/unix/sockaddr.c

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
#include <stdlib.h>
2626
#endif
2727

28+
#ifdef HAVE_NET_IF_H
29+
#include <net/if.h>
30+
#endif
31+
2832
#define APR_WANT_STRFUNC
2933
#include "apr_want.h"
3034

@@ -125,9 +129,31 @@ APR_DECLARE(apr_status_t) apr_sockaddr_ip_getbuf(char *buf, apr_size_t buflen,
125129
memmove(buf, buf + strlen("::ffff:"),
126130
strlen(buf + strlen("::ffff:"))+1);
127131
}
128-
#endif
132+
129133
/* ensure NUL termination if the buffer is too short */
130134
buf[buflen-1] = '\0';
135+
136+
#ifdef HAVE_IF_INDEXTONAME
137+
/* Append scope name for link-local addresses. */
138+
if (sockaddr->family == AF_INET6
139+
&& IN6_IS_ADDR_LINKLOCAL((struct in6_addr *)sockaddr->ipaddr_ptr)) {
140+
char scbuf[IF_NAMESIZE], *p = buf + strlen(buf);
141+
142+
if (if_indextoname(sockaddr->sa.sin6.sin6_scope_id, scbuf) == scbuf) {
143+
/* Space check, need room for buf + '%' + scope + '\0'.
144+
* Assert: buflen >= strlen(buf) + strlen(scbuf) + 2
145+
* Equiv: buflen >= (p-buf) + strlen(buf) + 2
146+
* Thus, fail in inverse condition: */
147+
if (buflen < strlen(scbuf) + (p - buf) + 2) {
148+
return APR_ENOSPC;
149+
}
150+
*p++ = '%';
151+
memcpy(p, scbuf, strlen(scbuf) + 1);
152+
}
153+
}
154+
#endif /* HAVE_IF_INDEXTONAME */
155+
#endif /* APR_HAVE_IPV6 */
156+
131157
return APR_SUCCESS;
132158
}
133159

@@ -899,11 +925,19 @@ APR_DECLARE(apr_status_t) apr_getservbyname(apr_sockaddr_t *sockaddr,
899925
&((struct in6_addr *)(b)->ipaddr_ptr)->s6_addr[12], \
900926
(a)->ipaddr_len))
901927

928+
#if APR_HAVE_IPV6
929+
#define SCOPE_OR_ZERO(sa_) ((sa_)->family != AF_INET6 ? 0 : \
930+
((sa_)->sa.sin6.sin6_scope_id))
931+
#else
932+
#define SCOPE_OR_ZERO(sa_) (0)
933+
#endif
934+
902935
APR_DECLARE(int) apr_sockaddr_equal(const apr_sockaddr_t *addr1,
903936
const apr_sockaddr_t *addr2)
904937
{
905-
if (addr1->ipaddr_len == addr2->ipaddr_len &&
906-
!memcmp(addr1->ipaddr_ptr, addr2->ipaddr_ptr, addr1->ipaddr_len)) {
938+
if (addr1->ipaddr_len == addr2->ipaddr_len
939+
&& !memcmp(addr1->ipaddr_ptr, addr2->ipaddr_ptr, addr1->ipaddr_len)
940+
&& SCOPE_OR_ZERO(addr1) == SCOPE_OR_ZERO(addr2)) {
907941
return 1;
908942
}
909943
#if APR_HAVE_IPV6
@@ -1182,3 +1216,63 @@ APR_DECLARE(int) apr_ipsubnet_test(apr_ipsubnet_t *ipsub, apr_sockaddr_t *sa)
11821216
#endif /* APR_HAVE_IPV6 */
11831217
return 0; /* no match */
11841218
}
1219+
1220+
APR_DECLARE(apr_status_t) apr_sockaddr_zone_set(apr_sockaddr_t *sa,
1221+
const char *zone_id)
1222+
{
1223+
#if !APR_HAVE_IPV6 || !defined(HAVE_IF_NAMETOINDEX)
1224+
return APR_ENOTIMPL;
1225+
#else
1226+
unsigned int idx;
1227+
1228+
if (sa->family != APR_INET6) {
1229+
return APR_EBADIP;
1230+
}
1231+
1232+
idx = if_nametoindex(zone_id);
1233+
if (idx) {
1234+
sa->sa.sin6.sin6_scope_id = idx;
1235+
return APR_SUCCESS;
1236+
}
1237+
1238+
if (errno != ENODEV) {
1239+
return errno;
1240+
}
1241+
else {
1242+
char *endptr;
1243+
apr_int64_t i = apr_strtoi64(zone_id, &endptr, 10);
1244+
1245+
if (*endptr != '\0' || errno || i < 1 || i > APR_INT16_MAX) {
1246+
return APR_EGENERAL;
1247+
}
1248+
1249+
sa->sa.sin6.sin6_scope_id = i;
1250+
return APR_SUCCESS;
1251+
}
1252+
#endif
1253+
}
1254+
1255+
APR_DECLARE(apr_status_t) apr_sockaddr_zone_get(const apr_sockaddr_t *sa,
1256+
const char **name,
1257+
apr_uint32_t *id,
1258+
apr_pool_t *p)
1259+
{
1260+
#if !APR_HAVE_IPV6 || !defined(HAVE_IF_INDEXTONAME)
1261+
return APR_ENOTIMPL;
1262+
#else
1263+
if (sa->family != APR_INET6 || !sa->sa.sin6.sin6_scope_id) {
1264+
return APR_EBADIP;
1265+
}
1266+
1267+
if (name) {
1268+
char *buf = apr_palloc(p, IF_NAMESIZE);
1269+
if (if_indextoname(sa->sa.sin6.sin6_scope_id, buf) == NULL)
1270+
return errno;
1271+
*name = buf;
1272+
}
1273+
1274+
if (id) *id = sa->sa.sin6.sin6_scope_id;
1275+
1276+
return APR_SUCCESS;
1277+
#endif
1278+
}

test/testsock.c

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,98 @@ static void test_freebind(abts_case *tc, void *data)
640640
#endif
641641
}
642642

643+
#define TEST_ZONE_ADDR "fe80::1"
644+
645+
#ifdef __linux__
646+
/* Reasonable bet that "lo" will exist. */
647+
#define TEST_ZONE_NAME "lo"
648+
/* ... fill in other platforms here */
649+
#endif
650+
651+
#ifdef TEST_ZONE_NAME
652+
#define TEST_ZONE_FULLADDR TEST_ZONE_ADDR "%" TEST_ZONE_NAME
653+
#endif
654+
655+
static void test_zone(abts_case *tc, void *data)
656+
{
657+
#if APR_HAVE_IPV6
658+
apr_sockaddr_t *sa;
659+
apr_status_t rv;
660+
const char *name = NULL;
661+
apr_uint32_t id = 0;
662+
663+
/* RFC 5737 address */
664+
rv = apr_sockaddr_info_get(&sa, "127.0.0.1", APR_INET, 8080, 0, p);
665+
APR_ASSERT_SUCCESS(tc, "Problem generating sockaddr", rv);
666+
667+
/* Fail for an IPv4 address! */
668+
ABTS_INT_EQUAL(tc, APR_EBADIP,
669+
apr_sockaddr_zone_set(sa, "1"));
670+
ABTS_INT_EQUAL(tc, APR_EBADIP,
671+
apr_sockaddr_zone_get(sa, &name, &id, p));
672+
673+
rv = apr_sockaddr_info_get(&sa, TEST_ZONE_ADDR, APR_INET6, 8080, 0, p);
674+
APR_ASSERT_SUCCESS(tc, "Problem generating sockaddr", rv);
675+
676+
rv = apr_sockaddr_info_get(&sa, TEST_ZONE_ADDR, APR_INET6, 8080, 0, p);
677+
APR_ASSERT_SUCCESS(tc, "Problem generating sockaddr", rv);
678+
679+
ABTS_INT_EQUAL(tc, APR_EBADIP, apr_sockaddr_zone_get(sa, &name, &id, p));
680+
681+
#ifdef TEST_ZONE_NAME
682+
{
683+
apr_sockaddr_t *sa2;
684+
char buf[50];
685+
686+
APR_ASSERT_SUCCESS(tc, "Set zone to " TEST_ZONE_NAME,
687+
apr_sockaddr_zone_set(sa, TEST_ZONE_NAME));
688+
689+
APR_ASSERT_SUCCESS(tc, "Get zone",
690+
apr_sockaddr_zone_get(sa, NULL, NULL, p));
691+
692+
APR_ASSERT_SUCCESS(tc, "Get zone",
693+
apr_sockaddr_zone_get(sa, &name, &id, p));
694+
ABTS_STR_EQUAL(tc, TEST_ZONE_NAME, name);
695+
ABTS_INT_NEQUAL(tc, 0, id); /* Only guarantee is that it should be non-zero */
696+
697+
/* Check string translation. */
698+
APR_ASSERT_SUCCESS(tc, "get IP address",
699+
apr_sockaddr_ip_getbuf(buf, 50, sa));
700+
ABTS_STR_EQUAL(tc, TEST_ZONE_FULLADDR, buf);
701+
702+
memset(buf, 'A', sizeof buf);
703+
ABTS_INT_EQUAL(tc, APR_ENOSPC, apr_sockaddr_ip_getbuf(buf, strlen(TEST_ZONE_ADDR), sa));
704+
ABTS_INT_EQUAL(tc, APR_ENOSPC, apr_sockaddr_ip_getbuf(buf, strlen(TEST_ZONE_FULLADDR), sa));
705+
706+
APR_ASSERT_SUCCESS(tc, "get IP address",
707+
apr_sockaddr_ip_getbuf(buf, strlen(TEST_ZONE_FULLADDR) + 1, sa));
708+
/* Check for overflow. */
709+
ABTS_INT_EQUAL(tc, 'A', buf[strlen(buf) + 1]);
710+
711+
rv = apr_sockaddr_info_copy(&sa2, sa, p);
712+
APR_ASSERT_SUCCESS(tc, "Problem copying sockaddr", rv);
713+
714+
/* Copy copied zone matches */
715+
APR_ASSERT_SUCCESS(tc, "Get zone",
716+
apr_sockaddr_zone_get(sa2, &name, &id, p));
717+
ABTS_STR_EQUAL(tc, TEST_ZONE_NAME, name);
718+
ABTS_INT_NEQUAL(tc, 0, id); /* Only guarantee is that it should be non-zero */
719+
720+
/* Should match self and copy */
721+
ABTS_INT_NEQUAL(tc, 0, apr_sockaddr_equal(sa, sa));
722+
ABTS_INT_NEQUAL(tc, 0, apr_sockaddr_equal(sa2, sa2));
723+
ABTS_INT_NEQUAL(tc, 0, apr_sockaddr_equal(sa2, sa));
724+
725+
/* Should not match against copy without zone set. */
726+
rv = apr_sockaddr_info_get(&sa2, TEST_ZONE_ADDR, APR_INET6, 8080, 0, p);
727+
APR_ASSERT_SUCCESS(tc, "Problem generating sockaddr", rv);
728+
729+
ABTS_INT_EQUAL(tc, 0, apr_sockaddr_equal(sa2, sa));
730+
}
731+
#endif /* TEST_ZONE_NAME */
732+
#endif /* APR_HAVE_IPV6 */
733+
}
734+
643735
abts_suite *testsock(abts_suite *suite)
644736
{
645737
suite = ADD_SUITE(suite)
@@ -657,6 +749,7 @@ abts_suite *testsock(abts_suite *suite)
657749
abts_run_test(suite, test_wait, NULL);
658750
abts_run_test(suite, test_nonblock_inheritance, NULL);
659751
abts_run_test(suite, test_freebind, NULL);
752+
abts_run_test(suite, test_zone, NULL);
660753
#if APR_HAVE_SOCKADDR_UN
661754
socket_name = UNIX_SOCKET_NAME;
662755
socket_type = APR_UNIX;

0 commit comments

Comments
 (0)