Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion src/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ SPDX-License-Identifier: LGPL-2.1-or-later
#include <stdio.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>

#include "util.h"

Expand Down Expand Up @@ -336,6 +337,20 @@ void append_address_to_userdata(const query_address_result_t* result,
if (u->count >= MAX_ENTRIES)
return;

memcpy(&(u->result[u->count]), result, sizeof(*result));
query_address_result_t* dst = &u->result[u->count];
memcpy(dst, result, sizeof(*dst));

// The scope id holds the interface index the record was seen on. That is
// only meaningful for link-local IPv6 addresses (fe80::/10), which cannot
// be used without it. RFC 4007 section 11.1 requires the scope id to be
// zero for global-scope addresses; a non-zero scope on a global address is
// malformed and breaks consumers such as mount.nfs. Normalize it here, at
// the single point where resolved addresses enter userdata, so every
// backend (Linux gethostbyname4 and the BSD path) sees a correct value.
if (!(dst->af == AF_INET6 &&
IN6_IS_ADDR_LINKLOCAL(
(const struct in6_addr*)dst->address.ipv6.address)))
dst->scopeid = 0;

u->count++;
}
45 changes: 43 additions & 2 deletions tests/check_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -552,8 +552,8 @@ static query_address_result_t create_address_result(int offset, int af) {
if (af == AF_UNSPEC) {
// Alternate between IPv4 and IPv6.
af = offset % 2 ? AF_INET : AF_INET6;
result.af = af;
}
result.af = af;

switch (af) {
case AF_INET:
Expand Down Expand Up @@ -588,7 +588,11 @@ static void validate_addrtuples(struct gaih_addrtuple* pat,
break;
case AF_INET6:
ck_assert_mem_eq(pat->addr, expected_ipv6, sizeof expected_ipv6);
ck_assert_int_eq(pat->scopeid, (i / 2) % 3);
// These are global-scope addresses (documentation prefix), so the
// scope id must be cleared regardless of the value Avahi reported.
// Link-local scoping is covered by
// test_append_address_normalizes_scopeid.
ck_assert_int_eq(pat->scopeid, 0);
break;
}

Expand All @@ -611,6 +615,39 @@ static userdata_t create_address_userdata(int num_addresses, int af) {
return u;
}

// The scope id (interface index) is only meaningful for link-local IPv6
// addresses. append_address_to_userdata must preserve it for fe80::/10 and
// clear it to zero for global-scope addresses, per RFC 4007 section 11.1.
START_TEST(test_append_address_normalizes_scopeid) {
static const uint8_t linklocal[16] = {0xfe, 0x80, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0x01};
static const uint8_t global[16] = {0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0x01};

userdata_t u;
u.count = 0;

query_address_result_t ll = {.af = AF_INET6, .scopeid = 3};
memcpy(ll.address.ipv6.address, linklocal, sizeof linklocal);
append_address_to_userdata(&ll, &u);

query_address_result_t gl = {.af = AF_INET6, .scopeid = 3};
memcpy(gl.address.ipv6.address, global, sizeof global);
append_address_to_userdata(&gl, &u);

ck_assert_int_eq(u.count, 2);

// Link-local address: scope id preserved.
ck_assert_int_eq(u.result[0].scopeid, 3);
ck_assert_mem_eq(u.result[0].address.ipv6.address, linklocal,
sizeof linklocal);

// Global address: scope id cleared.
ck_assert_int_eq(u.result[1].scopeid, 0);
ck_assert_mem_eq(u.result[1].address.ipv6.address, global, sizeof global);
}
END_TEST

static void poison(char* buf, size_t buflen) { memset(buf, 0x55, buflen); }

static void validate_poison(char* buf, size_t buflen, size_t full_buflen) {
Expand Down Expand Up @@ -1013,6 +1050,10 @@ static Suite* util_suite(void) {
suite_add_tcase(s, tc_userdata_to_addrtuple);
#endif

TCase* tc_append_address = tcase_create("append_address");
tcase_add_test(tc_append_address, test_append_address_normalizes_scopeid);
suite_add_tcase(s, tc_append_address);

TCase* tc_userdata_for_name_to_hostent =
tcase_create("userdata_for_name_to_hostent");
tcase_add_test(tc_userdata_for_name_to_hostent,
Expand Down
Loading